Components are the fundamental building block of Angular applications. Each component encapsulates its template, styles, and logic. Understanding the component lifecycle, the host element, content projection, and the new signal-based reactivity model is essential for building efficient Angular UIs.

Key Points

  • @Component decorator: selector, template/templateUrl, styles/styleUrls, standalone: true (Angular 17+)
  • Component lifecycle: constructor → ngOnChanges → ngOnInit → ngDoCheck → ngAfterContentInit → ngAfterViewInit → ngOnDestroy
  • ngOnInit: use for initialisation logic — constructor is for DI only; ngOnDestroy for subscription cleanup
  • @Input() / input(): receive data from parent; @Output() / output(): emit events to parent via EventEmitter
  • Signals (Angular 17+): signal(value), computed(fn), effect(fn) — fine-grained reactivity without Zone.js dirty checking
  • Content projection: <ng-content select="[slot]"> — parent passes child elements into named slots
  • ViewChild / ContentChild: get reference to a child component/element — access after ngAfterViewInit
  • ChangeDetectionStrategy.OnPush: component only re-renders when @Input references change, event fires, or Observable/signal updates — dramatically improves performance
  • Host element: the component selector tag itself — @HostBinding, @HostListener, or host: {} in @Component

Angular components: OnPush + Signals, computed, effect, content projection, ViewChild

// Standalone component with Signals (Angular 17+)
@Component({
  selector: 'app-counter',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="counter">
      <button (click)="decrement()">-</button>
      <span>{{ count() }}</span>
      <button (click)="increment()">+</button>
      <p>Double: {{ doubled() }}</p>
    </div>
  `
})
export class CounterComponent {
  // Signal-based inputs (Angular 17.1+)
  initial = input(0);

  // Writable signal
  count = signal(0);

  // Computed signal — automatically re-derives when count changes
  doubled = computed(() => this.count() * 2);

  constructor() {
    // Effect — runs when signal changes, automatically tracked
    effect(() => console.log('Count changed to', this.count()));
  }

  increment() { this.count.update(n => n + 1); }
  decrement() { this.count.update(n => n - 1); }
}

// Content projection with named slots
@Component({
  selector: 'app-card',
  template: `
    <div class="card">
      <header><ng-content select="[card-title]"/></header>
      <main><ng-content/></main>
      <footer><ng-content select="[card-footer]"/></footer>
    </div>
  `
})
export class CardComponent {}

// Usage:
// <app-card>
//   <h2 card-title>My Title</h2>
//   <p>Card body content</p>
//   <button card-footer>Save</button>
// </app-card>

// ViewChild — access child after view initialisation
@Component({ template: '<input #nameInput>' })
export class FormComponent implements AfterViewInit {
  @ViewChild('nameInput') nameInput!: ElementRef<HTMLInputElement>;
  ngAfterViewInit() { this.nameInput.nativeElement.focus(); }
}

Real-World Example

ChangeDetectionStrategy.OnPush combined with Signals is the key to high-performance Angular apps. A default Angular app runs change detection on every browser event for every component in the tree. With OnPush + Signals, only components whose signals actually changed re-render — a 10x improvement in large component trees. The Angular team is moving toward Signal-based components as the default in future versions.