Setter 在 Angular 中被双向绑定调用两次

Setter is called twice with two-way binding in Angular

在Angular中使用two-way binding时,子组件的setter好像被调用了两次

Here is a playground 说明了这个问题。如果单击“Toggle from component”按钮,则会调用两次 toggler.component.tsisShown setter。我在下面复制了有趣的代码:

父组件

@Component({changeDetection: ChangeDetectionStrategy.OnPush})
export class AppComponent implements DoCheck {
  public isShown = true;

  public onToggle() {
    this.isShown = !this.isShown;
  }
}

子组件

@Component({changeDetection: ChangeDetectionStrategy.OnPush})
export class TogglerComponent {

  public get isShown(): boolean {
    return this._isShown;
  }

  @Input()
  public set isShown(isShown: boolean) {
    console.log('Entering setter component');
    this._isShown = isShown;
    this.isShownChange.emit(isShown);
  }

  @Output()
  public isShownChange: EventEmitter<boolean> = new EventEmitter();

  private _isShown: boolean = true;

  public onToggle() {
    this.isShown = !this.isShown;
  }
}

如何防止 setter 被调用两次?当父组件异步初始化绑定变量时,这种行为是有问题的。

如果您不介意使用 rxjs 和流而不是 getters/setters

,请参阅此内容
import { Component, ChangeDetectionStrategy, DoCheck } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { shareReplay } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AppComponent implements DoCheck {
  private readonly showSubject = new BehaviorSubject<boolean>(false);

  public readonly show$ = this.showSubject.asObservable().pipe(shareReplay());

  public toggle() {
    this.showSubject.next(!this.showSubject.value);
    console.log(
      `%c Outside: isShown changed to: ${this.showSubject.value}`,
      'color: blue;'
    );
  }

  public ngDoCheck() {
    console.log(
      `%c Outside: DoCheck: isShown: ${this.showSubject.value}`,
      'color: blue;'
    );
  }
}

<div>Outer isShown value: {{ show$ | async }}</div>
<button (click)="toggle()">Toggle from outside</button>

<br />
<br />

<toggler [show]="show$ | async" (showChange)="toggle()"></toggler>
import {
  Component,
  ChangeDetectionStrategy,
  OnChanges,
  SimpleChanges,
  Input,
  Output,
  EventEmitter,
  DoCheck,
} from '@angular/core';

@Component({
  selector: 'toggler',
  template: `
    <div>
      <div>show: {{ show }}</div>

      <button (click)="toggle()">Toggle from component</button>
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  styles: [
    `
    :host {
      display: block;
      border: 1px dashed black;
      padding: 1em;
      color: green;
    }
  `,
  ],
})
export class TogglerComponent implements OnChanges, DoCheck {
  @Input() show?: boolean;

  @Output() showChange: EventEmitter<void> = new EventEmitter();

  public ngOnChanges(changes: SimpleChanges) {
    if (changes.show) {
      console.log(
        `%c OnChanges: isShown changed to: ${this.show}`,
        'color: green;'
      );
    }
  }

  public ngDoCheck() {
    console.log(`%c DoCheck: isShown: ${this.show}`, 'color: green;');
  }

  public toggle() {
    this.showChange.emit();
  }
}