Tout d'abord, ce dont vous avez besoin pour comprendre les relations entre les composants. Ensuite, vous pouvez choisir la bonne méthode de communication. Je vais essayer d'expliquer toutes les méthodes que je connais et utilise dans ma pratique pour la communication entre les composants.
Quels types de relations entre les composants peut-il y avoir?
1. Parent> Enfant
Partage de données via l'entrée
C'est probablement la méthode la plus courante de partage de données. Cela fonctionne en utilisant le @Input()
décorateur pour permettre aux données d'être transmises via le modèle.
parent.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'parent-component',
template: `
<child-component [childProperty]="parentProperty"></child-component>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent{
parentProperty = "I come from parent"
constructor() { }
}
child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'child-component',
template: `
Hi {{ childProperty }}
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
@Input() childProperty: string;
constructor() { }
}
C'est une méthode très simple. C'est facile a utiliser. Nous pouvons également détecter les modifications apportées aux données dans le composant enfant en utilisant ngOnChanges .
Mais n'oubliez pas que si nous utilisons un objet comme données et modifions les paramètres de cet objet, la référence à celui-ci ne changera pas. Par conséquent, si nous voulons recevoir un objet modifié dans un composant enfant, il doit être immuable.
2. Enfant> Parent
Partage de données via ViewChild
ViewChild permet à un composant d'être injecté dans un autre, donnant au parent l'accès à ses attributs et fonctions. Une mise en garde, cependant, est qu'il child
ne sera disponible qu'après l'initialisation de la vue. Cela signifie que nous devons implémenter le hook de cycle de vie AfterViewInit pour recevoir les données de l'enfant.
parent.component.ts
import { Component, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from "../child/child.component";
@Component({
selector: 'parent-component',
template: `
Message: {{ message }}
<child-compnent></child-compnent>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent implements AfterViewInit {
@ViewChild(ChildComponent) child;
constructor() { }
message:string;
ngAfterViewInit() {
this.message = this.child.message
}
}
child.component.ts
import { Component} from '@angular/core';
@Component({
selector: 'child-component',
template: `
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message = 'Hello!';
constructor() { }
}
Partage de données via Output () et EventEmitter
Une autre façon de partager des données consiste à émettre des données de l'enfant, qui peuvent être répertoriées par le parent. Cette approche est idéale lorsque vous souhaitez partager des modifications de données qui se produisent sur des éléments tels que des clics sur des boutons, des entrées de formulaire et d'autres événements utilisateur.
parent.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'parent-component',
template: `
Message: {{message}}
<child-component (messageEvent)="receiveMessage($event)"></child-component>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message:string;
receiveMessage($event) {
this.message = $event
}
}
child.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'child-component',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child.component.css']
})
export class ChildComponent {
message: string = "Hello!"
@Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
3. Frères et sœurs
Enfant> Parent> Enfant
J'essaie d'expliquer ci-dessous d'autres moyens de communiquer entre frères et sœurs. Mais vous pouvez déjà comprendre l'une des façons de comprendre les méthodes ci-dessus.
parent.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'parent-component',
template: `
Message: {{message}}
<child-one-component (messageEvent)="receiveMessage($event)"></child1-component>
<child-two-component [childMessage]="message"></child2-component>
`,
styleUrls: ['./parent.component.css']
})
export class ParentComponent {
constructor() { }
message: string;
receiveMessage($event) {
this.message = $event
}
}
child-one.component.ts
import { Component, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'child-one-component',
template: `
<button (click)="sendMessage()">Send Message</button>
`,
styleUrls: ['./child-one.component.css']
})
export class ChildOneComponent {
message: string = "Hello!"
@Output() messageEvent = new EventEmitter<string>();
constructor() { }
sendMessage() {
this.messageEvent.emit(this.message)
}
}
child-two.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'child-two-component',
template: `
{{ message }}
`,
styleUrls: ['./child-two.component.css']
})
export class ChildTwoComponent {
@Input() childMessage: string;
constructor() { }
}
4. Composants indépendants
Toutes les méthodes que j'ai décrites ci-dessous peuvent être utilisées pour toutes les options ci-dessus pour la relation entre les composants. Mais chacun a ses propres avantages et inconvénients.
Partage de données avec un service
Lorsque vous passez des données entre des composants qui n'ont pas de connexion directe, tels que des frères et sœurs, des petits-enfants, etc., vous devez utiliser un service partagé. Lorsque vous avez des données qui doivent toujours être synchronisées, je trouve le RxJS BehaviorSubject très utile dans cette situation.
data.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Injectable()
export class DataService {
private messageSource = new BehaviorSubject('default message');
currentMessage = this.messageSource.asObservable();
constructor() { }
changeMessage(message: string) {
this.messageSource.next(message)
}
}
first.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";
@Component({
selector: 'first-componennt',
template: `
{{message}}
`,
styleUrls: ['./first.component.css']
})
export class FirstComponent implements OnInit {
message:string;
constructor(private data: DataService) {
// The approach in Angular 6 is to declare in constructor
this.data.currentMessage.subscribe(message => this.message = message);
}
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
}
second.component.ts
import { Component, OnInit } from '@angular/core';
import { DataService } from "../data.service";
@Component({
selector: 'second-component',
template: `
{{message}}
<button (click)="newMessage()">New Message</button>
`,
styleUrls: ['./second.component.css']
})
export class SecondComponent implements OnInit {
message:string;
constructor(private data: DataService) { }
ngOnInit() {
this.data.currentMessage.subscribe(message => this.message = message)
}
newMessage() {
this.data.changeMessage("Hello from Second Component")
}
}
Partage de données avec un itinéraire
Parfois, vous devez non seulement transmettre des données simples entre les composants, mais également enregistrer un état de la page. Par exemple, nous voulons enregistrer un filtre sur le marché en ligne, puis copier ce lien et l'envoyer à un ami. Et nous nous attendons à ce qu'il ouvre la page dans le même état que nous. La première, et probablement la plus rapide, pour ce faire serait d'utiliser des paramètres de requête .
Les paramètres de requête ressemblent davantage à /people?id=
où id
peut égaler tout et vous pouvez avoir autant de paramètres que vous le souhaitez. Les paramètres de requête seraient séparés par le caractère esperluette.
Lorsque vous travaillez avec des paramètres de requête, vous n'avez pas besoin de les définir dans votre fichier de routes, et ils peuvent être nommés paramètres. Par exemple, prenez le code suivant:
page1.component.ts
import {Component} from "@angular/core";
import {Router, NavigationExtras} from "@angular/router";
@Component({
selector: "page1",
template: `
<button (click)="onTap()">Navigate to page2</button>
`,
})
export class Page1Component {
public constructor(private router: Router) { }
public onTap() {
let navigationExtras: NavigationExtras = {
queryParams: {
"firstname": "Nic",
"lastname": "Raboy"
}
};
this.router.navigate(["page2"], navigationExtras);
}
}
Dans la page de réception, vous recevrez ces paramètres de requête comme suit:
page2.component.ts
import {Component} from "@angular/core";
import {ActivatedRoute} from "@angular/router";
@Component({
selector: "page2",
template: `
<span>{{firstname}}</span>
<span>{{lastname}}</span>
`,
})
export class Page2Component {
firstname: string;
lastname: string;
public constructor(private route: ActivatedRoute) {
this.route.queryParams.subscribe(params => {
this.firstname = params["firstname"];
this.lastname = params["lastname"];
});
}
}
NgRx
Le dernier moyen, plus compliqué mais plus puissant, est d'utiliser NgRx . Cette bibliothèque n'est pas destinée au partage de données; c'est une puissante bibliothèque de gestion d'état. Je ne peux pas dans un court exemple expliquer comment l'utiliser, mais vous pouvez aller sur le site officiel et lire la documentation à ce sujet.
Pour moi, NgRx Store résout plusieurs problèmes. Par exemple, lorsque vous avez affaire à des observables et lorsque la responsabilité de certaines données observables est partagée entre différents composants, les actions de stockage et le réducteur garantissent que les modifications de données seront toujours effectuées «de la bonne manière».
Il fournit également une solution fiable pour la mise en cache des requêtes HTTP. Vous pourrez stocker les demandes et leurs réponses afin de pouvoir vérifier que la demande que vous faites n'a pas encore de réponse stockée.
Vous pouvez en savoir plus sur NgRx et comprendre si vous en avez besoin dans votre application ou non:
Enfin, je tiens à dire qu'avant de choisir certaines des méthodes de partage de données, vous devez comprendre comment ces données seront utilisées à l'avenir. Je veux dire peut-être que maintenant, vous pouvez utiliser juste un @Input
décorateur pour partager un nom d'utilisateur et un nom de famille. Ensuite, vous ajouterez un nouveau composant ou un nouveau module (par exemple, un panneau d'administration) qui a besoin de plus d'informations sur l'utilisateur. Cela signifie que cela peut être une meilleure façon d'utiliser un service pour les données utilisateur ou une autre façon de partager des données. Vous devez y réfléchir davantage avant de commencer à mettre en œuvre le partage de données.