【Angular专题】——(2)【译】Angular中的ForwardRef

原文地址:https://blog.thoughtram.io/angular/2015/09/03/forward-references-in-angular-2.html
译者注:文章内容比较老,控制台信息等与新框架不完全一致,理解思路即可。
一. 问题点在哪里
先做一个小声明,我们现在拥有一个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的更多相关文章
- [译] Angular 2 VS. React: 血色将至
Angular 2 VS. React: 血色将至 原文链接:https://medium.com/@housecor/angular-2-versus-react-there-will-be-blo ...
- angular.js和vue.js中实现函数去抖(debounce)
问题描述 搜索输入框中,只当用户停止输入后,才进行后续的操作,比如发起Http请求等. 学过电子电路的同学应该知道按键防抖.原理是一样的:就是说当调用动作n毫秒后,才会执行该动作,若在这n毫秒内又调用 ...
- Angular企业级开发(3)-Angular MVC实现
1.MVC介绍 Model-View-Controller 在20世纪80年代为程序语言Smalltalk发明的一种软件架构.MVC模式的目的是实现一种动态的程序设计,使后续对程序的修改和扩展简化,并 ...
- angular学习笔记02 angular指令大全
第一步 先要引入angular, 第二步 在 html 标签中<html ng-app> 加入ng-app(这是个必须的,不然会报错) 接下来就可以去使用angular的各种指令了. ...
- Angular学习笔记:Angular CLI
定义 Angular CLI:The Angular CLI is a command line interface tool that can create a project, add files ...
- angular enter事件,angular回车事件
angular回车键搜索,angular enter搜索 对于搜索框,用户在输入内容后的搜索习惯不是鼠标点击搜索按钮,而是直接按enter键,那我们怎么给enter键绑定事件呢,其实很简单,代码如下: ...
- Angular 6.X CLI(Angular.json) 属性详解
Angular CLI(Angular.json) 属性详解 简介 angular cli 是angular commond line interface的缩写,意为angular的命令行接口.在an ...
- Angular总结二:Angular 启动过程
要弄清楚 Angular 的启动过程,就要弄明白 Angular 启动时加载了哪个页面,加载了哪些脚本,这些脚本做了哪些事? 通过 Angular 的编译依赖文件 .angular-cli.json ...
- Angular系列一:Angular程序架构
Angular程序架构 Angular程序架构 组件:一段带有业务逻辑和数据的Html服务:用来封装可重用的业务逻辑指令:允许你向Html元素添加自定义行为模块: 环境搭建 安装nodeJs安装好no ...
- Angular 2 升级到 Angular 5
Angular 2 升级到 Angular 5 ts文件最上面的import语句里不要添加 .ts 后缀 , 不然 npm start 编译会失败 . 虽然浏览器能打开项目的URL , 但是内容会丢失 ...
随机推荐
- MySQL5.6启用sha256_password插件
一.背景: 使用MySQL5.6过程中,发现默认的加密插件为mysql_native_password.而sha256_password的安全程度要比mysql_native_password高,尝试 ...
- 让数字变化炫酷起来,数字滚动Text组件[Unity]
让数字滚动起来 上周我的策划又提了样需求,当玩家评分发生变动时,屏幕出现人物评分浮层UI,播放评分数字滚动动画.这类数字滚动需求非常常见,我就按一般思路,将startvalue与endvalue每隔一 ...
- js 原型链的介绍
对象的原型链:一个对象所拥有的属性不仅仅是它本身拥有的属性,他还会从其他对象中继承一些属性.当js在一个对象中找不到需要的属性时,它会到这个对象的父对象上去找,以此类催,这就构成了对象的原型链. 下面 ...
- oracle 安装提示未找到文件安装
安装oracle 过程中提示未找到文件 E:\app\xxj\product\11.2.0\dbhome_1\owb\external\oc4j_applications\applications\W ...
- Git 简单入门(二)
分支管理 分支的作用 提交不完整的代码到主分支上会导致别人不能正常开发 如果等代码全部写完再提交,存在丢失每天进度的风险 详见:https://segmentfault.com/q/101000001 ...
- javaweb聊天室源码免费
效果:可擴展源碼,免費技術指導,電話13956301647 链接:https://pan.baidu.com/s/1FWV8DNWacGaEpAQEsWsNaw 提取码:b6gf
- c#基础小练习
1.通过控制台接受输入的数字,将数字放到一维数组中,进行反转数据处理,然后将反转的数据打印到控制台应用程序里 方法一 步骤: 1.添加接收控制台输入的数据变量 2.将接收的字符串转换成一维数组 3.新 ...
- 解决idea中 mvn项目导了包找不到包的问题
----------------------------------------分割线--------------------------------------------------------- ...
- kali linux学习笔记(四) : 网络端口大全介绍
端口大全介绍 2端口:管理实用程序 3端口:压缩进程 5端口:远程作业登录 7端口:回显 9端口:丢弃 11端口:在线用户 13端口:时间 17端口:每日引用 18端口:消息发送协议 19端口:字符发 ...
- vue-cli的跨域设置
概述 今天打算快速使用vue-cli建立一个小应用用于测试,使用axios发送http请求,但是遇到了跨域问题,总结了一下,供以后开发时参考,相信对其他人也有用. vue-cli的跨域设置 在vue. ...