Я хочу написать перехватчик, чтобы добавить токен авторизации ко всем запросам. Мой токен исходит из библиотеки angularx-social-login, которая предоставляет только Observable для получения токена. Итак, я написал это, но мой запрос никогда не отправляется, как будто значение никогда не выводилось из наблюдаемого.
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import { SocialAuthService } from "angularx-social-login";
import {Observable, switchMap} from "rxjs";
import {Injectable} from "@angular/core";
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: SocialAuthService) {}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return this.authService.authState.pipe(
switchMap((user) => {
const token = user.idToken
if (token) {
request = request.clone({
setHeaders: {Authorization: `Bearer ${token}`}
});
}
return next.handle(request)
})
);
}
}





Я не уверен в этом на 100%, но я думаю, что с помощью switchMap вы переключаетесь на новый наблюдаемый объект, поэтому return next.handle(request) никогда не будет следующим. Похоже, что authState является наблюдаемым, поэтому мы могли бы сделать pipe(take(1)) без необходимости отписки.
Я был бы более краток и разделил получение токена и установку Bearer.
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { SocialAuthService } from 'angularx-social-login';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';
@Injectable()
export class AuthInterceptorService implements HttpInterceptor {
constructor(private authService: SocialAuthService) {}
intercept( request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
let token;
this.authService.authState.pipe(take(1)).subscribe(data => token = data);
if (token) {
// If we have a token, we set it to the header
request = request.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}
return next.handle(request).pipe(
catchError((err) => {
if (err instanceof HttpErrorResponse) {
if (err.status === 401) {
// redirect user to the logout page
}
}
return throwError(err);
})
);
}
}
Также рекомендовал бы реорганизовать способ получения токена. Например, обновить его до какого-то хранилища и получить из хранилища через authService. Каждый раз подписываться на observable — это боль.
AuthService.ts
getAuthToken():string {
return localeStarage.getItem('token')
}
Затем вы могли бы просто сделать:
const token = this.authService.getAuthToken();
Я предполагаю, что здесь происходит то, что к тому времени, когда происходит подписка на наблюдаемое, значение authState уже было выдано в процессе входа в систему, поэтому запрос зависает в ожидании выдачи нового значения, что уже произошло в процессе входа в систему.
Чтобы справиться с этим, я предлагаю вам внедрить службу (providedInRoot) в компонент входа и получить данные пользователя в процессе входа.
Вы можете подписаться на наблюдаемое authState службы SocialAuthService в OnInit компонента Login:
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
// ... the rest of import
@Component({
// ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
destroy = new Subject<boolean>();
constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
ngOnInit(): void {
this.authService.authState.pipe(takeUntil(this.destroy)).subscribe((user) => {
this.myUserService.user = user;
});
}
// ... the rest of the component
ngOnDestroy(): void {
this.destroy.next(true);
this.destroy.complete();
}
}
Затем вы можете использовать значение из myUserService.user в своем перехватчике для получения токена.
Я использовал оператор rxjs takeUntil с Subject для ngOnDestroy, но вы также можете сохранить подписку как переменную класса и выполнить отмену подписки.
import { Component, OnInit, OnDestroy } from '@angular/core';
import { SocialAuthService } from "angularx-social-login";
import { Subscription } from 'rxjs';
// ... the rest of import
@Component({
// ... Component decorator props (selector, templateUrl, styleUrls)
})
export class LoginComponent implements OnInit, OnDestroy {
authStateSubscription: Subscription;
constructor(private authService: SocialAuthService, myUserService: MyUserService) { }
ngOnInit(): void {
this.authStateSubscription = this.authService.authState.subscribe((user) => {
this.myUserService.user = user;
});
}
// ... the rest of the component
ngOnDestroy(): void {
this.authStateSubscription.unsubscribe();
}
}
В обоих случаях должно работать.
Вы можете отказаться от подписки в компоненте OnDestroy входа в систему, я собираюсь отредактировать ответ.
Я склонен согласиться с объяснением @cybering, чтобы добавить к его решению, вы можете определить authState как BehaviorSubject. таким образом, независимо от того, когда какой-либо компонент подписывается на него, он получит испускаемое значение, поскольку BehaviorSubject «сохраняет» последнее испускаемое значение и испускает его при новых подписках, не завися от времени.
Спасибо! Это работает, но тогда подписка никогда не заканчивается, и LoginComponent будет жить в памяти вечно. Можно ли подписаться в сервисе? Или, может быть, я могу отказаться от подписки после получения значения, но тогда как я получу нового пользователя, если, например, обновлю токен?