Угловая защита маршрутизатора и порядок эффекта ROUTER_NAVIGATION

Существует простой (Angular 4) сторож маршрута, который ожидает загрузки некоторых данных из бэкэнда:

@Injectable()
export class ContractsLoadedGuard implements CanActivate {
    constructor(private store: Store<State>) { }

    waitForData(): Observable<boolean> {
        return this.store.select(state => state.contracts)
            .map(contractList => !!contractList)
            .filter(loaded => loaded)
            .take(1);
    }

    canActivate(): Observable<boolean> { return this.waitForData(); }
}

Маршрутизация:

const routes: Routes = [
    { path: 'app-list', canActivate: [ContractsLoadedGuard], component: AppListComponent },
];

И, наконец, есть @ ngrx / effects, запускаемый действием @ ngrx / router-store v4 ROUTER_NAVIGATION:

@Effect() routeChange$ = this.actions$
    .ofType(ROUTER_NAVIGATION)
    .filter((action: RouterNavigationAction) => action.payload.routerState.url.indexOf('/app-list') > -1)
    .withLatestFrom(this.store.select(state => state.contracts))
    .switchMap(([action, contracts]: ([RouterNavigationAction, ContractList])) =>
        this.restClient.load(action.payload.routerState.queryParams, contract));

К сожалению, когда навигация изменяется на /app-list, сначала выполняется эффект ngrx (перед охраной), и поэтому данные state.contracts еще не доступны. Охрана еще не выполнена. Мне нужно добавить оператор .combineLatest() Rx, чтобы также дождаться появления contracts данных (это работа охранника):

@Effect() routeChange$ = this.actions$
    .ofType(ROUTER_NAVIGATION)
    .filter((action: RouterNavigationAction) => action.payload.routerState.url.indexOf('/app-list') > -1)
    .combineLatest(this.contractListGuard.waitForContractsToLoad(), a => a) <- HERE
    .withLatestFrom(this.store.select(state => state.contracts))
    .switchMap(/* same here */) 

Я не уверен, достаточно ли это хорошее решение. Должен быть способ сделать это лучше - не дублировать действующие функции защиты.

Подводя итог: при ускорении приложения мне нужно получить некоторые данные из бэкэнда - contracts. Если пользователь переходит на /app-list (немедленное перенаправление), с сервера извлекаются другие данные - на основе некоторых параметров запроса и contracts - порядок выполнения маршрутизатора ngrx ROUTER_NAVIGATION effect предшествует порядку выполнения защиты. Как с этим справиться?

На основе GitHub - state_management_ngrx4


person Felix    schedule 09.11.2017    source источник


Ответы (2)


Есть один способ добиться этого. Вы можете подписаться на событие ResolveEnd в Angular Router https://angular.io/api/router/ResolveEnd в свой эффект, а затем отправьте свое собственное действие для RESOLVE_END, где вы можете что-то делать с данными вашего преобразователя / защиты.

На самом деле есть PR в ngrx / платформе, которую я создал, где ngrx / router отправляет действие NAVIGATE_RESOLVE_END из коробки. Я жду, когда команда ngrx примет мой PR. https://github.com/ngrx/platform/pull/524/

Вы можете подписаться на события маршрутизатора и отфильтровать их для Resolve End и отправить свое собственное действие, назовите его действием Router_Resove_End и т. Д.

this.router.events.filter(e => e instanceof ResolveEnd).subscribe(s => {
 // dispatch your own action here.
});
person Rupesh Kumar Tiwari    schedule 09.11.2017
comment
Как я могу подписаться на событие ResolveEnd Angular Router в моем эффекте? Можете ли вы обрисовать поток некоторых событий или добавить некоторый (псевдокод), чтобы сначала выполнить проверку защиты? Спасибо - person Felix; 11.11.2017
comment
Вы можете подписаться на события маршрутизатора и отфильтровать их для Resolve End и отправить свое собственное действие, назовите это действие Router_Resove_End и т. Д. This.router.events.filter (e = ›e instanceof ResolveEnd) .subscribe (s =› {// отправьте свой здесь собственное действие.}); - person Rupesh Kumar Tiwari; 14.11.2017
comment
›Вы можете подписаться на событие ResolveEnd Angular Router в своем эффекте. Не могли бы вы изменить фрагмент кода и добавить @Effect() в ответ, пожалуйста? @Effect() routeChange$ = this.actions$.ofType(ROUTER_NAVIGATION) ... how to subscribe to resolve end in effect here ... - person Felix; 15.11.2017
comment
Привет, @Felix, я пытаюсь объяснить, что в вашем классе эффектов вы должны самостоятельно подписаться на событие маршрутизатора. Для этого вам не нужен @Effect (). Вы не должны подписываться на действие ROUTER_NAVIGATION. Здесь эффект вам не поможет. Другой вариант - не подписываться на действие router_navigation и из вашего contractListGuard отправить действие CONTRACT_LIST_LOADED, на которое вы можете подписаться в своем эффекте. - person Rupesh Kumar Tiwari; 23.11.2017

Есть очень приятное завершение Medium:

@Injectable()
export class RouterEffects {
    constructor(
        private actions$: Actions,private router: Router,private location: Location,private store: Store<any>
    ) {this.listenToRouter();}

    @Effect({ dispatch: false })
    navigate$ = this.actions$.pipe(ofType('[Router] Go')...);
    @Effect({ dispatch: false })
    navigateBack$ = this.actions$.pipe(ofType('[Router] Back'), tap(() => this.location.back()));

    @Effect({ dispatch: false })
    navigateForward$ = this.actions$.pipe(ofType('[Router] Forward'),...);

    private listenToRouter() {
        this.router.events.pipe(
            filter(event => event instanceof ActivationEnd)
        ).subscribe((event: ActivationEnd) =>
            this.store.dispatch(new RouteChange({
                params: { ...event.snapshot.params },
                path: event.snapshot.routeConfig.path
            }))
        );
    }
}

а затем вместо:

@Effect() routeChange$ = this.actions$.ofType(ROUTER_NAVIGATION)

использовать:

@Effect() routeChange$ = this.actions$.ofType(ofType('[Router] Route Change'))
person Felix    schedule 05.03.2018