0
在TypeScript,RxJS和Reactive Forms模塊中使用Angular 4。角度分量處理自身產生的輸入變化
請考慮下面的代碼:
import {ChangeDetectionStrategy, Component, EventEmitter, Input, NgModule, Output, SimpleChanges, VERSION} from '@angular/core'
import {FormBuilder, FormGroup, ReactiveFormsModule} from '@angular/forms';
import {BrowserModule} from '@angular/platform-browser'
import {BehaviorSubject,Subscription} from 'rxjs/Rx';
interface Question {
id: string;
label: string;
}
interface State {
question: Question;
}
@Component({
selector: 'my-app',
template: `
<div>
<h2>App</h2>
<edit-question [question]="question$ | async" (questionChange)="questionChanged($event)"></edit-question>
<edit-question [question]="question$ | async" (questionChange)="questionChanged($event)"></edit-question>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class App {
state$ = new BehaviorSubject<State>({
question: {id: 'q1', label: 'Question1'}
});
question$ = this.state$.map(s => s.question);
questionChanged(q: Question): void {
console.log('App.questionChanged', q);
this.state$.next({question: q});
}
}
@Component({
selector: 'edit-question',
template: `
<div [formGroup]="form">
<h3>Question</h3>
<div>ID: {{question.id}}</div>
<div>Label: <input type="text" formControlName="label"></div>
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EditQuestionComponent implements OnChanges, OnDestroy {
@Input() question: Question;
@Output() questionChange = new EventEmitter<Question>();
form: FormGroup;
formChangesSubscription: Subscription;
constructor(fb: FormBuilder) {
this.form = fb.group({id: '', label: ''});
this.formChangesSubscription = this.form.valueChanges.subscribe(value => {
console.log('EditQuestionComponent.valueChange', value);
let updated = {id: value.id, label: value.label};
console.log('EditQuestionComponent.questionChange.emit', updated);
this.questionChange.emit(updated);
});
}
ngOnChanges(changes: SimpleChanges): void {
console.log('EditQuestionComponent.ngOnChanges', changes);
this.form.reset({id: this.question.id, label: this.question.label}, {emitEvent: false});
}
ngOnDestroy(): void {
this.formChangesSubscription.unsubscribe();
}
}
@NgModule({
imports: [ BrowserModule, ReactiveFormsModule ],
declarations: [ App, EditQuestionComponent ],
bootstrap: [ App ]
})
export class AppModule {}
也可作爲Plunker here。
在這裏,我們有:
- 的父組件和
- 所有部件都採用OnPush變化檢測策略
- 家長使用BehaviorSubject 保持狀態的相同子組件的兩個實例(故意)
- 父使用異步管道將狀態向下發送到子組件
- 父寄存器用於狀態從子組件發生更改,並根據給定的狀態生成新狀態更改
當運行Plunker時,將焦點放在第一個文本輸入(Question1之前)的開始處並鍵入一個字母后,插入符將跳轉到輸入的末尾。
據我瞭解,這是可以預料到:
- 表單控件值發生變化,所以形式值改變
- 形式valueChanges訂閱踢併發出一個新的問題
- 父事件處理程序接收新的問題併產生新的狀態
- 生成新的狀態值,然後映射到新的問題值,並將新的問題值推送到子組件
- 子組件通過ngOnChanges和補丁表單值檢測新的問題值,從而推送到輸入的末尾
因此,雖然這是有道理的,但我想知道如何避免這種情況。子組件可以在發出更改之前設置一個ignoreNextChange
標誌,並在ngOnChanges
中對其進行重置並將其重置,以確保它忽略它本身啓動的更改,但這種感覺像是一團糟。
有沒有更好的方法來做到這一點?
plnkr對我來說工作正常。我編輯文本和光標停留在那裏,並且兩個輸入同步 – Skeptor
您可以檢查您的formcontrol(導致更改的那個)是否髒。如果不是,變化來自外部,如果是的話,變化來自它自己。有一個方法formgroup.controls ['yourFormControl']。dirty –