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:

      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

      `selector

      `

      Tag name used in templates

      `templateUrl`

      /

      <span class="editor-theme-code">template</span>

      Defines the UI

      `styleUrl`

      /

      <span class="editor-theme-code">styles</span>

      Defines component CSS

      `imports

      `

      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

          `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 <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

            `Default

            `

            Checks all components often

            `OnPush

            `

            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

              `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 (<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