【问题标题】:NGRX sequiental call actions from component来自组件的 NGRX 顺序调用操作
【发布时间】:2021-03-01 03:28:14
【问题描述】:

我正在尝试向 ngrx-store 发出顺序请求,以将两个不同的实体保存在两个随机 mongodb 集合中,并且需要从第一个 ngrx-store 效果中获得答案,以便在创建引用第一个实体时使用另一个中的数据.

我有什么:

减速器

export interface UserState {
  currentUser: IUser | null;
  checkUserResult: any | null;
  user: IUser | null;
  users: IUser[] | null;
  errorMessage: string | null;
  isLoading: boolean;
  isLoaded: boolean;
  isError: boolean;
}

export function userReducer(
  state = initialUserState,
  action: fromUser.UserActions
): UserState {
  switch (action.type) {
    case fromUser.USER_CREATE: {
      return {
        ...state,
        isLoading: true,
      };
    }
    case fromUser.USER_CREATE_SUCCESS: {
      return {
        ...state,
        user: {
          _id: action.user._id,
          userLogin: action.user.userLogin,
          userPassword: action.user.userPassword,
          userEmail: action.user.userEmail,
          userPhone: action.user.userPhone,
          userRole: action.user.userRole,
          userType: action.user.userType,
          userRef: action.user.userRef,
        },
        checkUserResult: null,
        errorMessage: null,
        isLoading: false,
        isLoaded: true,
        isError: false,
      };
    }
    case fromUser.USER_CREATE_FAILURE: {
      return {
        ...state,
        errorMessage: "Can not create user",
        isError: true,
      };
    }

[...]
}

动作

export class UserCreate implements Action {
  readonly type = USER_CREATE;
  constructor(public payload: IUser) {}
}
export class UserCreateSuccess implements Action {
  readonly type = USER_CREATE_SUCCESS;
  constructor(public user: IUser) {}
}
export class UserCreateFailure implements Action {
  readonly type = USER_CREATE_FAILURE;
  constructor(public error: string) {}
}

效果

@Effect()
  createUser$: Observable<Action> = this.action$.pipe(
    ofType(UserActions.USER_CREATE),
    map((action: UserActions.UserCreate) => action.payload),
    exhaustMap((payload) => {
      return this.userDataService.createUser(payload).pipe(
        map((data) => {
          return new UserActions.UserCreateSuccess(data);
        }),
        catchError((err) => of(new UserActions.UserCreateFailure(err)))
      );
    })
  );

选择器

export const getUserState = createSelector(
  fromFeature.getAppState,
  (state: fromFeature.AppState) => state.users
);

和服务

createUser(user: IUser) {
    this.store.dispatch(new fromStore.UserCreate(user));
    return this.store.select(fromStore.getUserState);
  }

以及另一个服务(user-data.service.ts),其方法从效果调用

createUser(user: IUser): Observable<IUser> {
    const url = `${this.BASE_URL}/user/create`;
    return this.http.post<IUser>(url, { ...user });
  }

当我只从组件创建一个实体时,所有这些设置都有效

user-create.component.ts

onCreateUser() {
    this.us
      .createUser({
        userLogin: this.createUserForm.value.userLogin,
        userPhone: this.createUserForm.value.userPhone,
        userEmail: this.createUserForm.value.userEmail,
        userPassword: this.createUserForm.value.userPassword,
        userRole: this.createUserForm.value.userRole,
        userRef: this.createUserForm.value.userRef,
        userType: this.createUserForm.value.userType,
      })
      .subscribe((user) => {
        if (user) {
          this.createUserForm.reset();
          this.router.navigate(["/users"]);
          this.us.getUsersList();
        }
      });
  }

创建不同实体的其他逻辑与上面列出的相同。当我尝试在这样的组件内调用 action inside 时

this.us
      .createUser({
        userLogin: this.createContactUserForm.value.userPhone,
        userPhone: this.createContactUserForm.value.userPhone,
        userEmail: this.createContactUserForm.value.userEmail,
        userPassword: this.createContactUserForm.value.userPassword,
        userRole: this.createContactUserForm.value.userRole,
        userRef: this.createContactUserForm.value.userRef,
        userType: this.createContactUserForm.value.userType,
      })
      .subscribe((userState) => {
        if (userState.user) {
          this.cs
            .createContact({
              contactFirstName: this.createContactUserForm.value
                .contactFirstName,
              contactLastName: this.createContactUserForm.value.contactLastName,
              contactPatronymicName: this.createContactUserForm.value
                .contactPatronymicName,
              contactPhone: this.createContactUserForm.value.userPhone,
              contactUserId: userState.user._id,
            })
            .subscribe((contactState) => {
              if (contactState) {
                this.router.navigate(["/contacts"]);
              }
            });
        }
      });

它按预期工作,但后来,当我尝试进行任何其他操作时 - 它破坏了应用程序状态。例如,我不能在它之后编辑或删除联系人。 另一方面,如果我将逻辑拆分为两个独立的调用 - 它不会起作用,因为在 createContact 方法调用的那一刻 - 应用程序还不知道 createdUser _id 或 userState.user._id 并引发错误。

我认为,这里一定是一个更复杂的逻辑,但如何使它正确 - 我不知道。请建议我执行此操作的正确方法。

提前致谢。

【问题讨论】:

    标签: angular typescript rxjs ngrx


    【解决方案1】:

    您有与订阅管理相关的问题。

    我将向您推荐的内容更像是您应该如何使用 NGRX 的总体思路

    首先,让我们看看您的“调度程序”服务/模型,或者现在人们如何调用负责调度操作的服务。

    createUser(user: IUser) {
        this.store.dispatch(new fromStore.UserCreate(user));
        return this.store.select(fromStore.getUserState);
      }
    

    有一个单独的方法来调用store 是一种很好的做法,从那里返回商店选择器,而不是太多。有两个原因:

    • 首先,您希望获取已存储在存储中的数据而不再次加载的可能性非常高。
    • 其次,希望在不需要从商店订阅特定字段的情况下调度操作的可能性也很高。

    重构版本

    user$ = this.store.select(fromStore.getUserState);
    createUser(user: IUser): void {
        this.store.dispatch(new fromStore.UserCreate(user)); 
      }
    

    现在让我们看看你发起创建用户请求的组件

    user-create.component.ts
    
    onCreateUser() {
        this.us
          .createUser({
            userLogin: this.createUserForm.value.userLogin,
            userPhone: this.createUserForm.value.userPhone,
            userEmail: this.createUserForm.value.userEmail,
            userPassword: this.createUserForm.value.userPassword,
            userRole: this.createUserForm.value.userRole,
            userRef: this.createUserForm.value.userRef,
            userType: this.createUserForm.value.userType,
          })
          .subscribe((user) => {
            if (user) {
              this.createUserForm.reset();
              this.router.navigate(["/users"]);
              this.us.getUsersList();
            }
          });
      }
    

    在您的实现上下文中查看这段代码时,我们可以看到,每次创建新用户时,我们都会创建一个新的subscription,这意味着在我们创建第一个用户并尝试创建第二个,我们将创建一个新的subscription 执行订阅方法中的代码 + 我们还将执行旧的subscription 中的代码,因为我们永远不会取消订阅。

    此外,如果我们已经创建了一个用户,我们将在成功创建新用户之前进入 if(user) 块,因为我们的商店中已经有一个用户实体。

    重构

    user.service.ts
    
    user$ = this.store.select(fromStore.getUserState);
    userCreatedSuccesfuly$ = this.actions$.pipe(ofType(UserActions.USER_CREATE))
    createUser(user: IUser): void {
        this.store.dispatch(new fromStore.UserCreate(user)); 
     }
    
    user-create.component.ts
    
    onCreateUser() {
        this.us
          .createUser({
            userLogin: this.createUserForm.value.userLogin,
            userPhone: this.createUserForm.value.userPhone,
            userEmail: this.createUserForm.value.userEmail,
            userPassword: this.createUserForm.value.userPassword,
            userRole: this.createUserForm.value.userRole,
            userRef: this.createUserForm.value.userRef,
            userType: this.createUserForm.value.userType,
          })
        this.us.userCreatedSuccesfuly$.pipe(take(1))
          .subscribe(() => {
              this.createUserForm.reset();
              this.router.navigate(["/users"]);
              this.us.getUsersList();
          });
      }
    

    所以这里的不同之处在于,现在我们依赖于成功操作,而不是依赖于用户字段内的更改(来自商店),这将在创建新用户后触发,也可以使用 @987654331 @operator 我们确保不会发生内存泄漏,因为一旦创建用户,订阅就会被终止。

    (旁注:如果用户创建失败,仍然存在内存泄漏的可能性,但我不想采用更复杂的解决方案)

    现在是嵌套订阅

    所以总体而言,subscribe 内部的 subscribe 是一个很大的禁忌

    this.us
          .createUser({
            userLogin: this.createContactUserForm.value.userPhone,
            userPhone: this.createContactUserForm.value.userPhone,
            userEmail: this.createContactUserForm.value.userEmail,
            userPassword: this.createContactUserForm.value.userPassword,
            userRole: this.createContactUserForm.value.userRole,
            userRef: this.createContactUserForm.value.userRef,
            userType: this.createContactUserForm.value.userType,
          })
          .subscribe((userState) => {
            if (userState.user) {
              this.cs
                .createContact({
                  contactFirstName: this.createContactUserForm.value
                    .contactFirstName,
                  contactLastName: this.createContactUserForm.value.contactLastName,
                  contactPatronymicName: this.createContactUserForm.value
                    .contactPatronymicName,
                  contactPhone: this.createContactUserForm.value.userPhone,
                  contactUserId: userState.user._id,
                })
                .subscribe((contactState) => {
                  if (contactState) {
                    this.router.navigate(["/contacts"]);
                  }
                });
            }
          });
    

    重构

    user.service.ts
    
    user$ = this.store.select(fromStore.getUserState);
    userCreatedSuccesfuly$ = this.actions$.pipe(ofType(UserActions.USER_CREATE_SUCCESS))
    createUser(user: IUser): void {
        this.store.dispatch(new fromStore.UserCreate(user)); 
     }
    
    contact.service.ts
    
    contact$ = this.store.select(fromStore.getUserState);
    contactCreatedSuccesfuly$ = this.actions$.pipe(ofType(ContactActions.CONTACT_CREATE_SUCCESS))
    createUser(contact: IContact): void {
        this.store.dispatch(new fromStore.ContactCreate(contact)); 
     }
    
    some-cmp.component.ts
    
    doSomething(){
    
    this.us.createUser({
            userLogin: this.createContactUserForm.value.userPhone,
            userPhone: this.createContactUserForm.value.userPhone,
            userEmail: this.createContactUserForm.value.userEmail,
            userPassword: this.createContactUserForm.value.userPassword,
            userRole: this.createContactUserForm.value.userRole,
            userRef: this.createContactUserForm.value.userRef,
            userType: this.createContactUserForm.value.userType,
          })
    
    this.us.userCreatedSuccesfuly$.pipe(
          switchMap(() => this.us.user$),
          filter(x => !!x),
          tap(userState => {
             this.cs
                .createContact({
                  contactFirstName: this.createContactUserForm.value
                    .contactFirstName,
                  contactLastName: this.createContactUserForm.value.contactLastName,
                  contactPatronymicName: this.createContactUserForm.value
                    .contactPatronymicName,
                  contactPhone: this.createContactUserForm.value.userPhone,
                  contactUserId: userState.user._id,
                })
          }),
          switchMap(() => this.cs.contactCreatedSuccesfuly$),
          take(1)
          )
          .subscribe((userState) => {
           this.router.navigate(["/contacts"]);
          });
    
    }
    
    

    那么让我们看一下最后一段代码:

    • 我们创建一个新用户
    • switchMap 我们指的是userCreatedSuccesfuly$,我们说,每当这个可观察对象发出时,切换到一个新的可观察对象this.us.users$
    • filter 过滤传递的值,使只有真值才能通过流(例如,仅当我们的商店中保存有用户时)
    • tap 一旦一个值达到这一点,通过使用传入的值创建一个联系人,而不影响当前流的值(例如使用用户信息创建一个联系人)
    • switchMap 这里我们指的是this.us.users$,我们是说每当一个值到达这一点时,切换到一个新的可观察对象this.cs.contactCreatedSuccesfuly
    • take这里我们确保在我们成功创建新联系人后清除我们的订阅
    • subscribe 通过调用这个方法我们触发了流

    哦,男孩,那太长了,无论如何,我对你的建议是多花点时间学习rxjs 总体而言

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-06-06
      • 1970-01-01
      • 2017-07-08
      • 2017-04-03
      • 2018-10-22
      • 1970-01-01
      • 1970-01-01
      • 2019-12-16
      相关资源
      最近更新 更多