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

时间:2019-06-25 13:55:19

标签: angular angular-routing ngrx

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

开始时流程如下:

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

app-routing.module.ts

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

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

login.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- 这是数据存储的重要部分

data.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) {}
}

data.effect.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;
      }
    }
  }
}

data.selector.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}

1 个答案:

答案 0 :(得分:4)

我不确定您是否完全了解您的情况和需求,但是我认为您的HomeComponent并未多次加载。但是,用combineLatest创建的observable会多次收到相同的值。

我可以建议您进行2种可能的改进:

1)使用选择器组成商店的多个片段

例如,您可以创建一个getHomeInfo选择器来接收所需的所有信息,而避免在combineLatest内部调用HomeComponent。它更清洁,文档记录得更好,而且对下一点也更好。

2)将记忆化的选择器与createSelector

一起使用

在Todd Motto中选中此good post

记住的选择器将避免无用的计算,也避免在可观察变量中发出无用的值。 仅在值更新的情况下,您才得到通知。

示例

为了说明这两点,我在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}'`;
})