Skip to main content
Components are the fundamental UI building blocks of Angular applications. Each component consists of a TypeScript class decorated with @Component, an HTML template, and optional CSS styles.

The @Component Decorator

The @Component decorator marks a class as an Angular component and provides configuration metadata:
import { Component } from '@angular/core';

@Component({
  selector: 'app-hero-detail',
  standalone: true,
  template: `
    <div class="hero-detail">
      <h2>{{ hero.name }}</h2>
      <p>ID: {{ hero.id }}</p>
    </div>
  `,
  styles: [`
    .hero-detail {
      padding: 1rem;
      border: 1px solid #ccc;
    }
  `]
})
export class HeroDetailComponent {
  hero = { id: 1, name: 'Windstorm' };
}

Component Metadata

All components are standalone by default in modern Angular. Set standalone: false only if you need to use NgModules.

Essential Properties

@Component({
  // CSS selector that identifies this component in templates
  selector: 'app-user-profile',
  
  // Component is standalone (default: true)
  standalone: true,
  
  // Dependencies needed by this component's template
  imports: [CommonModule, FormsModule],
  
  // Inline template (use this OR templateUrl)
  template: `<div>{{ name }}</div>`,
  
  // External template file (use this OR template)
  templateUrl: './user-profile.component.html',
  
  // Inline styles
  styles: [`
    .profile { padding: 1rem; }
  `],
  
  // External stylesheet
  styleUrl: './user-profile.component.css',
  
  // Multiple external stylesheets
  styleUrls: [
    './user-profile.component.css',
    './user-profile.theme.css'
  ]
})
export class UserProfileComponent {
  name = 'John Doe';
}

Component Lifecycle

Angular components have a well-defined lifecycle managed by Angular. Implement lifecycle hook interfaces to tap into key moments:
1

Constructor

Called when the component class is instantiated
2

ngOnInit

Called once after the first ngOnChanges
3

ngOnChanges

Called when input properties change
4

ngDoCheck

Called during every change detection run
5

ngAfterContentInit

Called after content projection is initialized
6

ngAfterContentChecked

Called after projected content is checked
7

ngAfterViewInit

Called after component’s view is initialized
8

ngAfterViewChecked

Called after component’s view is checked
9

ngOnDestroy

Called before the component is destroyed

Lifecycle Example

import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChanges } from '@angular/core';

@Component({
  selector: 'lifecycle-demo',
  standalone: true,
  template: `
    <h3>{{ title }}</h3>
    <p>Count: {{ count }}</p>
  `
})
export class LifecycleDemoComponent implements OnInit, OnChanges, OnDestroy {
  @Input() title: string = '';
  count = 0;
  private intervalId: any;

  constructor() {
    console.log('Constructor called');
  }

  ngOnChanges(changes: SimpleChanges) {
    console.log('OnChanges called', changes);
    if (changes['title']) {
      console.log('Title changed:', changes['title'].currentValue);
    }
  }

  ngOnInit() {
    console.log('OnInit called');
    this.intervalId = setInterval(() => {
      this.count++;
    }, 1000);
  }

  ngOnDestroy() {
    console.log('OnDestroy called');
    if (this.intervalId) {
      clearInterval(this.intervalId);
    }
  }
}
Always clean up resources like subscriptions, timers, and event listeners in ngOnDestroy to prevent memory leaks.

Component Inputs and Outputs

Input Properties

Receive data from parent components:
import { Component, Input } from '@angular/core';

@Component({
  selector: 'user-card',
  standalone: true,
  template: `
    <div class="card">
      <h3>{{ userName }}</h3>
      <p>{{ userEmail }}</p>
    </div>
  `
})
export class UserCardComponent {
  // Simple input
  @Input() userName: string = '';
  
  // Input with alias
  @Input({ alias: 'email' }) userEmail: string = '';
  
  // Required input
  @Input({ required: true }) userId!: number;
  
  // Input with transform function
  @Input({ transform: (value: string) => value.toUpperCase() })
  department: string = '';
}
Usage in parent template:
<user-card 
  [userName]="user.name" 
  [email]="user.email"
  [userId]="user.id"
  [department]="user.dept">
</user-card>

Output Properties

Emit events to parent components:
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'counter-button',
  standalone: true,
  template: `
    <button (click)="handleClick()">{{ label }}</button>
  `
})
export class CounterButtonComponent {
  @Output() counterChange = new EventEmitter<number>();
  private count = 0;
  label = 'Click me';

  handleClick() {
    this.count++;
    this.counterChange.emit(this.count);
  }
}
Parent component usage:
<counter-button (counterChange)="onCounterChange($event)"></counter-button>

View Encapsulation

Control how component styles are scoped:
import { Component, ViewEncapsulation } from '@angular/core';

@Component({
  selector: 'styled-component',
  standalone: true,
  template: `<div class="container">Content</div>`,
  styles: [`
    .container { color: blue; }
  `],
  // Options: Emulated (default), None, ShadowDom
  encapsulation: ViewEncapsulation.Emulated
})
export class StyledComponent { }

Emulated

Scopes styles to component (default)

None

Styles apply globally

ShadowDom

Uses native Shadow DOM

Change Detection Strategy

Optimize performance with OnPush change detection:
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';

interface User {
  id: number;
  name: string;
}

@Component({
  selector: 'user-list',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div *ngFor="let user of users">
      {{ user.name }}
    </div>
  `
})
export class UserListComponent {
  @Input() users: User[] = [];
}
OnPush only checks the component when:
  • Input references change
  • Events are triggered from the component or its children
  • Observables emit new values with the async pipe
  • Manually triggered with ChangeDetectorRef.markForCheck()

Host Element Binding

Bind to the component’s host element:
import { Component } from '@angular/core';

@Component({
  selector: 'highlighted-box',
  standalone: true,
  template: `<ng-content></ng-content>`,
  host: {
    'class': 'box highlighted',
    '[class.active]': 'isActive',
    '[attr.role]': '"region"',
    '(click)': 'handleClick()'
  }
})
export class HighlightedBoxComponent {
  isActive = true;

  handleClick() {
    this.isActive = !this.isActive;
  }
}

Best Practices

  1. Keep components focused - Single responsibility principle
  2. Use OnPush when possible for better performance
  3. Implement lifecycle hooks only when needed
  4. Clean up in ngOnDestroy - Prevent memory leaks
  5. Use standalone components for better tree-shaking
  6. Leverage signals for reactive state management

Next Steps

Templates

Learn template syntax and bindings

Directives

Extend component behavior with directives

Build docs developers (and LLMs) love