Components Modules
6 min read- Angular Components & Modules
- Components
- Component Basics
- Component Lifecycle Hooks
- Component Communication
- ViewChild & ContentChild
- Standalone Components (Angular 14+)
- Modules
- NgModule Basics
- Feature Modules
- Shared Module
- Lazy Loading Modules
- Change Detection
- Default Change Detection
- OnPush Change Detection
- Manual Change Detection
- Dynamic Components
- Questions & Answers
Angular Components & Modules
Complete guide to Angular components, modules, and architecture.
Components
Component Basics
import { Component } from '@angular/core';
@Component({
selector: 'app-user-profile',
templateUrl: './user-profile.component.html',
styleUrls: ['./user-profile.component.scss'],
// Or inline
// template: `<div>{{user.name}}</div>`,
// styles: [`h1 { color: blue; }`]
})
export class UserProfileComponent {
user = { name: 'John', age: 30 };
greet() {
console.log(`Hello, ${this.user.name}`);
}
}
Component Lifecycle Hooks
import { Component, OnInit, OnDestroy, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-lifecycle',
template: `<div>{{data}}</div>`
})
export class LifecycleComponent implements OnInit, OnChanges, OnDestroy {
@Input() data: string;
// 1. Constructor
constructor() {
console.log('Constructor');
}
// 2. OnChanges - when input properties change
ngOnChanges(changes: SimpleChanges) {
console.log('OnChanges', changes);
}
// 3. OnInit - after first ngOnChanges
ngOnInit() {
console.log('OnInit - component initialized');
// Initialize data, subscribe to observables
}
// 4. DoCheck - during every change detection
ngDoCheck() {
console.log('DoCheck');
}
// 5. AfterContentInit - after content projection
ngAfterContentInit() {
console.log('AfterContentInit');
}
// 6. AfterContentChecked - after projected content checked
ngAfterContentChecked() {
console.log('AfterContentChecked');
}
// 7. AfterViewInit - after view initialized
ngAfterViewInit() {
console.log('AfterViewInit');
}
// 8. AfterViewChecked - after view checked
ngAfterViewChecked() {
console.log('AfterViewChecked');
}
// 9. OnDestroy - cleanup
ngOnDestroy() {
console.log('OnDestroy - cleanup subscriptions');
// Unsubscribe from observables, detach event handlers
}
}
Component Communication
// Parent to Child: @Input
@Component({
selector: 'app-child',
template: `<div>{{title}}</div>`
})
export class ChildComponent {
@Input() title: string;
@Input() data: any;
}
// Parent template
`<app-child [title]="parentTitle" [data]="parentData"></app-child>`
// Child to Parent: @Output
@Component({
selector: 'app-child',
template: `<button (click)="sendData()">Send</button>`
})
export class ChildComponent {
@Output() dataEmitted = new EventEmitter<string>();
sendData() {
this.dataEmitted.emit('Hello from child');
}
}
// Parent template
`<app-child (dataEmitted)="onDataReceived($event)"></app-child>`
// Parent class
onDataReceived(data: string) {
console.log('Received:', data);
}
ViewChild & ContentChild
@Component({
selector: 'app-parent',
template: `
<app-child #childComponent></app-child>
<div #myDiv>Content</div>
`
})
export class ParentComponent implements AfterViewInit {
@ViewChild('childComponent') child: ChildComponent;
@ViewChild('myDiv') divElement: ElementRef;
@ViewChildren(ChildComponent) children: QueryList<ChildComponent>;
ngAfterViewInit() {
// Access child component
this.child.someMethod();
// Access DOM element
this.divElement.nativeElement.style.color = 'red';
// Access all children
this.children.forEach(child => console.log(child));
}
}
// Content projection
@Component({
selector: 'app-container',
template: `
<div class="header">
<ng-content select="[header]"></ng-content>
</div>
<div class="body">
<ng-content></ng-content>
</div>
`
})
export class ContainerComponent implements AfterContentInit {
@ContentChild('headerContent') header: ElementRef;
ngAfterContentInit() {
console.log('Projected content:', this.header);
}
}
// Usage
`<app-container>
<div header>Header Content</div>
<div>Body Content</div>
</app-container>`
Standalone Components (Angular 14+)
import { Component } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
@Component({
selector: 'app-standalone',
standalone: true,
imports: [CommonModule, FormsModule],
template: `
<div *ngIf="show">
<input [(ngModel)]="name">
<p>{{name}}</p>
</div>
`
})
export class StandaloneComponent {
show = true;
name = '';
}
// Bootstrap standalone component
import { bootstrapApplication } from '@angular/platform-browser';
bootstrapApplication(StandaloneComponent);
Modules
NgModule Basics
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { AppComponent } from './app.component';
import { UserComponent } from './user/user.component';
import { SharedModule } from './shared/shared.module';
@NgModule({
declarations: [
AppComponent,
UserComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule,
SharedModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Feature Modules
// Feature module
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule, Routes } from '@angular/router';
import { UsersComponent } from './users.component';
import { UserDetailComponent } from './user-detail.component';
const routes: Routes = [
{ path: '', component: UsersComponent },
{ path: ':id', component: UserDetailComponent }
];
@NgModule({
declarations: [
UsersComponent,
UserDetailComponent
],
imports: [
CommonModule,
RouterModule.forChild(routes)
]
})
export class UsersModule { }
Shared Module
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HighlightDirective } from './directives/highlight.directive';
import { PhonePipe } from './pipes/phone.pipe';
import { CardComponent } from './components/card.component';
@NgModule({
declarations: [
HighlightDirective,
PhonePipe,
CardComponent
],
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule
],
exports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
HighlightDirective,
PhonePipe,
CardComponent
]
})
export class SharedModule { }
Lazy Loading Modules
// app-routing.module.ts
const routes: Routes = [
{
path: 'users',
loadChildren: () => import('./users/users.module').then(m => m.UsersModule)
},
{
path: 'admin',
loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
canLoad: [AuthGuard]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
Change Detection
Default Change Detection
@Component({
selector: 'app-default',
template: `<div>{{data}}</div>`
})
export class DefaultComponent {
data = 'Initial';
// Angular checks this component every time change detection runs
}
OnPush Change Detection
import { Component, ChangeDetectionStrategy, Input } from '@angular/core';
@Component({
selector: 'app-onpush',
template: `<div>{{data.value}}</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class OnPushComponent {
@Input() data: { value: string };
// Only checks when:
// 1. Input reference changes
// 2. Event handler fires
// 3. Observable emits (with async pipe)
// 4. Manual detection (ChangeDetectorRef)
}
Manual Change Detection
import { Component, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-manual',
template: `<div>{{data}}</div>`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ManualComponent {
data: any;
constructor(private cdr: ChangeDetectorRef) {}
updateData() {
this.data = { value: 'New data' };
this.cdr.markForCheck(); // Mark for check
// or
this.cdr.detectChanges(); // Run change detection immediately
}
ngOnDestroy() {
this.cdr.detach(); // Detach from change detection
}
}
Dynamic Components
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver } from '@angular/core';
@Component({
selector: 'app-dynamic',
template: `<ng-template #container></ng-template>`
})
export class DynamicComponent {
@ViewChild('container', { read: ViewContainerRef }) container: ViewContainerRef;
constructor(private resolver: ComponentFactoryResolver) {}
loadComponent() {
// Clear existing
this.container.clear();
// Create component
const factory = this.resolver.resolveComponentFactory(DynamicChildComponent);
const componentRef = this.container.createComponent(factory);
// Set inputs
componentRef.instance.data = 'Some data';
// Subscribe to outputs
componentRef.instance.dataEmitted.subscribe(data => {
console.log('Received:', data);
});
}
// Angular 13+ - simplified
loadComponentSimple() {
const componentRef = this.container.createComponent(DynamicChildComponent);
componentRef.instance.data = 'Some data';
}
}
Questions & Answers
A: Constructor is for dependency injection and simple initialization. ngOnInit is called after Angular sets up input properties and is the right place for initialization logic, API calls, and subscriptions.
- Component relies only on inputs
- Inputs are immutable
- Component uses observables with async pipe
- You want to manually control change detection
A: Use OnPush for performance optimization when:
- Unsubscribe from observables
- Detach event handlers
- Clear intervals/timeouts
- Cancel pending HTTP requests
A: Clean up resources to prevent memory leaks:
- Services with Subject/BehaviorSubject
- State management (NgRx, Akita)
- Route parameters
- Local storage
- Query parameters
A: Use:
A: ViewChild queries template of the component itself. ContentChild queries projected content (ng-content). Use ViewChild for internal elements, ContentChild for projected elements.
- Use OnPush change detection
- Lazy load modules
- TrackBy with ngFor
- Unsubscribe from observables
- Avoid function calls in templates
- Use pure pipes
- Virtual scrolling for large lists
A:
declarations: Components, directives, pipes that belong to this moduleimports: Other modules needed by this moduleexports: Make declarations available to other modules
A:
A: <ng-content> allows parent to inject content into child template. Use select attribute for multiple slots. Useful for creating reusable container components.
A: Angular 14+ feature that eliminates need for NgModule. Components import dependencies directly. Simpler for small apps and component libraries.
A: Use ngOnChanges hook which receives SimpleChanges object showing previous and current values of changed inputs.