You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何避免Firebase Auth未完成认证时Login组件闪烁渲染

问题:已认证用户登录后Login组件闪烁

即使用户已完成认证,Login组件仍会出现闪烁现象,核心原因是Firebase Auth的user$可观察对象初始状态导致的:

  • 应用首次加载时,Firebase Authentication尚未确定用户是否处于登录状态,此时user$可观察对象为null,路由守卫会直接重定向至登录页面。
  • 尝试通过Loading组件在用户状态未确定时显示加载态,但由于authGuard.ts中的跳转代码,导致Login组件与加载组件同时闪烁。

解决方案:调整认证守卫与加载状态逻辑

1. 更新AuthService,区分「未确定状态」与「未登录状态」

当前user$的初始值为null,无法区分是「还在加载认证状态」还是「确实未登录」。我们需要改造user$,让它在加载完成前发出特定占位值(比如undefined),同时优化loading状态的更新逻辑:

// auth.ts
import { Injectable, inject, signal } from '@angular/core';
import {
  Auth,
  createUserWithEmailAndPassword,
  signInWithEmailAndPassword,
  user,
  signOut,
  UserCredential 
} from '@angular/fire/auth';
import { Observable, from, BehaviorSubject, switchMap, startWith } from 'rxjs';
import { IUser } from '../interfaces/user';
import { Router } from '@angular/router';
import { UserService } from './user.service';

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  router = inject(Router);
  // 初始发出undefined,表示认证状态未确定
  user$: Observable<IUser | null | undefined>;
  currentUserSig = signal<IUser | null | undefined>(undefined);
  private loadingSubject = new BehaviorSubject<boolean>(true);
  loading$ = this.loadingSubject.asObservable();

  userService = inject(UserService);

 constructor(private firebaseAuth: Auth) {
  // 使用startWith(undefined)标记初始未确定状态
  this.user$ = user(this.firebaseAuth).pipe(
    startWith(undefined),
    switchMap(fbUser => {
      if (fbUser) {
        return new Observable<IUser>(observer => {
          observer.next({ email: fbUser.email, uid: fbUser.uid });
          observer.complete();
        });
      } else {
        return new Observable<null>(observer => {
          observer.next(null);
          observer.complete();
        });
      }
    })
  );

  this.user$.subscribe((user) => {
    console.log(user);
    if (user === undefined) {
      // 状态未确定,保持加载
      this.loadingSubject.next(true);
      this.currentUserSig.set(undefined);
    } else {
      this.loadingSubject.next(false);
      this.currentUserSig.set(user);
    }
  });
}

register(email: string, password: string, nome: string, apelido: string): Observable<void> {
  return from(createUserWithEmailAndPassword(this.firebaseAuth, email, password)).pipe(
    switchMap((ref: UserCredential) => {
      const userData = {
        email: email,
        nome: nome,
        apelido: apelido,
        uid: ref.user.uid,
      };
      return this.userService.addUser(userData);
    })
  );
}

login(email: string, password: string): Observable<void> {
  const promise = signInWithEmailAndPassword(
    this.firebaseAuth,
    email,
    password
  ).then(() => {});
  return from(promise);
}

logout(): Observable<void> {
  const promise = signOut(this.firebaseAuth).then(() => {
    this.router.navigate(['/login']);
  });
  return from(promise);
}
}

2. 修改AuthGuard,等待认证状态确定后再处理路由

在守卫中,只有当user$发出非undefined的值时,才进行路由判断,避免在加载状态下跳转登录:

// authGuard.ts
import { CanActivateFn } from '@angular/router';
import { AuthService } from './auth';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { map, filter } from 'rxjs/operators';

export const AuthGuard: CanActivateFn = (route, state) => {
  const authService = inject(AuthService);
  const router = inject(Router);
  
  return authService.user$.pipe(
    // 过滤掉未确定状态(undefined),只处理已确定的状态(user或null)
    filter(user => user !== undefined),
    map(user => {
      if (user) {
        return true;
      } else {
        router.navigate(['/login'], { queryParams: { returnUrl: state.url } });
        return false;
      }
    })
  );
};

3. 优化AppComponent的加载逻辑(可选)

直接使用AuthService的loading$进行绑定,避免手动订阅的冗余代码:

// app.component.ts
import { Component,inject } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import { AuthService } from './services/auth';
import { LoadingComponent } from './layouts/loading/loading.component';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet,LoadingComponent,CommonModule],
  templateUrl: './app.component.html',
  styleUrl: './app.component.css'
})
export class AppComponent {
  authService = inject(AuthService);
  // 直接复用AuthService的loading状态
  loading$ = this.authService.loading$;
}

对应的模板简化为:

<!-- app.component.html -->
@if(loading$ | async){
  <app-loading></app-loading>
}
@else{
  <router-outlet></router-outlet>
} 

核心原理说明

  • 通过startWith(undefined)给user$添加初始未确定状态,让守卫能区分「正在加载」和「未登录」两种场景
  • 守卫中使用filter(user => user !== undefined)等待认证状态确定后再处理路由,避免在加载时错误跳转登录页面
  • 统一管理loading状态,确保AppComponent的加载态与认证状态完全同步,从根源上解决组件闪烁问题

内容的提问来源于stack exchange,提问作者Francisco Soares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 13:02:03