等待Angular 2加载/解析模型,然后再渲染视图/模板

时间:2016-01-11 21:55:24

标签: angular promise observable dependency-resolver

在Angular 1.x中,UI-Router是我的主要工具。通过回复承诺"解决"值,路由器只是等待承诺在呈现指令之前完成。

或者,在Angular 1.x中,null对象不会使模板崩溃 - 所以如果我不介意暂时不完整的渲染,我可以使用$digest来渲染{{1填充最初为空的模型对象。

在这两种方法中,如果可能的话,我更愿意等待加载视图,如果无法加载资源,则取消路由导航。这为我节省了" un-navigating"的工作。 编辑:请注意,这具体意味着此问题要求使用Angular 2期货兼容或最佳实践方法来执行此操作,并要求避免使用" Elvis运算符"如果可能的话!因此,我没有选择那个答案。

但是,这两种方法都不适用于Angular 2.0。当然,有一个标准的解决方案计划或可用于此。有谁知道它是什么?

promise.then()

以下问题可能反映了同一问题:Angular 2 render template after the PROMISE with data is loaded。请注意,问题中没有代码或接受的答案。

6 个答案:

答案 0 :(得分:85)

尝试{{model?.person.name}}这应该等待模型不是undefined然后渲染。

Angular 2将此?.语法称为Elvis operator。在文档中引用它很难找到,所以这里是它的副本,以防它们改变/移动它:

  

Elvis运算符(?。)和null属性路径

     

Angular“Elvis”运算符(?。)是一种流畅且方便的方法,可以防止属性路径中的null和undefined值。如果currentHero为null,则保护视图呈现失败。

     

The current hero's name is {{currentHero?.firstName}}

     

让我们详细说明问题和这个特殊的解决方案。

     

当以下数据绑定title属性为null时会发生什么?

     

The title is {{ title }}

     

视图仍然呈现但显示的值为空白;我们只看到“标题是”,之后什么都没有。这是合理的行为。至少应用程序不会崩溃。

     

假设模板表达式涉及属性路径,如下一个示例所示,我们将显示空英雄的firstName。

     

The null hero's name is {{nullHero.firstName}}

     

JavaScript抛出空引用错误,Angular也是如此:

     

TypeError: Cannot read property 'firstName' of null in [null]

     

更糟糕的是,整个视图都消失了。

     

如果我们认为英雄财产绝不能为空,我们可以声称这是合理的行为。如果它必须永远不是null而且它是null,那么我们已经编写了一个应该被捕获并修复的编程错误。抛出异常是正确的做法。

     

另一方面,属性路径中的空值可能会不时出现,特别是当我们知道数据最终会到达时。

     

当我们等待数据时,视图应该呈现而没有投诉,并且null属性路径应该像title属性一样显示为空白。

     

不幸的是,当currentHero为空时,我们的应用程序崩溃了。

     

我们可以使用NgIf来解决这个问题

     

<!--No hero, div not displayed, no error -->   <div *ngIf="nullHero">The null hero's name is {{nullHero.firstName}}</div>

     

或者我们可以尝试使用&amp;&amp;链接部分属性路径,知道当遇到第一个null时表达式会失效。

     

The null hero's name is {{nullHero && nullHero.firstName}}

     

这些方法具有优点,但它们可能很麻烦,尤其是在属性路径很长的情况下。想象一下,在a.b.c.d。

等长属性路径中的某处保护null      

Angular“Elvis”运算符(?。)是一种更流畅,更方便的方法来防范属性路径中的空值。当它达到第一个空值时,表达式会失效。显示为空白但应用程序不断滚动,没有错误。

     

<!-- No hero, no problem! -->   The null hero's name is {{nullHero?.firstName}}

     

它也适用于长属性路径:

     

a?.b?.c?.d

答案 1 :(得分:34)

@angular/router具有路由的Resolve属性。因此,您可以在渲染路径视图之前轻松解析数据。

请参阅:https://angular.io/docs/ts/latest/api/router/index/Resolve-interface.html

截至2017年8月28日的文档示例:

class Backend {
  fetchTeam(id: string) {
    return 'someTeam';
  }
}

@Injectable()
class TeamResolver implements Resolve<Team> {
  constructor(private backend: Backend) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<any>|Promise<any>|any {
    return this.backend.fetchTeam(route.params.id);
  }
}

@NgModule({
  imports: [
    RouterModule.forRoot([
      {
        path: 'team/:id',
        component: TeamCmp,
        resolve: {
          team: TeamResolver
        }
      }
    ])
  ],
  providers: [TeamResolver]
})
class AppModule {}

现在,在解析并返回数据之前,您的路线不会被激活。

访问组件中的已解析数据

要在运行时从组件中访问已解析的数据,有两种方法。因此,根据您的需求,您可以使用:

  1. route.snapshot.paramMap返回一个字符串,或
  2. route.paramMap返回一个您可以.subscribe()的Observable。
  3. 示例:

      // the no-observable method
      this.dataYouResolved= this.route.snapshot.paramMap.get('id');
      // console.debug(this.licenseNumber);
    
      // or the observable method
      this.route.paramMap
         .subscribe((params: ParamMap) => {
            // console.log(params);
            this.dataYouResolved= params.get('id');
            return params.get('dataYouResolved');
            // return null
         });
      console.debug(this.dataYouResolved);
    

    我希望有所帮助。

答案 2 :(得分:6)

编辑:角度团队发布了@Resolve装饰器。它仍然需要一些澄清,如何运作,但在此之前我会在这里采取其他人的相关答案,并提供其他来源的链接:

编辑:此答案仅适用于Angular 2 BETA。在此编辑中,未针对Angular 2 RC发布路由器。相反,在使用Angular 2 RC时,将router的引用替换为router-deprecated以继续使用beta路由器。

Angular2-未来的实现方式将通过@Resolve装饰器实现。在此之前,最接近的传真是CanActivate组件装饰,每个Brandon Roberts。见https://github.com/angular/angular/issues/6611

虽然测试版0不支持向组件提供已解析的值,但它已经过计划,此处还介绍了一种解决方法:Using Resolve In Angular2 Routes

可在此处找到测试版1示例:http://run.plnkr.co/BAqA98lphi4rQZAd/#/resolved。它使用非常类似的解决方法,但使用RouteData对象而不是RouteParams稍微更准确一些。

@CanActivate((to) => {
    return new Promise((resolve) => {
        to.routeData.data.user = { name: 'John' }

另请注意,还有一个示例解决方法,用于访问嵌套/父路由“已解析”值,以及您使用1.x UI-Router时所期望的其他功能。

请注意,您还需要手动注入完成此操作所需的任何服务,因为CanActivate装饰器中当前不提供Angular Injector层次结构。简单地导入Injector将创建一个新的注入器实例,无法访问bootstrap()的提供者,因此您可能希望存储应用程序范围的自举注入器副本。 Brandon在此页面上的第二个Plunk链接是一个很好的起点:https://github.com/angular/angular/issues/4112

答案 3 :(得分:4)

使用观察者设置本地值

...另外,不要忘记使用虚拟数据初始化值以避免uninitialized错误。

export class ModelService {
    constructor() {
      this.mode = new Model();

      this._http.get('/api/v1/cats')
      .map(res => res.json())
      .subscribe(
        json => {
          this.model = new Model(json);
        },
        error => console.log(error);
      );
    }
}

这假设Model是表示数据结构的数据模型。

没有参数的模型应该创建一个新实例,其中所有值都已初始化(但为空)。这样,如果模板在收到数据之前呈现,则不会抛出错误。

理想情况下,如果您希望保留数据以避免不必要的http请求,则应将其放在具有您自己的可以订阅的观察者的对象中。

答案 4 :(得分:2)

routerOnActivate中实施@Component并返回您的承诺:

https://angular.io/docs/ts/latest/api/router/OnActivate-interface.html

编辑:这显然不起作用,虽然目前的文档可能有点难以解释这个主题。有关详细信息,请参阅Brandon的第一条评论:https://github.com/angular/angular/issues/6611

编辑:关于其他通常准确的Auth0网站的相关信息不正确:https://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in-depth/

编辑:角度团队正在为此目的规划一个@Resolve装饰器。

答案 5 :(得分:2)

我发现一个很好的解决方案是在UI上执行以下操作:

<div *ngIf="vendorServicePricing && quantityPricing && service">
 ...Your page...
</div

仅在加载vendorServicePricingquantityPricingservice时才会呈现页面。