父子组件之间的通讯
父子组件使用后显示如下图所示:
随后代码结构,父子组件之间的代码层次如图所示:
父组件接收子组件的事件,在子组件中使用@Output输出事件
通过下方代码注释中的1,2,3,4,5点注释,完成子组件按钮点击到父组件得到方法响应的过程。是一个子层向父层传递事件的过程。这就是父组件需要响应子组件的事件,或者接收数据,可以使用@Output输入一个事件,将数据通过event传递,作为参数
子组件需要接收父组件的数据,在子组件中使用@Input接收数据
父组件如果想向子组件中传递数据,可以使用@Input装饰器的方式,具体看代码注释中的i,ii,iii
父组件要直接调用子组件中定义的方法,在父组件模板使用child时,使用#child定义模板局部变量
另外,如果我想父组件直接调用子组件的方法,我要怎么调用呢,我们可以使用”#”创建模板局部变量的这种方式,具体看代码注释中的I,II,III
首先看看child组件的模板文件:
<div class="panel panel-primary">
<!--iii,用了这样的一个setter方法之后,这里绑定这个东西就会调用对应的getter方法了,而不是直接访问对应属性。因为_panelTitle是私有变量,随后就会显示带有【】的内容。-->
<div class="panel-heading">{{panelTitle}}</div>
<div class="panel-body">
<!--4,那么follow事件是怎么触发的呢,这就是通过这个button的点击事件绑定触发的,在绑定的事件函数中,手动派发自定义的follow事件,见ts代码中的5-->
<button (click)="emitAnEvent($event)" class="btn btn-success">触发一个事件</button>
</div>
</div>
child组件的ts文件:
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {
private _panelTitle:string="我是子组件";
// ii,使用@Input装饰器,表示从父组件中输入数据;使用ES6中getter/setter的写法,调用setter并不是直接就赋值了,而是在前后各自加了一个【】,然后跳到iii
@Input()
set panelTitle(panelTitle:string){
this._panelTitle="【"+panelTitle+"】";
}
get panelTitle():string{
return this._panelTitle;
}
@Output() // 1.使用@Output装饰器,自定义了一个事件,随后事件如何绑定呢,跳到父模板文件中的2
public follow=new EventEmitter<string>();
constructor() { }
ngOnInit() {
}
// 5,这个是按钮点击事件的响应方法,在这个方法中手动进行派发自己自定义的follow事件
public emitAnEvent(event):void{
this.follow.emit("follow");
}
// I,首先,我们在子组件中定义了一个方法,那么如何在父组件中调用呢,跳到II
public childFn():void{
console.log("子组件的名字是>"+this.panelTitle);
}
}
随后看父组件的模板文件:
<div class="panel panel-primary">
<div class="panel-heading">第一种:父子组件之间通讯</div>
<div class="panel-body">
// 2,使用()事件绑定的方式,将自定义的follow事件绑定到父组件ts文件中的doSomething()方法进行响应,见3
// i,复制panelTitle=“一个新的标题”,这回传递给child,但是child使用的是get、set方法,所以这句会自动调用child的get的方法,见ii
// II,我们通过"#child"声明模板局部变量的方式,获取child子组件,然后使用child进行()事件绑定,调用子组件的方法,见III
<child #child (follow)="doSomething()" panelTitle="一个新的标题"></child>
// III,直接给按钮绑定(click)事件,然后通过child.childFn()调用子组件中定义的方法
<button (click)="child.childFn()" class="btn btn-success">调用子组件方法</button>
</div>
</div>
父组件中的ts文件:
import { Component, OnInit, Input, Output, EventEmitter, AfterViewInit, ViewChild } from '@angular/core';
import { ChildComponent } from './child/child.component';
@Component({
selector: 'parent-and-child',
templateUrl: './parent-and-child.component.html',
styleUrls: ['./parent-and-child.component.css']
})
export class ParentAndChildComponent implements OnInit {
@ViewChild(ChildComponent)
private childComponent: ChildComponent;
constructor() { }
ngOnInit() {
}
ngAfterViewInit() {
//this.childComponent.childFn();
}
// 3,这里是事件绑定之后的方法,所以子组件中的事件触发之后,父组件会调用这个方法
public doSomething():void{
alert("收到了子组件的自定义事件!");
}
}
非父子关系,作为兄弟组件之间的通讯
首先页面样式显示如下,需要做的就是同一个组件下的第一个组件和第二个组件之间的交互:
其次查看具体的代码结构,两个兄弟组件之间的交互可以通过一个共同的service进行,service充当一个事件总线,通过依赖注入的方式就会注入同一个service实例(因为依赖注入的对象就是一个全局的单例对象):
angular的依赖注入特性:通过依赖注入进来的对象,它是全局单例的,用的是单例模式。
最后,看各个部分的文件代码
首先查看中间人service的代码,作为事件总线,它是两个组件之间的桥梁:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
/**
* 用来充当事件总线的Service
*/
// 1,设置装饰器,表示这个可以注入,然后可以让两个兄弟组件都注入这个service,那么两个组件就有了同一个service实例(这是angular依赖注入的特性,注入的对象都是全局单例的,所以都是同一个实例),是同一个实例之后,我们就可以在这个类里面做一些事情了,跳到2
@Injectable()
export class EventBusService {
// 2,我们在service类中定义一个Subject类型的eventBus属性,Subject类型是一种可以subscribe到的主题,那么两个组件中其中一个组件通过next方法发出一个主题,然后另一个组件就可以通过subscribe方法接收到这个东西了,这样就完成通讯了,也就是跳到3
public eventBus:Subject<string> = new Subject<string>();
constructor() { }
}
随后查看第一个组件的html模板代码和ts组件类代码:
child-1.component.html
<div class="panel panel-primary">
<div class="panel-heading">第一个组件</div>
<div class="panel-body">
<button (click)="triggerEventBus()" class="btn btn-success">触发一个事件</button>
</div>
</div>
child-1.component.ts
import { Component, OnInit } from '@angular/core';
import { EventBusService } from '../service/event-bus.service';
@Component({
selector: 'child-1',
templateUrl: './child-1.component.html',
styleUrls: ['./child-1.component.css']
})
export class Child1Component implements OnInit {
// 1,在构造器中注入通用的那个service
constructor(public eventBusService:EventBusService) { }
ngOnInit() {
}
// 3,在点击事件中,手动调用eventBusService实例中eventBus属性的next方法,发一些信息出去,然后让那些订阅的组件通过subscribe接收到,跳到4
public triggerEventBus():void{
this.eventBusService.eventBus.next("第一个组件触发的事件");
}
}
然后是第二个组件的模板html文件和ts组件类文件:
child-2.component.html
<div class="panel panel-primary">
<div class="panel-heading">第二个组件</div>
<div class="panel-body">
<p *ngFor="let event of events">{{event}}</p>
</div>
</div>
child-2.component.ts
import { Component, OnInit } from '@angular/core';
import { EventBusService } from '../service/event-bus.service';
@Component({
selector: 'child-2',
templateUrl: './child-2.component.html',
styleUrls: ['./child-2.component.css']
})
export class Child2Component implements OnInit {
public events:Array<any>=[];
// 1,在构造器中注入通用的那个service
constructor(public eventBusService:EventBusService) {
}
ngOnInit() {
// 4,通过同一个eventBusService实例,调用eventBus属性的subscribe方法,获取发出的值,然后可以做相应的处理,这里是给自己的数组中添加数据,然后插值语法可以显示到页面上
this.eventBusService.eventBus.subscribe((value)=>{
this.events.push(value+"-"+new Date());
});
}
}
所以两个兄弟之间的通讯过程是这样的,首先我们定义一个通用的service,然后我们在两个兄弟组件的构造器中都注入这同一个service,这个时候,两个组件都有了同一个实例(这是angular依赖注入的特性,注入的实例都是全局单例的)。
再然后,我们就可以在这个通用的service中做一些事情了,我们在service中定义一个Subject类型的对象,Subject是一个可以订阅的一个主题(这个是RxJS中的内容,可以看过一个简单的观察者模式吧,有一个主题,其他依赖这个实例的就可以订阅它,观察它的变化之类的)
好了,我们的兄弟组件都有了同一个包含一个主题属性的service实例,那么我们就可以在其中一个组件中,通过点击事件啊什么的去手动调用this.eventBusService.eventBus.next(“第一个组件手动触发一个事件,然后发出去,之后订阅了这个service的其他对象就能够去subscribe这个发出来的东西”);
然后第二个组件通过通用的service实例subscribe发出的信息就可以接受到这个东西了。注意:这种方法不止可以应用在兄弟组件中,而是可以应用在任何关系的组件之间。只需要对应的组件都注入同一个service实例即可,这样在其中一个发出信息时,其他的组件都可以通过subscribe接收到信息。
使用localStorage通讯
这种方式就是通过在一个组件中往localStorage中写数据,然后在另一个组件通过localStorage读取数据的方式。这个是不用有组件之间的限制了,到处都能用,但是注意多人协作时,不要使用同样的key否则会被覆盖了。然后用了之后要清除掉释放空间,别一直占着。
这种方式是很简单,但是也是有局限性的。
首先,不同的浏览器中对localStorage能存储数据大小都是有不同限制的,我们不能存太多数据在localStorage中。
然后,localStorage里面是只能存储字符串的,不能存储对象,需要使用Json的stringify方法变为字符串。
看看显示样式和代码层次:
随后直接看代码就好了,一个组件中的事件往localStorage写数据另一个从localStorage里面取数据而已:
第一个组件:html模板文件和ts组件类文件:
<div class="panel panel-primary">
<div class="panel-heading">第一个组件</div>
<div class="panel-body">
<button (click)="writeData()" class="btn btn-success">写数据</button>
</div>
</div>
…
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'local-child1',
templateUrl: './local-child1.component.html',
styleUrls: ['./local-child1.component.css']
})
export class LocalChild1Component implements OnInit {
constructor() { }
ngOnInit() {
}
public writeData():void{
window.localStorage.setItem("json",JSON.stringify({name:'大漠穷秋',age:18}));
}
}
第二个组件的html模板文件和ts组件类文件:
<div class="panel panel-primary">
<div class="panel-heading">第二个组件</div>
<div class="panel-body">
<button (click)="readData()" class="btn btn-success">读数据</button>
</div>
</div>
…
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'local-child2',
templateUrl: './local-child2.component.html',
styleUrls: ['./local-child2.component.css']
})
export class LocalChild2Component implements OnInit {
constructor() { }
ngOnInit() {
}
public readData():void{
var json=window.localStorage.getItem("json");
// window.localStorage.removeItem("json");
var obj=JSON.parse(json);
console.log(obj.name);
console.log(obj.age);
}
}
利用后台session方式通讯
这个就是一个组件发送请求存数据到后台去,另一个组件发送请求从后台拿数据。也就是把数据都存在后台了,这样可能数据还安全些。
利用路由传递参数
路由的url地址里面是可以传参数的,如lcoalhost:4200/list/id/1,只要往url里面穿参数就能获取到。
本文详细介绍Angular中组件间的通讯方式,包括父子组件、兄弟组件及非父子关系组件间的通讯技巧。探讨了不同场景下最佳实践,如使用@Input/@Output装饰器、事件总线和服务、localStorage等。


被折叠的 条评论
为什么被折叠?



