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.ts
的 isShown
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();
}
}
在Angular中使用two-way binding时,子组件的setter好像被调用了两次
Here is a playground 说明了这个问题。如果单击“Toggle from component”按钮,则会调用两次 toggler.component.ts
的 isShown
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();
}
}