Angular ElementRef简介及其使用


Posted in Javascript onOctober 01, 2018

Angular 的口号是 - “一套框架,多种平台。同时适用手机与桌面 (One framework.Mobile & desktop.)”,即 Angular 是支持开发跨平台的应用,比如:Web 应用、移动 Web 应用、原生移动应用和原生桌面应用等。

为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异,统一了 API 接口。如定义了抽象类 Renderer 、抽象类 RootRenderer 等。此外还定义了以下引用类型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。

下面我们就来分析一下 ElementRef 类:

ElementRef 的作用

在应用层直接操作 DOM,就会造成应用层与渲染层之间强耦合,导致我们的应用无法运行在不同环境,如 web worker 中,因为在 web worker 环境中,是不能直接操作 DOM。有兴趣的读者,可以阅读一下 [Web Workers 中支持的类和方法][1] 这篇文章。通过 ElementRef 我们就可以封装不同平台下视图层中的 native 元素 (在浏览器环境中,native 元素通常是指 DOM 元素),最后借助于 Angular 提供的强大的依赖注入特性,我们就可以轻松地访问到 native 元素。

ElementRef 的定义

// angular-master/packages/core/src/linker/element_ref.ts
export class ElementRef<T = any> {
 public nativeElement: T;
 constructor(nativeElement: T) { this.nativeElement = nativeElement; }
}

ElementRef 的应用

我们先来介绍一下整体需求,我们想在页面成功渲染后,获取页面中的 div 元素,并改变该 div 元素的背景颜色。接下来我们来一步步,实现这个需求。

首先我们要先获取 div 元素,在文中 “ElementRef 的作用” 部分,我们已经提到可以利用 Angular 提供的强大的依赖注入特性,获取封装后的 native 元素。在浏览器中 native 元素就是 DOM 元素,我们只要先获取 my-app元素,然后利用 querySelector API 就能获取页面中 div 元素。具体代码如下:

import { Component, ElementRef } from '@angular/core';

@Component({
 selector: 'my-app',
 template: `
  <h1>Welcome to Angular World</h1>
  <div>Hello {{ name }}</div>
 `,
})
export class AppComponent {
 name: string = 'Semlinker';

 constructor(private elementRef: ElementRef) {
  let divEle = this.elementRef.nativeElement.querySelector('div');
  console.dir(divEle);
 }
}

运行上面代码,在控制台中没有出现异常,但是输出的结果却是 null 。什么情况 ? 没有抛出异常,我们可以推断 this.elementRef.nativeElement 这个对象是存在,但却找不到它的子元素,那应该是在调用构造函数的时候,my-app 元素下的子元素还未创建。

那怎么解决这个问题呢 ?沉思中… ,不是有 setTimeout 么,我们在稍微改造一下:

constructor(private elementRef: ElementRef) {
 setTimeout(() => { // 此处需要使用箭头函数哈,你懂的...
   let divEle = this.elementRef.nativeElement.querySelector('div');
   console.dir(divEle);
  }, 0);
}

更新一下代码,此时控制台成功输出了 div 。为什么添加个 setTimeout 就能成功获取到想要的 div 元素呢?此处就不展开了,有兴趣的读者可以参考 - [What the heck is the event loop anyway?][2] 这个演讲的示例。

问题解决了,但感觉不是很优雅 ?有没有更好的方案,答案是肯定的。Angular 不是有提供组件生命周期的钩子,我们可以选择一个合适的时机,然后获取我们想要的 div 元素。

import { Component, ElementRef, AfterViewInit } from '@angular/core';

@Component({
 selector: 'my-app',
 template: `
  <h1>Welcome to Angular World</h1>
  <div>Hello {{ name }}</div>
 `,
})
export class AppComponent {

 name: string = 'Semlinker';

 // 在构造函数中 this.elementRef = elementRef 是可选的,编译时会自动赋值
 // function AppComponent(elementRef) { this.elementRef = elementRef; }
 constructor(private elementRef: ElementRef) { } 

 ngAfterViewInit() { // 模板中的元素已创建完成
  console.dir(this.elementRef.nativeElement.querySelector('div'));
  // let greetDiv: HTMLElement = this.elementRef.nativeElement.querySelector('div'); 
  // greetDiv.style.backgroundColor = 'red';
 }
}

运行一下上面的代码,我们看到了意料中的 div 元素。我们直接选用 ngAfterViewInit 这个钩子,不要问我为什么,因为它看得最顺眼咯。不过我们后面也会有专门的文章,详细分析一下 Angular 组件的生命周期。成功取到 div 元素,就剩下的事情就好办了,直接通过 style 对象设置元素的背景颜色。

功能虽然已经实现了,但还有优化的空间么?当然有咯!其实在 Angular 框架内部已经为我们提供了解决方案,它为我们提供了内置的装饰器,如 @ContentChild、 @ContentChildren、@ViewChild、@ViewChildren 等。

具体使用示例如下:

import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';

@Component({
 selector: 'my-app',
 template: `
  <h1>Welcome to Angular World</h1>
  <div #greet>Hello {{ name }}</div>
 `,
})
export class AppComponent {
 name: string = 'Semlinker';

 @ViewChild('greet')
 greetDiv: ElementRef;

 ngAfterViewInit() {
  this.greetDiv.nativeElement.style.backgroundColor = 'red';
 }
}

是不是感觉瞬间高大上了,不过先等等,上面的代码是不是还有进一步的优化空间呢 ?我们看到设置 div 元素的背景,我们是默认应用的运行环境在是浏览器中。前面已经介绍了,我们要尽量减少应用层与渲染层之间强耦合关系,从而让我们应用能够灵活地运行在不同环境。

最后我们来看一下,最终优化后的代码:

import { Component, ElementRef, ViewChild, AfterViewInit, Renderer2 } from '@angular/core';

@Component({
 selector: 'my-app',
 template: `
  <h1>Welcome to Angular World</h1>
  <div #greet>Hello {{ name }}</div>
 `,
})
export class AppComponent {
 name: string = 'Semlinker';

 @ViewChild('greet')
 greetDiv: ElementRef;

 constructor(private elementRef: ElementRef, private renderer: Renderer2) { }

 ngAfterViewInit() {
  // this.greetDiv.nativeElement.style.backgroundColor = 'red';
  this.renderer.setStyle(this.greetDiv.nativeElement, 'backgroundColor', 'red');
 }
}

最后我们通过 Renderer2 实例提供的 API 优雅地设置了 div 元素的背景颜色。

我有话说

Renderer2 API 还有哪些常用的方法 ?

export abstract class Renderer2 {
 abstract createElement(name: string, namespace?: string|null): any;
 abstract createComment(value: string): any;
 abstract createText(value: string): any;
 abstract setAttribute(el: any, name: string, value: string,
  namespace?: string|null): void;
 abstract removeAttribute(el: any, name: string, namespace?: string|null): void;
 abstract addClass(el: any, name: string): void;
 abstract removeClass(el: any, name: string): void;
 abstract setStyle(el: any, style: string, value: any, 
  flags?: RendererStyleFlags2): void;
 abstract removeStyle(el: any, style: string, flags?: RendererStyleFlags2): void;
 abstract setProperty(el: any, name: string, value: any): void;
 abstract setValue(node: any, value: string): void;
 abstract listen(
   target: 'window'|'document'|'body'|any, eventName: string,
   callback: (event: any) => boolean | void): () => void;
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript级联下拉列表实例代码(自写)
May 10 Javascript
jQuery+CSS实现的网页二级下滑菜单效果
Aug 25 Javascript
js格式化时间的方法
Dec 18 Javascript
完美实现js焦点轮播效果(一)
Mar 07 Javascript
微信小程序中子页面向父页面传值实例详解
Mar 20 Javascript
Vuex之理解Store的用法
Apr 19 Javascript
浅谈vue自定义全局组件并通过全局方法 Vue.use() 使用该组件
Dec 07 Javascript
Vue组件内部实现一个双向数据绑定的实例代码
Apr 04 Javascript
vue router 组件的高级应用实例代码
Apr 08 Javascript
vue keep-alive 动态删除组件缓存的例子
Nov 04 Javascript
微信小程序自定义导航栏(模板化)
Nov 15 Javascript
vue+koa2搭建mock数据环境的详细教程
May 18 Javascript
element上传组件循环引用及简单时间倒计时的实现
Oct 01 #Javascript
使用iView Upload 组件实现手动上传图片的示例代码
Oct 01 #Javascript
vue项目持久化存储数据的实现代码
Oct 01 #Javascript
详解mpvue scroll-view自动回弹bug解决方案
Oct 01 #Javascript
在create-react-app中使用sass的方法示例
Oct 01 #Javascript
详解mpvue小程序中怎么引入iconfont字体图标
Oct 01 #Javascript
Vue 组件封装 并使用 NPM 发布的教程
Sep 30 #Javascript
You might like
php模仿asp Application对象在线人数统计实现方法
2015/01/04 PHP
php 参数过滤、数据过滤详解
2015/10/26 PHP
php使用pear_smtp发送邮件
2016/04/15 PHP
PHP7新特性foreach 修改示例介绍
2016/08/26 PHP
php array_values 返回数组的所有值详解及实例
2016/11/12 PHP
使用php完成常见的文件上传功能(推荐)
2017/01/13 PHP
thinkPHP框架乐观锁和悲观锁实例分析
2019/10/30 PHP
JQery 渐变图片导航效果代码 漂亮
2010/01/01 Javascript
基于jsTree的无限级树JSON数据的转换代码
2010/07/27 Javascript
JS实现带缓冲效果打开、关闭、移动一个层的方法
2015/05/09 Javascript
javascript检查某个元素在数组中的索引值
2016/03/30 Javascript
Javascript 判断两个IP是否在同一网段实例代码
2016/11/28 Javascript
vue2.0实战之基础入门(1)
2017/03/27 Javascript
Node.js之网络通讯模块实现浅析
2017/04/01 Javascript
jQuery 获取除某指定对象外的其他对象 ( :not() 与.not())
2018/10/10 jQuery
vue拖拽组件使用方法详解
2018/12/01 Javascript
js实现搜索提示框效果
2020/09/05 Javascript
JavaScript 绘制饼图的示例
2021/02/19 Javascript
python利用不到一百行代码实现一个小siri
2017/03/02 Python
Python实现iOS自动化打包详解步骤
2018/10/03 Python
Python版中国省市经纬度
2020/02/11 Python
python3 使用ssh隧道连接mysql的操作
2020/12/05 Python
使用Python爬虫爬取小红书完完整整的全过程
2021/01/19 Python
HTML5 Canvas实现文本对齐的方法总结
2016/03/24 HTML / CSS
瑞典香水、须后水和美容产品购物网站:Parfym-Klick.se
2019/12/29 全球购物
娱乐地球:Entertainment Earth
2020/01/08 全球购物
美国最大最全的亚洲购物网站:美国亚米网(Yamibuy)
2020/05/05 全球购物
报纸媒体创意广告词
2014/03/17 职场文书
项目经理任命书内容
2014/06/06 职场文书
安全生产年活动总结
2014/08/29 职场文书
中职毕业生自我鉴定范文(3篇)
2014/09/28 职场文书
县委班子四风对照检查材料思想汇报
2014/09/29 职场文书
交警正风肃纪剖析材料
2014/10/29 职场文书
医生辞职信范文
2015/03/02 职场文书
用python基于appium模块开发一个自动收取能量的小助手
2021/09/25 Python
收音机爱好者玩机13年,简评其使用过的19台收音机
2022/04/30 无线电