NgRx selector体内赋值的类变量无法在外部访问怎么解决
问题原因
- 异步执行顺序问题:
store.select返回的是异步Observable,subscribe的回调函数只有当Store中的对应状态发生变化时才会触发执行,而你写在回调外的console.log是同步代码,会在ngOnInit执行时立刻运行,此时回调还没有被触发,this.Profile还是初始的空对象,自然取不到值。 - Selector使用错误:你在
ngOnInit中订阅的getloadingProfile对应的是State中的ProfileLoaded布尔值,你直接将布尔值赋值给类型为Profile的this.Profile变量,本身就存在类型不匹配的问题,你要获取Profile实体数据应该订阅专门返回Profile对象的getProfileEntitiesselector。 - 状态逻辑倒置:你当前的Reducer逻辑里,
loadProfileSuccess(加载成功)时反而将ProfileLoaded设为false,初始值却设为true,和正常的加载状态逻辑完全相反,也会导致Resolver的触发逻辑异常。 - 内存泄漏风险:你在constructor和ngOnInit中两次订阅
getloadingProfile,且没有做取消订阅的处理,组件销毁后会存在内存泄漏问题。 - Effect错误处理位置错误:你将
catchError写在了map的第二个参数位置,无法正常捕获接口请求的异常,接口报错时不会触发loadProfileFail动作。
解决方案
- 优先使用Angular的
async管道处理Observable数据,避免手动订阅,既可以自动取消订阅防止内存泄漏,也不用手动赋值给组件变量。 - 修正Selector的选择,获取Profile数据用对应的
getProfileEntities,加载状态用getloadingProfile。 - 修正加载状态的逻辑,初始
ProfileLoaded设为false,加载成功后设为true。 - 调整Effect的错误处理位置,确保异常能被正常捕获。
- 不要在同步代码中依赖异步回调赋值的变量,所有需要用到Profile数据的逻辑都放在订阅回调中,或者配合RxJS操作符处理。
修正后的state.ts(调整初始值)
import { EntityState, createEntityAdapter, EntityAdapter } from '@ngrx/entity'; import { Profile } from '../../models/Profile.model'; export interface ProfileState extends EntityState<Profile> { ProfileLoaded: boolean; Profile:Profile; error: boolean; } export const ProfileAdapter: EntityAdapter<Profile> = createEntityAdapter<Profile>({ selectId: (customer: Profile) => customer.id }); export const initialState: ProfileState = ProfileAdapter.getInitialState({ ProfileLoaded: false, // 初始未加载设为false error: false, Profile:{} as Profile });
修正后的reducer.ts
import {loadProfileSuccess,loadProfileFail,} from '../actions/Profile.actions'; import { createReducer, on } from '@ngrx/store'; import { initialState, ProfileAdapter } from '../state/Profile.state'; const _ProfileReducer = createReducer( initialState, on(loadProfileSuccess, (state, action) => ({ ...state, Profile:action.profile, ProfileLoaded: true // 加载成功后设为true })), on(loadProfileFail, (state) => { return ProfileAdapter.removeAll({ ...state, error: true, ProfileLoaded: false, }); }), ); export const { selectAll, selectIds } = ProfileAdapter.getSelectors(); export function ProfileReducer(state:any, action:any) { return _ProfileReducer(state, action); }
修正后的Effect.ts
@Injectable() export class ProfileEffects { constructor( private actions$: Actions, private PagesService: PagesService, ) { } loadProfile$ = createEffect(() => this.actions$.pipe( ofType(loadProfile), concatMap(() => this.PagesService.GetProfile().pipe( map((Profile:Profile) => loadProfileSuccess({ profile:Profile })), catchError((error) => of(loadProfileFail(error))) )) ) ); }
修正后的ProfileComponent(推荐用async管道版本)
模板代码:
<div *ngIf="profile$ | async as profile"> <!-- 直接使用profile变量渲染 --> {{ profile.name }} </div> <div *ngIf="loading$ | async">加载中...</div>
组件代码:
export class ProfileComponent implements OnInit, OnDestroy { profile$ = this.store.select(getProfileEntities); loading$ = this.store.select(getloadingProfile); user$ = this.store.select(getUser); private destroy$ = new Subject<void>(); constructor(private store: Store<AppState>, private spinner: NgxSpinnerService) { // 全局加载动画处理 this.loading$.pipe(takeUntil(this.destroy$)).subscribe(isLoading => { isLoading ? this.spinner.show() : this.spinner.hide(); }); } ngOnInit() { // 如果需要手动处理Profile数据,在这里订阅 this.profile$.pipe(takeUntil(this.destroy$)).subscribe(profile => { console.log(profile); // 这里可以拿到正确的Profile数据 // 所有需要用到profile的逻辑都写在这里 }); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); this.spinner.hide(); } }
内容的提问来源于stack exchange,提问作者bashar anjroo
相关产品推荐
相关产品推荐

