在渲染视图/模板之前,等待Angular 2加载/解析模型


73

在Angular 1.x中,UI-Router是我的主要工具。通过返回对“ resolve”值的承诺,路由器只需在呈现指令之前等待承诺完成即可。

或者,在Angular 1.x中,空对象不会使模板崩溃-因此,如果我不介意暂时不完整的渲染,则可以$digestpromise.then()填充最初为空的模型对象之后使用渲染。

在这两种方法中,如果可能的话,我宁愿等待加载视图,如果无法加载资源,则取消路线导航。这为我节省了“取消导航”的工作。编辑:请注意,这特别意味着此问题要求使用Angular 2兼容或最佳实践的方法来执行此操作,并在可能的情况下要求避免使用“猫王运算符”!因此,我没有选择答案。

但是,这两种方法都无法在Angular 2.0中使用。肯定有为此计划或可用的标准解决方案。有谁知道它是什么?

@Component() {
    template: '{{cats.captchans.funniest}}'
}
export class CatsComponent {

    public cats: CatsModel;

    ngOnInit () {
        this._http.get('/api/v1/cats').subscribe(response => cats = response.json());
    }
}

以下问题可能反映了相同的问题:加载带有数据的PROMISE之后的Angular 2渲染模板。请注意,问题中没有代码或未接受的答案。


1
您可以阅读@TGH的延迟加载文章
Eric Martinez

1
@shannon整个路由器都已弃用并重新编写。希望他们今天在NgConf讨论这个问题。
兰里

@Langley您的意思是他们将完全重新编写angular 2路由器吗?
帕斯卡

@Pascal似乎是这样,请看一下:angular.io/docs/ts/latest/guide/router-deprecated.html
Langley,

好吧...比他们还可以写出“预期的beta角度2”替换为“ RC角度2”让我们看一下RC路由器能否处理像aureliajs这样的无限子路由器;-)
Pascal,

Answers:


38

该包@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这将返回一个Observable .subscribe()

例:

  // 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);

希望对您有所帮助。


它可以工作,但是如何在您的组件中访问由解析返回的数据?
dryoezoe

3
看起来你可以从快照得到它stackoverflow.com/a/38313301/1138984
威尔鲍曼

1
解析程序不适用于应用程序中的第一条路线;
卡尔

1
您的代码有点过时了。请查看angular.io/api/router/Resolve,以获取更新的语法。
Javan R.

85

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

Angular 2将此?.语法称为Elvis运算符。在文档中很难找到它的引用,因此如果他们更改/移动它,这里是它的副本:

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

Angular“ Elvis”运算符(?。)是一种流畅而便捷的方法,可以防止属性路径中的空值和未定义值。在这里,如果currentHero为null,则可以防止视图渲染失败。

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

让我们详细说明问题和此特定解决方案。

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

The title is {{ title }}

视图仍然呈现,但是显示的值为空白;我们只会看到“标题是”,后面没有任何内容。那是合理的行为。至少该应用程序不会崩溃。

假设模板表达式包含一个属性路径,如在下一个示例中,我们显示的是空英雄的名字。

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

JavaScript会引发空引用错误,Angular也是如此:

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

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

如果我们认为hero属性绝不能为null,则可以声称这是合理的行为。如果它一定不能为null却仍然为null,则我们已经犯了一个程序错误,应予以纠正。抛出异常是正确的事情。

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

当我们等待数据时,视图应呈现无抱怨,并且null属性路径应与title属性一样显示为空白。

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

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

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

或者,我们也可以尝试使用&&链接属性路径的各个部分,因为知道表达式遇到第一个null时就会崩溃。

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

这些方法都有优点,但它们可能很麻烦,尤其是在属性路径较长的情况下。想象一下在长属性路径(如abcd)中防止空值

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

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

它也很适合长属性路径:

a?.b?.c?.d


谢谢。这可以很好地解决,但最终我认为将对象及其所有子对象都视为必需对象,并按照其他答案的建议(如果我可以让他们使用,请稍候)可用于渲染。 )。否则,这种修饰将出现在我所有页面的每个值上,在这方面,我将返回Angular 1.x功能。它还可能存在性能问题(额外的脏检查)。
香农

3
@shannon我不这么认为。请参阅我的更新答案和ng2文档。
tehaaron '16

我对性能有所怀疑的原因是Observable(对于,情况相同Promise)已经解开。现在,确定何时更改的唯一方法是使用我们可靠的Angular 1.x机制(脏检查)。将此与使用相比,模板中Observableasync管道支持它。Angular内部可以(我怀疑确实如此)将管道连接到,从而无需观察者,并且仅当可观察对象到达时才渲染节点。它实际上是具有事件驱动动作的消息总线。
香农

另一个小问题是,让猫王操作员无处不在在某种程度上破坏了Angular团队改变心意的目标,因为他们悄悄地吞下了错误。您自己可能已经遇到了问题,发现当期望函数处理空值时,模板(以及整个应用程序)更难调试。此外,如果我必须选择显示的内容(例如在A:B场景中),那么现在我必须实现其他ngIf逻辑。我真的很想让“等待渲染组件”方法起作用。
香农

3
现在将该操作员称为“安全导航操作员”
ssc-hrep3 '16

7

编辑:角度小组已经发布了@Resolve装饰器。仍然需要澄清其工作方式,但是在此之前,我将在这里获取其他人的相关答案,并提供指向其他来源的链接:


编辑:此答案仅适用于Angular 2 BETA。截至此编辑,尚未针对Angular 2 RC发布路由器。相反,在使用Angular 2 RC时,请router使用router-deprecated以继续使用beta路由器。

实现该目标的Angular2-future方法将通过@Resolve装饰器。在此之前CanActivate,根据布兰登·罗伯茨的说法,最接近的传真是组件装饰器。参见https://github.com/angular/angular/issues/6611

尽管beta 0不支持向组件提供解析的值,但它是已计划的,并且这里描述了一种解决方法:在Angular2路由中使用解析

您可以在此处找到Beta 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时期望的其他功能。

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


1
您推荐的plnker:run.plnkr.co/BAqA98lphi4rQZAd/ #/ resolved不再可用。感谢您使用此功能。也许您可以将当前针对RC1版本发布的功能添加到您的答案中:github.com/angular/angular/issues/4015
Philip

RouteData是不可变的。
赫里斯托·韦涅夫

@HristoVenev:当Angular 2 Beta路由器仍然是最新的时,您的评论(以及相关的下注)是否与解决方案的适用性有关?
香农

RouteData即使对于beta路由器(router-deprecated),@ shannon也是一成不变的。
赫里斯托·韦内夫

是的,我知道RouteData被记录为“不可变的”。让我们暂时忽略这对JavaScript属性集意味着什么。让我们深入您的观点。是说解决方案不起作用,还是有更好的解决方案可用?最后我检查了一下,我的应用程序在这方面没有缺陷,这个解决方案是由Angular团队提出的,其他任何人都没有提供竞争解决方案。尽管遵循文档的指导可能会很好,但是Resolve尚未实现,因此该指导不值得贬低。
香农

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请求,则应将其放在具有自己的观察者且可以订阅的对象中。


埃文,这对我来说毫无意义。通过将可观察序列展平为需要脏检查的普通对象,这是否不会绕过Angular 2性能更好的Observable模式?另外,我真的希望不必维护一堆空的初始化程序,客户端中的每个模型都应使用一个初始化程序。我希望有另一种方式。
香农

我完全同意,这ngIf也是一种功能性的解决方法-在预期结果对象很复杂的情况下,与其他建议相比,它需要的重复代码更少。但是,它也具有相同的弱点,因为它会解包Observable并要求模板执行脏检查(或不必要地重新渲染)。我知道您在上一条评论中不同意这一点,但是如果您对此进行进一步考虑,我怀疑您会同意。另请参阅我对@tehaaron的评论,其回答具有类似的效果。
香农

@shannon在Angular2中进行肮脏的检查很便宜,但这无关紧要,因为它不能回答问题。您想要的是在页面加载之前预热的数据缓存,因此没有重新渲染闪存。为此,您需要将数据作为单独的服务加载,并且需要一种在页面加载之前触发数据加载的方法。
埃文·普赖斯

(续)服务只有在首次注入时才构建。因此,其他一些组件必须触发构建。一种方法是将服务注入到父组件中。另一个方法是延迟加载路由,并在加载组件之前以某种方式触发服务启动。
埃文·普赖斯

小批量检查总是很便宜的,在Angular 1.x中我也反对使用它。我同意简单性的好处胜于成本。但是,做出了一个基本的体系结构决策来支持ObservableAngular 2中的驱动机制,并且在包含大量项目的页面上提高性能是原因之一。它具有更高的性能,而且仍然很容易成为消费者的“正确”角色。所以,我想使用它。
香农

3

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

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

只有当:vendorServicePricingquantityPricingservice被加载的页面呈现。


这应该放在首位,因为它是最简单的解决方案。无需解决或?.“ notnull-Operator”
Javan

如果您有一个很大的模板怎么办?将所有内容存储在大的IF子句中?像React中一样,拥有一种拦截预渲染的方法会更好,只是返回null直到有一些数据为止。
劳尔·雷内

2

实施routerOnActivate您的@Component并兑现您的承诺:

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

编辑:这显然不起作用,尽管当前文档可能很难解释这个主题。有关更多信息,请参见此处的布兰登第一评论:https : //github.com/angular/angular/issues/6611

编辑:否则,通常是正确的Auth0网站上的相关信息不正确:https ://auth0.com/blog/2016/01/25/angular-2-series-part-4-component-router-in-深度/

编辑:角度小组正为此计划一个@Resolve装饰器。


兑现诺言是否ngOnInit具有相同的效果?
agconti 2016年

1
我不这么认为,但是如果这样做的话就无济于事,因为该函数直到之后才被调用。来自:angular.io/docs/ts/latest/api/core/…,“在首次检查指令的数据绑定属性后立即调用ngOnInit”
Langley

有趣:如果您无法使用它初始化数据,那么他们在所有演示中都使用它似乎很愚蠢。
agconti

2
该文档说> *如果routerOnActivate返回一个诺言,则路由更改将一直等到该诺言稳定下来以实例化并激活子组件。
君特Zöchbauer

2
在双向绑定中不允许使用elvis运算符。
兰里(Langley)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.