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

}
相關文章
相關標籤/搜索