angular - 组件通信

196 阅读4分钟

组件通信

来自官网学习个人总结

父-->子

  1. 生成一个新组件:ng generate component product-alerts
    命令会自动生成 ProductAlertsComponent 组件并将模块自动添加到 AppModule让别的应用都能够直接调用
  2. @angular/core引入Input.
  3. 定义一个由 @Input() 装饰的变量.使子组件可以获取父组件所传变量。
// child component
import { Component, OnInit, Input } from "@angular/core";
import { Product } from "../products";
export class ProductAlertsComponent implements OnInit {
  //定义masterName接收自父组件所传入变量master
  @Input('master') masterName = '';
  //定义product接收自父组件所传入变量product
  @Input() product!: Product;
  constructor() {}

  ngOnInit() {}
}
// parent component
<app-product-alerts
  [product]="product"
  [master]="master">
</app-product-alerts>

子-->父

  1. import Output and EventEmitter from @angular/core.
  2. 在 class 中,以 EventEmitter()实例定义一个被@Output()装饰的方法变量. 这样notify改变时,父组件就能够被通知到
//child - component.ts
export class ProductAlertsComponent {
  @Input() product: Product | undefined;
  @Output() notify = new EventEmitter();
}
//child - component.html
<p *ngIf="product && product.price > 700">
  <button type="button" (click)="notify.emit()">Notify Me</button>
</p>

//parent component.html
<app-product-alerts
  [product]="product"
  (notify)="onNotify()">
</app-product-alerts>
//parent component.ts
onNotify() {
  window.alert('You will be notified');
}

对父组件传来的属性进行setter操作

🌰 对传递的string值空值替换并去除字符串前后空格
最终将展示 'Jcole the', '♥', 'best'

//name-parent.component.ts
content_copyimport { Component } from '@angular/core';

@Component({
  selector: 'app-name-parent',
  template: `
    <h2>Master controls {{names.length}} names</h2>
    <app-name-child *ngFor="let name of names" [name]="name"></app-name-child>
  `
})
export class NameParentComponent {
  names = ['Jcole the', '   ', '  best'];
}
//name.component.ts
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-name-child',
  template: '<h3>"{{name}}"</h3>'
})
export class NameChildComponent {
  @Input()
  get name(): string { return this._name; }
  set name(name: string) {
    this._name = (name && name.trim()) || '♥';
  }
  private _name = '';
}

ngOnChanges中处理父组件所传属性

ngOnChanges会在ngOnInit之前执行一次,并在每次父组件所传值更新的时候执行
🌰 子组件监听major and minor的改变并打印在控制台

//version-parent.child.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-version-parent',
  template: `
    <h2>Source code version</h2>
    <button type="button" (click)="newMinor()">New minor version</button>
    <button type="button" (click)="newMajor()">New major version</button>
    <app-version-child [major]="major" [minor]="minor"></app-version-child>
  `
})
export class VersionParentComponent {
  major = 1;
  minor = 23;

  newMinor() {
    this.minor++;
  }

  newMajor() {
    this.major++;
    this.minor = 0;
  }
}
//version-parent.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-version-parent',
  template: `
    <h2>Source code version</h2>
    <button type="button" (click)="newMinor()">New minor version</button>
    <button type="button" (click)="newMajor()">New major version</button>
    <app-version-child [major]="major" [minor]="minor"></app-version-child>
  `
})
export class VersionParentComponent {
  major = 1;
  minor = 23;

  newMinor() {
    this.minor++;
  }

  newMajor() {
    this.major++;
    this.minor = 0;
  }
}

父组件通过局部变量直接操作子组件内参数

父组件是不可双向绑定子组件数据&直接调用子组件方法的。
有🌰如下:
子组件CountdownTimerComponent为一个倒数组件,能够通过方法startstop控制倒计时的起始。并展示所倒计时间

//countdown-timer.component.ts
import { Component, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-countdown-timer',
  template: '<p>{{message}}</p>'
})
export class CountdownTimerComponent implements OnDestroy {

  intervalId = 0;
  message = '';
  seconds = 11;

  ngOnDestroy() { this.clearTimer(); }

  start() { this.countDown(); }
  stop()  {
    this.clearTimer();
    this.message = `Holding at T-${this.seconds} seconds`;
  }

  private clearTimer() { clearInterval(this.intervalId); }

  private countDown() {
    this.clearTimer();
    this.intervalId = window.setInterval(() => {
      this.seconds -= 1;
      if (this.seconds === 0) {
        this.message = 'Blast off!';
      } else {
        if (this.seconds < 0) { this.seconds = 10; } // reset
        this.message = `T-${this.seconds} seconds and counting`;
      }
    }, 1000);
  }
}

父组件CountdownLocalVarParentComponent中涵括了倒数器组件CountdownTimerComponent
并通过两个按钮去控制子组件的起始
在这里我们将本地变量#timer放置在 <countdown-timer>去代表子组件。我们可以通过timmer去操控子组件中的任何变量与函数

//countdown-parent.component.ts
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';

@Component({
  selector: 'app-countdown-parent-lv',
  template: `
    <h3>Countdown to Liftoff (via local variable)</h3>
    <button type="button" (click)="timer.start()">Start</button>
    <button type="button" (click)="timer.stop()">Stop</button>
    <div class="seconds">{{timer.seconds}}</div>
    <app-countdown-timer #timer></app-countdown-timer>
  `,
  styleUrls: ['../assets/demo.css']
})
export class CountdownLocalVarParentComponent { }

image

父组件调用 @ViewChild() 函数

刚才局部变量的方法很简单8?
但是父组件本身无权访问子组件,使用时父子连接必须完全在父模板内完成。
组件的父子关系不是在每个组件各自的中通过局部变量技术建立的。
因为 实例没有相互连接,所以父类不能访问子类的属性和方法
如果父组件的依赖于子组件的,则不能使用局部变量技术。

我们可以将子组件作为ViewChild 注入到父组件

以下🌰的子组件CountdownTimerComponent与上🌰保持一致。
我们将父组件从通过局部变量处理改为ViewChild注入处理

//countdown-parent.component.ts
import { AfterViewInit, ViewChild } from '@angular/core';
import { Component } from '@angular/core';
import { CountdownTimerComponent } from './countdown-timer.component';

@Component({
  selector: 'app-countdown-parent-vc',
  template: `
    <h3>Countdown to Liftoff (via ViewChild)</h3>
    <button type="button" (click)="start()">Start</button>
    <button type="button" (click)="stop()">Stop</button>
    <div class="seconds">{{ seconds() }}</div>
    <app-countdown-timer></app-countdown-timer>
  `,
  styleUrls: ['../assets/demo.css']
})
export class CountdownViewChildParentComponent implements AfterViewInit {

  @ViewChild(CountdownTimerComponent)
  private timerComponent!: CountdownTimerComponent;

  seconds() { return 0; }

  ngAfterViewInit() {
    // 重新定义 `seconds()`。与 `CountdownTimerComponent.seconds` 保持一致
    // 为避免一次性开发模式直接修改子组件数据 && 单向数据流错误
    setTimeout(() => this.seconds = () => this.timerComponent.seconds, 0);
  }

  start() { this.timerComponent.start(); }
  stop() { this.timerComponent.stop(); }
}

父子组件通过service交互

通过service我们也可以实现父子组件的双向通信。
下🌰中MissionService将连接MissionControlComponent到多个AstronautComponent子组件

//mission.service.ts
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';

@Injectable()
export class MissionService {

  // Observable string sources
  private missionAnnouncedSource = new Subject<string>();
  private missionConfirmedSource = new Subject<string>();

  // Observable string streams
  missionAnnounced$ = this.missionAnnouncedSource.asObservable();
  missionConfirmed$ = this.missionConfirmedSource.asObservable();

  // Service message commands
  announceMission(mission: string) {
    this.missionAnnouncedSource.next(mission);
  }

  confirmMission(astronaut: string) {
    this.missionConfirmedSource.next(astronaut);
  }
}
//missioncontrol.component.ts
import { Component } from '@angular/core';

import { MissionService } from './mission.service';

@Component({
  selector: 'app-mission-control',
  template: `
    <h2>Mission Control</h2>
    <button type="button" (click)="announce()">Announce mission</button>

    <app-astronaut
      *ngFor="let astronaut of astronauts"
      [astronaut]="astronaut">
    </app-astronaut>

    <h3>History</h3>
    <ul>
      <li *ngFor="let event of history">{{event}}</li>
    </ul>
  `,
  providers: [MissionService]
})
export class MissionControlComponent {
  astronauts = ['Lovell', 'Swigert', 'Haise'];
  history: string[] = [];
  missions = ['Fly to the moon!',
              'Fly to mars!',
              'Fly to Vegas!'];
  nextMission = 0;

  constructor(private missionService: MissionService) {
    missionService.missionConfirmed$.subscribe(
      astronaut => {
        this.history.push(`${astronaut} confirmed the mission`);
      });
  }

  announce() {
    const mission = this.missions[this.nextMission++];
    this.missionService.announceMission(mission);
    this.history.push(`Mission "${mission}" announced`);
    if (this.nextMission >= this.missions.length) { this.nextMission = 0; }
  }
}
//astronaut.component.ts
import { Component, Input, OnDestroy } from '@angular/core';

import { MissionService } from './mission.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        type="button"
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})
export class AstronautComponent implements OnDestroy {
  @Input() astronaut = '';
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}