这篇文章中,我们将要讨论如何用ngrx/effects连接Angular表单和ngrx/store
我们最终的结果是这样的
\\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 {...}
The Reducer
方便起见,我们会写一个简单的reducer来组织管理我们应用里的所有的表单
状态(the state)将由一个用ID作为key,表格数据作为value的简单对象构成
举个例子
\\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组成
\\connect-form1.reducer.ts store.dispatch({ type: UPDATE_FORM,payload: { path: 'newStory',value: formValue } });
然后这个reducer将负责更新state
\\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 } } }
连接表单的组件——ConnectForm Directive
获取State
我们想要基于state更新表单,所以我们需要path作为输入,然后取出store中正确的片段
\\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里更新表单数据
更新State
当表单数据改变时我们也需要更新表单状态。我们可以通过订阅(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同步所要做的全部工作了
通知和重置
有两件事我们要在这个部分完成
有两点原因
通常,没有其他的组件需要这个信息
我们不想每次都重置store
当提交成功时重置表单
我们将让Angular尽其所能,处理好前端表单校验并重置表单
成功的Action
成功的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
同成功的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,} }); }); 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: ... }) } }