Po pierwsze, czego potrzebujesz, aby zrozumieć relacje między komponentami. Następnie możesz wybrać odpowiednią metodę komunikacji. Postaram się wyjaśnić wszystkie metody, które znam i wykorzystuję w swojej praktyce do komunikacji między komponentami.
Jakie mogą istnieć relacje między komponentami?
1. Rodzic> Dziecko
Udostępnianie danych poprzez wejście
Jest to prawdopodobnie najpopularniejsza metoda udostępniania danych. Działa przy użyciu@Input()
dekoratora, aby umożliwić przekazywanie danych przez szablon.
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() { }
}
To bardzo prosta metoda. Jest łatwy w użyciu. Możemy również wychwycić zmiany w danych w komponencie podrzędnym za pomocą ngOnChanges .
Ale nie zapominajmy, że jeśli użyjemy obiektu jako danych i zmienimy parametry tego obiektu, odniesienie do niego się nie zmieni. Dlatego jeśli chcemy otrzymać zmodyfikowany obiekt w komponencie potomnym, musi on być niezmienny.
2. Dziecko> Rodzic
Udostępnianie danych przez ViewChild
ViewChild umożliwia jednego komponentu do innego, dając rodzicowi dostęp do jego atrybutów i funkcji. Jest jednak jedno zastrzeżeniechild
nie będą one dostępne do czasu zainicjowania widoku. Oznacza to, że musimy zaimplementować hak cyklu życia AfterViewInit, aby otrzymać dane od dziecka.
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() { }
}
Udostępnianie danych przez Output () i EventEmitter
Innym sposobem udostępniania danych jest emitowanie danych od dziecka, które może wyświetlić rodzic. Takie podejście jest idealne, gdy chcesz udostępniać zmiany danych, które mają miejsce w przypadku kliknięć przycisków, wpisów formularzy i innych zdarzeń użytkownika.
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. Rodzeństwo
Dziecko> Rodzic> Dziecko
Poniżej staram się wyjaśnić inne sposoby komunikacji między rodzeństwem. Ale mogłeś już zrozumieć jeden ze sposobów zrozumienia powyższych metod.
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. Niepowiązane komponenty
Wszystkie metody, które opisałem poniżej, można zastosować do wszystkich powyższych opcji relacji między komponentami. Ale każdy ma swoje zalety i wady.
Udostępnianie danych usłudze
Podczas przekazywania danych między komponentami pozbawionymi bezpośredniego połączenia, takimi jak rodzeństwo, wnuki itp., Należy korzystać z usługi współdzielonej. Kiedy masz dane, które powinny być zawsze zsynchronizowane, uważam, że RxJS BehaviorSubject jest bardzo przydatne w tej sytuacji.
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")
}
}
Udostępnianie danych trasom
Czasami trzeba nie tylko przekazywać proste dane między komponentami, ale także zapisać stan strony. Na przykład chcemy zapisać jakiś filtr na rynku online, a następnie skopiować ten link i wysłać znajomemu. Oczekujemy, że otworzy stronę w tym samym stanie co my. Pierwszym i prawdopodobnie najszybszym sposobem na zrobienie tego byłoby użycie parametrów zapytania .
Parametry zapytania wyglądają bardziej zgodnie z linią /people?id=
gdzieid
mogą być równe wszystkim i możesz mieć tyle parametrów, ile chcesz. Parametry zapytania byłyby oddzielone znakiem ampersand.
Podczas pracy z parametrami zapytania nie musisz definiować ich w pliku tras i można je nazwać parametrami. Na przykład weź następujący kod:
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);
}
}
Na stronie odbiorczej otrzymasz następujące parametry zapytania, takie jak:
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
Ostatnim sposobem, który jest bardziej skomplikowany, ale potężniejszy, jest użycie NgRx . Ta biblioteka nie służy do udostępniania danych; jest to potężna biblioteka zarządzania stanem. Nie potrafię w krótkim przykładzie wyjaśnić, jak go używać, ale możesz wejść na oficjalną stronę i przeczytać dokumentację na jej temat.
Dla mnie NgRx Store rozwiązuje wiele problemów. Na przykład, gdy masz do czynienia z obserwowalnymi i gdy odpowiedzialność za pewne obserwowalne dane jest dzielona między różnymi komponentami, akcje przechowywania i reduktor zapewniają, że modyfikacje danych zawsze będą przeprowadzane „we właściwy sposób”.
Zapewnia również niezawodne rozwiązanie do buforowania żądań HTTP. Będziesz mógł przechowywać żądania i ich odpowiedzi, abyś mógł sprawdzić, czy żądanie, które składasz, nie ma jeszcze zapisanej odpowiedzi.
Możesz przeczytać o NgRx i zrozumieć, czy potrzebujesz go w swojej aplikacji, czy nie:
Na koniec chcę powiedzieć, że przed wybraniem niektórych metod udostępniania danych należy zrozumieć, w jaki sposób dane te będą wykorzystywane w przyszłości. Chodzi mi o to, że może teraz możesz użyć tylko @Input
dekoratora do udostępnienia nazwy użytkownika i nazwiska. Następnie dodasz nowy komponent lub nowy moduł (na przykład panel administracyjny), który potrzebuje więcej informacji o użytkowniku. Oznacza to, że może to być lepszy sposób korzystania z usługi dla danych użytkownika lub inny sposób udostępniania danych. Musisz się nad tym bardziej zastanowić, zanim zaczniesz wdrażać udostępnianie danych.