Component Basics
Defining a Component
Components are the core building blocks of an Angular application, encapsulating the view (template), styles, and logic (class). The @Component decorator is used to define a component.
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
template: `
<h1>Welcome to Angular!</h1>
<button (click)="onButtonClick()">Click me!</button>
`,
styles: [`
h1 {
color: blue;
}
`]
})
export class AppComponent {
onButtonClick() {
console.log('Button clicked!');
}
}Component Tree
Angular applications are structured as a tree of components, with the root component typically being AppComponent. Child components can be nested within parent component templates.
<!-- app.component.html -->
<app-child></app-child>// child.component.ts
@Component({
selector: 'app-child',
template: `<h2>I'm a child component.</h2>`
})
export class ChildComponent { }Component Templates
Template Syntax
Angular templates support various syntaxes, including interpolation ({{ expression }}), property binding ([property]="expression"), event binding ((event)="method($event)"), and directives.
<!-- app.component.html -->
<h1>{{ title }}</h1>
<input [(ngModel)]="name" placeholder="Enter your name">
<button (click)="logName()">Log Name</button>*ngIf and *ngFor
The ngIf directive is used for conditional rendering, while ngFor is used for list rendering.
<!-- app.component.html -->
<div *ngIf="showMessage; else noMessage">
Hello, {{ name }}!
</div>
<ng-template #noMessage>
Please enter your name.
</ng-template>
<ul>
<li *ngFor="let item of items">{{ item }}</li>
</ul>Component Interaction
Data Binding
Parent components pass data to child components using property binding, while child components emit events to parents using event binding.
<!-- parent.component.html -->
<app-child [data]="parentData" (childEvent)="handleChildEvent($event)"></app-child>// child.component.ts
@Input() data: any;
@Output() childEvent = new EventEmitter();
onButtonClick() {
this.childEvent.emit(this.data);
}Service Sharing
The best way to share data and services between components is through dependency injection.
// shared.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class SharedService {
data: any;
}Injecting a Service in a Component:
// component.ts
import { Component } from '@angular/core';
import { SharedService } from './shared.service';
@Component({
selector: 'app-component',
template: `...`
})
export class Component {
constructor(private sharedService: SharedService) {}
}Lifecycle Hooks
Angular components have several lifecycle hooks that are invoked at different stages of their lifecycle.
import { Component, OnInit, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-lifecycle',
template: `...`
})
export class LifecycleComponent implements OnInit, OnChanges {
ngOnChanges(changes: SimpleChanges): void {
console.log('Component input properties have changed.');
}
ngOnInit(): void {
console.log('Component has been initialized.');
}
}Input and Output Properties
Input properties receive data from parent components, while output properties emit events to parent components.
// child.component.ts
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `...`
})
export class ChildComponent {
@Input() message: string;
@Output() messageSent = new EventEmitter<string>();
sendMessage() {
this.messageSent.emit('Hello from child!');
}
}View Containers
ViewContainerRef and TemplateRef enable dynamic insertion and removal of views.
import { Component, ViewContainerRef, TemplateRef } from '@angular/core';
@Component({
selector: 'app-dynamic',
template: `...`
})
export class DynamicComponent {
constructor(private viewContainer: ViewContainerRef, private templateRef: TemplateRef<any>) {}
addView() {
this.viewContainer.createEmbeddedView(this.templateRef);
}
removeView() {
this.viewContainer.clear();
}
}Custom Directives
Custom directives extend the behavior of HTML elements.
import { Directive, ElementRef, Renderer2 } from '@angular/core';
@Directive({
selector: '[appHighlight]'
})
export class HighlightDirective {
constructor(private el: ElementRef, private renderer: Renderer2) {
this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow');
}
}Unit Testing
Use Karma and Jasmine for unit testing Angular components.
// component.spec.ts
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component } from './component';
describe('Component', () => {
let component: Component;
let fixture: ComponentFixture<Component>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [Component]
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(Component);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});Two-Way Data Binding
Angular’s two-way data binding, facilitated by the [(ngModel)] directive, simplifies synchronization between form controls and component properties.
<!-- app.component.html -->
<input type="text" [(ngModel)]="username" placeholder="Enter your username">Pure Functions and Change Detection
Angular’s change detection mechanism relies on pure functions and dirty checking. Template updates depend on input property changes, and pure functions ensure consistent outputs for the same inputs, reducing unnecessary rendering.
// pure-function.component.ts
import { Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-pure-function',
template: `...`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class PureFunctionComponent {
// Ensure the method is a pure function
calculateValue(input: number): number {
return input * 2;
}
}Asynchronous Programming
Angular components often handle asynchronous data flows, such as HTTP requests. The RxJS library provides powerful tools for these scenarios.
// async.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiService } from './api.service';
@Component({
selector: 'app-async',
template: `...`
})
export class AsyncComponent implements OnInit {
public data$: Observable<any>;
constructor(private apiService: ApiService) {}
ngOnInit() {
this.data$ = this.apiService.getData().pipe(
map(response => response.data)
);
}
}Dynamic Component Loading
Dynamic component loading allows runtime determination of which components to render, ideal for highly customizable UIs.
// dynamic-loading.component.ts
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
import { ComponentFactoryResolver, ComponentRef } from '@angular/core';
import { DynamicComponent } from './dynamic.component';
@Component({
selector: 'app-dynamic-loading',
template: `...`
})
export class DynamicLoadingComponent {
@ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) {}
loadComponent() {
const factory = this.resolver.resolveComponentFactory(DynamicComponent);
this.container.createComponent(factory);
}
}Local State Management
While NgRx or Redux are suitable for global state management, simple local state management is sufficient at the component level.
// local-state.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-local-state',
template: `...`
})
export class LocalStateComponent {
public count = 0;
increment() {
this.count++;
}
}Error Handling
Error handling is critical for robust applications. Angular provides multiple mechanisms to capture and handle errors.
// error-handling.component.ts
import { Component, OnInit } from '@angular/core';
import { catchError, tap } from 'rxjs/operators';
import { throwError } from 'rxjs';
@Component({
selector: 'app-error-handling',
template: `...`
})
export class ErrorHandlingComponent implements OnInit {
public data: any;
constructor(private apiService: ApiService) {}
ngOnInit() {
this.apiService.getData().pipe(
tap(data => this.data = data),
catchError(error => {
console.error('Error fetching data:', error);
return throwError(error);
})
).subscribe();
}
}Performance Optimization
- Change Detection Strategy: Use
ChangeDetectionStrategy.OnPushto reduce unnecessary change detection. - Lazy Loading: Improve startup speed with module lazy loading.
- Preloading Strategy: Preload modules or components that may be needed soon.
Internationalization and Localization
Angular provides robust support for internationalization (i18n), including date, currency formatting, and translation.
// i18n.component.ts
import { Component } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
@Component({
selector: 'app-i18n',
template: `...`
})
export class I18nComponent {
constructor(private translateService: TranslateService) {
this.translateService.use('en'); // Set default language to English
}
}Web Workers
While not directly related to components, using Web Workers in Angular applications can offload CPU-intensive tasks, enhancing user experience.
// web-worker.component.ts
import { Component, OnInit } from '@angular/core';
import { WorkerService } from './worker.service';
@Component({
selector: 'app-web-worker',
template: `...`
})
export class WebWorkerComponent implements OnInit {
constructor(private workerService: WorkerService) {}
ngOnInit() {
this.workerService.runTask();
}
}Code Splitting and Modularization
Organizing and splitting code effectively improves maintainability and performance.
// app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { LazyModule } from './lazy/lazy.module';
const routes: Routes = [
{ path: 'lazy', loadChildren: () => LazyModule }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }Upgrading and Migration
As Angular evolves, understanding how to upgrade and migrate applications is essential.
# Upgrade Angular version
ng update @angular/core @angular/cli



