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:
- A TypeScript class (UI logic)
- A template (
<span class="editor-theme-code">.html</span>) - A style sheet (
<span class="editor-theme-code">.css</span>)
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 |
|---|---|
| Tag name used in templates |
/ | Defines the UI |
/ | Defines component CSS |
| Standalone components this one depends on |
Modern Angular (v16+) note
Standalone components are now the standard.
They use the <span class="editor-theme-code">imports</span> array instead of NgModules.
Legacy note
Older projects used NgModules and did not include the <span class="editor-theme-code">imports</span> array in the <span class="editor-theme-code">@Component</span> 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)
<span class="editor-theme-code">*ngIf</span> 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 <span class="editor-theme-code">track</span> is important
It helps Angular identify which DOM element corresponds to which data item, improving rendering performance.
Legacy note
Older Angular templates used <span class="editor-theme-code">*ngFor</span>:
<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>
<span class="editor-theme-code">(click)</span>— 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 |
|---|---|
| Scoped CSS (default) |
| CSS leaks globally |
| 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 <span class="editor-theme-code">viewChild</span>.
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 |
|---|---|
| Checks all components often |
| Only updates when @Input references change or events occur |
Enable <span class="editor-theme-code">OnPush</span>:
changeDetection: ChangeDetectionStrategy.OnPush
<span class="editor-theme-code">OnPush</span> 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 |
|---|---|
| After inputs are set; component initialized |
| Before component is removed |
| Whenever an input value changes |
| 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 (
<span class="editor-theme-code">@if</span>,<span class="editor-theme-code">@for</span>,<span class="editor-theme-code">@switch</span>) - 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