angular - NGRX 流程和路由 - 组件被访问次数过多

标签 angular angular-routing ngrx

在我的应用程序中实现 NGRX 存储后,我发现我的 HomeComponent 被加载了太多次。

流程如下,从头开始:

1 - 调用页面时,尝试加载仪表板,但 AuthGuard 告诉我用户未登录并加载 LoginComponent。

应用路由.module.ts

const routes: Routes = [
  {
    path: 'login',
    loadChildren: './landing/landing.module#LandingModule'
  },
  {
    path: '',
    canActivate: [AuthGuard],
    loadChildren: './dashboard/dashboard.module#DashboardModule'
  }
];

2 - 然后,用户选择通过 Facebook 登录。

登录.component.ts

signInWithFacebook() {
  this.store.dispatch(new FacebookLogIn());
}

3 - reducer 被调用,调用我的 LoginService,如果身份验证正常,发送到 LogInSuccess 效果。继续,我不会发布这部分。

4 - 如果登录成功,我必须加载关于用户的其他信息,所以我调用其他商店,然后导航到我的 DashboardComponent。

@Effect({ dispatch: false })
LogInSuccess: Observable<any> = this.actions.pipe(
  ofType(LoginActionTypes.LOGIN_SUCCESS),
  tap(action => {
    this.zone.run(() => {
      this.store.dispatch(new GetData(action.payload.user.email));
      this.store.dispatch(new GetData2(action.payload.user.uid));
      localStorage.setItem('user', JSON.stringify(action.payload.user));
      this.router.navigate(['/dashboard'])
    });
  })
);

5 - 仪表板一起加载 HomeComponent。

dashboard-routing.module.ts

{
  path: 'dashboard',
  component: DashboardComponent,
  canActivate: [AuthGuard],
  children: [
    {
      path: '',
      component: HomeComponent,
    },
    ...
    ...
  ]
}

6 - 商店调用的结果是:

enter image description here

7 - 这就是问题所在。如果我在 HomeComponent 中执行 console.log,我会看到它为每个调用的商店调用了 1 次,如下所示。

enter image description here

问题是:

为什么?

我应该怎么做才能避免所有这些不必要的负载?

如果我删除上面的调度之一,它只会进入 HomeComponent 3 次,而不是图片中的 5 次,因为它删除了 2 个效果。

-- 更新--

HomeComponent.ts

isTermSigned = false;
homeInfo = {
  isBeta: false,
  isTermSigned: false,
  displayName: '',
  email: ''
};
homeSubscription: Subscription;

constructor(
  private afs: AngularFirestore,
  private router: Router,
  private store: Store<AppState>
) { }

ngOnInit() {
  this.homeSubscription = combineLatest(
    this.store.pipe(select(selectData)),
    this.store.pipe(select(selectStatusLogin))
  ).subscribe(([data, login]) => {
    console.log(login);
    if (login.user) {
      this.homeInfo = {
        isBeta: data.isBeta,
        isTermSigned: data.isBeta,
        displayName: login.user.displayName,
        email: login.user.email
      };
    }
  });
}

-- 更新 2 -- 这是数据存储的重要部分

数据.action.ts

export class GetData implements Action {
  readonly type = PlayerActionTypes.GET_BETA_USER;
  constructor(public payload: any) {}
}

export class GetDataSuccess implements Action {
  readonly type = PlayerActionTypes.GET_DATA_SUCCESS;
  constructor(public payload: any) {}
}

数据.效果.ts

@Effect()
GetData: Observable<any> = this.actions.pipe(
  ofType(PlayerActionTypes.GET_DATA),
  mergeMap(email =>
    this.dataService
      .getData(email)
      .then(data=> {
        return new GetDataSuccess({
          isBeta: data.email ? true : false,
          isTermSigned: data.acceptTerms ? true : false
        });
      })
      .catch(error => {
        return new GetDataError({
          isBetaUser: false,
          isTermSigned: false
        });
      })
  )
);

@Effect({ dispatch: false })
GetDataSuccess: Observable<any> = this.actions.pipe(
  ofType(PlayerActionTypes.GET_DATA_SUCCESS),
  tap(action => {
    localStorage.setItem('data', JSON.stringify(action.payload));
  })
);

data.reducer.ts

export interface State {
  isBeta: boolean;
  isTermSigned: boolean;
}

export const initialState: State = {
  isBeta: false,
  isTermSigned: false
};

export function reducer(state = initialState, action: All): State {
  switch (action.type) {
    case DataActionTypes.GET_DATA_SUCCESS: {
      return {
        ...state,
        isBeta: action.payload.isBeta,
        isTermSigned: action.payload.isTermSigned
      };
    }
    case DataActionTypes.GET_DATA_ERROR: {
      return {
        ...state,
        isBeta: action.payload.isBeta,
        isTermSigned: action.payload.isTermSigned
      };
    }
    ...
    default: {
      const data = JSON.parse(localStorage.getItem('data'));
      if (data) {
        return {
          ...state,
          isBeta: betaUser.isBeta,
          isTermSigned: betaUser.isTermSigned
        };
      } else {
        return state;
      }
    }
  }
}

数据选择器.ts

import { AppState } from '../reducers';

export const selectData = (state: AppState) => state.data;

-- 更新 3 --

另一件事可能会有所帮助并且让我心碎,当我注销时,一个而且只有一个效果被调用但是我的 HomeComponent 被调用了两次,它根本没有重定向到它:

{isAuthenticated: true, user: {…}, errorMessage: null}
{isAuthenticated: false, user: null, errorMessage: null}

最佳答案

我不确定是否完全了解您的上下文和需求,但我认为您的 HomeComponent 没有被多次加载。但是,使用 combineLatest 创建的 observable 收到多次相同的值。

我可以建议你 2 个可能的改进:

1) 使用选择器组成商店的多个部分

例如,您可以创建一个getHomeInfo 选择器来接收您需要的所有信息,并避免在HomeComponent 中调用combineLatest。它更清晰,文档更完整,也更适合下一点。

2) 通过 createSelector 使用内存选择器

检查这个good post来自 Todd Motto。

Memoized 选择器将避免无用的计算以及在可观察对象中发出的无用值。 您只会在值更新时收到通知。

例子

为了说明这两点,我在 stackblitz 上创建了一个项目: https://stackblitz.com/edit/angular-ajhyz4

没有createSelector:

export const getValueWithoutCreateSelector = (state) => {
  return state.app.value;
};

使用createSelector:

export const getValue = createSelector(getAppState, (state) => {
  return state.value;
});

组合选择器:

export const getCustomMessage = createSelector(getValue, getText,
  (value, text) => {
    return `Your value is ${value} and text is '${text}'`;
})

关于angular - NGRX 流程和路由 - 组件被访问次数过多,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56755745/

相关文章:

用于 Visual Studio 的 Angular-CLI

Angular 7 : PreloadingStrategy extended method being called infinite time

javascript - Ngrx Store - 所有 UI 更改都应该存储吗?

javascript - Redux DevTools 有时会被禁用

angular - 在验证访问 token Angular oauth2 oidc 时添加加载页面

angular - 功能模块中未提供注入(inject) token

css - 如何在不同的设备上改变整体风格?

angular - 无法更改 Clarity Design System 的路标图标

Angular 9 + CLI (TypeScript) - 如何停止生成 .spec.ts 测试文件

angularjs - 基于用户角色的 Angular $routeProvider