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

基于Angular的APP_INITIALIZER实现带状态反馈的动态加载消息

实现Angular初始化请求的实时状态与错误展示

我来帮你解决这个初始化阶段请求状态展示的问题,咱们一步步实现你要的「实时显示加载内容+失败时展示错误信息」的效果:

核心思路

首先要解决两个关键问题:

  1. 原来的forkJoin没法跟踪单个请求的状态,得给每个请求单独添加状态监听
  2. index.html里的app-root内容还没初始化,没法用Angular指令绑定变量,所以需要一个独立的加载状态管理服务和加载组件

步骤1:创建加载状态管理服务

这个服务用来集中管理所有初始化请求的状态,方便组件订阅更新:

// loading-status.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

// 定义每个加载项的结构
export interface LoadingItem {
  key: string; // 唯一标识
  displayName: string; // 显示给用户的加载文本
  status: 'loading' | 'success' | 'error'; // 状态
  errorMessage?: string; // 错误信息(失败时用)
}

@Injectable({ providedIn: 'root' })
export class LoadingStatusService {
  private loadingItemsSubject = new BehaviorSubject<LoadingItem[]>([]);
  loadingItems$: Observable<LoadingItem[]> = this.loadingItemsSubject.asObservable();

  // 初始化加载项,设置初始状态为loading
  initializeItems(items: Omit<LoadingItem, 'status'>[]) {
    const initialItems = items.map(item => ({ ...item, status: 'loading' }));
    this.loadingItemsSubject.next(initialItems);
  }

  // 更新单个请求的状态
  updateStatus(key: string, status: 'success' | 'error', errorMessage?: string) {
    const currentItems = this.loadingItemsSubject.value;
    const updatedItems = currentItems.map(item => 
      item.key === key 
        ? { ...item, status, errorMessage } 
        : item
    );
    this.loadingItemsSubject.next(updatedItems);
  }
}

步骤2:改造APP_INITIALIZER工厂函数

修改原来的初始化逻辑,给每个请求添加状态监听,同时保留初始化失败的全局通知:

// app.module.ts 里的工厂函数替换为以下内容
export function appInitializer(
  userService: UserService,
  productService: ProudctService,
  loadingStatusService: LoadingStatusService
) {
  return () => {
    // 先初始化要加载的项
    loadingStatusService.initializeItems([
      { key: 'users', displayName: 'Loading User...' },
      { key: 'products', displayName: 'Loading Product...' }
    ]);

    // 给用户请求添加状态监听
    const userRequest$ = userService.getUsers().pipe(
      tap(() => loadingStatusService.updateStatus('users', 'success')),
      catchError((err) => {
        loadingStatusService.updateStatus('users', 'error', err.message || 'Failed to load users');
        return throwError(() => err); // 继续抛出错误,让Angular知道初始化失败(可选,根据业务需求调整)
      })
    );

    // 给产品请求添加状态监听
    const productRequest$ = productService.getAllProducts().pipe(
      tap(() => loadingStatusService.updateStatus('products', 'success')),
      catchError((err) => {
        loadingStatusService.updateStatus('products', 'error', err.message || 'Failed to load products');
        return throwError(() => err);
      })
    );

    // 并行执行请求(和原来的forkJoin逻辑一致)
    return forkJoin([userRequest$, productRequest$]).pipe(
      catchError((err) => {
        console.error('Initialization failed:', err);
        return throwError(() => err);
      })
    );
  };
}

然后更新AppModule的providers,添加LoadingStatusService依赖:

providers: [
  { 
    provide: APP_INITIALIZER, 
    multi: true, 
    deps: [UserService, ProudctService, LoadingStatusService], 
    useFactory: appInitializer 
  }
],

步骤3:创建加载状态展示组件

这个组件用来实时渲染每个请求的状态,使用Angular Material图标直观展示:

loading.component.ts

import { Component } from '@angular/core';
import { LoadingStatusService, LoadingItem } from './loading-status.service';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-loading',
  templateUrl: './loading.component.html',
  styleUrls: ['./loading.component.css']
})
export class LoadingComponent {
  loadingItems$: Observable<LoadingItem[]> = this.loadingStatusService.loadingItems$;

  constructor(private loadingStatusService: LoadingStatusService) {}
}

loading.component.html

<div class="splash-container">
  <div class="loading-item" *ngFor="let item of loadingItems$ | async">
    <span>{{ item.displayName }}</span>
    <span class="status-icon">
      <!-- 加载中:旋转的图标 -->
      <mat-icon *ngIf="item.status === 'loading'" class="spinner">refresh</mat-icon>
      <!-- 成功:绿色对勾 -->
      <mat-icon *ngIf="item.status === 'success'" color="primary">check_circle_outline</mat-icon>
      <!-- 失败:红色错误图标 -->
      <mat-icon *ngIf="item.status === 'error'" color="warn">error_outline</mat-icon>
    </span>
    <!-- 失败时显示错误信息 -->
    <div *ngIf="item.status === 'error'" class="error-message">
      {{ item.errorMessage }}
    </div>
  </div>
</div>

loading.component.css

.splash-container {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  background-color: #f5f5f5;
  gap: 1.5rem;
  padding: 2rem;
  box-sizing: border-box;
}

.loading-item {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  font-size: 1.3rem;
}

.spinner {
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.error-message {
  color: #f44336;
  font-size: 1rem;
  margin-top: 0.5rem;
  text-align: center;
  width: 100%;
}

别忘了把LoadingComponent添加到AppModule的declarations数组里:

declarations: [
  AppComponent,
  TreeNodeComponent,
  ListElementComponent,
  LoadingComponent // 添加这个
],

步骤4:调整index.html

把原来的硬编码加载内容替换为我们的加载组件,因为app-root初始化前无法使用Angular指令:

<body class="mat-typography">
  <app-root></app-root>
  <app-loading></app-loading>
</body>

步骤5:初始化完成后隐藏加载组件

在AppComponent里监听所有加载项完成的状态,然后移除加载组件:

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { LoadingStatusService } from './loading-status.service';
import { filter, map } from 'rxjs';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  constructor(private loadingStatusService: LoadingStatusService) {}

  ngOnInit() {
    // 监听所有加载项完成(成功/失败都算完成)
    this.loadingStatusService.loadingItems$
      .pipe(
        map(items => items.every(item => item.status !== 'loading')),
        filter(isComplete => isComplete)
      )
      .subscribe(() => {
        const loadingElement = document.querySelector('app-loading');
        if (loadingElement) {
          loadingElement.remove();
        }
      });
  }
}

最终效果

  • 初始化开始时,显示两个加载项,每个后面跟着旋转的加载图标
  • 单个请求成功后,对应项的图标变为绿色对勾
  • 单个请求失败后,对应项的图标变为红色错误图标,并显示具体错误信息
  • 所有请求完成后,加载组件自动消失

内容的提问来源于stack exchange,提问作者Klaus M.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 19:52:33