原文地址:https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.htmlhtml
做者:Christoph Burgdorf安全
译者注:文章内容比较老,控制台信息等与新框架不彻底一致,理解思路便可。app
先作一个小声明,咱们如今拥有一个AppComponent
,并使用DI系统向其中注入了一个NameService
,由于咱们使用的是Typescript
,因此须要作的工做就是在构造函数的参数中声明变量nameService
的类型为NameService
,这样作的目的是为了向Angular提供运行时解析依赖所须要的相关信息。框架
app.ts函数
import { Component } from '@angular/core'; import { NameService } from './name.service'; @Component({ selector: 'my-app', template: '<h1>Favourite framework: {{ name }}</h1>' }) class AppComponent { name: string; constructor(nameService: NameService) { this.name = nameService.getName(); } }
nameService.tsthis
export class NameService { getName () { return "Angular"; } }
上述代码是能够正常工做的,若是咱们将nameService.ts中的代码直接嵌入app.ts时,会产生哪些变化呢?别着急反对,先听听我但愿声明的问题点。调试
import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '<h1>Favourite framework: {{ name }}</h1>' }) class AppComponent { name: string; constructor(nameService: NameService) { this.name = nameService.getName(); } } class NameService { getName () { return "Angular"; } }
当咱们试图运行上面的代码时,它并未可以正常工做。可是在控制台上却没法获得报错信息,我猜测是由于调试Typescript代码时使用了source map。不管如何,当咱们在调试器中打开Pause on caught exceptions功能时,就会在Angular框架中捕获这个错误:code
Cannot resolve all parameters for AppComponent(undefined). Make sure they all have valid type or annotationshtm
错误信息显示,AppComponent
的构造函数在被调用时,同一个文件中声明的NameService
类型的变量是undefined。这个错误提示是合理的,由于咱们在定义NameService
以前就在AppComponent
的构造函数中使用了它,可是另外一方面来看,在普通的ES5代码中就不会出现报错,由于函数声明会被Js解释器提高至做用域头部,不是说ES6仅仅是ES5的语法糖么?blog
那若是咱们将NameService
的定义代码进行提早,会出现什么状况呢:
import { Component } from '@angular/core'; class NameService { getName () { return "Angular"; } } @Component({ selector: 'my-app', template: '<h1>Favourite framework: {{ name }}</h1>' }) class AppComponent { name: string; constructor(nameService: NameService) { this.name = nameService.getName(); } }
此时它彷佛能够正常工做了。那么问题来了:
Javascript解释器进行这样的改动意义何在呢?
先来理解一下Javascript语言的机制,Javascript解释器不进行类的提高,是由于变量提高会致使在使用extend
关键字实现继承时会致使错误,例如当被继承者是一个合法的函数表达式时。来看这样一段ES6代码:
class Dog extends Animal { } function Animal() { this.move = function () { alert(defaultMove); } } var defaultMove = "moving"; var dog = new Dog(); dog.move();
上述代码是可以正常工做的,由于Javascript解释器对其进行了提高重组,实际至关于以下代码:
var defaultMove, dog; function Animal() { this.move = function () { alert(defaultMove); } } class Dog extends Animal { } defaultMove = "moving"; dog = new Dog(); dog.move();
然而,若是将Animal
从一个函数声明改变成一个函数表达式时,它是不会被提高的。
//将函数声明改变为函数表达式 class Dog extends Animal { } var Animal = function Animal() { this.move = function () { alert(defaultMove); } } var defaultMove = "moving"; var dog = new Dog(); dog.move();
提高后的真实执行顺序以下,函数表达式并无被提高:
var Animal, defaultMove, dog; class Dog extends Animal { } Animal = function Animal() { this.move = function () { alert(defaultMove); } } defaultMove = "moving"; dog = new Dog(); dog.move();
若是函数表达式也被提高,那么当Dog
类继承Animal
类时就会报错,由于它尚未被声明。从上面的示例中不难看出,若是Javascript解释器对class声明也进行提高处理,就容易在类继承时出现基类未定义的错误。
咱们理解了class为何不适合被提高执行顺序,这对于以前的Angular的示例来讲有什么指导意义呢?咱们只能经过将NameService
移动到代码顶部的方式来解除以前的报错吗?
答案是咱们可使用另外一种解决方案。咱们使用@Inject
注解和forwardRef
函数来替代以前方式,也就是声明一个NameService
类型的参数nameService
,以下所示:
import { Component, Inject, forwardRef } from '@angular/core'; @Component({ selector:'my-app', template:'<h1>Favourite framework:{{ name }}</h1>' }) class AppComponent{ name: string; constructor(@Inject(forwardRef(()=> NameService)) nameService){ this.name = nameService.getName(); } } class NameService{ getName(){ return "Angular" } }
forwardRef
所作的工做,就是接收一个函数做为参数,而后返回一个class,由于这个函数并非当即被调用的,而是在NameService
声明以后才会安全地返回NameService
,也就是说当()=>NameService
执行的时候,NameService
的值已经不是undefined了。
这个场景并不会常常出现,通常它只在当咱们想要注入在同一个文件中声明的类时才会发生,大多数状况下咱们在一个文件中只会声明一个类,而且会在文件的头部引入其余依赖的类,以此来保证不会被class不进行变量提高的特性形成困扰。
如下内容摘录自Angular中文网:
在Typescript里面,类声明的顺序很重要,若是一个类还没有定义,就不能引用它。
这一般都没有问题的,特别是遵循一个文件一个类规则的时候。但有时候循环引用可能没法避免,当类A引用类B,同时B又引用A时,就会陷入困境:它们中的某一个必须先定义。
forwardRef( )
创建一个间接引用,供Angular随后解析。