Angular
01 / 10

Components & Modules

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 { }

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free