`
})
export class FeatureCardComponent extends AppBaseComponent {
@Input() feature!: FeatureDto;
@Input() isSelected = false;
@Output() onEdit = new EventEmitter();
get canEdit(): boolean {
return this.hasRole('Admin', 'Manager');
}
}
```
## Key Platform APIs
### Lifecycle & Subscriptions
```typescript
// Auto-cleanup subscription
this.data$.pipe(this.untilDestroyed()).subscribe();
// Store named subscriptions
this.storeSubscription('key', observable.subscribe());
this.cancelStoredSubscription('key');
```
### Loading/Error State
```typescript
// Track request state
observable.pipe(this.observerLoadingErrorState('requestKey'));
// Check states in template
isLoading$('requestKey')();
getErrorMsg$('requestKey')();
isStateLoading()();
isStateError()();
```
### Response Handling
```typescript
// Handle success/error
observable.pipe(
this.tapResponse(
result => {
/* success */
},
error => {
/* error */
}
)
);
```
### Track-by Functions
```typescript
// For @for loops
trackByItem = this.ngForTrackByItemProp('id');
trackByList = this.ngForTrackByImmutableList(this.items);
```
## Code Responsibility Hierarchy (CRITICAL)
**Place logic in the LOWEST appropriate layer to enable reuse and prevent duplication:**
| Layer | Responsibility |
| ---------------- | ------------------------------------------------------------------------- |
| **Entity/Model** | Display helpers, static factory methods, default values, dropdown options |
| **Service** | API calls, command factories, data transformation |
| **Component** | UI event handling ONLY - delegates all logic to lower layers |
```typescript
// ❌ WRONG: Logic in component (leads to duplication if another component needs it)
readonly authTypes = [{ value: AuthType.OAuth2, label: 'OAuth2' }, ...];
getDefaultBaseUrl(type) { return this.providerUrls[type] ?? ''; }
// ✅ CORRECT: Logic in entity/model (single source of truth, reusable)
readonly authTypes = AuthConfigurationDisplay.getApiAuthTypeOptions();
getDefaultBaseUrl(type) { return JobBoardProviderConfiguration.getDefaultBaseUrl(type); }
```
**Common Refactoring Patterns:**
- Dropdown options → static method in entity: `Entity.getOptions()`
- Display logic (CSS class, text) → instance method in entity: `entity.getStatusCssClass()`
- Default values → static method in entity: `Entity.getDefaultValue()`
- Command building → factory class in service: `CommandFactory.buildSaveCommand(formValues)`
## Anti-Patterns to AVOID
:x: **Using wrong base class**
```typescript
// WRONG - using PlatformComponent when auth needed
export class MyComponent extends PlatformComponent {}
// CORRECT - using AppBaseComponent for auth context
export class MyComponent extends AppBaseComponent {}
```
:x: **Manual subscription management**
```typescript
// WRONG
private sub: Subscription;
ngOnDestroy() { this.sub.unsubscribe(); }
// CORRECT
this.data$.pipe(this.untilDestroyed()).subscribe();
```
:x: **Direct HTTP calls**
```typescript
// WRONG
constructor(private http: HttpClient) { }
// CORRECT
constructor(private featureApi: FeatureApiService) { }
```
:x: **Missing loading states**
```html
{{ items }}
{{ items }}
```
## Verification Checklist
- [ ] Correct base class selected for use case
- [ ] Store provided at component level (if using store)
- [ ] Loading/error states handled with `app-loading-and-error-indicator`
- [ ] Subscriptions use `untilDestroyed()`
- [ ] Track-by functions used in `@for` loops
- [ ] Form validation configured properly
- [ ] Auth checks use `hasRole()` from base class
- [ ] API calls use service extending `PlatformApiService`