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

Posted

技术标签:

【中文标题】如何处理 angular2 延迟加载路由失败【英文标题】:How to handle angular2 lazy loading route failure 【发布时间】:2017-05-01 02:29:05 【问题描述】:

我正在使用 Angular 2 惰性路由。客户端与 webpack2 捆绑使用 AOT 和 angular-router-loader 以延迟加载子级。浏览器连接后一切正常,即我可以成功地将路由器导航到延迟加载的模块,块加载成功,我可以查看组件等。

但是,如果我模拟断开连接(例如,通过使用离线 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

【问题讨论】:

这个问题有什么进展吗?我有同样的问题,我正在寻找答案。 【参考方案1】:

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

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


选中此处以预加载所有模块:PreloadAllModules


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

自定义预加载.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);
     
   

应用程序路由.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  

【讨论】:

这是一篇关于预加载的博客文章vsavkin.com/angular-router-preloading-modules-ba3c75e424cb【参考方案2】:

每次我遛狗并尝试在电梯中测试我的网站时,我都会收到此错误。很惊讶没有更好的文档来处理错误——尤其是所有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 = evt.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._errorHandled)
          
              this._errorHandled = undefined;
              return;
          

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

根据您的用户群,您需要改变消息和行为,但一般方法似乎有效。

我还不知道所有可能的 NavigationError 的全部范围,所以现在它只处理 Loading chunk 错误。如果您发现替代消息错误,请发表评论。

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

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

【讨论】:

我在这个插件上也取得了一些成功,由于某些网络发布了npmjs.com/package/webpack-retry-chunk-load-plugin,它重新尝试下载块 - 似乎大大减少了加载失败,我怀疑其中大部分是电话用户覆盖参差不齐。

以上是关于如何处理 angular2 延迟加载路由失败的主要内容,如果未能解决你的问题,请参考以下文章

Angular2:无路由的延迟加载和动态加载

Angular 2 延迟加载技术

路由器插座的Angular 2延迟渲染

Flux:如何处理多个异步请求

使用 Alamofire 在 UITableview 中延迟加载 API 请求

延迟加载到Angular 2中的空路由以外的路由