| name | angular-component |
| description | Modern Angular component patterns used throughout this workspace — signals, input()/output() functions, @if/@for control flow, OnPush, and standalone imports. Use when creating or editing any component, directive, pipe or template, and when a lint rule rejects @Input, *ngIf or CommonModule. |
Skill: Modern Angular Component Patterns
All components in this project follow Angular 20+ standards. These rules are non-negotiable.
Component declaration
All components are standalone. Never use NgModule to declare components.
@Component({
selector: 'lib-user-info',
imports: [],
templateUrl: './user-info.html',
styleUrls: ['./user-info.scss'],
})
export class UserInfoComponent {}
File naming: <name>.ts — no .component.ts suffix in this project.
Inputs and Outputs
Use the input() and output() functions. Never use @Input or @Output decorators.
import { Component, input, output } from '@angular/core';
import { User } from '@libs/entity';
@Component({ ... })
export class UserInfoComponent {
user = input.required<User>();
editable = input<boolean>(false);
userSelected = output<User>();
select() {
this.userSelected.emit(this.user());
}
}
Consuming in a template:
<lib-user-info [user]="profileData" (userSelected)="onSelect($event)" />
Template control flow
Use Angular's built-in control flow. Never use structural directives.
@if (user()) {
<div>Welcome, {{ user()!.name }}</div>
} @for (item of items(); track item.id) {
<li>{{ item.name }}</li>
} @empty {
<li>No items found.</li>
} @switch (status()) { @case ('active') { <span class="green">Active</span> }
@case ('inactive') { <span class="red">Inactive</span> } @default {
<span>Unknown</span> } }
<div *ngIf="user">...</div>
<li *ngFor="let item of items">...</li>
<ng-container [ngSwitch]="status">...</ng-container>
Dependency injection
Use inject() inside the class body. Never use constructor parameter injection.
export class ProfilePage {
private readonly store = inject(Store);
private readonly router = inject(Router);
}
export class ProfilePage {
constructor(
private store: Store,
private router: Router,
) {}
}
Signals and reactive state
Feature state comes from a signalStore, whose members are already signals — read
them directly, there is nothing to bridge. Use computed() for values derived in the
component; derive in the store instead when more than one component needs them.
import { Component, computed, inject } from '@angular/core';
import { ProfileStore } from '../../+state/profile.store';
export class ProfilePage {
private readonly store = inject(ProfileStore);
readonly profile = this.store.profile;
readonly loading = this.store.loading;
readonly displayName = computed(() => this.profile()?.name ?? 'Anonymous');
}
Lifecycle — use constructor, not ngOnInit
Angular Signals and modern patterns initialize reactively. Dispatch initial actions from the constructor.
export class ProfilePage {
constructor() {
this.store.dispatch(fetchProfile());
}
}
export class ProfilePage implements OnInit {
ngOnInit() {
this.store.dispatch(fetchProfile());
}
}
If you need a cleanup equivalent, use the DestroyRef:
import { DestroyRef, inject } from '@angular/core';
export class ProfilePage {
private readonly destroyRef = inject(DestroyRef);
constructor() {
this.destroyRef.onDestroy(() => {
});
}
}
Atomic Design — component classification
| Level | Description | Location |
|---|
| Atom | Single-purpose UI element (button, badge, avatar) | atoms/ |
| Molecule | Combination of atoms with a specific function | molecules/ |
| Organism | Full section composed of molecules (form, card list) | organisms/ |
| Page | Route-level component, orchestrates organisms | pages/ |
Rules:
- Atoms and Molecules are presentational — they receive data via
input() and emit events via output(). They do not inject the Store.
- Pages are container components — they inject the Store, dispatch actions, and pass data down to molecules/atoms.
export class UserInfoComponent {
user = input<User>();
editClicked = output<void>();
}
export class ProfilePage {
private readonly store = inject(ProfileStore);
readonly profile = this.store.profile;
}
Selector prefix
All component selectors in libs use the lib- prefix (configured in project.json).
selector: 'lib-user-info';
selector: 'app-home';
Checklist