| name | angular-components |
| description | Create, build, refactor, and optimize Angular components with signals, inputs, outputs, lifecycle hooks, change detection, content projection, and smart/presentational patterns. Use when building UI components, fixing component bugs, or improving performance. |
| license | MIT |
| metadata | {"version":"1.0.0","audience":"developers","workflow":"frontend-development"} |
Angular Components
What I Do
- Create Angular components following modern best practices
- Implement signal-based inputs, outputs, and model bindings
- Configure change detection strategies (Default vs OnPush)
- Set up content projection with
ng-content
- Apply smart/presentational component architecture
- Use lifecycle hooks correctly (ngOnInit, ngOnDestroy, afterRender)
- Query child components with viewChild/contentChild signals
- Fix common errors (ExpressionChangedAfterItHasBeenChecked)
When to Use Me
Use this skill when you:
- Create, generate, or scaffold new Angular components
- Refactor components to use signals and modern APIs
- Implement parent-child component communication
- Configure content projection with ng-content slots
- Optimize components with OnPush change detection
- Debug lifecycle hook or change detection issues
Component Architecture
Smart vs Presentational Pattern
Presentational - Pure UI, no services, OnPush, input/output only:
@Component({
selector: 'user-card',
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<div class="card"><h3>{{ name() }}</h3></div>`
})
export class UserCardComponent {
readonly name = input.required<string>();
readonly select = output<string>();
}
Smart - Inject services, manage state, coordinate children:
@Component({
selector: 'user-list-page',
template: `@for (user of users(); track user.id) { <user-card [name]="user.name" /> }`
})
export class UserListPageComponent {
private userService = inject(UserService);
users = signal<User[]>([]);
}
Inputs and Outputs
value = input(0);
userId = input.required<string>();
label = input('', { transform: trimString });
disabled = input(false, { transform: booleanAttribute });
value = model(0);
saved = output<void>();
valueChanged = output<number>();
this.valueChanged.emit(42);
Lifecycle Hooks
Order: constructor → ngOnChanges → ngOnInit → ngAfterContentInit → ngAfterViewInit
export class MyComponent implements OnInit {
private destroyRef = inject(DestroyRef);
ngOnInit() { this.loadData(); }
constructor() {
this.destroyRef.onDestroy(() => this.cleanup());
afterNextRender(() => this.measureElement());
}
}
Change Detection
OnPush triggers only when: input reference changes, event occurs, signal changes, or markForCheck() called.
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class OptimizedComponent {
data = signal<Data | null>(null);
displayName = computed(() => this.data()?.name ?? 'Unknown');
}
Content Projection
@Component({
selector: 'card',
template: `
<ng-content select="[card-header]" />
<ng-content select="[card-body]" />
<ng-content>Fallback content</ng-content>
`
})
export class CardComponent {}
Component Queries
header = viewChild(HeaderComponent);
header = viewChild.required(HeaderComponent);
items = viewChildren(ItemComponent);
toggle = contentChild(ToggleComponent);
items = contentChildren(ItemComponent, { descendants: true });
Context7 Integration
For current Angular documentation, use Context7 MCP server:
context7_resolve-library-id: angular
context7_query-docs: libraryId: /angular/angular, query: "signal inputs"
Common Errors
ExpressionChangedAfterItHasBeenChecked - Schedule state changes:
ngAfterViewInit() { this.title = 'Changed'; }
constructor() {
afterNextRender(() => this.title.set('Changed'));
}
OnPush not updating - Create new references:
this.items = [...this.items, newItem];
Memory leaks - Use takeUntilDestroyed or toSignal:
data = toSignal(this.data$);
Best Practices
- Use signals for component state
- Use OnPush for presentational components
- Use computed instead of getters
- Use inject() over constructor injection
- Use readonly for inputs, outputs, queries
- Track items in @for with unique IDs
Reactive Forms Integration
@Component({
template: `<input [formControl]="nameControl">`
})
export class FormComponent {
nameControl = new FormControl('', Validators.required);
name = toSignal(this.nameControl.valueChanges, { initialValue: '' });
}
Router Integration
@Component({...})
export class UserComponent {
private route = inject(ActivatedRoute);
userId = input.required<string>();
userId = toSignal(this.route.paramMap.pipe(
map(params => params.get('id'))
));
}
Related Skills
References