技术原理

Angular:在元件之间传递资料的4种方式

1. 父元件至子元件: @Input

子元件内的@Input装饰器定义属性,父元件再透过属性繫结(Property Binding)将资料传递给子元件。
ChildComponent

import { Component, OnInit, Input } from '@angular/core';
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {
  @Input() bankName: string;
  @Input() accountId: number;
  constructor() { }
  ngOnInit(): void {
  }
}

ChildComponent HTML

<p>{{ bankName }}</p>
<p>{{ accountId }}</p>

ParentComponent

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss']
})
export class ParentComponent implements OnInit {
  bankName = 'ABC Bank';
  accountId = 123456789;
  constructor() { }
  ngOnInit(): void {
  }
}

ParentComponent HTML

<app-child [bankName]="bankName" [accountId]="accountId"></app-child>

结果:
https://ithelp.ithome.com.tw/upload/images/20200520/20112573DvzrzqS8xb.png

2. 子元件至父元件:@Output

子元件藉由@Output装饰器定义属性,该属性为EventEmitter实体,可以设定要传送的资料型别,透过事件繫结(Event Binding)通知父元件有事件发生。

ChildComponent

import { Component, OnInit, EventEmitter, Output } from '@angular/core';
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {
  @Output() counterEvt = new EventEmitter<string>();
  constructor() { }
  ngOnInit(): void { }
  counterChange(cal: string) {
    this.counterEvt.emit(cal);
  }
}

ChildComponent HTML

<button (click)="counterChange('add')">加</button>
<button (click)="counterChange('minus')">减</button>

ParentComponent

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss']
})
export class ParentComponent implements OnInit {
  counter = 0;
  constructor() { }
  ngOnInit(): void {
  }
  counterCal(cal: string) {
    cal === 'add' ? this.counter++ : this.counter--;
  }
}

ParentComponent HTML

<p>counter : {{ counter }}</p>
<app-child (counterEvt)="counterCal($event)"></app-child>

基于元件的维护性与程式的逻辑性,我们只会在子元件送出事件资料(加或减),当父元件接收到事件资料后,真正的实作应该要在父元件完成,而不是在子元件完成。

结果:

3. 子元件至父元件: @ViewChild

以上的方式可以让父元件-子元件传递资料,但还是有限制:父元件无法存取子元件的属性与方法。
这时可以使用‵@ViewChild在父元件建立子元件的实体,让父元件获得子元件的属性与方法。

实作一个简单的timer。

ChildComponent

import { Component, OnInit } from '@angular/core';
@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.scss']
})
export class ChildComponent implements OnInit {

  seconds = 0;
  timer: any;
  constructor() { }
  ngOnInit(): void { }

  start() {
    this.timer = setInterval(() => {
      this.seconds++;
    }, 1000);
  }

  stop() {
    clearInterval(this.timer);
  }
}

所有计时逻辑都在子元件中。

ParentComponent

import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
import { ChildComponent } from '../child/child.component';

@Component({
  selector: 'app-parent',
  templateUrl: './parent.component.html',
  styleUrls: ['./parent.component.scss']
})
export class ParentComponent implements OnInit, AfterViewInit {
  @ViewChild(ChildComponent) childComponent: ChildComponent;
  constructor() { }
  ngOnInit(): void {
  }
  seconds() {
    return 0;
  }

  ngAfterViewInit() {
    setTimeout(() => {
      this.seconds = () => this.childComponent.seconds;
    }, 1000);
  }

  onStart() {
    this.childComponent.start();
  }
  onStop() {
    this.childComponent.stop();
  }
}

父元件显示秒数。

整个生命週期中,必须在ngAfterViewInit才能取得子元件的实体。

由于我们目前处于Development Mode,为了避免ExpressionChangedAfterItHasBeenCheckedError的问题,所以在ngAfterViewInit使用setTimeout,详细说明之后会另开文章。

ParentComponent HTML

<button (click)="onStart()">start</button>
<button (click)="onStop()">stop</button>
<p>{{ seconds() }}</p>
<app-child></app-child>

结果:

4. 不相关的元件:service

A元件跟B元件,是两个不相关的原件,可以藉由service作为他们之间沟通的媒介。

实作在A元件输入讯息,在B元件同步显示的功能:
DataService

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

@Injectable({
  providedIn: 'root'
})
export class DataService {
  msgContent = new Subject<string>();
  constructor() { }

  setMessage(value: string) {
    this.msgContent.next(value);
  }

  getMessage() {
    return this.msgContent.asObservable();
  }
}

AComponent

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

@Component({
  selector: 'app-a',
  templateUrl: './a.component.html',
  styleUrls: ['./a.component.scss']
})
export class AComponent implements OnInit {

  constructor(private dataSvc: DataService) { }

  ngOnInit(): void {
  }

  onMsgChange(message: string) {
    this.dataSvc.setMessage(message);
  }
}

AComponent HTML

input message : <input (keyup)="onMsgChange($event.target.value)">

BComponent

import { Component, OnInit, OnDestroy } from '@angular/core';
import { DataService } from '../data.service';
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-b',
  templateUrl: './b.component.html',
  styleUrls: ['./b.component.scss']
})
export class BComponent implements OnInit, OnDestroy {
  message: string;
  subscription: Subscription;
  constructor(private dataSvc: DataService) { }

  ngOnInit(): void {
    this.subscription = this.dataSvc.getMessage().subscribe(val => {
      this.message = val;
    });
  }
  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

BComponent HTML

here is message : <span>{{ message }}</span>

AppComponent HTML

<app-a></app-a>
<p></p>
<app-b></app-b>

结果:

参考来源:
Angular:如何在多个组件之间通信

你也可能喜欢

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片
人工智能应用图片 投稿者
我还没有学会写个人说明!
最近文章
  • * 没有更多文章了
  • 热门搜索

    分类目录