原文:Connect Angular Forms to @ngrx/storejavascript
這篇文章中,咱們將要討論如何用ngrx/effects鏈接Angular表單和ngrx/store前端
咱們最終的結果是這樣的java
\\new-story0.component.ts @Component({ selector: 'new-story-form', template: ` <form [formGroup]="newStory" (submit)="submit($event)" (success)="onSuccess()" (error)="onError($event)" connectForm="newStory"> ...controls </form> <div *ngIf="success">Success!</div> <div *ngIf="error">{{error}}</div> }) class NewStoryFormComponent {...}
方便起見,咱們會寫一個簡單的reducer來組織管理咱們應用裏的全部的表單git
狀態(the state)將由一個用ID做爲key,表格數據做爲value的簡單對象構成github
舉個例子this
\\connect-form.reducer.ts const initialState = { newStory: { title: '', description: '' }, contactUs: { email: '', message: '' } } export function forms(state = initialState, action) { }
咱們先構建一個action——UPDATE_FORM。這個action由兩個key:path和value組成spa
\\connect-form1.reducer.ts store.dispatch({ type: UPDATE_FORM, payload: { path: 'newStory', value: formValue } });
而後這個reducer將負責更新statecode
\\connect-form2.reducer.ts export function forms(state = initialState, action) { if(action.type === UPDATE_FORM) { // newStory: formValue return { ...state, [action.payload.path]: action.payload.value } } }
咱們想要基於state更新表單,因此咱們須要path做爲輸入,而後取出store中正確的片斷component
\\connect-form.directive.ts @Directive({ selector: '[connectForm]' }) export class ConnectFormDirective { @Input('connectForm') path: string; constructor(private formGroupDirective: FormGroupDirective, private store: Store<AppState> ) { ngOnInit() { // Update the form value based on the state this.store.select(state => state.forms[this.path]).take(1).subscribe(formValue => { this.formGroupDirective.form.patchValue(formValue); }); } } }
咱們抓取表單directive實例而後從store裏更新表單數據orm
當表單數據改變時咱們也須要更新表單狀態。咱們能夠經過訂閱(subscribe)這個valueChanges
的可觀察對象(observable)而後調度(dispatch)這個UPDATE_FORM的action來獲取值
\\connect-form1.directive.ts this.formChange = this.formGroupDirective.form.valueChanges .subscribe(value => { this.store.dispatch({ type: UPDATE_FORM, payload: { value, path: this.path, // newStory } }); })
這就是表單和State同步所要作的所有工做了
有兩件事咱們要在這個部分完成
基於HTTP響應返回來顯示通知給用戶——咱們須要保證通知直接傳給組件而且不儲存信息在store裏
有兩點緣由
一般,沒有其餘的組件須要這個信息
咱們不想每次都重置store
當提交成功時重置表單
咱們將讓Angular盡其所能,處理好前端表單校驗並重置表單
成功的Action包含表單的path屬性因此咱們能夠知道到底哪一個表單須要重置,同時何時須要去使用(emit)這個成功的事件
\\connect-form2.directive.ts const FORM_SUBMIT_SUCCESS = 'FORM_SUBMIT_SUCCESS'; const FORM_SUBMIT_ERROR = 'FORM_SUBMIT_ERROR'; const UPDATE_FORM = 'UPDATE_FORM'; export const formSuccessAction = path => ({ type: FORM_SUBMIT_SUCCESS, payload: { path } });
同成功的action同樣,由於有path的存在,咱們也知道什麼時候去使用(emit)錯誤異常 的事件
\\connect-form3.directive.ts export const formErrorAction = ( path, error ) => ({ type: FORM_SUBMIT_ERROR, payload: { path, error } });
咱們須要建立 成功 和 錯誤異常 的輸出 而後 監聽 FORM_SUBMIT_ERROR
和 FORM_SUBMIT_SUCCESS
的 action。
由於咱們正好要使用 ngrx/effects ,此時咱們就能夠用 Action 的服務(service)來監聽actions了
\\connect-form3.directive.ts @Directive({ selector: '[connectForm]' }) export class ConnectFormDirective { @Input('connectForm') path : string; @Input() debounce : number = 300; @Output() error = new EventEmitter(); @Output() success = new EventEmitter(); formChange : Subscription; formSuccess : Subscription; formError : Subscription; constructor( private formGroupDirective : FormGroupDirective, private actions$ : Actions, private store : Store<any> ) { } ngOnInit() { this.store.select(state => state.forms[this.path]) .debounceTime(this.debounce) .take(1).subscribe(val => { this.formGroupDirective.form.patchValue(val); }); this.formChange = this.formGroupDirective.form.valueChanges .debounceTime(this.debounce).subscribe(value => { this.store.dispatch({ type: UPDATE_FORM, payload: { value, path: this.path, } }); }); this.formSuccess = this.actions$ .ofType(FORM_SUBMIT_SUCCESS) .filter(( { payload } ) => payload.path === this.path) .subscribe(() => { this.formGroupDirective.form.reset(); this.success.emit(); }); this.formError = this.actions$ .ofType(FORM_SUBMIT_ERROR) .filter(( { payload } ) => payload.path === this.path) .subscribe(( { payload } ) => this.error.emit(payload.error)) } }
固然,咱們不能忘了清空訂閱
\\connect-form4.directive.ts ngOnDestroy() { this.formChange.unsubscribe(); this.formError.unsubscribe(); this.formSuccess.unsubscribe(); }
最後一步就是在有返回的時候調用表單的actions
\\connect-form4.directive.ts import { formErrorAction, formSuccessAction } from '../connect-form.directive'; @Effect() addStory$ = this.actions$ .ofType(ADD_STORY) .switchMap(action => this.storyService.add(action.payload) .switchMap(story => (Observable.from([{ type: 'ADD_STORY_SUCCESS' }, formSuccessAction('newStory')]))) .catch(err => (Observable.of(formErrorAction('newStory', err)))) )
如今咱們能夠在組件裏顯示提醒了
\\new-story.component.ts @Component({ selector: 'new-story-form', template: ` <form [formGroup]="newStory" (submit)="submit($event)" (success)="onSuccess()" (error)="onError($event)" connectForm="newStory"> ...controls <button [disabled]="newStory.invalid" type="submit">Submit</button> </form> <div *ngIf="success">Success!</div> <div *ngIf="error">{{error}}</div> ` }) class NewStoryFormComponent { constructor(private store: Store<AppState> ) {} onError(error) { this.error = error; } onSuccess() { this.success = true; } submit() { // You can also take the payload from the form state in your effect // with the withLatestFrom observable this.store.dispatch({ type: ADD_STORY, payload: ... }) } }