如何处理angular2延迟加载路由失败

时间:2016-12-15 13:08:20

标签: angular webpack lazy-loading offline angular2-aot

我使用了角度2懒惰路由。客户端使用AOT和angular-router-loader捆绑webpack2到延迟加载子级。当浏览器连接时,一切都按预期工作,即我可以成功地将路由器导航到延迟加载的模块,块成功加载,我可以查看组件等。

但是,如果我模拟断开连接(例如使用离线chrome开发人员工具选项),则路由失败,正如预期的那样,因为它无法加载相关的块。错误是“加载块[块号]失败'

之后没有路由命令工作,就像路由器坏了一样。

我尝试使用全局ErrorHandler处理错误。我的想法是,也许我可以在它破坏路由器之前捕获错误,但似乎到时候已经太晚了。当我发现错误时,路由器无法正常工作。

import { Injectable, ErrorHandler, Injector } from '@angular/core';

import { Router } from '@angular/router';

@Injectable()
export class CustomErrorHandler extends ErrorHandler {
    constructor(private injector: Injector) {
        super(false);
    }

    private get router(): Router {
        return this.injector.get(Router, null);
    }

    public handleError(error: Error) {
        if (/Loading chunk [\d]+ failed/.test(error.message)) {
            console.info('Offline mode');
            let router = this.router;
            if (router) {
                router.navigateByUrl('/offline').then(t => console.debug(t+'')).catch(t=> console.debug(t));
            } else {
                window.location.href = '/';
            }
        }
    }
}

自定义错误处理程序的工作原理是因为“离线”模式'邮件已打印。注入也有效,路由器不为空,但路由器导航不起作用,路由器承诺未解决也不会被拒绝。

我想要完成的是处理错误(例如向用户显示信息性消息)并同时使路由器处于工作状态,以便用户可以稍后导航(当无需重新加载整个页面即可恢复互联网连接。

更新1:尝试在没有aot和webpack的情况下重现

为了查看这是否是一个角度路由器问题,我试图看看尝试使用jit编译离线工作时会发生什么。我使用:angular router plunker导航到登录选项卡,切换到离线并按下登录。一些错误:XHR错误加载'客户端尝试加载管理模块时记录错误。 最终导航失败了,但是之后路由导航没有破坏。我能够导航到其他选项卡,在切换回在线后,我甚至能够导航到管理员。 也许问题是angular-router-loader webpack插件如何尝试加载模块。

更新2:似乎是一个已知问题

Feature: more robust module loader for router

2 个答案:

答案 0 :(得分:4)

每次我dog狗并尝试在电梯中测试我的网站时,我都会遇到此错误。感到惊讶的是,没有更好的文档来处理错误-特别是对于所有non solutions in the github issue

话虽如此,来自路由器的NavigationError事件。因此,您可以拦截它,阻止正常的错误处理程序运行并显示一个弹出窗口。关于此事件的好处是它包含完整的尝试URL-而不是无用的Error: Uncaught (in promise): Error: Loading chunk common failed.错误。

这就是我的想法。

app.component.ts中(必须注入private router: Router

从构造函数中调用initErrorHandler()函数。

// filter router events of type NavigationError
navigationError$ = this.router.events.pipe
                   (filter((e): e is NavigationError => e instanceof NavigationError));

// Keep a reference to the error handled, so we can ignore it later
_errorHandled: any;
initErrorHandler()
{
   this.navigationError$.subscribe((evt) => 
   {
       let showPopup = false;

       // handle known error messages that can be retried
       if (evt.error.message.startsWith('Loading chunk')) 
       {
           showPopup = true;
       }
       else 
       {
          debugger;   // consider handling other messages
       }

       if (showPopup) {

          // display a popup however you want...
          // I use Angular Material, and popup is MY OWN dialog box service
          this.popup.showChoicesDialog({

              title: 'Network Error',
              desc: 'Sorry we were unable to load that page',
              fullscreen: true,

              choices: [
                  {
                      action: () => 
                      {
                          // this is the important part - retry URL
                          this.router.navigateByUrl(evt.url)
                      },
                      buttonText: 'Retry'
                  }
              ]
          });

          // mark the error as handled to avoid global handler
          this._errorHandled = e.error;
      }

      // normal global error handler
      this.zone.onError.subscribe((error: any) => 
      {
          // ignore errors that were already handled but couldn't be cancelled
          if (error.rejection === this._ignoreError)
          {
              this._ignoreError = undefined;
              return;
          }

          // Whatever you want to do here for true unhandled errors
          console.warn(error); 
      });
  }

根据您的用户群,您需要更改消息和行为,但是通用方法似乎可行。

我尚不知道所有可能的NavigationError的全部范围,因此,现在这仅处理Loading chunk错误。如果发现其他消息错误,请发表评论。

在测试时,请务必使用“离线”模式。在快速测试中,如果我启用了“离线”模式以触发错误,然后尝试再次导航,那么一旦禁用“离线”,它确实可以成功导航。

enter image description here

注意:有关“块错误”的许多讨论主题都与新部署后的缓存问题有关。我尚未解决这些问题,但是错误消息可能会有所不同,因此您可能会显示不同的弹出窗口。

答案 1 :(得分:1)

防止路由失败而不是处理它。

您可以预加载延迟加载的模块,以便在浏览器检测到不活动时将它们加载到客户端,这样它们就可以在脱机模式下使用。

点击此处预加载所有模块:PreloadAllModules

您还可以创建自定义加载策略并仅预加载选定的模块:

定制preloading.ts
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/mergeMap';
import { PreloadingStrategy, Route } from '@angular/router';

export class CustomPreloading implements PreloadingStrategy {

   public preload(route: Route, fn: () => Observable<boolean>): Observable<boolean> {
     if (route.data && route.data.preload) {
       return Observable.of(true).flatMap((_: boolean) => fn());
     } else {
       return Observable.of(false);
     }
   }
}
APP-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { CustomPreloading } from './custom-preloading';

const routes: Routes = [
  { ..other paths.. },
  { path: 'lazy', loadChildren: 'app/lazy/lazy.module#LazyModule', data: { preload: true }  }
];

export const appRoutingProviders = [
   CustomPreloading
];

@NgModule({
   imports: [RouterModule.forRoot(routes, { preloadingStrategy: CustomPreloading })],
   exports: [RouterModule]
})
export class AppRoutingModule { }