首先,您需要了解组件之间的关系。然后你可以选择正确的沟通方式。我将尝试解释我所知道的和在实践中用于组件之间通信的所有方法。
组件之间可以有什么样的关系?
1. 父母>孩子
通过输入共享数据
这可能是共享数据的最常用方法。它通过使用@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允许将一个组件注入到另一个组件中,使父组件可以访问它的属性和函数。但是需要注意的是,在视图初始化之前,子对象是不可用的。这意味着我们需要实现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 Store解决了多个问题。例如,当你必须处理可观察数据时,当一些可观察数据的责任在不同的组件之间共享时,存储操作和减速器确保数据修改总是以“正确的方式”执行。
它还为HTTP请求缓存提供了可靠的解决方案。您将能够存储请求及其响应,以便您可以验证正在发出的请求还没有存储响应。
你可以阅读NgRx,了解你的应用程序是否需要它:
Angular的服务层:Redux、RxJs和Ngrx Store——什么时候使用Store,为什么?
Ngrx商店-架构指南
Finally, I want to say that before choosing some of the methods for sharing data you need to understand how this data will be used in the future. I mean maybe just now you can use just an @Input decorator for sharing a username and surname. Then you will add a new component or new module (for example, an admin panel) which needs more information about the user. This means that may be a better way to use a service for user data or some other way to share data. You need to think about it more before you start implementing data sharing.