Skip to main content

Part 2 — Structuring User Interfaces with Components


Angular applications are built from components — small, isolated units responsible for rendering part of the UI and handling its logic. Each component controls a section of the screen, can receive data, can output events, and participates in a hierarchical tree of parent and child components.

In this chapter you will learn:

  • How to create components
  • How to connect a component to its HTML template and CSS
  • How to display dynamic data
  • How to control what is shown using Angular’s modern control-flow syntax
  • How components communicate with each other
  • How to style and encapsulate component-specific CSS
  • How to work with component lifecycle hooks
  • How Angular handles change detection

You will also see short notes explaining how older Angular versions handled these tasks, so you understand both modern best practice and past conventions.


1. Anatomy of an Angular Component

A component consists of:

  • TypeScript class (UI logic)
  • template (.html)
  • style sheet (.css)

Example:

import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrl: './app.component.css',
  imports: [RouterOutlet]
})
export class AppComponent {
  title = 'World';
}

Key metadata properties

Property

Purpose

selector

Tag name used in templates

templateUrl 

/

 template

Defines the UI

styleUrl 

/

 styles

Defines component CSS

imports

Standalone components this one depends on

Modern Angular (v16+) note

Standalone components are now the standard.
They use the imports array instead of NgModules.

Legacy note

Older projects used NgModules and did not include the imports array in the @Component decorator.


3. Creating a Component with the CLI

Inside your Angular project folder:

ng generate component product-list

This generates:

product-list/
  product-list.component.ts
  product-list.component.html
  product-list.component.css
  product-list.component.spec.ts

To use your new component, import it into another component:

import { ProductListComponent } from './product-list/product-list.component';

@Component({
  selector: 'app-root',
  imports: [RouterOutlet, ProductListComponent],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {}

And place its selector in the HTML:

<app-product-list />

4. Displaying Data in the Template

Angular templates support multiple ways to bring component data into the UI.

4.1 Interpolation

<h1>Hello, {{ title }}</h1>

4.2 Property Binding

<h1 [innerText]="title"></h1>

Important

Property binding targets DOM properties, not HTML attributes.


5. Angular’s Modern Control Flow Syntax (@if, @for, @switch)

Angular 17 introduced a new built-in control-flow syntax that is:

  • More readable
  • Faster
  • Closer to JavaScript
  • Smaller bundle size compared to directive-based templates

5.1 @if (conditional rendering)

@if (products.length > 0) {
  <h1>Products ({{ products.length }})</h1>
} @else {
  <p>No products found!</p>
}

Legacy note (pre-Angular 17)

*ngIf was used:

<h1 *ngIf="products.length > 0">Products</h1>

5.2 @for (looping)

<ul>
  @for (product of products; track product.id) {
    <li>{{ product.title }}</li>
  } @empty {
    <p>No products found</p>
  }
</ul>

Why track is important

It helps Angular identify which DOM element corresponds to which data item, improving rendering performance.

Legacy note

Older Angular templates used *ngFor:

<li *ngFor="let product of products">{{ product.title }}</li>

5.3 @switch

@switch (product.title) {
  @case ('Keyboard') { 🎹 }
  @case ('Microphone') { 🎤 }
  @default { 📦 }
}

Legacy note

Previously:

<div [ngSwitch]="product.title">
  <p *ngSwitchCase="'Keyboard'"></p>
  <p *ngSwitchDefault></p>
</div>

6. Adding Interactivity: Events and User Input

To react to user actions, Angular uses event binding.

<li (click)="selectedProduct = product">{{ product.title }}</li>
  • (click) — target event
  • The right-hand expression — TypeScript code executed when clicked

All browser events are supported.


7. Styling Components

7.1 Class Binding

<p [class.selected]="isSelected"></p>

You can pass an entire object with boolean conditions:

currentClasses = {
  selected: true,
  highlighted: false
};
<p [class]="currentClasses"></p>

7.2 Style Binding

<p [style.color]="'red'"></p>
<p [style.width.px]="120"></p>

Object syntax:

currentStyles = {
  color: 'red',
  width: '100px'
};
<p [style]="currentStyles"></p>

7.3 View Encapsulation

Angular normally isolates component CSS:

encapsulation: ViewEncapsulation.Emulated // default

Other modes:

Mode

Description

Emulated

Scoped CSS (default)

None

CSS leaks globally

ShadowDom

Uses browser’s native Shadow DOM

Example turning isolation off:

encapsulation: ViewEncapsulation.None

Use this carefully.


8. Component Communication

Components interact via:

  • Inputs (data goes down into a child)
  • Outputs (events go up from a child to a parent)

8.1 Passing Data Down (Input Binding)

Child component:

import { Component, input } from '@angular/core';
import { Product } from '../product';

@Component({
  selector: 'app-product-detail',
  templateUrl: './product-detail.component.html'
})
export class ProductDetailComponent {
  product = input<Product>();
}

Parent template:

<app-product-detail [product]="selectedProduct" />

Required inputs

product = input.required<Product>();

8.2 Sending Events Up (Output Binding)

Child:

import { output } from '@angular/core';

added = output<Product>();

addToCart() {
  this.added.emit(this.product()!);
}

Parent:

<app-product-detail
  [product]="selectedProduct"
  (added)="onAdded($event)"
/>

Parent TS:

onAdded(product: Product) {
  alert(`${product.title} added to cart!`);
}

Legacy note

In older Angular:

@Input() product!: Product;
@Output() added = new EventEmitter<Product>();

9. Template Reference Variables

Used to access a component or element instance:

<app-product-detail #detail [product]="selectedProduct"></app-product-detail>

<p>{{ detail.product()!.title }}</p>

A reference variable exposes the public API of the component.


10. Accessing Child Components from TypeScript

Instead of using a template variable, we can query the child via viewChild.

Parent component:

import { viewChild, AfterViewInit } from '@angular/core';
import { ProductDetailComponent } from '../product-detail/product-detail.component';

export class ProductListComponent implements AfterViewInit {
  productDetail = viewChild(ProductDetailComponent);

  ngAfterViewInit(): void {
    console.log('Child product:', this.productDetail()?.product());
  }
}

Legacy note

Previously:

@ViewChild(ProductDetailComponent) productDetail!: ProductDetailComponent;

11. Change Detection Strategy

Angular refreshes the UI when it detects changes.
Two strategies are available:

Strategy

Behavior

Default

Checks all components often

OnPush

Only updates when @Input references change or events occur

Enable OnPush:

changeDetection: ChangeDetectionStrategy.OnPush

OnPush dramatically improves performance for:

  • Large lists
  • Data-heavy UIs
  • Dashboard-like interfaces

12. Component Lifecycle Hooks

Lifecycle hooks let you run code at specific moments.

Hook

When it runs

ngOnInit

After inputs are set; component initialized

ngOnDestroy

Before component is removed

ngOnChanges

Whenever an input value changes

ngAfterViewInit

After child components and views are ready

12.1 ngOnInit

export class ProductDetailComponent implements OnInit {
  ngOnInit() {
    console.log('Product:', this.product());
  }
}

Why not use the constructor?

Inputs are not yet assigned when the constructor runs.


12.2 ngOnDestroy

Used for cleanup:

export class ProductDetailComponent implements OnDestroy {
  ngOnDestroy(): void {
    console.log('Component destroyed');
  }
}

Common use cases:

  • Clearing timers
  • Unsubscribing from RxJS streams
  • Releasing event listeners

Alternative: DestroyRef (modern, recommended)

constructor(destroyRef: DestroyRef) {
  destroyRef.onDestroy(() => {
    // cleanup
  });
}

12.3 ngOnChanges

Triggered when an input changes:

ngOnChanges(changes: SimpleChanges): void {
  const product = changes['product'];
  if (!product.isFirstChange()) {
    console.log('Old:', product.previousValue);
    console.log('New:', product.currentValue);
  }
}

Modern alternative

Signals can track changes more elegantly (Chapter 7).


12.4 ngAfterViewInit

Useful for reading child component data:

ngAfterViewInit() {
  console.log(this.productDetail()?.product());
}

13. Summary

In this chapter, you learned:

  • How to create standalone components
  • How to connect templates, classes, and styles
  • How to use modern control flow (@if@for@switch)
  • How to display and update data
  • How to handle events and user interactions
  • How components communicate (inputs & outputs)
  • How to use template reference variables
  • How to manage CSS with view encapsulation
  • How to optimize updates with change detection
  • How to hook into the component lifecycle