乐趣区

Angular8-组件通讯基础使用场景一

组件交互

父子组件交互(常见)

输入输出属性 Input,Output

最基础的场景之一,使用非常广泛。使用方法,将父组件中的数据与子组件声明的变量进行绑定,保证子组件可以用于内部渲染模板,主要关注下子组件内部代码格式:


import {Component, OnInit, Input} from '@angular/core';
@Component({
  selector: 'app-children',
  templateUrl: './children.component.html',
  styleUrls: ['./children.component.scss']
})
export class ChildrenComponent implements OnInit {@Input() List: Array<any>;
  constructor() {}
  ngOnInit() {}
}

通过 @Input 装饰器来声明这个变量是个输入属性,用于像 dom 那样绑定属性一样使用,但是有点区别:

 当前总记录条数:{{dataList.length}}
<app-children [List]="dataList"></app-children>

用 [] 符号与当前组件或者页面中的数据进行绑定。这种场景数据传输方向比较单一、简单,实际开发中更多的是子组件也会操作部分数据,父组件也要时刻接收到变化。
在 Angular 中,子组件传播事件需要从 @angular/core 中引入 EventEmitter 类,demo 如下:

// 组件内代码
@Output () removeHandler = new EventEmitter();
// 组件模板中操作事件
removeAll(){this.List = [];
    this.removeHandler.emit(数据或者事件名称);
}

模板:

<h4> 子组件显示表格:</h4>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Phone</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let item of List">
      <td>{{item.name}}</td>
      <td>{{item.phone}}</td>
    </tr>
  </tbody>
</table>
<button (click)="removeAll()"> 清空全部 </button>

父组件:

<app-children (removeHandler)="removeData($event)" [List]="dataList"></app-children>
removeData(e) {console.log(e);
    this.dataList = e; // 如何将数据更新到本组件根据场景实现
  }

onChanges 生命周期截听输入属性变化

除此之外,官方 demo 中还提供了 2 种截听输入值的变化来更新本地数据,一种是对子组件绑定的属性进行 getter 和 setter 处理;另外一种用的蛮多的在开发中,利用组件的声明周期钩子 ngOnChanges 函数进行处理,上述 demo 我作下修改,子组件:

export class ChildrenComponent implements OnInit, OnChanges {@Input() List: Array<any>;
  changeList = [];
  @Output() removeHandler = new EventEmitter();
  constructor() {}
  ngOnInit() {}
  ngOnChanges(changes: { [propKey: string]: SimpleChange }) {let logs = [];
    if (!changes.List.firstChange) {
      logs = changes.List.currentValue;
      this.changeList = logs;
    }
  }
  removeAll() {this.changeList = [];
    this.removeHandler.emit(this.changeList);
  }
}
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Phone</th>
    </tr>
  </thead>
  <tbody>
    <tr *ngFor="let item of changeList">
      <td>{{item.name}}</td>
      <td>{{item.phone}}</td>
    </tr>
  </tbody>
</table>
<button (click)="removeAll()"> 清空全部 </button>

注意,本案例因我只绑定了一个输入属性,如果是多个,应当使用 for 循环更新指定的数据。

本地变量互动

官方定义:

父组件不能使用数据绑定来读取子组件的属性或调用子组件的方法。但可以在父组件模板里,新建一个本地变量来代表子组件,然后利用这个变量来读取子组件的属性和调用子组件的方法

子组件修改;

export class ChildrenComponent implements OnInit {// @Input() List: Array<any>;
  changeList = [];
  // @Output() removeHandler = new EventEmitter();
  constructor() {}
  ngOnInit() {}
  addData(data) {this.changeList = [...this.changeList, data];
  }
  removeAll() {this.changeList = [];
  }
}

html 模板不变;
父组件:

<h4> 新增一条记录 </h4>
<div class="basic-data">
  <label>name:</label>
  <input type="text" name="name" [(ngModel)]="name">
  <label>phone:</label>
  <input type="text" name="phone" [(ngModel)]="phone">
  <button (click)="table.addData({name:name,phone:phone})"> 添加 </button>
  <button (click)="table.removeAll()"> 清除 </button>
</div>
当前总记录条数:{{table.changeList.length}}
<app-children #table></app-children>
export class BasicComponent implements OnInit {constructor() { }
  name = '';
  phone = '';
  ngOnInit() {}
}

组件内逻辑均删除,因为通过绑定的 #table 可以直接访问子组件属性和方法。

父组件调用 @ViewChild

某些时候我们需要在组件中直接访问子组件内属性和方法,本地变量就不适用了,可以考虑使用 @ViewChild()

父组件修改:

<h4> 新增一条记录 </h4>
<div class="basic-data">
  <label>name:</label>
  <input type="text" name="name" [(ngModel)]="name">
  <label>phone:</label>
  <input type="text" name="phone" [(ngModel)]="phone">
  <button (click)="addData()"> 添加 </button>
  <button (click)="removeAll()"> 清除 </button>
</div>
当前总记录条数:{{dataList.length}}
<app-children></app-children>
import {Component, OnInit, ViewChild} from '@angular/core';
import {ChildrenComponent} from '../children/children.component';
@Component({
  selector: 'app-basic',
  templateUrl: './basic.component.html',
  styleUrls: ['./basic.component.scss']
})
export class BasicComponent implements OnInit {@ViewChild(ChildrenComponent, { static: false})
  private children: ChildrenComponent;
  constructor() {}
  name = '';
  phone = '';
  dataList = [];
  ngOnInit() {}
  addData() {
    this.dataList = [...this.dataList, {
      name: this.name,
      phone: this.phone
    }];
    this.children.addData({
      name: this.name,
      phone: this.phone
    });
  }
  removeAll() {this.dataList = [];
    this.children.removeAll();}
}

如果子组件内有初始化操作的可以在父组件的 AfterViewInit 声明周期中初始化。

任意组件之间 - 服务(Service)

业务中比较多的交互往往不存在父子组件的关联,这时候使用服务交互是个不错的选择。

服务文件代码:

import {Injectable} from '@angular/core';
import {Subject} from 'rxjs';

@Injectable({providedIn: 'root'})
export class WorkerListService {workList = [];
  workListSource = new Subject<any>();
  private workListSource$ = this.workListSource.asObservable();
  constructor() {}
  addInfo(info) {this.workList = [...this.workList, info];
    this.workListSource.next(this.workList);
  }
  updateWorkList(infos: Array<any>) {
    this.workList = infos;
    this.workListSource.next(this.workList);
  }

}

解释下代码部分,首先一个数据可以被订阅的前提必须是一个 Observable 类型,对于普通数组 of(SomeArray) 已经是一个 Observable 类型可以订阅,对于返回的 http 请求也是个 Observable 类型,在页面中可以直接订阅处理,在 Ng 官方文档有列举 Observable 类型的说明,了解更多可以前往查看,next() 方法告知这个可观察对象更新内容,这样你的订阅(subscribe)才会抓取到更新后的信息。

原父组件:

import {Component, OnInit} from '@angular/core';
import {WorkerListService} from '../worker-list.service';
@Component({
  selector: 'app-basic',
  templateUrl: './basic.component.html',
  styleUrls: ['./basic.component.scss']
})
export class BasicComponent implements OnInit {
  constructor(private workListService: WorkerListService) { }
  name = '';
  phone = '';
  dataList = [];
  ngOnInit() {
    this.workListService.workListSource.subscribe(res => {this.dataList = res;});
  }
  addData() {
    this.workListService.addInfo({
      name: this.name,
      phone: this.phone
    });
  }
  removeAll() {this.dataList = [];
    this.workListService.updateWorkList(this.dataList);
  }
}

原子组件:

import {Component, OnInit} from '@angular/core';
import {WorkerListService} from '../worker-list.service';
@Component({
  selector: 'app-children',
  templateUrl: './children.component.html',
  styleUrls: ['./children.component.scss']
})
export class ChildrenComponent implements OnInit {changeList = [];
  constructor(private workListService: WorkerListService) {
    this.workListService.workListSource.subscribe(res=>{this.changeList = res;});
  }
  ngOnInit() {}
}

这样就可以保证组件可以随时获取到最新的数据更新,而不用担心是否为父子级组件,也是最常用的方式。

退出移动版