RXJS Остановить распространение наблюдаемой цепи, если выполнено определенное условие

Вступление

Я пытаюсь создать маршрутную охрану в Angular2+ используя Observable из общего сервиса, который содержит строковое значение текущей роли пользователя.
Проблема, очевидно, заключается в том, чтобы переключить мой разум с Обещаний на Наблюдаемые.

То, что я сделал до сих пор, основано на эвристике и методе проб и ошибок, но я упал в стену, убив браузер. Решено благодаря danday74

,

Попытка (улучшена благодаря @danday74)

С помощью последовательности RxJS, эквивалентной обещанию.then()? я перевел то, что я хочу сделать в эту цепочку:

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | boolean {
        return this.auth.isRoleAuthenticated(route.data.roles)
            .mergeMap((isRoleAuthenticated: boolean) => {
                return isRoleAuthenticated ? Observable.of(true) : this.auth.isRole(Roles.DEFAULT_USER);
            })
            .do((isDefaultUser: boolean) => {
                const redirectUrl: string = isDefaultUser ? 'SOMEWHERE' : 'SOMEWHERE_ELSE';
                this.router.navigate([redirectUrl]);
            })
            .map((isDefaultUser: boolean) => {
                return false;
            });
    }

Вопрос

Как остановить дальнейшее распространение наблюдаемой цепи, если isRoleAuthenticated = true? Мне нужно вернуть это логическое значение, если такое условие выполнено, и убедиться, что .do блок оператора не вызывается после.
Ограничением является то, что логическое значение должно быть возвращено из canActivate охранник.

3 ответа

Решение

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

Вы используете mergeMap только тогда, когда возвращаете Observable, но нет необходимости возвращать Observable из 2-го mergeMap. Просто делать:

  .mergeMap((isRoleAuthenticated: boolean) => {
    if (isRoleAuthenticated) {
      return Observable.of(true)
    }
    return this.auth.isRole(Roles.DEFAULT_USER)
  })
  .tap((isDefaultUser: boolean) => {
    if (isDefaultUser) {
      this.router.navigate(['SOMEWHERE'])
    } else {
      this.router.navigate(['SOMEWHERE_ELSE'])
    }
  })
  .map((isDefaultUser: boolean) => {
    return false
  })

Кроме того, вы используете синтаксис RxJs v5, но должны использовать v6. В v6 операторы - mergeMap, tap, map - разделяются запятой в трубе.

Возможно, навигация маршрутизатора препятствует окончательному возврату и вызывает зависание? Прокомментируйте это и посмотрите, остановит ли это зависание.

Не уверен, что это полностью исправит вашу проблему, но, надеюсь, некоторые полезные идеи для 1 утра

Я предполагаю, что эти возвращаемые Observables:

  • this.auth.isRoleAuthenticated
  • this.auth.isRole (Roles.DEFAULT_USER)

Если этого не произойдет, у вас возникнут проблемы.

Решение

Вместо того, чтобы сосредоточиться на остановке цепочки, вы можете создать объект, состоящий из результатов собранных Observables, и распространять его дальше, что решает проблему:

canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | boolean {
    return this.auth.getRole()
        .mergeMap((role: string) => {
            return this.auth.isRoleAuthorized(route.data.roles)
                .map((authorized: boolean) => ({
                    role: role,
                    authorized: authorized
                }));
        })
        .do((markers: { role: string, authorized: boolean }) => {
            const redirectUrl: string = markers.role === Roles.DEFAULT_USER ? 'SOMEWHERE' : 'SOMEWHERE_ELSE';
            this.router.navigate([redirectUrl]);
        })
        .map((markers: { role: string, authorized: boolean }) => {
            return markers.authorized;
        });
}

do() оператор работает только с next уведомления, так что если вы не хотите обрабатывать то, что приходит после .mergeMap Вы можете отфильтровать это с filter():

return this.auth.isRoleAuthenticated(route.data.roles)
  .mergeMap((isRoleAuthenticated: boolean) => {
    return isRoleAuthenticated ? Observable.of(true) : this.auth.isRole(Roles.DEFAULT_USER);
  })
  .filter(authenticated => authenticated !== true)

Тем не менее, похоже, что вы могли бы просто вернуться Observable.empty() вместо Observable.of(true) потому что это будет только излучать complete уведомление и нет next предметы, так что нечего будет передавать do():

.mergeMap((isRoleAuthenticated: boolean) => {
  return isRoleAuthenticated ? Observable.empty() : this.auth.isRole(Roles.DEFAULT_USER);
})

Вместо возвращения Observable.of(true), вы можете вернуться Observable.empty(), который будет просто завершен, не испуская никаких значений. Таким образом, следующие цепочки не будут выполнены.

Другие вопросы по тегам