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

Angular 6应用启动加载进度展示方案咨询

解决Angular应用初始加载进度提示的方案

嘿,针对你遇到的慢网下vendor.js加载耗时过长的问题,我整理了几个实用的加载进度实现方案,结合你已经启用的PWA/Service Worker,能很好地让用户感知应用加载状态:

一、静态初始进度条(Angular启动前显示)

因为vendor.js加载完成前Angular还未初始化,所以先在index.html里添加一个纯HTML/CSS的进度条,再配合简单JS监听加载状态:

<!-- index.html 头部添加样式与进度条容器 -->
<style>
  .loader-container {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 4px;
    background-color: #f0f0f0;
    z-index: 9999;
  }
  .loader-bar {
    height: 100%;
    width: 0%;
    background-color: #1976d2; /* 匹配Material主题色 */
    transition: width 0.3s ease;
  }
</style>

<div class="loader-container">
  <div class="loader-bar" id="loaderBar"></div>
</div>

<!-- 页面底部、vendor.js引入前添加监听脚本 -->
<script>
  let loadedBytes = 0;
  let totalBytes = 0;

  // 预统计所有初始资源总大小
  document.querySelectorAll('link[rel="stylesheet"], script').forEach(el => {
    const resourceUrl = el.href || el.src;
    if (resourceUrl) {
      const xhr = new XMLHttpRequest();
      xhr.open('HEAD', resourceUrl, true);
      xhr.onload = () => {
        totalBytes += parseInt(xhr.getResponseHeader('Content-Length')) || 0;
      };
      xhr.send();
    }
  });

  // 监听资源加载完成事件,更新进度
  document.addEventListener('load', (e) => {
    if (['LINK', 'SCRIPT'].includes(e.target.tagName)) {
      const resourceSize = parseInt(e.target.getAttribute('data-size')) || 0;
      loadedBytes += resourceSize;
      const progress = Math.min(Math.round((loadedBytes / totalBytes) * 100), 100);
      document.getElementById('loaderBar').style.width = `${progress}%`;

      // 加载完成后延迟隐藏进度条
      if (progress >= 100) {
        setTimeout(() => {
          document.querySelector('.loader-container').style.display = 'none';
        }, 500);
      }
    }
  }, true);
</script>

二、Angular启动后的进度跟踪(结合APP_INITIALIZER)

当Angular开始初始化后,可以利用APP_INITIALIZER跟踪初始化任务(比如Firebase初始化、配置加载),并在组件中实时显示进度:

  1. 在app.module.ts定义初始化工厂:
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { ProgressService } from './progress.service';

export function appInitializerFactory(progressService: ProgressService) {
  return () => new Promise<void>((resolve) => {
    // 模拟实际初始化任务,比如Firebase初始化
    progressService.updateProgress(20);
    setTimeout(() => {
      progressService.updateProgress(60);
      setTimeout(() => {
        progressService.updateProgress(100);
        resolve();
      }, 800);
    }, 1200);
  });
}

@NgModule({
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: appInitializerFactory,
      deps: [ProgressService],
      multi: true
    }
  ]
})
export class AppModule {}
  1. 创建进度状态服务progress.service.ts:
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class ProgressService {
  private progressSubject = new BehaviorSubject<number>(0);
  progress$ = this.progressSubject.asObservable();

  updateProgress(value: number) {
    this.progressSubject.next(Math.min(value, 100));
  }
}
  1. 在app.component.ts中订阅并显示进度:
import { Component, OnInit } from '@angular/core';
import { ProgressService } from './progress.service';

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="progress < 100" class="app-loader">
      <div class="loader-bar" [style.width.%]="progress"></div>
      <p>应用加载中... {{ progress }}%</p>
    </div>
    <router-outlet *ngIf="progress >= 100"></router-outlet>
  `,
  styles: [`
    .app-loader { 
      position: fixed; 
      top: 0; left: 0; 
      width: 100%; height: 100%; 
      background: white; 
      display: flex; 
      flex-direction: column; 
      align-items: center; 
      justify-content: center; 
    }
    .loader-bar { 
      width: 50%; height: 4px; 
      background: #1976d2; 
      margin-bottom: 1rem; 
      border-radius: 2px;
    }
  `]
})
export class AppComponent implements OnInit {
  progress = 0;

  constructor(private progressService: ProgressService) {}

  ngOnInit() {
    this.progressService.progress$.subscribe(value => {
      this.progress = value;
    });
  }
}

三、结合Service Worker的缓存/加载进度

因为你已经启用了Service Worker,可以监听SW的资源缓存事件,把进度同步给前端UI:

在自定义Service Worker文件(或修改ngsw-worker.js)中添加进度监听:

// Service Worker 片段
self.addEventListener('install', (event) => {
  const cacheName = 'my-app-cache-v1';
  const resourcesToCache = [
    '/vendor.js',
    '/main.js',
    // 其他需要缓存的资源
  ];

  event.waitUntil(
    caches.open(cacheName)
      .then((cache) => {
        const totalResources = resourcesToCache.length;
        let loadedCount = 0;

        return Promise.all(resourcesToCache.map(resource => {
          return cache.add(resource)
            .then(() => {
              loadedCount++;
              // 向所有打开的页面发送进度消息
              self.clients.matchAll().then(clients => {
                clients.forEach(client => {
                  client.postMessage({
                    type: 'CACHE_PROGRESS',
                    progress: Math.round((loadedCount / totalResources) * 100)
                  });
                });
              });
            });
        }));
      })
  );
});

然后在Angular应用中监听SW消息:

// app.component.ts 中添加监听逻辑
import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <div *ngIf="cacheProgress < 100" class="cache-loader">
      缓存更新中... {{ cacheProgress }}%
    </div>
    <!-- 原有模板内容 -->
  `,
  styles: [`
    .cache-loader {
      position: fixed;
      bottom: 1rem; right: 1rem;
      padding: 0.5rem 1rem;
      background: rgba(0,0,0,0.7);
      color: white;
      border-radius: 4px;
    }
  `]
})
export class AppComponent implements OnInit {
  cacheProgress = 0;

  ngOnInit() {
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.addEventListener('message', (event) => {
        if (event.data.type === 'CACHE_PROGRESS') {
          this.cacheProgress = event.data.progress;
        }
      });
    }
  }
}

额外优化建议:减小vendor.js体积

虽然进度提示能提升用户体验,但从根源上缩小文件大小能更有效解决慢网问题:

  • 启用代码分割:用loadChildren懒加载模块,将Material、Firebase等依赖拆分为按需加载的chunk
  • 清理未使用的Material组件:仅导入项目实际用到的Material模块,避免全量导入
  • 精简Firebase依赖:只导入需要的子模块(比如@angular/fire/auth而非整个@angular/fire)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:25:39