原文地址:https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html

作者:Christoph Burgdorf

译者注:文章内容比较老,控制台信息等与新框架不完全一致,理解思路即可。

一. 问题点在哪里

先做一个小声明,我们现在拥有一个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.ts

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框架中捕获这个错误:

Cannot resolve all parameters for AppComponent(undefined). Make sure they all have valid type or annotations

错误信息显示,AppComponent的构造函数在被调用时,同一个文件中声明的NameService类型的变量是undefined。这个错误提示是合理的,因为我们在定义NameService之前就在AppComponent的构造函数中使用了它,但是另一方面来看,在普通的ES5代码中就不会出现报错,因为函数声明会被Js解释器提升至作用域头部,不是说ES6仅仅是ES5的语法糖么?

那如果我们将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解释器进行这样的改动意义何在呢?

二. 不对Class定义进行提升的理由

先来理解一下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在使用前必须声明吗?

我们理解了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随后解析。

来源:华为云社区  作者:大史不说话

【Angular专题】——(2)【译】Angular中的ForwardRef相关推荐

  1. [译] ES6+ 中的 JavaScript 工厂函数(第八部分)

    本文讲的是[译] ES6+ 中的 JavaScript 工厂函数(第八部分), 原文地址:JavaScript Factory Functions with ES6+ 原文作者:Eric Elliot ...

  2. 破境Angular(三)Angular构件之模块

    一.知识点 Angular模块核心知识点如下: 1.模块的作用. 2.模块各个元数据的含义和作用 3.模块有哪些分类,分类原则 4.模块的惰性加载机制 5.开发时如何对模块进行规划 二.模块作用 首先 ...

  3. Angular企业级开发(3)-Angular MVC实现

    1.MVC介绍 Model-View-Controller 在20世纪80年代为程序语言Smalltalk发明的一种软件架构.MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并 ...

  4. swagger 源代码_我们如何使用swagger代码生成器从Angular 4更新到Angular 5

    swagger 源代码 by Mark Grichanik 马克·格里卡尼克(Mark Grichanik) 我们如何使用swagger代码生成器从Angular 4更新到Angular 5 (How ...

  5. angular技巧_提升Angular技能的5个技巧

    angular技巧 This summer me and Roman started a series of tweets with helpful tips and tricks about Ang ...

  6. angular 代码生成器_使用Angular 10构建QR代码生成器

    angular 代码生成器 In this tutorial, we'll learn how to build a QR Codes generator application using the ...

  7. angular html模板,使用Angular HTML 模板

    Angular HTML 模板定义页面在 Web 应用程序中的布局.我们对Angular HTML 模板的支持包括验证和代码智能补全. 设置舞台 Angular HTML 模板提供了一种结构化的绑定数 ...

  8. Angular 服务端渲染 Angular Universal 实例

    标准的 Angular 应用运行在浏览器中,它会在 DOM 中渲染页面,以响应用户的操作. 而Angular Universal 会在服务端运行,生成一些静态的应用页面,稍后再通过客户端进行启动. 这 ...

  9. [译] JavaScript 中的 CSS:基于组件的样式的未来

    本文讲的是[译] JavaScript 中的 CSS:基于组件的样式的未来, 原文地址:CSS in JavaScript: The future of component-based styling ...

  10. cockroachdb mysql_CockroachDB学习笔记——[译]CockroachDB中的SQL:映射表中数据到键值存储...

    CockroachDB学习笔记--[译]CockroachDB中的SQL:映射表中数据到键值存储 原文标题:SQL in CockroachDB: Mapping Table Data to Key- ...

最新文章

  1. 微信小程序开发实战(一)开发指南
  2. haproxy调度web案例
  3. 使用kubectl port-forward暴露minikube k8s service端口
  4. 226. Invert Binary Tree 1
  5. Android成长日记-Android监听事件的方法
  6. 【金融申请评分卡】数据准备 - 缺失值数据清洗
  7. Angularjs-项目搭建
  8. 使用C#调用Java带MIME附件WebService方法的初步设想
  9. 网络通信安全基础和OpenSSL
  10. 养殖环控程序 三菱plc可以带物联网模块,7寸触摸屏程序
  11. java 阴阳师个人脚本
  12. MyBatis源码的学习(9)---映射器mappers的的XML解析
  13. 【医学图像处理】X-ray 数字射线成像
  14. 基于云原生的大数据产品前端实践 | 第七期图文直播文字回放
  15. 机器学习11-聚类,孤立点判别
  16. 万字报告拆解:Web3 浪潮风靡,NFT 找到新增长点,AIGC 如火如荼
  17. 机器学习算法系列之K近邻算法
  18. 优质开源:共享图书小程序3.0 全新UI 免费下载
  19. xp运行linux软件下载,为你演示xp系统利用U盘直接运行Linux软件 的解决方案
  20. devexpress gridcontrol gridview小结

热门文章

  1. pcb板可挖孔吗_PCB板微孔加工方法之机械钻孔
  2. linux线程能删除自身吗,Linux内核本身和进程的区别 内核线程、用户进程、用户...
  3. 一文搞懂RSOP偏振态旋转
  4. python之路《七》文件的处理
  5. 防止a标签跳转的几种方法
  6. Hibernate--关系映射和关联关系的CRUD
  7. struct和union,enum分析
  8. 实战weblogic集群之创建节点和集群
  9. UNICODE与ANSI的区别
  10. java调用android打包_Android Gradle打包基础