Один из столпов Angular - Rx JS. Итак, нам нужно изменить то, как мы думаем об услугах, методах, переменных.
Давайте поговорим о вашей службе.
AuthService
звонит на ваш сервер и выясняет, аутентифицирован ли пользователь. AuthGuard
использует эту службу для чего-то. Как видно из синтаксиса canActivate
, Angular ожидает, что вы вернете один из следующих типов:
Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree
То, что теперь возвращает AuthGuard
, равно boolean | UrlTree
. Также можно вернуть Observable<boolean | UrlTree>
. Это должно нам кое-что сказать. AuthService
имеет внутренний Observable
, однако он подписывается на него и не возвращает его. Итак, давайте проведем некоторый рефакторинг
export class AuthService {
private apiUrl = environment.api_uri;
constructor(private http: HttpClient) { }
public authorize(): Observable<boolean> {
return this.http.get(this.apiUrl+'/auth')
.pipe(
map(data => {
// some logic here
let authorized = ....
return authorized;
}),
catchError(error => {
/** do some error handling
* I assume you wouldn't activate routing in case of an error
* Also, you need to wrap your return value in an `Observable`
* of is the simplest way to do that
*/
return of(false);
}));
}
}
Теперь, когда AuthService
возвращает Observable
, мы можем использовать некоторые операторы поверх этого и вернуть его из AuthGuard
.
export class AuthGuard implements CanActivate {
constructor(private auth: AuthService, private router: Router){}
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return this.auth.authorize().pipe(
map(isAuthorized => isAuthorized || this.router.parseUrl("/403"))
);
}
}
То, что я только что предложил, сделало вашу службу безгражданской, что означает, что она не содержит никаких данных. Он просто делает то, что делает снова, каждый раз, когда кто-то вызывает один из его методов.
Иногда нашим сервисам требуется хранить некоторые данные, и новички должны получать эти старые извлеченные данные. В этом случае мы можем использовать оператор Subject
, BehaviorSubject
или shareReplay
(мой любимый).
Давайте поговорим о Subject
.
Для получения дополнительной информации вы можете прочитать его здесь
Что такое тема? Rx JS Subject - это особый тип Observable, который позволяет многоадресно передавать значения многим Observers. В то время как простые Observable являются одноадресными (каждый подписанный Observer владеет независимым выполнением Observable), Subjects является многоадресным.
Итак, давайте создадим Subject
(из документации)
const subject = new Subject<number>();
const subscriber1 = subject.subscribe(val => console.log(val))
const subscriber2 = subject.subscribe(val => console.log(val))
// this will trigger callback functions of the subscribes above
subject.next(5);
Однако опоздавшие не получат значение 5 (последнее выданное значение), следующий оператор console.log не будет выполняться
const subscriber3 = subject.subscribe(val => console.log(val))
Если вы заботитесь о том, чтобы новички могли получить то, что было отправлено раньше вы можете просто использовать BehaviorSubject
Для получения дополнительной информации вы можете прочитать его здесь
const subject = new BehaviorSubject(123);
// two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);
// two subscribers will get new value => output: 456, 456
subject.next(456);
// new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);
// all three subscribers will get new value => output: 789, 789, 789
subject.next(789);
Это очень полезно, если у вас должна быть возможность обновлять "источник" позже. Однако в некоторых случаях для разных компонентов требуются одни и те же данные, и они никогда не меняются, и вы хотите выполнить HTTP-вызов только один раз. Например, пользователь начинает аутентификацию (в вашем случае).
Использование BehaviorSubject
потребует некоторого начального значения, и ваши подписчики также получат это значение. Вы можете этого не хотеть. Вместо этого давайте использовать shareReplay
Давайте немного отрефакторим AuthService
.
Я создам член класса с именем isAuthorized
(an Observable
)
Я буду использовать тот же logi c, указанный выше, и, поскольку это Observable, он не будет звонить, пока кто-то на него не подпишется.
И, наконец, я добавлю оператор shareReplay
, чтобы этот запрос был выполнен только один раз.
export class AuthService {
private apiUrl = environment.api_uri;
private isAuthorized = this.http.get(this.apiUrl+'/auth')
.pipe(
map(data => {
// some logic here
let authorized = ....
return authorized;
}),
catchError(error => {
return of(false);
}),
shareReplay()
);
constructor(private http: HttpClient) { }
}
Теперь вы можете использовать его следующим образом, и независимо от того, сколько раз вы его используете, базовый HTTP-вызов будет выполнен только один раз.
authService.isAuthorized.subscribe(_ => .....)