Claude-skill-registry frontend-angular-patterns

Use when editing Angular TypeScript files (.ts, .tsx) in src/Frontend/ or libs/. Provides component hierarchy, state management with PlatformVmStore, API services, reactive forms, and platform utilities for EasyPlatform Angular 19 development.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/frontend-angular-patterns" ~/.claude/skills/majiayu000-claude-skill-registry-frontend-angular-patterns && rm -rf "$T"
manifest: skills/data/frontend-angular-patterns/SKILL.md
source content

Frontend Angular Code Patterns

When implementing frontend Angular code in EasyPlatform, follow these patterns exactly.

Full Pattern Reference

See the complete code patterns with examples: frontend-code-patterns.md

Quick Reference

Pattern Index

#PatternKey Interface/Contract
1Component Hierarchy
PlatformComponent → AppBaseComponent → Feature
(never extend Platform* directly)
2Component API
observerLoadingErrorState()
,
untilDestroyed()
,
tapResponse()
,
isLoading$()
3State Store
PlatformVmStore<T>
,
effectSimple()
,
updateState()
,
select()
4API ServiceExtend
PlatformApiService
,
get apiUrl
, typed CRUD methods
5Forms
PlatformFormComponent
,
initialFormConfig()
,
validateForm()
, FormArray support
6Advanced
@Watch
,
skipDuplicates()
,
distinctUntilObjectValuesChanged()
, platform utilities

Critical Rules

  1. Component Hierarchy: Extend
    AppBaseComponent
    ,
    AppBaseVmStoreComponent
    , or
    AppBaseFormComponent
    - NEVER raw
    Component
  2. State Management: Use
    PlatformVmStore
    for state management - NEVER manual signals
  3. API Services: Extend
    PlatformApiService
    for HTTP calls - NEVER direct
    HttpClient
  4. Subscriptions: Always use
    .pipe(this.untilDestroyed())
    for subscriptions - NEVER manual unsubscribe
  5. BEM Classes: All template elements MUST have BEM classes (
    block__element --modifier
    )
  6. API Calls: Use
    effectSimple()
    for API calls - auto-handles loading/error state

Component Hierarchy

PlatformComponent → PlatformVmComponent → PlatformFormComponent
                  → PlatformVmStoreComponent

AppBaseComponent → AppBaseVmComponent → AppBaseFormComponent
                 → AppBaseVmStoreComponent

FeatureComponent extends AppBaseVmStoreComponent<State, Store>

Platform Component API

// PlatformComponent
status$: WritableSignal<'Pending'|'Loading'|'Success'|'Error'>;
observerLoadingErrorState<T>(key?: string): OperatorFunction<T, T>;
isLoading$(key?: string): Signal<boolean | null>;
untilDestroyed<T>(): MonoTypeOperatorFunction<T>;
tapResponse<T>(next?, error?, complete?): OperatorFunction<T, T>;

// PlatformVmStoreComponent
constructor(public store: TStore) {}
vm: Signal<T | undefined>;
reload(): void;

// PlatformFormComponent
form: FormGroup<PlatformFormGroupControls<T>>;
mode: 'create'|'update'|'view';
validateForm(): boolean;
abstract initialFormConfig: () => PlatformFormConfig<T>;

Anti-Patterns

// ❌ Direct HttpClient → ✅ Extend PlatformApiService
// ❌ Manual signals → ✅ Use PlatformVmStore
// ❌ Missing untilDestroyed() → ✅ Always use .pipe(this.untilDestroyed())
// ❌ No BEM classes → ✅ Every element needs BEM class

Templates

Component with Store Template

@Component({
    selector: 'app-{entity}-list',
    template: `
        <app-loading [target]="this">
            @if (vm(); as vm) {
                @for (item of vm.items; track item.id) {
                    <div class="{entity}-list__item">{{item.name}}</div>
                }
            }
        </app-loading>
    `,
    providers: [{Entity}Store]
})
export class {Entity}Component extends AppBaseVmStoreComponent<{Entity}State, {Entity}Store> {
    ngOnInit() {
        this.store.load();
    }
}

API Service Template

@Injectable({ providedIn: 'root' })
export class {Entity}ApiService extends PlatformApiService {
    protected get apiUrl() {
        return environment.apiUrl + '/api/{Entity}';
    }

    getAll(query?: Query): Observable<{Entity}[]> {
        return this.get('', query);
    }

    save(cmd: SaveCommand): Observable<Result> {
        return this.post('', cmd);
    }
}

Detailed Instructions

For task-specific guidance, also reference: