5、Angular 2组件间交互

本文详细介绍Angular中组件间的通讯方式,包括父子组件、兄弟组件及非父子关系组件间的通讯技巧。探讨了不同场景下最佳实践,如使用@Input/@Output装饰器、事件总线和服务、localStorage等。

父子组件之间的通讯

父子组件使用后显示如下图所示:

这里写图片描述
随后代码结构,父子组件之间的代码层次如图所示:

这里写图片描述

父组件接收子组件的事件,在子组件中使用@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("收到了子组件的自定义事件!");
  }
}

非父子关系,作为兄弟组件之间的通讯

首先页面样式显示如下,需要做的就是同一个组件下的第一个组件和第二个组件之间的交互:
xianshi

其次查看具体的代码结构,两个兄弟组件之间的交互可以通过一个共同的service进行,service充当一个事件总线,通过依赖注入的方式就会注入同一个service实例(因为依赖注入的对象就是一个全局的单例对象):

angular的依赖注入特性:通过依赖注入进来的对象,它是全局单例的,用的是单例模式。
code

最后,看各个部分的文件代码

首先查看中间人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里面穿参数就能获取到。

然后还可以各种方式进行通讯。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值