Angular est-il vraiment plus compliqué que React ?

Quand on débute avec Angular, il est facile de se sentir découragé face à la multitude de concepts à assimiler. Cette complexité peut inciter à se tourner vers des frameworks comme React, qui semblent plus simples à première vue. Mais est-ce vraiment le cas ?

Abonnez-vous à notre chaîne

Pour profiter des prochaines vidéos sur Angular, abonnez-vous à la nouvelle chaîne YouTube !

Skip to content

Vous souhaitez recevoir de l'aide sur ce sujet ? rejoignez la communauté Angular.fr sur Discord.

Utiliser output() pour communiquer avec un composant parent

Utiliser les composants avec des signaux

Depuis Angular 17.x, les signaux sont introduits en tant que réactifs comme alternative aux décorateurs @Input(), @Output(), etc.

Donc, pour utiliser input(), output(), model(), contentChild(), contentChildren(), viewChild() et viewChildren(), il est essentiel de comprendre la notion des signaux.

Lisez l'article sur les signaux pour en savoir plus.

Imaginons une situation de la vie courante : vous êtes dans un restaurant et le serveur (composant enfant) doit informer la cuisine (composant parent) des commandes des clients. Le serveur "émet" l'information vers la cuisine qui peut alors la traiter.

Dans Angular, c'est exactement le même principe : un composant enfant peut envoyer des informations à son parent via un Output.

Reprenons notre exemple de gestion d'utilisateurs. Créons un composant qui permet d'éditer un utilisateur et d'informer le parent des modifications.

ts
import { Component, output } from '@angular/core';
import { User } from './user';

@Component({
  selector: 'app-user-edit',
  standalone: true,
  template: `
    <div>
      <button (click)="updateUser()">Sauvegarder</button>
    </div>
  `
})
export class UserEditComponent {
  onUserUpdate = output<User>();

  updateUser() {
    // Émettre la nouvelle valeur
    this.onUserUpdate.emit(this.user);
  }
}
ts
import { Component } from '@angular/core';
import { UserEditComponent } from './user-edit.component';
import { User } from './user.interface';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UserEditComponent],
  template: `
    <div>
      <app-user-edit (onUserUpdate)="handleUserUpdate($event)" />
      <div>Dernier utilisateur mis à jour : {{lastUpdatedUser?.name}}</div>
    </div>
  `
})
export class AppComponent {
  lastUpdatedUser?: User;

  handleUserUpdate(user: User) {
    this.lastUpdatedUser = user;
  }
}
ts
export interface User {
    id: number;
    name: string;
    username?: string;
    email: string;
    address?: {
        street: string;
        suite: string;
        city: string;
        zipcode: string;
        geo: {
            lat: string;
            lng: string;
        }
    };
    phone?: string;
    website?: string;
    company?: {
        name: string;
        catchPhrase: string;
        bs: string;
    };
}

Voici comment ça fonctionne :

  1. output<User>() crée un point de sortie qui peut émettre des valeurs de type User
  2. .emit() est utilisé pour envoyer une valeur au composant parent
  3. Dans le parent, on utilise les parenthèses () pour écouter l'output
  4. $event est une variable spéciale qui contient la valeur émise par l'output

Comprendre $event

$event représente toujours la valeur émise par l'output. Dans notre exemple :

  • Si onUserUpdate.emit(user) émet un utilisateur
  • Alors $event dans le parent sera cet objet utilisateur

C'est comme un système de messagerie :

  • Le composant enfant (UserEditComponent) est l'expéditeur
  • L'output est le canal de communication
  • Le composant parent (AppComponent) est le destinataire
  • $event est le message transmis

Utilisation avec RxJS

Angular 17.3 introduit également outputFromObservable() pour une meilleure intégration avec RxJS :

ts
import { Component } from '@angular/core';
import { User } from './user.interface';
import { Observable } from 'rxjs';
import { outputFromObservable } from '@angular/core/rxjs-interop';

@Component({
  selector: 'app-user-updates',
  standalone: true,
  template: `<div>Surveillance des mises à jour...</div>`
})
export class UserUpdatesComponent {
  private userUpdates$ = new Observable<User>(/* ... */);
  onUserUpdate = outputFromObservable(this.userUpdates$);
}

CONSEIL

Utilisez outputFromObservable() quand vous avez besoin d'exposer des valeurs provenant de flux RxJS. C'est particulièrement utile pour les mises à jour en temps réel ou les websockets.

Écoute programmatique des Outputs

La nouvelle API introduit également une interface OutputRef pour une écoute programmatique plus cohérente :

ts
import { Component, viewChild, AfterViewInit } from '@angular/core';
import { outputToObservable } from '@angular/core/rxjs-interop';
import { filter, tap } from 'rxjs';
import { UserEditComponent } from './user-edit.component';
import { User } from './user.interface';

@Component({
  selector: 'app-parent',
  standalone: true,
  imports: [UserEditComponent],
  template: `
    <div>
      <h2>Gestion des utilisateurs</h2>
      
      <!-- Écoute classique via template -->
      <app-user-edit 
        (onUserUpdate)="handleUserUpdate($event)" 
        #userEditComponent
      />

      <div class="user-updates">
        <h3>Dernières mises à jour</h3>
        <p>Via template: {{lastTemplateUpdate?.name}}</p>
        <p>Via programme: {{lastProgrammaticUpdate?.name}}</p>
      </div>
    </div>
  `
})
export class ParentComponent implements AfterViewInit {
  userEditComponent!: UserEditComponent = viewChild('UserEditComponent');

  lastTemplateUpdate?: User;
  lastProgrammaticUpdate?: User;

  // Gestion via template binding
  handleUserUpdate(user: User) {
    this.lastTemplateUpdate = user;
  }

  ngAfterViewInit() {
    // Écoute programmatique de l'output
    outputToObservable(this.userEditComponent().onUserUpdate)
      .pipe(
        // Exemple de transformation avec RxJS
        filter(user => user.name.length > 3),
        tap(user => console.log('Mise à jour utilisateur:', user))
      )
      .subscribe(user => {
        this.lastProgrammaticUpdate = user;
      });
  }
}

Personnaliser le nom des outputs

Il est possible de personnaliser le nom de l'événement utilisé dans le template en passant un paramètre à la fonction output() :

ts
import { Component, output } from '@angular/core';
import { User } from './user';

@Component({
  selector: 'app-user-edit',
  standalone: true,
  template: `
    <button (click)="updateUser()">Sauvegarder</button>
  `
})
export class UserEditComponent {
  // L'output sera accessible via (userModified) dans le template
  onUserUpdate = output<User>({alias: 'userModified'});

  updateUser() {
    this.onUserUpdate.emit(this.user);
  }
}
ts
import { Component } from '@angular/core';
import { UserEditComponent } from './user-edit.component';
import { User } from './user';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [UserEditComponent],
  template: `
    <app-user-edit 
      (userModified)="handleUserUpdate($event)" 
    />
  `
})
export class AppComponent {
  handleUserUpdate(user: User) {
    console.log('User updated:', user);
  }
}
ts
export interface User {
    id: number;
    name: string;
    username?: string;
    email: string;
    address?: {
        street: string;
        suite: string;
        city: string;
        zipcode: string;
        geo: {
            lat: string;
            lng: string;
        }
    };
    phone?: string;
    website?: string;
    company?: {
        name: string;
        catchPhrase: string;
        bs: string;
    };
}

CONSEIL

L'alias n'affecte que l'utilisation dans le template HTML. Dans le code TypeScript, vous continuez à utiliser le nom original de la propriété.

ATTENTION

Évitez de renommer les outputs sans raison valable. Les cas d'utilisation principaux sont :

  • Maintenir la rétrocompatibilité en conservant un ancien nom
  • Éviter les conflits avec des événements DOM natifs

Utiliser le décorateur @Output()

Avant Angular 17

Le décorateur @Output() est la façon traditionnelle de définir des outputs dans Angular avant la version 17.

Voici une structure simple pour comprendre comment communiquer entre un composant enfant et un composant parent:

plaintext
app
├── count
│   ├── count.component.ts
└── app.component.ts

Dans notre composant, nous souhaitons effectuer un événement. Par exemple, quand l'utilisateur clique sur un bouton, un compteur s'incrémente.

ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-count',
  standalone: true,
  template: `
    <p>{{n}}</p>
    <button (click)="up()"></button>
  `
})
export class CountComponent {
  n = 0;

  up() {
    this.n++;
  }
}

Nous mettons des parenthèses autour du nom de l'événement pour indiquer une valeur de sortie.

Mais comment envoyer cette valeur à un composant parent, c'est à dire le composant app ?

Nous devons utiliser le décorateur @Output et EventEmitter pour émettre un événement:

ts
import {Component, EventEmitter, Output} from '@angular/core';

@Component({
  selector: 'app-count',
  standalone: true,
  template: `
    <p>{{n}}</p>
    <button (click)="up()">Up</button>
  `
})
export class CountComponent {
  n = 0;
  @Output() numberChange: EventEmitter<number> = new EventEmitter();

  up() {
    this.n++;
    this.numberChange.emit(this.n);
  }
}

Deux importations :

  • EventEmitter : Emettre l'événement aux composants parents
  • @Output : Décorateur indiquant la propriété envoyant l'information.

Retenir facilement

@Output est utilisé pour envoyer des informations à un composant parent. De cette manière, vous avez accès à un nouvel attribut HTML numberChange dans le template du composant parent. Vous pouvez ensuite écouter les changements de cet attribut car il est un événement.

La propriété numberChange est donc utilisé dans le template pour recevoir l'événement :

ts
import { Component } from '@angular/core';
import { CountComponent } from './count/count.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [CountComponent],
  template: `
    <div>
	    <app-count (numberChange)="multiplicate($event)" />
      {{result}}
	  </div>
  `
})
export class AppComponent {
  result = 0;

  multiplicate(val: number) {
    this.result = val * 2;
  }
}
  1. Dans le template du composant parent, nous utilisons (numberChange)="multiplicate($event)" pour écouter les changements de la propriété numberChange du composant enfant.
  2. Nous utilisons $event pour récupérer la valeur envoyée par le composant enfant.

$event ?

$event est une variable spéciale qui contient la valeur envoyée par le composant enfant. Son type est défini par le type de l'EventEmitter dans le composant enfant.

Chaque mois, recevez en avant-première notre newsletter avec les dernières actualités, tutoriels, astuces et ressources Angular directement par email !