Components & Modules
Angular Components & Modules Components are the building blocks of Angular applications. Every Angular app has at least one component, the root component. Modul…
Angular Components & Modules
Components are the building blocks of Angular applications. Every Angular app has at least one component, the root component. Modules organize components and dependencies into cohesive blocks of functionality.
Component Fundamentals
A component controls a patch of screen called a view. It consists of a TypeScript class, an HTML template, and CSS styles.
// Basic component
import { Component } from '@angular/core';
@Component({
selector: 'app-hello',
template: `
<div>
<h1>{{ title }}</h1>
<p>{{ description }}</p>
<button (click)="handleClick()">Click Me</button>
</div>
`,
styles: [`
h1 {
color: blue;
font-size: 24px;
}
`]
})
export class HelloComponent {
title = 'Hello Angular';
description = 'Welcome to Angular components';
handleClick() {
console.log('Button clicked!');
}
}
// Component with external template and styles
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.css']
})
export class UserProfileComponent {
user = {
name: 'John Doe',
email: 'john@example.com',
age: 30
};
isEditing = false;
toggleEdit() {
this.isEditing = !this.isEditing;
}
saveUser() {
console.log('Saving user:', this.user);
this.isEditing = false;
}
}Component Lifecycle Hooks
Angular provides lifecycle hooks that give visibility into key moments in the component lifecycle.
import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges,
AfterViewInit, AfterContentInit, Input } from '@angular/core';
@Component({
selector: 'app-lifecycle-demo',
template: `<div>{{ data }}</div>`
})
export class LifecycleDemoComponent implements OnInit, OnDestroy, OnChanges,
AfterViewInit, AfterContentInit {
@Input() data: string = '';
private subscription: any;
// Called once after first ngOnChanges
ngOnInit(): void {
console.log('ngOnInit: Component initialized');
// Initialize data, subscribe to observables
this.subscription = someObservable$.subscribe(data => {
this.data = data;
});
}
// Called when input properties change
ngOnChanges(changes: SimpleChanges): void {
console.log('ngOnChanges:', changes);
if (changes['data']) {
console.log('Data changed from', changes['data'].previousValue,
'to', changes['data'].currentValue);
}
}
// Called after component's view has been initialized
ngAfterViewInit(): void {
console.log('ngAfterViewInit: View initialized');
// Access @ViewChild elements here
}
// Called after content has been projected
ngAfterContentInit(): void {
console.log('ngAfterContentInit: Content initialized');
// Access @ContentChild elements here
}
// Called just before component is destroyed
ngOnDestroy(): void {
console.log('ngOnDestroy: Cleaning up');
// Unsubscribe, detach event handlers
if (this.subscription) {
this.subscription.unsubscribe();
}
}
}Component Communication
@Input() and @Output()
// Child component
import { Component, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<div>
<h2>{{ title }}</h2>
<p>Count: {{ count }}</p>
<button (click)="increment()">Increment</button>
<button (click)="sendMessage()">Send to Parent</button>
</div>
`
})
export class ChildComponent {
// Input from parent
@Input() title: string = '';
@Input() count: number = 0;
// Output to parent
@Output() countChange = new EventEmitter<number>();
@Output() message = new EventEmitter<string>();
increment() {
this.count++;
this.countChange.emit(this.count);
}
sendMessage() {
this.message.emit('Hello from child!');
}
}
// Parent component
@Component({
selector: 'app-parent',
template: `
<app-child
[title]="parentTitle"
[count]="parentCount"
(countChange)="onCountChange($event)"
(message)="onMessage($event)"
></app-child>
<p>Parent count: {{ parentCount }}</p>
<p>Last message: {{ lastMessage }}</p>
`
})
export class ParentComponent {
parentTitle = 'Child Component';
parentCount = 0;
lastMessage = '';
onCountChange(newCount: number) {
this.parentCount = newCount;
}
onMessage(msg: string) {
this.lastMessage = msg;
}
}ViewChild and ContentChild
import { Component, ViewChild, ContentChild, AfterViewInit, ElementRef } from '@angular/core';
@Component({
selector: 'app-parent',
template: `
<input #myInput type="text" />
<button (click)="focusInput()">Focus Input</button>
<app-child>
<p #projectedContent>This is projected content</p>
</app-child>
`
})
export class ParentComponent implements AfterViewInit {
// Access child component or element
@ViewChild('myInput') inputElement!: ElementRef;
@ViewChild(ChildComponent) childComponent!: ChildComponent;
// Access projected content
@ContentChild('projectedContent') content!: ElementRef;
ngAfterViewInit() {
// Can access ViewChild and ContentChild here
console.log('Input element:', this.inputElement.nativeElement);
}
focusInput() {
this.inputElement.nativeElement.focus();
}
}NgModules
NgModules consolidate components, directives, and pipes into cohesive blocks of functionality. Every Angular app has at least one module, the root module.
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
// Root module
@NgModule({
declarations: [
// Components, directives, pipes that belong to this module
AppComponent,
HeaderComponent,
FooterComponent
],
imports: [
// Other modules whose exported classes are needed
BrowserModule,
FormsModule,
ReactiveFormsModule,
HttpClientModule,
SharedModule,
FeatureModule
],
providers: [
// Services available app-wide
AuthService,
DataService
],
bootstrap: [AppComponent] // Root component
})
export class AppModule { }
// Feature module
@NgModule({
declarations: [
UserListComponent,
UserDetailComponent
],
imports: [
CommonModule,
SharedModule
],
exports: [
// Make these available to importing modules
UserListComponent
]
})
export class UserModule { }
// Shared module
@NgModule({
declarations: [
LoadingSpinnerComponent,
ErrorMessageComponent
],
imports: [
CommonModule
],
exports: [
// Export both declarations and imported modules
CommonModule,
LoadingSpinnerComponent,
ErrorMessageComponent
]
})
export class SharedModule { }Standalone Components (Angular 14+)
Standalone components eliminate the need for NgModules, making Angular simpler and more modular.
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { ChildComponent } from './child.component';
@Component({
selector: 'app-standalone',
standalone: true, // Mark as standalone
imports: [
CommonModule,
FormsModule,
ChildComponent // Import other standalone components
],
template: `
<div>
<input [(ngModel)]="name" />
<p *ngIf="name">Hello, {{ name }}!</p>
<app-child [data]="name"></app-child>
</div>
`
})
export class StandaloneComponent {
name = '';
}
// Bootstrap standalone component
import { bootstrapApplication } from '@angular/platform-browser';
import { provideRouter } from '@angular/router';
import { provideHttpClient } from '@angular/common/http';
bootstrapApplication(StandaloneComponent, {
providers: [
provideRouter(routes),
provideHttpClient()
]
});Content Projection (ng-content)
// Card component
@Component({
selector: 'app-card',
template: `
<div class="card">
<div class="card-header">
<ng-content select="[card-header]"></ng-content>
</div>
<div class="card-body">
<ng-content></ng-content>
</div>
<div class="card-footer">
<ng-content select="[card-footer]"></ng-content>
</div>
</div>
`
})
export class CardComponent { }
// Usage
@Component({
template: `
<app-card>
<h2 card-header>Card Title</h2>
<p>This is the main card content.</p>
<button card-footer>Action</button>
</app-card>
`
})
export class AppComponent { }