在Angular中使用类Redux工具—ngrx/store

原文:Connect Angular Forms to @ngrx/storejavascript

![head](https://cdn-images-1.medium.com/max/1000/1*r_2RBJXnxtJM3ZjOGNVtug.png)

这篇文章中,咱们将要讨论如何用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 {...}

![gif](https://cdn-images-1.medium.com/max/800/1*IY8vvVANltaxbCQz2kU50g.gif)

The Reducer

方便起见,咱们会写一个简单的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 }
  }
}

链接表单的组件——ConnectForm Directive

获取State

咱们想要基于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

更新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同步所要作的所有工做了

通知和重置

有两件事咱们要在这个部分完成

  1. 基于HTTP响应返回来显示通知给用户——咱们须要保证通知直接传给组件而且不储存信息在store里

有两点缘由

  • 一般,没有其余的组件须要这个信息

  • 咱们不想每次都重置store

  1. 当提交成功时重置表单

咱们将让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_ERRORFORM_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: ...
    })
  }

}
相关文章
相关标签/搜索