为什么需要ElementRef

Angular一直在做的一件事情就是降低视图层和应用层之间的耦合,在应用层直接操作DOM,会导致应用层和视图层之间强耦合,导致我们不能将应用运行在不同的环境中。比如令js能够实现多线程的webWorker,在webWorker中,却不能直接操作DOM,angular为我们封装了一个对象,叫做ElementRef,能够获取到视图层中的native对象,比如在浏览器中,native对象就是DOM对象。

以下是Angular中的ElementRef的具体实现:

export class ElementRef {public nativeElement: any;constructor(nativeElement: any) { this.nativeElement = nativeElement; }
}

如何使用ElementRef

下面来看一个简单的例子,我们声明一个组件,我们要实现的功能是,点击点击按钮,根据输入框的输入值来更改子组件的颜色。
那么整个组件大概会是这个样子。

首先我们编写app.ts,也就是图上的parent component。

app.ts

import { Child } from './child.component';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { Component, ViewChild, ElementRef } from '@angular/core';@Component({selector:'app',template:`<div><input #colorIndex type='text' /><button (click)='changeColor(colorIndex.value)'>change color</button><child></child></div>`,directives:[Child]
})
export class App{private defaultColor:string;private targetColor:string;constructor(private elementRef:ElementRef){//默认颜色初始化this.defaultColor = 'transparent';}//change colorchangeColor(value:string):void{console.log(value);this.targetColor = this.defaultColor || value;let Div = this.elementRef.nativeElement.querySelector('child div');Div.style.backgroundColor = value;}}bootstrap(App);

下面是子组件的代码,没有任何功能,只是简单的声明了一下样式。

child.component.ts

import { Component } from '@angular/core';@Component({selector:"child",template:`<div></div>`,styles:[`div{width:100px;height:100px;}`]
})
export class Child{constructor(){}
}

这样,我们就实现了上述的功能。

该在什么时候获取

这里出现了一个问题,关于我们是怎么获取到child的DOM对象的,我们通过angular的依赖注入引入了elementRef,再通过elementRef的nativeElement属性获取到了对应的native元素(在这里指代DOM对象)。这样理论上看上去是没有问题的(实际也没有问题:))

下面我们换个思路,我现在要一进去就给个初始的颜色,比如红色,我们稍微更改一下代码逻辑,把改变颜色的算法放在构造函数里执行

constructor(private elementRef:ElementRef){//默认颜色初始化this.defaultColor = 'transparent';let Div = this.elementRef.nativeElement.querySelector('child div');Div.style.backgroundColor = 'red';
}

我们发现抛出了一系列的异常(angular),下面我们打印一下div,

constructor(private elementRef:ElementRef){//默认颜色初始化this.defaultColor = 'transparent';let Div = this.elementRef.nativeElement.querySelector('child div');console.log(Div);// Div.style.backgroundColor = 'red';}

输出是:

null

这是因为app组件内部的视图子组件还没创建完毕,因此打印出来是null。

大家都知道angular的组件是有个生命周期的:

  1. onChanges
  2. onInit
  3. doCheck
  4. afterContentInit
  5. afterContentChecked
  6. afterViewInit
  7. afterViewChecked
  8. onDestroy

因此我们需要在父组件内部的视图子组件创建完毕后才能获取到它的DOM对象的引用,初始代码我们是把改变颜色的算法放在了点击事件的回调函数里,因为点击事件是一个异步的操作,它会因此进入事件循环,因此,当我们要获取到它的DOM对象引用的时候,组件以及完全创建完毕。

那么现在要实现我们上面的需求,我们要一进去就初始化为红色,我们可以在外面套一层setTimeout,因为setTimeout也是一个异步操作。

setTimeout(() => {let Div = this.elementRef.nativeElement.querySelector('child div');console.log(Div);Div.style.backgroundColor = 'red';},10);

我们可以看到这是可行的

尽管我们已经把延迟时间改的尽量的小(由于浏览器的特性,最小时间有个限度,低于这个限度,就会按最低限度来执行,我这里写的10,但是chrome 的延迟时间最低限度是16,因此,这里实际的延迟时间是16),但是setTimeout可能在这里是一件奢侈的事情,因为使用它的时候,在浏览器内部会创建一颗红黑树用来执行间断时间执行操作,对于这个demo来说,这个开销是非常大的。
因此我们可能会直接基于angular提供的生命周期钩子来执行执行改变颜色的操作。

app.ts

import { Child } from './child.component';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { Component, ViewChild, ElementRef } from '@angular/core';@Component({selector:'app',template:`<div><input #colorIndex type='text' /><button (click)='changeColor(colorIndex.value)'>change color</button><child></child></div>`,directives:[Child]
})
export class App{private defaultColor:string;private targetColor:string;constructor(private elementRef:ElementRef){//默认颜色初始化this.defaultColor = 'transparent';}ngAfterViewInit(){let Div = this.elementRef.nativeElement.querySelector('child div');console.log(Div);Div.style.backgroundColor = 'red';}//change colorchangeColor(value:string):void{console.log(value);this.targetColor = this.defaultColor || value;let Div = this.elementRef.nativeElement.querySelector('child div');Div.style.backgroundColor = value;}}bootstrap(App);

具体咱们看这一部分

ngAfterViewInit(){let Div = this.elementRef.nativeElement.querySelector('child div');console.log(Div);Div.style.backgroundColor = 'red';}

这里实现了Angular提供的AfterViewInit接口的抽象方法,但是我们并没有显示的实现AfterViewInit接口,直接实现其方法也可以被ts编译器所匹配,这也是ts区别于其他OOP语言的不同之处。

以上的逻辑是可行的。

但是接下来我们还可以进行进一步的优化,比如我们可以用Angular提供的ViewChild装饰器来获取Child DOM对象的引用。
app.ts

import { Child } from './child.component';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { Component, ViewChild, ElementRef,AfterViewInit} from '@angular/core';@Component({selector:'app',template:`<div><input #colorIndex type='text' /><button (click)='changeColor(colorIndex.value)'>change color</button><child></child></div>`,directives:[Child]
})
export class App{private defaultColor:string;private targetColor:string;constructor(){//默认颜色初始化this.defaultColor = 'transparent';}@ViewChild(Child)child:Child;ngAfterViewInit(){// let Div = this.child.querySelector('child div');// console.log(Div);this.child.nativeElement.style.backgroundColor = 'red';console.log(this.child.nativeElement);}//change colorchangeColor(value:string):void{console.log(value);this.targetColor = this.defaultColor || value;this.child.nativeElement.style.backgroundColor = value;}}bootstrap(App);

我们通过@ViewChild获取到了Child的引用,但是现在Child组件是实际没有nativeElement这个属性的。

console.log(this.child.nativeElement); //undefined

我们需要在Child里注入ElementRef,然后声明一个nativeElement成员变量。
child.component.ts

import { Component, ElementRef } from '@angular/core';@Component({selector:"child",template:`<div></div>`,styles:[`div{width:100px;height:100px;}`]
})
export class Child{nativeElement:any;constructor(private elementRef:ElementRef){}ngAfterViewInit(){this.nativeElement = this.elementRef.nativeElement.querySelector('div');}
}

但是我想我们现在对视图层和应用层的分离做的还不够彻底,没事,Angular为我们提供了一个叫做Renderer的对象,它作为视图层和应用层的粘合剂,能够使我们把视图层和应用层做到最大程度的分离。

改动后的代码如下

import { Child } from './child.component';
import { bootstrap } from '@angular/platform-browser-dynamic';
import { Component, ViewChild, ElementRef,AfterViewInit,Renderer} from '@angular/core';@Component({selector:'app',template:`<div><input #colorIndex type='text' /><button (click)='changeColor(colorIndex.value)'>change color</button><child></child></div>`,directives:[Child]
})
export class App{private defaultColor:string;private targetColor:string;constructor(private renderer:Renderer){//默认颜色初始化this.defaultColor = 'transparent';}@ViewChild(Child)child:Child;ngAfterViewInit(){// let Div = this.child.querySelector('child div');// console.log(Div);// this.child.nativeElement.style.backgroundColor = 'red';this.renderer.setElementStyle(this.child.nativeElement,"backgroundColor",'red');console.log(this.child.nativeElement);}//change colorchangeColor(value:string):void{console.log(value);this.targetColor = this.defaultColor || value;this.renderer.setElementStyle(this.child.nativeElement,"backgroundColor",value);}}bootstrap(App);

通过注入Renderer,我们可以轻松的操作DOM,并且对视图层和应用的分离做到最大限度。

为此Renderer提供了很多实用的方法

/*** @experimental*/
export declare abstract class Renderer {abstract selectRootElement(selectorOrNode: string | any, debugInfo?: RenderDebugInfo): any;abstract createElement(parentElement: any, name: string, debugInfo?: RenderDebugInfo): any;abstract createViewRoot(hostElement: any): any;abstract createTemplateAnchor(parentElement: any, debugInfo?: RenderDebugInfo): any;abstract createText(parentElement: any, value: string, debugInfo?: RenderDebugInfo): any;abstract projectNodes(parentElement: any, nodes: any[]): void;abstract attachViewAfter(node: any, viewRootNodes: any[]): void;abstract detachView(viewRootNodes: any[]): void;abstract destroyView(hostElement: any, viewAllNodes: any[]): void;abstract listen(renderElement: any, name: string, callback: Function): Function;abstract listenGlobal(target: string, name: string, callback: Function): Function;abstract setElementProperty(renderElement: any, propertyName: string, propertyValue: any): void;abstract setElementAttribute(renderElement: any, attributeName: string, attributeValue: string): void;/*** Used only in debug mode to serialize property changes to dom nodes as attributes.*/abstract setBindingDebugInfo(renderElement: any, propertyName: string, propertyValue: string): void;abstract setElementClass(renderElement: any, className: string, isAdd: boolean): any;abstract setElementStyle(renderElement: any, styleName: string, styleValue: string): any;abstract invokeElementMethod(renderElement: any, methodName: string, args?: any[]): any;abstract setText(renderNode: any, text: string): any;abstract animate(element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[], duration: number, delay: number, easing: string): AnimationPlayer;
}

(具体请参考Angular源码)

Angular通过ElementRef实现跨平台,用Renderer实现视图应用相分离,想必也是一种很赞的设计模式。

Angular2 ElementRef 实现低耦合高内聚 视图应用分离相关推荐

  1. c语言如何实现高内聚低耦合_如何实现高内聚低耦合?高内聚低耦合的现实例子...

    下面要给大家分享的是一个高内聚低耦合例子,那么编程应该如何实现高内聚低耦合呢?一起来看看下面的实例吧! 案例: 在一个学校里面,有着老师若干名,依次编号. 有学生若干名,依次编号. 现在的话,是要求要 ...

  2. 很强大!低耦合高内聚的MCU实用软件框架

    大家好,我是晓宇,不知道大家有没有听过软件设计中的低耦合,高内聚的两个原则. 具体是什么意思呢? 在一个项目中:每个模块之间相联系越紧密,则耦合性越高:这样你改动其中一个模块,其他模块也需要一起改动, ...

  3. Java中的低耦合高内聚法则

    java框架模式_低耦合高内聚法则 定义:一个对象应该对其他对象保持最少的了解. 问题由来:类与类之间的关系越来越密切,耦合度越来越大,当一个类发生改变时,对另外一个类的影响也越大. 解决方案:尽量降 ...

  4. 程序开发之——低耦合高内聚

    内聚概念 内聚性,又称块内联系.指模块的功能强度的度量,即一个模块内部各个元素彼此结合的紧密程度的度量. 内聚性是对一个模块内部各个组成元素之间相互结合的紧密程度的度量指标.模块中组成元素结合的越紧密 ...

  5. 详解高耦合低内聚,低耦合高内聚

    什么是高耦合低内聚,低耦合高内聚 耦合:不就是耦合系数高与低吗,就是关联性强不强 内聚:内聚是指是不是具有很强的功能性,一个模块或方法是不是只干一件事,越强的内聚或者高内聚模块应当恰好只做一件事. 用 ...

  6. 低耦合高内聚什么意思?

    低耦合高内聚 很多小伙伴不理解低耦合高内聚什么意思?我给大家通俗的讲一下 低耦合 解决冗余(rongyu)代码,提高代码重复使用率 冗余:很多相似的代码

  7. 简单理解高内聚低耦合-高内聚低耦合通俗理解是什么?

    低耦合: 耦合就是元素与元素之间的连接,感知和依赖量度.这里说的元素即是功能,对象,系统,子系统.模块. 例如:现在有方法A和方法B 我们在A元素去调用B元素,当B元素有问题或者不存在的时候,A元素就 ...

  8. 3分钟Tips:用大白话告诉你什么是低耦合|高内聚

    1.高内聚 首先我们来看看内聚的含义:软件含义上的内聚其实是从化学中的分子的内聚演变过来的,化学中的分子间的作用力,作用力强则表现为内聚程度高.在软件中内聚程度的高低,标识着软件设计的好坏. 我们在进 ...

  9. 耦合关系从强到弱顺序_低耦合 高内聚

    一 什么是低耦合 耦合度(Coupling)是对模块间关联程度的度量.耦合的强弱取决与模块间接口的复杂性.调用模块的方式以及通过界面传送数据的多少. 模块间的耦合度是指模块之间的依赖关系,包括控制关系 ...

最新文章

  1. [Java并发编程(一)] 线程池 FixedThreadPool vs CachedThreadPool ...
  2. BZOJ 2716: [Violet 3]天使玩偶 | CDQ分治
  3. 深入了解自动化测试方案
  4. python能做什么excel-python能做什么,python自学行吗?
  5. python报表自动化系列 - python中索引pandas.DataFrame的内容
  6. Mysql基本语句(个人笔记)
  7. 让自制脚本随系统开机运行
  8. html5调用系统声音1s响一次_为你的html5网页添加音效示例
  9. (转)鼎晖投资总裁焦震:别把投资高雅化,就是个做买卖的
  10. delphi 运算符
  11. springboot自定义过滤器的方法
  12. Found existing installation:xxxx
  13. 登录验证,如果输入错误次数超过3次,则锁定该账户
  14. 学习 《电路》(尼尔森著,第十版)第一章笔记(电流)
  15. nodename nor servname provided, or not known
  16. 灵魂三问:什么是接口测试,接口测试怎么玩,接口自动化测试怎么玩?
  17. springboot mybatis easyui 整合的一个小demo
  18. pkg-config到底是个啥
  19. 巫师3计算机,游戏测试:巫师3_联想笔记本电脑_笔记本评测-中关村在线
  20. Android实现 曲线路径动画

热门文章

  1. 异常被 ”吃“ 掉导致事务无法回滚
  2. input常见输入限制及金额转货币
  3. 51nod.1916 购物
  4. JAVA初/中/高级程序员必须知道的知识
  5. 苏州优步车主之家司机端下载
  6. 【大数据】Spark开源REST服务--Apache Livy的安装和使用
  7. 2019暑期个人排位集训补题--思维题
  8. PyGame弹珠游戏双人改良版
  9. 9面阿里Java岗,最终定级P6拿P7工资,分享学习经验
  10. Java语言对于大数据而言是什么样的存在?