在Angular 2中加载惰性加载模块时显示活动指示器

44

我的场景如下。我有一个菜单,里面有多个选项。每个菜单应该根据用户权限进行显示(已经解决),大多数菜单项被封装为模块,并且大多数模块是惰性加载的,因此当用户第一次点击菜单项时,它会加载(到这里一切正常)。现在我的要求是,为了提供更好的用户体验,在用户点击菜单项并且惰性加载模块时需要显示活动指示器。

到目前为止,我尝试使用 Angular 路由器的 canActive、canLoad 和 canActivateChild 接口,但没有成功。

有什么想法吗?

5个回答

98

你可以监听两个路由器事件:

  • RouteConfigLoadStart
  • RouteConfigLoadEnd

当延迟加载的模块被加载时,它们会触发。与诸如NavigationStart之类的标准路由器事件相比,使用这些事件的优点在于它们不会在每次路由更改时触发。

在根AppComponent中监听这些事件,以显示/隐藏旋转符号。

app.component.ts

import { Router, RouteConfigLoadStart, RouteConfigLoadEnd } from '@angular/router';

...

export class AppComponent implements OnInit {

    loadingRouteConfig: boolean;

    constructor (private router: Router) {}

    ngOnInit () {
        this.router.events.subscribe(event => {
            if (event instanceof RouteConfigLoadStart) {
                this.loadingRouteConfig = true;
            } else if (event instanceof RouteConfigLoadEnd) {
                this.loadingRouteConfig = false;
            }
        });
    }
}

app.component.html

这里只是一个简单的字符串,但是你可以使用一个旋转组件。

<router-outlet></router-outlet>

<ng-container *ngIf="loadingRouteConfig">Loading route config...</ng-container>

我正在使用这种方法,版本为Angular v4.2.3


3
这个功能可以显示文本,但如果使用了旋转器或带有CSS动画的其他组件,一旦模块开始加载,动画就会停止(尽管动画组件本身被显示出来,但会停留在某一帧)。请注意,这不是旋转器本身的问题 - 只会在加载模块时发生。 - WillyC
在这种情况下有没有办法使用CSS动画? - Florian Ludewig
2
如果您正在使用PreloadAllModules策略,那么这也会显示所有模块在后台加载。在这种情况下,最好还要捕获NavigationStart和NavigationEnd,并忽略这些事件之外的RouteConfigLoadStart和RouteConfigLoadEnd。 - Cito
可以添加以下代码:else if (event instanceof NavigationCancel) { this.isLoading = false },以处理当懒加载尚未完成时移动到另一个路由/模块的情况。 - Uriy MerkUriy

29

你可以像这样做:

  1. 在 app.component.html 文件中:
<div class="main-loader" *ngIf="loading">
  <div class="cssload-container" >
      <div class="cssload-whirlpool"></div>
  </div>
</div>
  1. in app.component.ts

    import { Router, NavigationStart, NavigationEnd } from '@angular/router';
    
    
    loading:boolean = false;
    constructor(private router:Router) { 
      router.events.subscribe(event => {
        if(event instanceof NavigationStart) {
          this.loading = true;
          console.log("event started")
        }else if(event instanceof NavigationEnd) {
          this.loading = false;
          console.log("event end")
        }
        // NavigationEnd
        // NavigationCancel
        // NavigationError
        // RoutesRecognized
      });
    
    }
    
  2. in css any loading animation

希望这对你有所帮助。谢谢。


您不需要订阅两次。就像Daniel Crisp的回答一样,您只需要订阅一次即可。这样做是有效的,而且只订阅一次更好。 - Sangwin Gawande
如果您想在显示加载状态之前延迟一段时间(即仅在加载缓慢时显示加载状态),则可以使用过滤器和switchMap,在延迟后才将this.loading = true设置为真。灵感来自https://stackoverflow.com/a/51998101/3370010 - waternova

2
你可以直接使用 CSS!
<routler-outlet></routler-outlet>
<div class='.loader>
  Just, wait a sec ! I'm loading
</div>

在您的模板中
router-outlet + .loader {
  opacity : 1;
}

.loader {
  opacity : 0;
}

然后您可以使用 HTML/CSS 创建 华丽的旋转器

你能详细说明一下你的答案吗?我的意思是,我有一个根模块和一个特性模块来管理所有菜单,然后当我点击菜单项时,它会加载一个惰性加载的模块,所以在加载时我想显示活动指示器,但是一旦惰性模块加载完成,就隐藏活动指示器。我不知道如何应用这个功能。 - vicmac
我猜你有一个router-outlet来加载你的组件。当组件没有被加载时,<router-outlet>将是空的。这将触发container:empty + .loader选择器并显示你的加载器。当数据被加载时,<router-outlet>将不再为空,然后container:empty + .loader选择器将不再处于活动状态,你的加载器将回到0的透明度。 - YounesM
实际上,我有一个路由出口。事实上,我有一个带有根路由出口的应用程序组件,还有另一个模块负责管理所有菜单,因此它有另一个路由出口。我刚刚尝试了你的答案,在我的“子路由出口”中放置了类容器,但即使在加载模块后也会显示出来。 - vicmac
@vimac 抱歉,我的错,我知道问题出在哪里了。我假设组件在路由出口内部加载,但实际上它们是在出口之后加载的。我已经编辑了我的代码,现在应该没问题了。 - YounesM
我有一个带有路由出口(router-outlet)的应用程序组件和一个IndexComponent,用于显示一个按钮以通过oauth进行身份验证,一旦完成身份验证,用户将被重定向到IndexComponet,但这次已经登录了,所以我检测到并将用户重定向到主页。主页是另一个功能模块,具有管理所有菜单的MenuBarComponent,其模板如下,样式也在样式标签中。<nav> ... 菜单在这里 </nav> <router-outlet class='container'></router-outlet> <div class='.loader'> 等一下!我正在加载 </div> - vicmac
显示剩余3条评论

1

在加载第一个懒加载路由时,有可能会单击链接到其他懒加载路由。因此我们需要计算正在加载的路由数量:

app.component.ts

```

export class AppComponent { 

  currentlyLoadingCount = this._router.events.scan((c, e) => this._countLoads(c, e), 0);

  constructor(private _router: Router) { }

  private _countLoads(counter: number, event: any): number {
    if (event instanceof RouteConfigLoadStart) return counter + 1;
    if (event instanceof RouteConfigLoadEnd) return counter - 1;
    return counter;
  }

```

app.component.html <ng-container *ngIf="currentlyLoadingCount | async">正在加载路由配置...</ng-container>


我已经离开一段时间,一直在处理我的项目。我刚刚解决了一个共享服务的问题,当某个HTTP请求开始时触发,这在一定程度上解决了我的问题,但我会尽快尝试它。谢谢。 - vicmac

0
对于那些需要与IE11兼容性(和处理)的人,接受的答案不起作用,因为IE11在从一个模块切换到另一个模块时不会呈现加载程序,所以我做了一个(不太优雅但有效的)解决方法:
在我的menuItem开关点击事件中:
  public navigate(url: string) {
    this.configurationService.loading = true; //service variable linked with html loader

    //let's give time to tortoise IE11 to render loader before navigation...
    let obj = this;
    setTimeout(function() 
    {
      obj.router.navigateByUrl(url);
    }, 1);
  }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接