# Angular自定义预加载策略

> 预加载是介于直接加载与懒加载的一种方式。懒加载是为了降低初始化所需时间，预加载是为了提升即将进入路由模块时间。

Author: DeathGhost
Published: 2021-01-07 20:52:43
Category: angular
Canonical: https://www.deathghost.cn/article/angular/88
Keywords: Angular, 路由, 预加载, 懒加载, 延迟加载, 按需加载, 自定义预加载, 模块加载, 动画, 过渡

Angular实际应用中我们会根据业务或角色权限拆分出各个模块，然后在路由中除初始化模块外设置懒加载。但是为了能够快速导航到即将进入的模块，我们会对路由模块设置预加载。Angular路由提供了preloadingStrategy配置属性，这个属性定义了用于预加载和处理延迟加载的Angular模块的逻辑，将PreloadAllModules指定给preloadingStrategy。

import { RouterModule, PreloadAllModules } from '@angular/router';
// &hellip;

RouterModule.forRoot([
  &hellip;
], {
  preloadingStrategy: PreloadAllModules
})
// &hellip;
详情可阅读上篇文章关于&ldquo;[Angular路由预加载](https://www.deathghost.cn/article/angular/73)&rdquo;。

![Angular自定义预加载策略](https://www.deathghost.cn/public/upload/article/2021/01/07/1610030863712259.jpg)

Angular自定义预加载策略

今天谈谈另一种需求场景，就是对各个模块可配置性预加载；上述方法默认是预加载所有模块，但随着业务模块增多，未必是个好方法，对服务器资源也是一种消耗。我们设置一个自定义预加载策略，然后对每个模块进行选择性配置是否对其预加载。

## 创建preloading-strategy.service.ts预加载策略服务

import { Injectable } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root',
})

export class SelectivePreloadingStrategyService implements PreloadingStrategy {
  preloadedModules: string[] = [];
  preload(route: Route, load: () => Observable<any>): Observable<any> {
    if (route.data && route.data['preload']) {
      this.preloadedModules.push(route.path);
      return load();
    } else {
      return of(null);
    }
  }
}

## 在路由模块中导入并配置

import { PreloadingStrategyService } from '../preloading-strategy.service';
// ...
{ path: '', loadChildren: () => import('./user/user.module').then(m => m.UserModule), data: {preload: false} },
// ...
{ preloadingStrategy: PreloadingStrategyService, relativeLinkResolution: 'legacy' }
// ...
其中data: {preload: true}就是设置是否预加载。

那么，问题来了，未设置预加载的模块，还是原来的懒加载。我们都知道，懒加载情况下，点击跳转路由时，若该模块未加载完成，当前触发犹如未操作一般，用户体验非常糟糕。这时，我们可以在路由插座上设置CSS过渡效果以缓解这个尴尬的场面。

## 路由插座RouterOutlet中模块加载设置CSS过渡样式

RouterOutlet是一个占位符，Angular 会根据当前的路由器状态动态填充它。我们在页面整体布局结构中会使用到它。

<router-outlet>
  <div class="pageLoad" *ngIf="loadModule">
    <div class="loading_module">
      <div class="dot white"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
      <div class="dot"></div>
    </div>
    <p class="text">数据模块加载中，请耐心等待...</p>
  </div>
</router-outlet>
import { Router, NavigationEnd, RouterEvent, NavigationStart, GuardsCheckEnd } from '@angular/router';
// ...
loadModule: boolean;
constructor(
  private router: Router,
) {
  this.loadModule = true;
  this.router.events.subscribe(
    (event: RouterEvent): void => {
      if (event instanceof NavigationStart) {
        this.loadModule = true;
      } else if (event instanceof NavigationEnd) {
        this.loadModule = false;
      } else if (event instanceof GuardsCheckEnd) {
        this.loadModule = false;
      } else if () {}
    }
  );
}
// ...
NavigationStart 导航开始时，显示定义的那段&ldquo;pageLoad&rdquo;，当NavigationEnd导航成功结束时将插座中的那段HTML文本移除掉。

过渡样式，示例中用SCSS文件编写。

.pageLoad{
    position: absolute;
    left:0;
    right:0;
    top:0;
    bottom:0;
    background:white;
    display:flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    z-index: 1986;
    .text{
        margin-top: 10em;
        letter-spacing: 1px;
        color:#999;
        font-size: 12px;
        text-align: center;
    }
}
.loading_module {
    position: absolute;
    margin: auto;
    top: 0; bottom: 0; left: 0; right: 0;
    width: 6.250em; height: 6.250em;
    animation: rotate 2.4s linear infinite;
    .white {
      top: 0; bottom: 0; left: 0; right: 0;
      background: white;
      animation: flash 2.4s linear infinite;
      opacity: 0;
    }
    .dot {
      position: absolute;
      margin: auto;
      width: 2.4em; height: 2.4em;
      border-radius: 100%;
      transition: all 1s ease;
    }
    .dot:nth-child(2) { top: 0; bottom: 0; left: 0; background: #FF4444; animation: dotsY 2.4s linear infinite; }
    .dot:nth-child(3) { left: 0; right: 0; top: 0; background: #FFBB33; animation: dotsX 2.4s linear infinite; }
    .dot:nth-child(4) { top: 0; bottom: 0; right: 0; background: #99CC00; animation: dotsY 2.4s linear infinite; }
    .dot:nth-child(5) { left: 0; right: 0; bottom: 0; background: #33B5E5; animation: dotsX 2.4s linear infinite; }
  }
  @keyframes rotate {
    0% { transform: rotate( 0 ); }
    10% { width: 6.250em; height: 6.250em; }
    66% { width: 2.4em; height: 2.4em; }
    100%{ transform: rotate(360deg); width: 6.250em; height: 6.250em; }
  }

  @keyframes dotsY {
    66% { opacity: .1; width: 2.4em; }
    77%{ opacity: 1; width: 0; }
  }
  @keyframes dotsX {
    66% { opacity: .1; height: 2.4em;}
    77%{ opacity: 1; height: 0; }
  }

  @keyframes flash {
    33% { opacity: 0; border-radius: 0%; }
    55%{ opacity: .6; border-radius: 100%; }
    66%{ opacity: 0; }
  }

![CSS加载过渡效果](https://www.deathghost.cn/public/upload/article/2021/01/07/1610029204190524.gif)

CSS加载过渡效果

选择性预加载，可以针对存在不同管理角色，加载相对应的所涉及的模块。避免加载一些非本角色的模块，消耗占用服务端及其用户资源。

## Related

- Previous: [Angular CDK 工具包 Clipboard 剪贴板](https://www.deathghost.cn/article/angular/87)
