简体   繁体   English

如何使用@ngrx/store 有效地重置状态?

[英]How can I effectively reset a state using @ngrx/store?

I seem to have gotten stuck on this matter for the last couple of days.最近几天我似乎被这个问题困住了。

We're working on an Angular 2 application, and I need to create a wizard for users to fill out a form.我们正在开发一个 Angular 2 应用程序,我需要为用户创建一个向导来填写表单。

I've successfully managed to make the data flow through each step of the wizard, and save it in order to freely move back and forth.我已经成功地使数据流过向导的每个步骤,并保存它以便自由来回移动。 However, I can't seem to be able to reset it once the form is submitted.但是,一旦提交表单,我似乎无法重置它。

I should add that each component is behind a wall.我应该补充一点,每个组件都在墙后。 Maybe a better solution would be a singleton service injected directly at the AppModule.也许更好的解决方案是在 AppModule 中直接注入单例服务。 But I can't seem to make it work.但我似乎无法让它发挥作用。

Here's my code so far:到目前为止,这是我的代码:

Step 1第1步

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '@ngrx/store';
import { NewEventService } from '../shared/new-event.service';
import { Event } from '../../../events/shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';

@Component({
  selector: 'app-upload-images',
  templateUrl: './upload-images.component.html',
  styleUrls: ['../../../events/new-event/new-event.component.css']
})
export class UploadImagesComponent implements OnInit {
  form: FormGroup;
  private event;
  private images = [];

  constructor(
    private _store: Store<any>,
    private formBuilder: FormBuilder,
    private router: Router,
    private newEventService: NewEventService,
    private friendService: FriendService
  ) {
    _store.select('newEvent').subscribe(newEvent => {
      this.event = newEvent;
    })
  }

  ngOnInit() {
    this.initForm(this.event);
    if (this.event.counter === 0) {
      let friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
      for (let friend in friends) {
        this.event.userIds.push(friends[friend]['id']);
      }
    }
  }

  initForm(event: Event) {
    this.images.push({ imageUrl: 'test0', voteCount: 0 });
    this.images.push({ imageUrl: 'test1', voteCount: 0 });
    this.images.push({ imageUrl: 'test2', voteCount: 0 });
    this.images.push({ imageUrl: 'test3', voteCount: 0 });
    this.form = this.formBuilder.group({
      firstImage: [this.event.length > 0 ? this.event.eventOption[0].imageUrl : null],
      secondImage: [this.event.length > 0 ? this.event.eventOption[1].imageUrl : null],
      thirdImage: [this.event.length > 0 ? this.event.eventOption[2].imageUrl : null],
      fourthImage: [this.event.length > 0 ? this.event.eventOption[3].imageUrl : null],
    })
  }

  next() {
    this.event.eventOptions = this.images;
    this.newEventService.updateEvent(this.event);
    this.router.navigate(['events/new-event/choose-friends']);
  }

}

Step 2第2步

import { Component, OnInit, Input } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { EventOption } from '../../../events/shared/event-option.model';
import { Store } from '@ngrx/store';
import { Event } from '../../shared/event.model';
import { NewEventService } from '../shared/new-event.service';
import { FriendService } from '../../../friends/shared/friend.service';
import { SearchPipe } from '../../../core/search.pipe';

@Component({
  selector: 'app-choose-friends',
  templateUrl: './choose-friends.component.html',
  styleUrls: ['../../../events/new-event/new-event.component.css', './choose-friends.component.css']
})
export class ChooseFriendsComponent implements OnInit {

  private searchTerm = '';
  private event;
  private friends = [];
  private friendsError = false;

  constructor(
    private _store: Store<any>,
    private formBuilder: FormBuilder,
    private router: Router,
    private newEventService: NewEventService,
    private friendService: FriendService
  ) {
    _store.select('newEvent').subscribe(newEvent => {
      this.event = newEvent;
    })
  }

  ngOnInit() {
    this.friends = this.friendService.getFriends('58aaf6304fabf427e0acc08d');
  }

  selectedFriend(friendId: string) {
    return this.friendService.selectedFriend(friendId, this.event.userIds);
  }

  toggleFriend(friendId: string) {
    return this.friendService.toggleFriend(friendId, this.event.userIds);
  }

  toggleAllFriends() {
    return this.friendService.toggleAllFriends(this.friends, this.event.userIds);
  }

  submit() {
    if (this.event.userIds.length > 0) {
      this.newEventService.resetEvent();
      this.router.navigate(['events/vote-events']);
    } else {
      this.friendsError = true;
    }
  }

  back() {
    this.newEventService.updateEvent(this.event);
    this.router.navigate(['events/new-event/upload-images']);
  }

}

Event Service活动服务

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Store, Action } from '@ngrx/store';
import { Event } from '../../../events/shared/event.model';
import { EventOption } from '../../../events/shared/event-option.model';
import { newEvent, newEventModel } from './new-event.reducer';
import 'rxjs/add/operator/take';
import 'rxjs/add/operator/find';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class NewEventService {
  public newEvent$: Observable<newEventModel>;

  constructor(private store: Store<newEventModel>) {
    this.newEvent$ = this.store.select('newEvent');
  }

  getEvent(event) {
    return this.store.dispatch({
      type: 'GET_EVENT',
      payload: event
    })
  }

  updateEvent(event) {
    return this.store.dispatch({
      type: 'UPDATE_EVENT',
      payload: event
    })
  }

  resetEvent() {
    return this.store.dispatch({
      type: 'RESET_EVENT',
    })
  }

}

Event Reducer事件减少器

import { EventOption } from '../../shared/event-option.model';
import { EventType } from '../../shared/event-type.model';
import { ActionReducer, Action } from '@ngrx/store';
import { Event } from '../../shared/event.model';
import { FriendService } from '../../../friends/shared/friend.service';

export interface newEventModel {
  eventOptions: EventOption[];
  eventTypeId: number,
  duration: number,
  comment: string,
  privacyId: number,
  isGlobal: boolean,
  id: string,
  userIds: string[],
  counter: number
}

let blankState: newEventModel = {
  eventOptions: [],
  eventTypeId: null,
  duration: 1440,
  comment: '',
  privacyId: 0,
  isGlobal: false,
  id: '',
  userIds: [],
  counter: 0
}

let initialState: newEventModel = {
  eventOptions: [],
  eventTypeId: null,
  duration: 1440,
  comment: '',
  privacyId: 0,
  isGlobal: false,
  id: '',
  userIds: [],
  counter: 0
}

export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
  // return new state
  switch (action.type) {
    case 'GET_EVENT':
      return state;
    case 'UPDATE_EVENT':
      action.payload.counter = action.payload.counter + 1;
      return action.payload;
    case 'RESET_EVENT':
      return Object.assign({}, state, {
        eventOptions: [],
        eventTypeId: null,
        duration: 1440,
        comment: '',
        privacyId: 0,
        isGlobal: false,
        id: '',
        userIds: [],
        counter: 0
      });
    default:
      return state;
  }
}

I could provide a working plunkr if needed, but I need to create it first.如果需要,我可以提供一个有效的 plunkr,但我需要先创建它。

TLDR: How can I reset the state on @ngrx/store? TLDR:如何重置@ngrx/store 上的状态?

Thanks for any help provided!感谢您提供的任何帮助!

You can reset the state to initialState in your reducer by using Object.assign to copy all properties of initialState to a new object.您可以通过使用Object.assigninitialState所有属性复制到新对象,从而在您的减速器中将状态重置为initialState

export const newEvent: ActionReducer<newEventModel> = (state: newEventModel = initialState, action: Action) => {
  // return new state
  switch (action.type) {
    // ...
    case 'RESET_EVENT':
      return Object.assign({}, initialState);
    // ...
  }
}

A note on the reducer减速机的注意事项

The reducer should be a pure function , so should not modify the arguments. reducer 应该是一个纯函数,所以不应该修改参数。 Your UPDATE_EVENT requires a little tweak:您的UPDATE_EVENT需要稍作调整:

case 'UPDATE_EVENT':
  let counter = { counter: action.payload.counter + 1 };
  return Object.assign({}, action.payload, counter);

The pattern to follow is Object.assign({}, source1, source2, ...) where source1 , source2 etc contain properties to be assigned.到后续的图案是Object.assign({}, source1, source2, ...)其中source1source2等包含要分配的属性。 Properties in source1 are overwritten by duplicate properties in source2 etc.在属性source1是由重复的属性覆盖source2等。

there is much easier way, you just need to set the initialState instead of state :有更简单的方法,您只需要设置initialState而不是state

  const reducer = createReducer(initialState,
  on(proofActions.cleanAdditionalInsuredState, (state, action) => ({
    ...initialState
  })),

Noy Levi had the right thinking in her answer to this question, which assigns initialState back into state, however, there is a way to assign initialState for each reducer automatically. Noy Levi 对这个问题的回答是正确的,它将initialState 分配回状态,但是,有一种方法可以自动为每个reducer 分配initialState。

The key concept to understand is that if the value of 'state' passed into a reducer is 'undefined' (not 'null', it needs to be 'undefined') then the reducer will automatically assign into 'state' the initialState provided to the reducer when it was created.要理解的关键概念是,如果传递给减速器的“状态”的值是“未定义”(不是“空”,它需要是“未定义”),那么减速器将自动将提供给的初始状态分配给“状态”减速器创建时。 Because of this default behavior, you can create a 'metareducer' that recognizes an action, say 'logout', and then passes a state of 'undefined' into all the subsequent reducers called.由于这种默认行为,您可以创建一个识别动作的“元减速器”,例如“注销”,然后将“未定义”状态传递给所有后续调用的减速器。

This behavior is described well in this article about redux, this article about NgRx, and also in this answer about NgRx.这篇关于 redux 的文章这篇关于 NgRx 的文章以及关于 NgRx 的这个答案中都很好地描述了这种行为。

The relevant code would look like this:相关代码如下所示:

export function logoutClearState(reducer) {
    return function (state, action) {
        if (action.type === ActionTypes.LOGOUT) {
            state = undefined;
        }
        return reducer(state, action);
    };
}

@NgModule({
    imports: [
        StoreModule.forRoot(reducers, { metaReducers: [logoutClearState] }),
    ],
    declarations: [],
    providers: [],
})

I'm assuming your RESET_EVENT is suppose to return a fresh object.我假设您的 RESET_EVENT 应该返回一个新对象。 Though you are filling in the object with your state data and another object:尽管您正在使用state数据和另一个对象填充对象:

case 'RESET_EVENT':
  return Object.assign({}, state, {
    eventOptions: [],
    eventTypeId: null,
    duration: 1440,
    comment: '',
    privacyId: 0,
    isGlobal: false,
    id: '',
    userIds: [],
    counter: 0
  });

The syntax for Object.assign is Object.assign(target, ...sources) and your providing two items as sources: state and the object containing eventOptions , eventTypeId , etc. Object.assign的语法是Object.assign(target, ...sources)并且您提供两个项目作为源: state和包含eventOptionseventTypeId等的对象。

Instead you'll want to return Object.assign({}, initialState);相反,您需要返回Object.assign({}, initialState);

sorry, I took a day off in order to study for some exams.对不起,我请了一天假为了准备一些考试。 I ended up "solving" it by doing the following:我最终通过执行以下操作来“解决”它:

....
case 'RESET_EVENT':
  action.payload.eventOptions = blankState.eventOptions;
  action.payload.eventTypeId = blankState.eventTypeId;
  action.payload.duration = blankState.duration;
  action.payload.comment = blankState.comment;
  action.payload.privacyId = blankState.privacyId;
  ....
  return action.payload;
....

It might not be the prettiest or best solution, but at least it works.它可能不是最漂亮或最好的解决方案,但至少它有效。 Thanks for all the help @iblamefish and everyone.感谢@iblamefish 和所有人的帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM