Angular is a component-based framework, meaning that applications are structured as a hierarchy of components. Components encapsulate HTML templates, CSS styles, and TypeScript logic, allowing developers to build modular, reusable, and maintainable user interfaces.
Understanding components is essential for any Angular developer, from beginners creating their first SPA to experts building large-scale enterprise applications. This article explores everything about Angular components—from basic creation to advanced concepts like communication, lifecycle hooks, and best practices.
What is an Angular Component?
A component in Angular is a TypeScript class that:
- Contains application logic (methods and properties).
- Uses a template to define its view (HTML).
- Can include styles for encapsulated CSS.
- Is reusable across the application.
Angular components follow the Model-View-Controller (MVC) pattern by combining data (model) and presentation (view) with logic (controller).
Creating an Angular Component
Angular CLI makes component creation simple.
Step 1: Generate a Component
ng generate component header
or shorthand:
ng g c header
This creates the following files:
src/app/header/
├─ header.component.ts
├─ header.component.html
├─ header.component.css
└─ header.component.spec.ts
.ts
→ Component logic.html
→ Template.css
→ Styles.spec.ts
→ Unit tests
Step 2: Basic Component Example
header.component.ts
:
import { Component } from '@angular/core';
@Component({
selector: 'app-header',
template: <h1>{{ title }}</h1>
,
styleUrls: ['./header.component.css']
})
export class HeaderComponent {
title = 'Welcome';
}
@Component
decorator tells Angular this is a component.selector
is the HTML tag used to include this component elsewhere:<app-header></app-header>
template
contains inline HTML (or usetemplateUrl
for a separate file).styleUrls
links component-specific styles.
Component Templates
Templates define the UI of a component. Angular provides data binding to connect the component class with the template.
Types of Data Binding
- Interpolation (
{{ property }}
)
Example:
<p>{{ title }}</p>
- Property Binding (
[property]="value"
)
Example:
<img [src]="imageUrl" alt="Image"/>
- Event Binding (
(event)="handler()"
)
Example:
<button (click)="onClick()">Click Me</button>
- Two-Way Binding (
[(ngModel)]="property"
)
Example:
<input [(ngModel)]="title" placeholder="Edit title"/>
<p>Your title: {{ title }}</p>
Component Styles
Components can have encapsulated CSS, meaning styles do not leak outside the component.
header.component.css
:
h1 {
color: #2c3e50;
font-family: Arial, sans-serif;
text-align: center;
}
- Angular View Encapsulation ensures that these styles only apply to
HeaderComponent
. - Options:
Emulated
(default),None
,ShadowDom
.
Component Lifecycle
Angular components go through lifecycle hooks, allowing developers to run code at specific stages.
Key Lifecycle Hooks
Hook | Purpose |
---|---|
ngOnInit() | Called after the component is initialized |
ngOnChanges() | Called when input properties change |
ngDoCheck() | Detects and acts on changes manually |
ngAfterViewInit() | Runs after the component’s view is initialized |
ngOnDestroy() | Cleanup before the component is destroyed |
Example:
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-header',
template: <h1>{{ title }}</h1>
})
export class HeaderComponent implements OnInit, OnDestroy {
title = 'Welcome';
ngOnInit() {
console.log('HeaderComponent initialized');
}
ngOnDestroy() {
console.log('HeaderComponent destroyed');
}
}
Input and Output Properties
Components can receive data from a parent component using @Input()
and emit events using @Output()
.
Parent to Child Communication
child.component.ts
:
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
template: <p>{{ message }}</p>
})
export class ChildComponent {
@Input() message: string = '';
}
parent.component.html
:
<app-child [message]="'Hello from Parent'"></app-child>
Child to Parent Communication
child.component.ts
:
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: <button (click)="sendMessage()">Send</button>
})
export class ChildComponent {
@Output() messageEvent = new EventEmitter<string>();
sendMessage() {
this.messageEvent.emit('Hello Parent!');
}
}
parent.component.html
:
<app-child (messageEvent)="receiveMessage($event)"></app-child>
<p>{{ parentMessage }}</p>
parent.component.ts
:
parentMessage: string = '';
receiveMessage(msg: string) {
this.parentMessage = msg;
}
Nested Components
Angular allows nesting components to build complex UIs.
Example:
<app-header></app-header>
<app-child></app-child>
<app-footer></app-footer>
- Each component handles its own logic and view.
- Promotes modularity and reusability.
Dynamic Components
Angular supports dynamic component creation at runtime using ViewContainerRef
and ComponentFactoryResolver
.
Example:
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
import { ChildComponent } from './child/child.component';
@Component({
selector: 'app-dynamic',
template: <ng-container #container></ng-container>
})
export class DynamicComponent {
@ViewChild('container', { read: ViewContainerRef }) container!: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) {}
loadChild() {
const factory = this.resolver.resolveComponentFactory(ChildComponent);
this.container.createComponent(factory);
}
}
Component Interaction Patterns
1. Service-Based Communication
Services can store shared state and allow multiple components to access it.
@Injectable({ providedIn: 'root' })
export class DataService {
data: string = 'Shared Data';
}
Components can inject this service to access or update data.
2. Observable Patterns with RxJS
Use BehaviorSubject
to share reactive data:
import { BehaviorSubject } from 'rxjs';
export class DataService {
private dataSubject = new BehaviorSubject<string>('Initial Data');
data$ = this.dataSubject.asObservable();
updateData(value: string) {
this.dataSubject.next(value);
}
}
Component Best Practices
- Single Responsibility: Each component should have a clear, specific purpose.
- Keep Components Small: Avoid large, monolithic components.
- Use Inputs and Outputs: Prefer property and event bindings for communication.
- Encapsulate Styles: Use component-specific CSS for maintainability.
- Leverage Lifecycle Hooks: Manage initialization and cleanup efficiently.
- Reuse Components: Build a library of reusable components for your app.
Advanced Component Features
- Content Projection with
<ng-content>
@Component({
selector: 'app-card',
template: `
<div class="card">
<ng-content></ng-content>
</div>
`
})
export class CardComponent {}
Usage:
<app-card>
<h2>Title</h2>
<p>Some content inside the card.</p>
</app-card>
- Structural Directives in Components
*ngIf
,*ngFor
to control rendering dynamically.
- Host Binding and Host Listener
@HostBinding('class.active') isActive = false;
@HostListener('click') toggle() { this.isActive = !this.isActive; }
- Dynamically bind classes and listen to DOM events on the host element.
Testing Components
Angular uses Karma and Jasmine for testing components.
Example test:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HeaderComponent } from './header.component';
describe('HeaderComponent', () => {
let component: HeaderComponent;
let fixture: ComponentFixture<HeaderComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({ declarations: [HeaderComponent] }).compileComponents();
fixture = TestBed.createComponent(HeaderComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should render title', () => {
const compiled = fixture.nativeElement;
expect(compiled.querySelector('h1').textContent).toContain('Welcome');
});
});
- Test creation, property binding, and template rendering.
Leave a Reply