我的效果调度了一个无效的动作或无限循环
My effect dispatched an invalid action or infinite loop
我是 ngrx 的新手,我希望有人能好心帮助我,因为我阅读了很多关于这个问题的主题,但我仍然处于同一点。
我正在测试 @ngrx/effects
,我只想在应用程序开始时 return 一组简单的角色用户(只是为了了解它目前的工作原理)。
当我在下面使用此代码时:
...
...
switchMap(() => this.roleService.getAllRoles().pipe(
map(roles => {
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
// I got infinite loop
我遇到了一个无限循环。
通过对 stackflow 的研究,我发现我必须使用 .tap()
而不是 .map()
。
但是当我使用 .tap()
:
...
...
switchMap(() => this.roleService.getAllRoles().pipe(
tap(roles => {
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
// I got this:
// core.js:6014 ERROR Error: Effect "RoleEffects.loadRoles$" dispatched an invalid action: ...
// core.js:6014 ERROR TypeError: Actions must have a type property
对于无效操作,我试过:
- Effect dispatched an invalid action:
- 带有装饰器的版本 @Effect
(ngrx 8.0.1),版本 createEffect()
(ngrx 8.3.0).
- swithMap()
mergeMap()
exhaustMap()
等等...我得到了相同的结果。
这里是完整的代码,也许你能明白这里发生了什么。
// #actions/role.actions.ts
// ngrx version 8.0.1
export enum RoleActionTypes {
RequestLoadRoles = '[Role] Request Load data Roles',
AllRolesLoaded = '[Roles API] All Roles Loaded',
ErrorLoadRole = '[Roles API] Error load roles',
}
export class RequestLoadRoles implements Action {
readonly type = RoleActionTypes.RequestLoadRoles;
}
export class AllRolesLoaded implements Action {
readonly type = RoleActionTypes.AllRolesLoaded;
constructor(public payload: { roles: Role[] }) {}
}
export class ErrorLoadRole {
readonly type = RoleActionTypes.ErrorLoadRole;
constructor(public payload: HttpErrorResponse) {}
}
// ngrx version 8.3.0 but i cannot handle it yet
// export const RequestLoadRoles = createAction('[Role] Request Load data Roles');
// export const AllRolesLoaded = createAction('[Roles API] All Roles Loaded', props<{ role: Role[] }>());
export type RoleActions =
| RequestLoadRoles
| AllRolesLoaded
| ErrorLoadRole
;
// #effects/role.effect.ts
@Injectable()
export class RoleEffects {
// ngrx version 8.0.1
@Effect()
loadRoles$ = this.actions$.pipe(
ofType<RequestLoadRoles>(RoleActionTypes.RequestLoadRoles),
withLatestFrom(this.store.pipe(select(roles => roles.rolesLoaded))),
filter(([action, loaded]) => !loaded),
switchMap(() => this.roleService.getAllRoles().pipe(
tap(roles => { // with .tap() i got the invalid dispatched action, i dont know why, and .map() give my an infinite loop
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
);
// ngrx version 8.3.0 but i cannot handle it yet
// loadRoles$ = createEffect(() => this.actions$.pipe(
// ofType('[Roles API] All Roles Loaded'),
// mergeMap(() => this.roleService.getAllRoles()
// .pipe(
// map(roles => ({ type: '[Roles API] All Roles Loaded', payload: roles })),
// catchError(() => EMPTY)
// ))
// )
// );
@Effect()
init$: Observable<RoleActions> = defer(() => {
return of(new RequestLoadRoles());
});
constructor(private actions$: Actions, private store: Store<RolesStateEntity>, private roleService: RoleService) {}
}
# reducer/role.reducer.ts
export const roleFeatureKey = 'role';
export interface RolesStateEntity {
rolesLoaded: boolean;
rolesLoading: boolean;
queryResult: Role[];
}
export const initialRolesState: RolesStateEntity = {
rolesLoaded: false,
rolesLoading: false,
queryResult: []
};
export function roleReducer(state = initialRolesState, action: RoleActions): RolesStateEntity {
switch (action.type) {
case RoleActionTypes.AllRolesLoaded:
return {
...state,
queryResult: action.payload.roles,
rolesLoading: false,
rolesLoaded: true,
};
case RoleActionTypes.ErrorLoadRole:
return {
...state,
rolesLoading: false,
};
default:
return state;
}
}
# service/role.service.ts there is only 1 method
getAllRoles(): Observable<Role[]> {
return new Observable(observer => {
const array = [
{
name: 'ADMIN',
permissions: ['fullAccessUserManagement', 'canDeleteUserManagement', 'canUpdateUserManagement', 'canReadUserManagement'],
},
{
name: 'MODERATOR',
permissions: ['canDeleteUserManagement', 'canUpdateUserManagement', 'canReadUserManagement'],
},
{
name: 'USER',
permissions: ['canReadUserManagement'],
},
{
name: 'GUEST',
permissions: [],
},
]
observer.next(array);
});
}
这里是控制台的完整错误:
core.js:6014 ERROR Error: Effect "RoleEffects.loadRoles$" dispatched an invalid action: [{"name":"ADMIN","permissions":["fullAccessUserManagement","canDeleteUserManagement","canUpdateUserManagement","canReadUserManagement"]},{"name":"MODERATOR","permissions":["canDeleteUserManagement","canUpdateUserManagement","canReadUserManagement"]},{"name":"USER","permissions":["canReadUserManagement"]},{"name":"GUEST","permissions":[]}]
at reportInvalidActions (effects.js:338)
at MapSubscriber.project (effects.js:428)
at MapSubscriber._next (map.js:29)
at MapSubscriber.next (Subscriber.js:49)
at ExhaustMapSubscriber.notifyNext (exhaustMap.js:60)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
at MergeMapSubscriber.notifyNext (mergeMap.js:69)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
...
Show 139 more frames
core.js:6014 ERROR TypeError: Actions must have a type property
at ActionsSubject.next (store.js:168)
at Store.next (store.js:709)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
at MergeMapSubscriber.notifyNext (mergeMap.js:69)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
at Notification.observe (Notification.js:20)
我在这里再说一遍,我得到了数据,但我也遇到了这个错误(当我使用.tap()
时)。
另一个错误是无限循环(.map()
)。
如果你花时间阅读这篇文章,我提前感谢你,我将感谢你的帮助,因为此时此刻我正处于黑洞中。
My photo of infinite loop here
您应该使用 map
而不是 tap
。
tap
不是 return 值,因此这意味着您会将服务的结果分派回导致无效操作错误的商店。使用 map
,您可以将服务响应转换为有效的 NgRx 操作。
您显示的代码是有效的,所以我的猜测是其他原因导致了无限循环。
switchMap(() => this.roleService.getAllRoles().pipe(
map(roles => {
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
我相信这个效果是罪魁祸首:
@Effect()
init$: Observable<RoleActions> = defer(() => {
return of(new RequestLoadRoles());
});
删除它并在服务的构造函数中执行
this.store.dispatch(new RequestLoadRoles());
PS .map() 用于转换管道的数据(这就是您需要的:将角色列表转换为 AllRolesLoaded 操作),而 .tap() 充当副作用,所以管道实际上没有受到影响
好的,我解决了我的问题。我把解释放在下面。
来自 app.module.ts
我有这个:
imports: [
...
EffectsModule.forRoot([]),
StoreModule.forRoot(reducers, { // this is made by cmd : ng g store AppState --root --module app.module.ts
metaReducers,
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
},
}),
]
我的 front.module.ts
上有这个:
注意:我决定制作 2 个模块(1 个正面,1 个背面)
imports: [
...
StoreModule.forFeature(roleFeatureKey, rolesReducer),
EffectsModule.forFeature([PermissionEffects, RolesEffects]),
]
我在这里找到了很多使用 nrgx 初始化数据的例子:
- https://dev.to/jonrimmer/where-to-initiate-data-load-in-ngrx-358l
我也做了同样的事情。
所以我的新app.module.ts
现在是这样的:
import { rolesReducer } from './reducer/role';
...
...
imports: [
...
EffectsModule.forRoot([RolesEffects]),
StoreModule.forRoot({ roles: rolesReducer }),
StoreDevtoolsModule.instrument({
maxAge: 25,
}),
]
在front.module.ts
中:
imports: [
...
i deleted,
i deleted,
]
我仍然不知道为什么它以前没有工作,我认为它可以工作,也许这是我的错误。
医生说:
For feature modules, register your effects by adding the EffectsModule.forFeature() method in the imports array of your NgModule.
也许我们在初始化数据时不能那样做
像这样它完美地工作。我希望我不会有更多的问题^^。
我是 ngrx 的新手,我希望有人能好心帮助我,因为我阅读了很多关于这个问题的主题,但我仍然处于同一点。
我正在测试 @ngrx/effects
,我只想在应用程序开始时 return 一组简单的角色用户(只是为了了解它目前的工作原理)。
当我在下面使用此代码时:
...
...
switchMap(() => this.roleService.getAllRoles().pipe(
map(roles => {
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
// I got infinite loop
我遇到了一个无限循环。
通过对 stackflow 的研究,我发现我必须使用 .tap()
而不是 .map()
。
但是当我使用 .tap()
:
...
...
switchMap(() => this.roleService.getAllRoles().pipe(
tap(roles => {
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
// I got this:
// core.js:6014 ERROR Error: Effect "RoleEffects.loadRoles$" dispatched an invalid action: ...
// core.js:6014 ERROR TypeError: Actions must have a type property
对于无效操作,我试过:
- Effect dispatched an invalid action:
- 带有装饰器的版本 @Effect
(ngrx 8.0.1),版本 createEffect()
(ngrx 8.3.0).
- swithMap()
mergeMap()
exhaustMap()
等等...我得到了相同的结果。
这里是完整的代码,也许你能明白这里发生了什么。
// #actions/role.actions.ts
// ngrx version 8.0.1
export enum RoleActionTypes {
RequestLoadRoles = '[Role] Request Load data Roles',
AllRolesLoaded = '[Roles API] All Roles Loaded',
ErrorLoadRole = '[Roles API] Error load roles',
}
export class RequestLoadRoles implements Action {
readonly type = RoleActionTypes.RequestLoadRoles;
}
export class AllRolesLoaded implements Action {
readonly type = RoleActionTypes.AllRolesLoaded;
constructor(public payload: { roles: Role[] }) {}
}
export class ErrorLoadRole {
readonly type = RoleActionTypes.ErrorLoadRole;
constructor(public payload: HttpErrorResponse) {}
}
// ngrx version 8.3.0 but i cannot handle it yet
// export const RequestLoadRoles = createAction('[Role] Request Load data Roles');
// export const AllRolesLoaded = createAction('[Roles API] All Roles Loaded', props<{ role: Role[] }>());
export type RoleActions =
| RequestLoadRoles
| AllRolesLoaded
| ErrorLoadRole
;
// #effects/role.effect.ts
@Injectable()
export class RoleEffects {
// ngrx version 8.0.1
@Effect()
loadRoles$ = this.actions$.pipe(
ofType<RequestLoadRoles>(RoleActionTypes.RequestLoadRoles),
withLatestFrom(this.store.pipe(select(roles => roles.rolesLoaded))),
filter(([action, loaded]) => !loaded),
switchMap(() => this.roleService.getAllRoles().pipe(
tap(roles => { // with .tap() i got the invalid dispatched action, i dont know why, and .map() give my an infinite loop
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
);
// ngrx version 8.3.0 but i cannot handle it yet
// loadRoles$ = createEffect(() => this.actions$.pipe(
// ofType('[Roles API] All Roles Loaded'),
// mergeMap(() => this.roleService.getAllRoles()
// .pipe(
// map(roles => ({ type: '[Roles API] All Roles Loaded', payload: roles })),
// catchError(() => EMPTY)
// ))
// )
// );
@Effect()
init$: Observable<RoleActions> = defer(() => {
return of(new RequestLoadRoles());
});
constructor(private actions$: Actions, private store: Store<RolesStateEntity>, private roleService: RoleService) {}
}
# reducer/role.reducer.ts
export const roleFeatureKey = 'role';
export interface RolesStateEntity {
rolesLoaded: boolean;
rolesLoading: boolean;
queryResult: Role[];
}
export const initialRolesState: RolesStateEntity = {
rolesLoaded: false,
rolesLoading: false,
queryResult: []
};
export function roleReducer(state = initialRolesState, action: RoleActions): RolesStateEntity {
switch (action.type) {
case RoleActionTypes.AllRolesLoaded:
return {
...state,
queryResult: action.payload.roles,
rolesLoading: false,
rolesLoaded: true,
};
case RoleActionTypes.ErrorLoadRole:
return {
...state,
rolesLoading: false,
};
default:
return state;
}
}
# service/role.service.ts there is only 1 method
getAllRoles(): Observable<Role[]> {
return new Observable(observer => {
const array = [
{
name: 'ADMIN',
permissions: ['fullAccessUserManagement', 'canDeleteUserManagement', 'canUpdateUserManagement', 'canReadUserManagement'],
},
{
name: 'MODERATOR',
permissions: ['canDeleteUserManagement', 'canUpdateUserManagement', 'canReadUserManagement'],
},
{
name: 'USER',
permissions: ['canReadUserManagement'],
},
{
name: 'GUEST',
permissions: [],
},
]
observer.next(array);
});
}
这里是控制台的完整错误:
core.js:6014 ERROR Error: Effect "RoleEffects.loadRoles$" dispatched an invalid action: [{"name":"ADMIN","permissions":["fullAccessUserManagement","canDeleteUserManagement","canUpdateUserManagement","canReadUserManagement"]},{"name":"MODERATOR","permissions":["canDeleteUserManagement","canUpdateUserManagement","canReadUserManagement"]},{"name":"USER","permissions":["canReadUserManagement"]},{"name":"GUEST","permissions":[]}]
at reportInvalidActions (effects.js:338)
at MapSubscriber.project (effects.js:428)
at MapSubscriber._next (map.js:29)
at MapSubscriber.next (Subscriber.js:49)
at ExhaustMapSubscriber.notifyNext (exhaustMap.js:60)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
at MergeMapSubscriber.notifyNext (mergeMap.js:69)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
...
Show 139 more frames
core.js:6014 ERROR TypeError: Actions must have a type property
at ActionsSubject.next (store.js:168)
at Store.next (store.js:709)
at SafeSubscriber.__tryOrUnsub (Subscriber.js:183)
at SafeSubscriber.next (Subscriber.js:122)
at Subscriber._next (Subscriber.js:72)
at Subscriber.next (Subscriber.js:49)
at MergeMapSubscriber.notifyNext (mergeMap.js:69)
at InnerSubscriber._next (InnerSubscriber.js:11)
at InnerSubscriber.next (Subscriber.js:49)
at Notification.observe (Notification.js:20)
我在这里再说一遍,我得到了数据,但我也遇到了这个错误(当我使用.tap()
时)。
另一个错误是无限循环(.map()
)。
如果你花时间阅读这篇文章,我提前感谢你,我将感谢你的帮助,因为此时此刻我正处于黑洞中。
My photo of infinite loop here
您应该使用 map
而不是 tap
。
tap
不是 return 值,因此这意味着您会将服务的结果分派回导致无效操作错误的商店。使用 map
,您可以将服务响应转换为有效的 NgRx 操作。
您显示的代码是有效的,所以我的猜测是其他原因导致了无限循环。
switchMap(() => this.roleService.getAllRoles().pipe(
map(roles => {
return new AllRolesLoaded({ roles });
}),
catchError((error) => of(new ErrorLoadRole(error)))
)),
我相信这个效果是罪魁祸首:
@Effect()
init$: Observable<RoleActions> = defer(() => {
return of(new RequestLoadRoles());
});
删除它并在服务的构造函数中执行
this.store.dispatch(new RequestLoadRoles());
PS .map() 用于转换管道的数据(这就是您需要的:将角色列表转换为 AllRolesLoaded 操作),而 .tap() 充当副作用,所以管道实际上没有受到影响
好的,我解决了我的问题。我把解释放在下面。
来自 app.module.ts
我有这个:
imports: [
...
EffectsModule.forRoot([]),
StoreModule.forRoot(reducers, { // this is made by cmd : ng g store AppState --root --module app.module.ts
metaReducers,
runtimeChecks: {
strictStateImmutability: true,
strictActionImmutability: true,
},
}),
]
我的 front.module.ts
上有这个:
注意:我决定制作 2 个模块(1 个正面,1 个背面)
imports: [
...
StoreModule.forFeature(roleFeatureKey, rolesReducer),
EffectsModule.forFeature([PermissionEffects, RolesEffects]),
]
我在这里找到了很多使用 nrgx 初始化数据的例子:
- https://dev.to/jonrimmer/where-to-initiate-data-load-in-ngrx-358l
我也做了同样的事情。
所以我的新app.module.ts
现在是这样的:
import { rolesReducer } from './reducer/role';
...
...
imports: [
...
EffectsModule.forRoot([RolesEffects]),
StoreModule.forRoot({ roles: rolesReducer }),
StoreDevtoolsModule.instrument({
maxAge: 25,
}),
]
在front.module.ts
中:
imports: [
...
i deleted,
i deleted,
]
我仍然不知道为什么它以前没有工作,我认为它可以工作,也许这是我的错误。
医生说:
For feature modules, register your effects by adding the EffectsModule.forFeature() method in the imports array of your NgModule.
也许我们在初始化数据时不能那样做
像这样它完美地工作。我希望我不会有更多的问题^^。