这篇文章中,我们将要议论如何用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同步所要做的悉数工作了
关照和重置
有两件事我们要在这个部份完成
基于HTTP相应返返来显现关照给用户——我们须要保证关照直接传给组件并且不贮存信息在store里
有两点缘由
一般,没有其他的组件须要这个信息
我们不想每次都重置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,
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: ...
})
}
}