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' };
}
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:
Constructor
Called when the component class is instantiated
ngOnInit
Called once after the first ngOnChanges
ngOnChanges
Called when input properties change
ngDoCheck
Called during every change detection run
ngAfterContentInit
Called after content projection is initialized
ngAfterContentChecked
Called after projected content is checked
ngAfterViewInit
Called after component’s view is initialized
ngAfterViewChecked
Called after component’s view is checked
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.
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
Keep components focused - Single responsibility principle
Use OnPush when possible for better performance
Implement lifecycle hooks only when needed
Clean up in ngOnDestroy - Prevent memory leaks
Use standalone components for better tree-shaking
Leverage signals for reactive state management
Next Steps
Templates Learn template syntax and bindings
Directives Extend component behavior with directives