原文: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: ... }) } }