Используйте наблюдаемую внутри перехватчика

Я хочу написать перехватчик, чтобы добавить токен авторизации ко всем запросам. Мой токен исходит из библиотеки 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)
      })
    );
  }
}
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
0
0
52
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Я не уверен в этом на 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();
  }

}

В обоих случаях должно работать.

Спасибо! Это работает, но тогда подписка никогда не заканчивается, и LoginComponent будет жить в памяти вечно. Можно ли подписаться в сервисе? Или, может быть, я могу отказаться от подписки после получения значения, но тогда как я получу нового пользователя, если, например, обновлю токен?

Hugo 14.05.2022 14:16

Вы можете отказаться от подписки в компоненте OnDestroy входа в систему, я собираюсь отредактировать ответ.

cybering 14.05.2022 14:41

Я склонен согласиться с объяснением @cybering, чтобы добавить к его решению, вы можете определить authState как BehaviorSubject. таким образом, независимо от того, когда какой-либо компонент подписывается на него, он получит испускаемое значение, поскольку BehaviorSubject «сохраняет» последнее испускаемое значение и испускает его при новых подписках, не завися от времени.

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