Components, Templates and Signals

First lesson of the Angular course, free to read: about 18 minutes, 4 exercises and 3 quiz questions. This is the catalogue's base lesson; in the student space, it is rewritten for your profile.

Objectives

  • Create a project with the Angular CLI and understand the structure of a standalone application
  • Write a component with its template: interpolation, property and event bindings, @if and @for
  • Manage local state with signals and pass data with input() and output()

Standalone componentsTemplates and control flow blocksSignals (signal, computed)input() and output()

Creating an Angular project

Angular is a complete framework, maintained by Google and written in TypeScript: router, forms, HTTP client, testing and command-line tooling come built in. You start with the Angular CLI:

npm install -g @angular/cli   # Node.js 22.22, 24.15 or 26 (LTS recommended)
ng new boutique          # questions: stylesheet, server-side rendering, AI tools…
cd boutique
ng serve                 # http://localhost:4200, automatic reload
ng generate component liste-taches   # shortcut: ng g c liste-taches

The project created today uses Angular 22.2 with the TypeScript version it supports (6.0); it works without zone.js (change detection driven by signals and events) and its unit tests (ng test) run with Vitest. Files no longer carry a suffix: the root component is the App class in src/app/app.ts.

The entry point src/main.ts starts the application with bootstrapApplication(App, appConfig); app.config.ts declares the global providers (router, HTTP client).

Historically, components were grouped in NgModules. Recent versions make components standalone by default: each component declares what it uses in imports. You will still come across NgModules in existing projects; the principle of components remains the same.

Anatomy of a component

import { Component, signal, computed } from '@angular/core';

interface Tache { id: number; libelle: string; faite: boolean }

@Component({
  selector: 'app-liste-taches',
  template: `
    <h2>{{ titre }}</h2>
    <ul>
      @for (tache of taches(); track tache.id) {
        <li [class.faite]="tache.faite">
          <input type="checkbox" [checked]="tache.faite" (change)="basculer(tache.id)" />
          {{ tache.libelle }}
        </li>
      } @empty {
        <li>No tasks.</li>
      }
    </ul>
    @if (restantes() > 0) {
      <p>{{ restantes() }} task(s) remaining</p>
    } @else {
      <p>All done.</p>
    }
  `,
  styles: `.faite { text-decoration: line-through; }`,
})
export class ListeTaches {
  titre = 'Mes tâches';
  taches = signal<Tache[]>([
    { id: 1, libelle: 'Acheter du pain', faite: false },
    { id: 2, libelle: 'Réviser Angular', faite: true },
  ]);
  restantes = computed(() => this.taches().filter((t) => !t.faite).length);

  basculer(id: number) {
    this.taches.update((liste) =>
      liste.map((t) => (t.id === id ? { ...t, faite: !t.faite } : t)));
  }
}

The selector app-liste-taches lets you use the component in another template: <app-liste-taches />. The template can also be placed in a separate file (templateUrl), which is what the CLI does.

Template bindings

Syntax Direction Example
{{ expr }} interpolation (text) {{ produit.nom }}
[prop]="expr" property, from the component to the DOM [disabled]="panierVide()"
(evt)="instr" event, from the DOM to the component (click)="ajouter()"
[(prop)]="val" two-way binding [(ngModel)]="recherche" (with FormsModule)

Useful variants: [class.active]="estActif", [style.width.px]="largeur", [attr.aria-label]="libelle".

Conditions and lists

The control flow blocks @if, @else, @for, @empty and @switch are the modern syntax (the older *ngIf and *ngFor directives remain common in existing code). @for requires a track expression: Angular uses it to know which DOM element matches which piece of data when the list changes. Use a stable identifier (track tache.id).

Signals

A signal is a reactive value: you read it by calling it (taches()), and modify it with set(newValue) or update(fn). Angular knows which templates read which signals and updates only what depends on them. computed() derives a value from other signals, recomputed only when they change. As with any state, replace arrays and objects with new values rather than modifying them in place.

Communicating: input() and output()

@Component({
  selector: 'app-carte-produit',
  template: `
    <h3>{{ nom() }}</h3>
    <button (click)="ajouter.emit(id())">Add</button>
  `,
})
export class CarteProduit {
  id = input.required<number>();
  nom = input.required<string>();
  ajouter = output<number>();
}

The parent writes: <app-carte-produit [id]="p.id" [nom]="p.nom" (ajouter)="onAjouter($event)" />. The parent component must import CarteProduit in its imports array. The @Input() and @Output() decorators play the same role in older code.

Pipes

A pipe transforms a value for display: {{ prix | currency:'EUR' }}, {{ date | date:'dd/MM/yyyy' }}. A custom pipe is a class with a transform method; it is a pure function, ideal for a test.

Takeaways

Class + decorator + template; [ ] towards the DOM, ( ) from the DOM; @for with track; signals read by calling them; input()/output() to communicate.

Exercises

Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.

  1. CodeDifficulty 12 hints

    Summary of a task list

    Logic of the component’s resume computed. Declare the Tache interface (id number, libelle string, faite boolean) and write resumeTaches(taches: readonly Tache[]): Resume that returns { total, faites, pourcentage }, the percentage of completed tasks being rounded to the nearest integer (0 for an empty list).

    // TODO : interface Tache et type Resume
    
    function resumeTaches(taches) {
      // TODO
    }
  2. CodeDifficulty 1JavaScript in the browser2 hints

    tronquer pipe (executed logic)

    Write in JavaScript the transform method of a tronquer pipe: tronquer(texte, longueur = 20, suffixe = "…").

    • null or undefined → empty string;
    • if the text (leading and trailing whitespace removed) fits within longueur, it is returned as is (without the leading and trailing whitespace);
    • otherwise, keep the first longueur characters, remove trailing whitespace, then add the suffix.
    function tronquer(texte, longueur = 20, suffixe = "…") {
      // TODO
    }
  3. TaskDifficulty 22 hints

    First Angular component

    Create a project with ng new then a ListeTaches component (ng g c liste-taches) displayed in the root component.

    1. State: a signal initialized with these tasks:
    id,libelle,faite
    1,Acheter du pain,false
    2,Réviser Angular,true
    3,Appeler le garage,false
    1. Display them with @for (…; track tache.id) and a checkbox bound with [checked] and (change).
    2. Add a resume computed that uses your resumeTaches function, displayed as “1 / 3 tâches faites (33 %)”.
    3. Add a text field and an “Ajouter” button that adds a task with update (new identifier = largest identifier + 1). The button is disabled ([disabled]) when the field is empty.
    • The tasks are in a signal and read with taches() in the template
    • @for uses track tache.id
    • The summary is a computed, recalculated automatically
    • Adding replaces the array with update instead of using push
  4. Multiple choiceDifficulty 11 hint

    Reading a signal

    In the component, total = signal(0). How do you display its value in the template?

    • {{ total }}
    • {{ total() }}
    • {{ total.value }}
    • {{ $total }}

End-of-lesson quiz

  1. What does [disabled]="panierVide()" do?

    • It listens to the disabled event
    • It binds the element's disabled property to the value of the expression
    • It creates a disabled variable
  2. Why does @for require a track expression?

    • To sort the list
    • To associate each DOM element with a piece of data and reuse it correctly when the list changes
    • To count the elements
  3. How does a child component notify its parent of an action?

    • By modifying its input
    • By emitting an event with an output()
    • By directly calling a method of the parent

The rest of the course

  1. Services and Dependency Injection18 min
  2. RxJS, Observables and the HTTP Client20 min
  3. Reactive Forms and Validation19 min
  4. Routing, Lazy Loading and Guards18 min

Angular · Intermediate · 1 h 33 min