Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
403 views
in Technique[技术] by (71.8m points)

typescript - Constructor variable are undefined on my interceptor (Angular 8)

I have a service called interceptor, that get all the error's response that my back-end sent. On this service, i have a service, called AuthService (the service that make the requisitions to back-end), declared on my constructor variables. I use this another service to call the method that show the message to the user. Meantime, when the interceptor is triggered, the AuthService comes undefined, and my message is not showed.

On my attempts to solve the issue, i tried to declare the authService on the providers of appModule. It solved the undefined problem, but another appeared. So, i conclude that this is not the solution.

bellow is following my fonts:

Interceptor.ts

import { LoadingController } from '@ionic/angular';
import { Injectable } from '@angular/core';
import {
    HttpEvent,
    HttpHandler,
    HttpInterceptor,
    HttpRequest,
    HttpHeaders,
    HttpErrorResponse
} from '@angular/common/http';

import { BehaviorSubject, throwError, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { Router } from '@angular/router';
import { AuthService } from '../services/auth.service';

@Injectable()
export class HTTPStatus {
    private requestInFlight$: BehaviorSubject<boolean>;

    constructor() {
        this.requestInFlight$ = new BehaviorSubject(false);
    }
    setHttpStatus(inFlight: boolean) {
        this.requestInFlight$.next(inFlight);
    }
    getHttpStatus(): Observable<boolean> {
        return this.requestInFlight$.asObservable();
    }
}

@Injectable()
export class Interceptor implements HttpInterceptor {

    constructor(
        private _loadingController: LoadingController,
        private _authService: AuthService, //This is the variable that comes undefined
        private _router: Router
    ) {

    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return this.handleAccess(request, next);
    }

    private handleAccess(request: HttpRequest<any>, next: HttpHandler):
        Observable<HttpEvent<any>> {
        const token = JSON.parse(localStorage.getItem('currentToken'));
        let changedRequest = request;
        // HttpHeader object immutable - copy values
        const headerSettings: { [name: string]: string | string[]; } = {};

        for (const key of request.headers.keys()) {
            headerSettings[key] = request.headers.getAll(key);
        }

        if (token && !(request.url.indexOf("viacep") != -1)) {
            headerSettings['Authorization'] = token;
        }
        //headerSettings['Content-Type'] = 'application/json';
        const newHeader = new HttpHeaders(headerSettings);

        changedRequest = request.clone({
            headers: newHeader
        });

        //console.log('Request', changedRequest);
        return next.handle(changedRequest).pipe(catchError(err => {
            this._authService.deuErro = true;
            let mensagem: string = 'Ocorreu um erro desconhecido ao tentar processar a opera??o!'
            switch (err.status){
                case 400: if (err instanceof HttpErrorResponse && err.error instanceof Blob && err.error.type === "application/json") {
                            mensagem = "N?o existem dados para serem gerados. Erro: " + err.status;
                          } else {
                            mensagem = err.error.message;
                            this._loadingController.dismiss();
                          }        
                          break;
                
                case 401: if (err.error.message === "Unauthorized"){
                            if (this._authService.userLogged.value.user) {
                               mensagem = 'Sua sess?o foi expirada. Por gentileza, logue novamente! Erro: ' + err.status;
                               this._router.navigate(['auth/login']);
                            }else{
                               mensagem = mensagem + ' Erro: ' + err.status;
                            }
                          }
                          break;
                
                case 500: if (err.error.message === "INVALID_CREDENTIALS") {
                               mensagem = 'Usuario ou senha incorretos. Digite novamente para realizar o login'
                             }else{
                               mensagem = mensagem + ' Erro: ' + err.status;   
                             }
                          break;
                default:  mensagem = mensagem + ' (' + err.status + ')' + ' - ' + err.error.message
                          break;            
                }
            this.exibir(mensagem)
            return [];
        }))
    }

    public exibir(msg) {
        this._authService.open(msg); //This is the method that it tries to call
    }
}

AuthService.ts

    import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
import { Resolve, RouterStateSnapshot, ActivatedRouteSnapshot, Router } from '@angular/router';
import { ToastController, MenuController, AlertController, NavController } from '@ionic/angular';
import { environment } from 'src/environments/environment';
import { Storage } from '@ionic/storage';
import { promise } from 'protractor';
const API_STORAGE_KEY = 'spinopsstoragekey';

@Injectable({
    providedIn: 'root'
})
export class AuthService implements Resolve<any>{

    public userLogged: BehaviorSubject<any> = new BehaviorSubject({ imagem: null
                                                                  , user: null
                                                                  , nrCarteirinha: null 
                                                                  , listaBanners: null });
    public token: string;
    public users: BehaviorSubject<any[]> = new BehaviorSubject([]);
    public deuErro: boolean = false;
    public fl_comunicado: BehaviorSubject<boolean> = new BehaviorSubject(null);

    constructor(
        private _http: HttpClient,
        private toastController: ToastController,
        private _storage: Storage,
        private _navController: NavController,
        public _menuController: MenuController,
        public _router: Router,
        public alertController: AlertController,
    ) {
        if (JSON.parse(localStorage.getItem('currentToken'))) {        
            const currentUser = JSON.parse(localStorage.getItem('currentUser'));
            const currentToken = JSON.parse(localStorage.getItem('currentToken'));
            const currentNrCarteirinha = JSON.parse(localStorage.getItem('currentUserLogin'));
            const currentUserImage = JSON.parse(localStorage.getItem('currentUserImage'));
            const currentListaBanner = JSON.parse(localStorage.getItem('currentListaBanner'));

            this.token = currentUser && currentToken;

            this.userLogged.next({
                user: currentUser,
                imagem: currentUserImage,
                carteirinha: currentNrCarteirinha,
                listaBanners: currentListaBanner
            });
        }
    }

    private URL = `${environment.apiUrl}/login`;
    private User_URL = `${environment.apiUrl}/usuario`;

    resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
    ): Observable<any> | Promise<any> | any {

    }

    login(dados: any, acessoRapido: boolean = false): Observable<any> {
        let logon = dados;
        if (acessoRapido){
            logon = { ds_login: dados.dsLogin, ds_senha: dados.dsSenha, fl_salvar: dados.fl_salvar }
        }

        return this._http.post<any>(`${this.URL}`, logon)
            .pipe(map(async user => {
                    const toast = await this.toastController.create({
                        message: 'Login realizado com sucesso.',
                        duration: 2000
                    });

                    if (user && user.token) {
                        localStorage.setItem('currentToken', JSON.stringify(user.token));
                        localStorage.setItem('currentUser', JSON.stringify(user.nomeUsuario));
                        localStorage.setItem('currentUserLogin', JSON.stringify(user.dsLogin));
                        localStorage.setItem('currentUserIdSegurado', JSON.stringify(user.idSegurado));
                        localStorage.setItem('currentUserImage', JSON.stringify(user.imagem));
                        localStorage.setItem('currentListaBanner', JSON.stringify(user.listaBanners));

                        this.userLogged.next({
                            user: user.nomeUsuario,
                            imagem: user.imagem,
                            carteirinha: user.dsLogin,
                            listaBanners: user.listaBanners
                        });                
                        
                        await this.setUserLogged(user);                                        
                        await toast.present();
                    }

                    return user;
            }));
    }

    logout(): void {
        localStorage.removeItem('currentToken');
        localStorage.removeItem('currentUser');
        localStorage.removeItem('currentUserLogin');
        localStorage.removeItem('currentUserIdSegurado');
        localStorage.removeItem('currentUserImage');
        localStorage.removeItem('currentListaBanner');

        this._navController.navigateBack(['auth/login']).then(() => {
            this._menuController.enable(false);
            this.editUserLogged(null)
        });
    }

    cadastrarConta(dados: any): Observable<any> {
        this.deuErro = false
        return this._http.post(`${environment.apiUrl}/beneficiario/novo`, dados);
    }
    
    alterarSenha(dados: any): Observable<any> {
        this.deuErro = false
        return this._http.put(`${environment.apiUrl}/beneficiario/alterar-senha`, dados);
    }

    public getUserRemember(): Promise<any> {
        return this._storage.get(`${API_STORAGE_KEY}-lembrarUsuarios`);
    }

    public async setUserRemember(user): Promise<any> {
        return this._storage.set(`${API_STORAGE_KEY}-lembrarUsuarios`, user);
    }

    public getUsersLogged(): Promise<any> {
        return this._storage.get(`${API_STORAGE_KEY}-usuarioAutenticado`);
    }

    public setUserLogged(data: any) {
        if (data) {
            let users = [];
            users.push(data);
            return this._storage.set(`${API_STORAGE_KEY}-usuarioAutenticado`, users);
        }
    }

    public editUserLogged(data: any) {
        if (data.length > 0) {
            return this._storage.set(`${API_STORAGE_KEY}-usuarioAutenticado`, data);
        } else {
            return this._storage.set(`${API_STORAGE_KEY}-usuarioAutenticado`, []);
        }
    }


    async open(message, duration = 4000) {
        const toast = await this.toastController.create({ message: `${message}`, duration: duration });
        toast.present();
    }

    possuiComunicado() {
        this._http.get(`${environment.apiUrl}/comunicado/possui`).subscribe((resolve: any) => {
            this.fl_comunicado.next(resolve);
        });
    }

    getComunicados() {
        return this._http.get(`${environment.apiUrl}/comunicado/listar`);
    }

    setComunicado(nr_seq) {
        return this._

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

This is a known issue caused by a cyclic dependency with the HttpInterceptor and the HttpClient.

Explanation

HttpClient has a dependency on all HttpInterceptors.
AuthService has a dependency on the HttpClient.

Injecting the AuthService in the HttpInterceptor will create an infinite loop.

HttpInterceptor -> AuthService -> HttpClient -> HttpInterceptor -> ∞

Depending on your Angular version, you will either get a cyclic dependency error, or no error at all resulting in having the concerned injected service being undefined, and your code will silently fail.

Even though it's supposed to be fixed since Angular 5.2.3, I recently faced the exact same issue on an Ionic project as well. The fix probably allow the HttpClient to be directly injected in the HttpInterceptor, but might not work when injected through a service.

Solution

Inject the Injector in the interceptor constructor instead of the AuthService, then manually retrieve the service with the injector. I don't know why, but you have to use some kind of delay here, otherwise the AuthService might be still undefined. platform.ready() or setTimeout will do.

I also used a ready$ ReplaySubject to make sure the AuthService is loaded before using it.

...
import {Injectable, Injector} from '@angular/core';
import {AuthService} from '../services/auth.service';
import {Platform} from '@ionic/angular';
...

@Injectable()
export class Interceptor implements HttpInterceptor {

  private ready$ = new ReplaySubject<void>(1);
  private authService: AuthService;

  constructor(private platform: Platform, private injector: Injector ) {
    this.platform.ready().then(() => {
      this.authService = this.injector.get(AuthService);
      this.ready$.next();
    });
  }

  get ready$(): Observable<void> {
    return this.ready$.asObservable();
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return this.ready$.pipe(
      first(),
      switchMapTo(next.handle(req)),
      catchError((error: HttpErrorResponse) => {
        ...
        return throwError(error);
      })
    );
  }
  ...
}

That should work, let me know if you need further help.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...