2017-06-21 15 views
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中對其進行重置並將其重置,以確保它忽略它本身啓動的更改,但這種感覺像是一團糟。

有沒有更好的方法來做到這一點?

+0

plnkr對我來說工作正常。我編輯文本和光標停留在那裏,並且兩個輸入同步 – Skeptor

+0

您可以檢查您的formcontrol(導致更改的那個)是否髒。如果不是,變化來自外部,如果是的話,變化來自它自己。有一個方法formgroup.controls ['yourFormControl']。dirty –

回答

0

您需要保持選擇狀態。這是一個骯髒的方式:

// get reference to input element 
    @ViewChild('inEl') inEl; 

    ngOnChanges(changes: SimpleChanges): void { 
    let start = this.inEl.nativeElement.selectionStart; 
    let end = this.inEl.nativeElement.selectionEnd; 

    this.form.reset(....) 

    this.inEl.nativeElement.setSelectionRange(start, end); 
} 

,你可以實現自己的ControlValueAccessor與writeValue方法,將讓選擇。