По-перше, що потрібно для розуміння зв’язків між компонентами. Тоді ви можете вибрати правильний спосіб спілкування. Я спробую пояснити всі методи, які я знаю і використовую у своїй практиці для спілкування між компонентами.
Які існують відносини між компонентами?
1. Батько> Дитина
Обмін даними через Input
Це, мабуть, найпоширеніший метод обміну даними. Це працює за допомогою @Input()
декоратора, щоб передати дані через шаблон.
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() { }
}
Це дуже простий метод. Він простий у використанні. Ми також можемо вносити зміни до дочірнього компонента за допомогою ngOnChanges .
Але не забувайте, що якщо ми використовуємо об’єкт як дані та змінимо параметри цього об’єкта, посилання на нього не зміниться. Тому, якщо ми хочемо отримати модифікований об’єкт у дочірньому компоненті, він повинен бути незмінним.
2. Дитина> Батько
Обмін даними через ViewChild
ViewChild дозволяє вводити один компонент в інший, надаючи батькові доступ до його атрибутів та функцій. Однак одне застереження - це те, що воно child
буде недоступним до моменту ініціалізації перегляду. Це означає, що нам потрібно реалізувати гачок життєвого циклу AfterViewInit, щоб отримувати дані від дитини.
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() { }
}
Обмін даними через Output () та EventEmitter
Ще один спосіб обміну даними - це передавання даних від дитини, які можуть бути перелічені батьком. Цей підхід ідеально підходить, коли ви хочете поділитися змінами даних, які відбуваються в таких речах, як натискання кнопки, записи форми та інші події користувача.
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. Брати і сестри
Дитина> Батько> Дитина
Я намагаюся пояснити інші способи спілкування між побратимами нижче. Але ви вже могли зрозуміти один із способів розуміння вищевказаних методів.
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. Непов'язані компоненти
Усі описані нижче методи можна використовувати для всіх вищезазначених варіантів взаємозв'язку між компонентами. Але у кожного є свої переваги та недоліки.
Обмін даними сервісом
При передачі даних між компонентами, які не мають прямого зв’язку, наприклад, братів і сестер, онуків тощо, ви повинні використовувати спільну послугу. Коли у вас є дані, які завжди повинні синхронізуватися, я вважаю, що RxJS BehaviorSubject дуже корисний у цій ситуації.
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")
}
}
Обмін даними маршрутом
Іноді вам потрібно не просто передавати прості дані між компонентами, але зберігати певний стан сторінки. Наприклад, ми хочемо зберегти якийсь фільтр на інтернет-ринку, а потім скопіювати це посилання та надіслати другу. І ми очікуємо, що вона відкриє сторінку в тому ж стані, що і ми. Перший і, мабуть, найшвидший спосіб зробити це - використовувати параметри запиту .
Параметри запиту виглядають більше в рядках, /people?id=
де id
можна дорівнювати будь-що, і ви можете мати стільки параметрів, скільки вам потрібно. Параметри запиту будуть розділені символом амперсанда.
Працюючи з параметрами запиту, вам не потрібно визначати їх у файлі маршрутів, і вони можуть бути названі параметрами. Наприклад, візьміть такий код:
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);
}
}
На сторінці отримання ви отримаєте такі параметри запиту, як:
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
Останній спосіб, який є більш складним, але більш потужним, - використовувати NgRx . Ця бібліотека не призначена для обміну даними; це потужна бібліотека управління державою. Я не можу в короткому прикладі пояснити, як ним користуватися, але ви можете зайти на офіційний сайт і прочитати документацію про нього.
Для мене магазин NgRx вирішує кілька питань. Наприклад, коли вам доводиться мати справу зі спостережуваними даних і коли відповідальність за деякі видимі дані розподіляються між різними компонентами, дії магазину та редуктор забезпечують, щоб зміни даних завжди виконувались «правильно».
Він також забезпечує надійне рішення для кешування запитів HTTP. Ви зможете зберігати запити та їх відповіді, щоб ви могли переконатися, що запит, який ви робите, ще не має збереженої відповіді.
Ви можете прочитати про NgRx і зрозуміти, потрібен він у вашій програмі чи ні:
Наостанок хочу сказати, що перш ніж вибрати деякі методи обміну даними, потрібно зрозуміти, як ці дані будуть використовуватися в майбутньому. Я маю на увазі, можливо, зараз ви можете використовувати лише @Input
декоратор для спільного використання імені та прізвища. Потім ви додасте новий компонент або новий модуль (наприклад, панель адміністратора), який потребує додаткової інформації про користувача. Це означає, що це може бути кращим способом використання послуги для користувацьких даних або іншим способом обміну даними. Вам потрібно більше подумати над цим, перш ніж почати впроваджувати обмін даними.