Как прикрепить ngrx / store с угловой защитой маршрутизатора

Я новичок в ngrx / store, и это мой первый проект, использующий его.

Я успешно настроил свой проект angular с помощью ngrx / store, и я могу отправить действие загрузки после инициализации моего основного компонента следующим образом:

ngOnInit() { this.store.dispatch({type: LOAD_STATISTICS}); }

Я настроил эффект для загрузки данных при отправке этого действия:

@Effect()
loadStatistic = this.actions.ofType(LOAD_STATISTICS).switchMap(() => {
    return this.myService.loadStatistics().map(response => {
        return {type: NEW_STATISTICS, payload: response};
    });
});

Мой редуктор выглядит так:

reduce(oldstate: Statistics = initialStatistics, action: Action): Statistic {
    switch (action.type) {
        case LOAD_STATISTICS:
            return oldstate;
        case NEW_STATISTICS:
            const newstate = new Statistics(action.payload);

            return newstate;
    ....

Хотя это работает, я не могу понять, как использовать это с защитой маршрутизатора, поскольку в настоящее время мне нужно отправить LOAD_ACTION только один раз.

Кроме того, то, что компонент должен отправить действие LOAD для загрузки исходных данных, мне не подходит. Я ожидал, что сам магазин знает, что ему нужно загрузить данные, и мне не нужно сначала отправлять действие. Если бы это было так, я мог бы удалить метод ngOnInit () в своем компоненте.

Я уже изучал приложение ngrx-example-app, но я не понял, как это работает.

РЕДАКТИРОВАТЬ:

После добавления защиты разрешения, которая возвращает наблюдаемое хранилище ngrx, маршрут не активируется. Вот решение:

   @Injectable()
  export class StatisticsResolver implements Resolve<Statistic> {
    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Statistic> {
        // return Observable.of(new Statistic());
        return this.store.select("statistic").map(statistic => {
        console.log("stats", statistic);

        return statistic;
    });
}

Это маршрут:

const routes: Routes = [
    {path: '', component: TelefonanlageComponent, resolve: {statistic:  TelefonieStatisticResolver}},
];

person Marco Rinck    schedule 28.02.2017    source источник


Ответы (6)


Я не совсем понимаю, почему вы отправляете разрешенные данные в свой компонент через преобразователь. Весь смысл настройки хранилища ngrx заключается в том, чтобы иметь единый источник данных. Итак, что вы просто хотите здесь сделать, это убедиться, что данные верны. Остальное, компонент может получить данные из магазина с помощью селектора.

Я вижу, что вы вызываете LOAD_ACTION из метода ngOnInit вашего компонента. Вы не можете вызвать действие для разрешения данных, которое затем приведет к компоненту, Init которого вы вызываете действие! Вот почему ваш маршрутизатор не загружает маршрут.

Не уверен, что вы это поняли, но в этом есть смысл!

Проще говоря, вы делаете вот что. Вы запираете комнату, а затем проталкиваете ключ в щель под дверью, а затем задаетесь вопросом, почему дверь не открывается.

Ключ держите при себе!

Метод разрешения охранника должен вызывать LOAD_ACTION. Затем решение должно дождаться загрузки данных, а затем маршрутизатор должен перейти к компоненту.

Как ты это делаешь?

Другие ответы - это настройка подписок, но дело в том, что вы не хотите этого делать в своих охранниках. Не рекомендуется подписываться в охранниках, так как тогда их нужно будет отменить, но если данные не разрешены или охранники вернут false, тогда маршрутизатор никогда не сможет отказаться от подписки, и у нас будет беспорядок.

Итак, используйте take(n). Этот оператор получит n значений из подписки, а затем автоматически завершит подписку.

Также в ваших действиях вам понадобятся LOAD_STATISTICS_SUCCESS и LOAD_STATISTICS_FAIL. Как ваш метод обслуживания может дать сбой!

В редукторе State вам понадобится свойство loaded, которое превращается в true, когда вызов службы успешен и вызывается действие LOAD_STATISTICS_SUCCESS.

Добавьте селектор в главный редуктор, getStatisticsLoaded, а затем в свой gaurd, ваша установка будет выглядеть так:

resolve(): Observable<boolean> {

this.store.dispatch({type: LOAD_STATISTICS});

return this.store.select(getStatisticsLoaded)
    .filter(loaded => loaded)
    .take(1);

}

Таким образом, только когда загруженное свойство изменится на true, фильтр позволит продолжить поток. take примет первое значение и передаст его дальше. На этом разрешение завершается, и маршрутизатор может продолжить.

Опять же take убьет подписку.

person notANerdDev    schedule 01.04.2017
comment
Спасибо, отличный ответ! Ключевой момент: На этом этапе разрешение завершается, и маршрутизатор может продолжить. Опять же, take убьет подписку. - person Roy Ling; 02.11.2017
comment
@notANerdDev, значит, в компоненте вы получаете данные из магазина, а не из данных маршрутизатора, не так ли? кстати очень хороший повтор :) - person Whisher; 15.12.2017
comment
@Whisher Да. Это соответствует цели использования магазина. - person notANerdDev; 15.12.2017

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

Я реализовал CanActivate Guard следующим образом:

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

    waitForDataToLoad(): Observable<Statistic> {
        return this.store.select(state => state.statistic)
            .filter(statistic => statistic && !statistic.empty);
    }

    canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
        this.store.dispatch({type: LOAD_STATISTIC});

        return this.waitForDataToLoad()
            .switchMap(() => {
                return Observable.of(true);
            });
        }
    }
}

Метод canActivate (...) сначала отправляет действие для загрузки данных. В waitForDataToLoad () мы фильтруем, что данные уже есть, а не пусты (деталь реализации моей бизнес-логики).

После того, как это вернет true, мы вызываем switchMap, чтобы вернуть Observable, равное true.

person Marco Rinck    schedule 28.02.2017
comment
а если статистика пуста, он будет ждать вечно? - person Martin Cremer; 28.07.2017
comment
это урезанная версия настоящего кода из-за простоты. Да, здесь отсутствует код, обрабатывающий пустые ответы или сообщения об ошибках. - person Marco Rinck; 31.07.2017

Я предполагаю, что под "охраной маршрутизатора" вы имеете в виду resolve охранника? Вы хотите, чтобы данные загружались перед активацией маршрута?

Если да, то все должно хорошо сочетаться:

  • Значения из ngrx / store представлены как наблюдаемые (например, в их документах store.select('counter') содержит наблюдаемое).
  • В Angular resolve охранники могут возвращать наблюдаемые.

Таким образом, вы можете просто вернуть наблюдаемый ngrx / store из своего решения:

class MyResolver implements Resolve<any> {

  constructor(private store: Store<any>) {}

  resolve(): Observable<any> {
    // Adapt code to your situation...
    return this.store.select('statistics');
  }
}

При этом ваша установка кажется немного сложной. Я склонен думать о состоянии как о переходных данных (свернутое состояние меню, данные, которые должны быть разделены между несколькими экранами / компонентами в течение сеанса, такими как текущий пользователь), но я не уверен, что загружу большой кусок данных из бэкэнда в состояние.

Что вы получаете от хранения статистики в состоянии? (по сравнению с простой загрузкой и отображением в каком-либо компоненте)

person AngularChef    schedule 28.02.2017
comment
Да я имел в виду решимость охранников. Однако, когда я просто возвращаю выбранный Observable, маршрут вообще не активируется. Но я могу подписаться на него и получать данные. Мне нужно что-то особенное, чтобы решить эту проблему? PS: Это простой демонстрационный проект, чтобы узнать о redux / ngrx, поэтому выгода от хранения статистики - это то, что я узнал. - person Marco Rinck; 28.02.2017
comment
Сам маршрутизатор должен подписаться на ваш наблюдаемый. В разрешении попробуйте вернуть фиктивную наблюдаемую, такую ​​как return Observable.of("foo"), чтобы выяснить, является ли проблема наблюдаемой ngrx или это способ, которым вы объявили маршрут с помощью решения. - person AngularChef; 28.02.2017
comment
да, это работает. Я только что отредактировал свой вопрос с помощью класса решателя. - person Marco Rinck; 28.02.2017
comment
Таким образом, фиктивный obs работает, а ngrx - нет. При использовании ngrx obs видите ли вы результат console.log, который вы разместили в своей карте ()? Можете ли вы подтвердить, что другой охранник (например, на родительском маршруте) не препятствует активации вашего маршрута? В вашем коде нет опечаток? (ваш маршрут ссылается на TelefonieStatistikResolver, но показанный вами преобразователь называется StatisticsResolver) - person AngularChef; 28.02.2017
comment
да, я вижу вывод оператора журнала. Другого охранника нет. Если я поменяю его на фиктивное обследование, маршрут активируется. Разница в написании состоит в том, что я пытался перевести оригинальные немецкие имена, но забыл поменять их везде. Его компиляция и работа в порядке. - person Marco Rinck; 28.02.2017

Если загрузка не удалась, возможно, вы захотите отменить навигацию. Это можно сделать, выдав ошибку. Самое главное - возвращаемый наблюдаемый должен быть завершен или завершиться с ошибкой.

resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
): Observable<boolean> {

    this.store.dispatch(new LoadStatistic(route.params.someParam));

    return this.store.pipe(select(selectStatisticModel),
        filter(s => !s.isLoading),
        take(1),
        map(s => {
            if (!s.isLoadSuccess) {
                throw s.error;
            }
            return true;
        })
    );
}
person Mārcis    schedule 23.05.2018

Это также учитывает неудавшийся запрос:

canActivate(): Observable<boolean> {
    this.store.dispatch({type: LOAD_STATISTICS);

    return this.store.select((state) => state.statistics)
      .filter((statistics) => statistics.loaded || statistics.loadingFailed)
      .map((statistics) => statistics.loaded);
  }
person Martin Cremer    schedule 29.07.2017

Этот ответ для людей, которые разочаровываются в использовании ngrx / effect в этой ситуации. Может быть, лучше использовать простой подход без ngrx / effects.

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
    // this will only dispatch actions to maybe clean up, NO EFFECTS 
    this.store.dispatch({type: LOAD_STATISTIK});     

  return this.service.loadOne()
     .do((data) => {
         this.store.dispatch({type: LoadSuccess, payload: data })
      }).map(() => {
      return true; 
    })  
}
person alexKhymenko    schedule 13.09.2017