这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战
1. 先泼个冷水:不建议直接操作DOM
在 Angular 中不建议直接操作DOM元素,正如 ElementRef 函数的源码中介绍的一样:
Permitting direct access to the DOM can make your application more vulnerable to XSS attacks. Carefully review any use of ElementRef in your code.
允许直接访问DOM会让您的应用程序更容易受到 XSS 攻击。
export declare class ElementRef {
/**
* 如果不支持原生元素直接访问底层的本地元素或`null`
* (e.g. 当应用程序在一个网络工作者运行).
*
* <div class="callout is-critical">
* <header>请谨慎使用</header>
* <p>
* 当需要直接访问DOM时,使用此API作为最后的手段。 使用模板和
* 数据绑定由Angular提供。 或者,您可以查看{@link Renderer}
* 它提供的API即使在直接访问本地元素时也可以安全地使用
** 支持的。
* </p>
* <p>
* 依靠直接的DOM访问可以在应用程序和呈现之间建立紧密的耦合
* 层将使它不可能分开两个和部署你的应用程序到一个
* 网络工作者。
* </p>
* </div>
* @stable
*/
nativeElement: any;
constructor(nativeElement: any);
}
2. Angular中使用 ElementRef 获取DOM
在浏览器中,通过 ElementRef 我们就可以封装不同平台下视图层中的 DOM 元素,最后借助于 Angular 提供的强大的依赖注入特性,我们就可以轻松地访问到 DOM 元素。
class ElementRef<T = any> {
constructor(nativeElement: T)
nativeElement: T
}
它是通过依赖注入特性注入到宿舍元素中,所以它一般会用来获取宿主元素的引用。若想获取宿主元素内其他的元素,可以使用@ViewChild或@ViewChildren。
举个例子
js 可以使用 querySelector 获取 DOM
this.doc.querySelector("body").getBoundingClientRect()
在 Angular 中我们可以使用 ElementRef 获取DOM。
HTML
<div class='deep'>蛮子</div>
ts
import { Component, ElementRef, ngAfterViewInit } from '@angular/core';
export class AppComponent {
constructor(private eleRef: ElementRef) { }
ngAfterViewInit(){
const deepEle = this.eleRef.nativeElement.querySelector('.deep');
console.log(deepEle); // 打印出 '<div class='deep'>蛮子</div>'
}
}
再获取其属性
const props = deepEle.getBoundingClientRect();
console.log(props);
可以打印出如下属性
{
bottom: 555.875
height: 274
left: 736
right: 1103
top: 281.875
width: 367
x: 736
y: 281.875
...
}
3. 通过装饰器 @ViewChild 获取 DOM
在 DOM 元素渲染之后,才可以使用它去获取 DOM。
它是一个属性装饰器,接收三个参数(待补充)
- selector - 用于查询的指令类型或名字。
- read - 从查询到的元素中读取另一个令牌。
- static - 如果为 true,则在变更检测运行之前解析查询结果,如果为 false,则在变更检测之后解析。默认为 false。
HTML
<div #deep>蛮子</div>
TS
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
export class AppComponent {
@ViewChild('deep') deepRef: ElementRef;
constructor() {}
ngAfterViewInit(){
const deepEle = this.eleRef.nativeElement;
console.log(deepEle); // 打印出 '<div class='deep'>蛮子</div>'
}
}
特别注意: 当通过ElementRef.nativeElement获取我们自定义的元素时,它返回的值是null,因为它访问的是原生元素,而我们自定义的元素不是原生元素
4. renderer2 对象提供的 API 优雅改变样式
// HTML
<div #deep>蛮子</div>
// TS
import { Component, ElementRef, ViewChild, ngAfterViewInit, Renderer2 } from '@angular/core';
export class AppComponent {
@ViewChild('deep') willChangeDiv: ElementRef;
constructor(private elementRef: ElementRef, private renderer: Renderer2) {}
ngAfterViewInit(){
this.renderer.setStyle(this.willChangeDiv.nativeElement, 'backgroundColor', 'red');
}
}
renderer2 提供了哪些方法?
export declare abstract class render2 {
/**
* This field can be used to store arbitrary data on this renderer instance.
* This is useful for renderers that delegate to other renderers.
*/
readonly abstract data: {
[key: string]: any;
};
abstract destroy(): void;
abstract createElement(name: string, namespace?: string | null): any;
abstract createComment(value: string): any;
abstract createText(value: string): any;
/**
* This property is allowed to be null / undefined,
* in which case the view engine won't call it.
* This is used as a performance optimization for production mode.
*/
destroyNode: ((node: any) => void) | null;
abstract appendChild(parent: any, newChild: any): void;
abstract insertBefore(parent: any, newChild: any, refChild: any): void;
abstract removeChild(parent: any, oldChild: any): void;
abstract selectRootElement(selectorOrNode: string | any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract parentNode(node: any): any;
/**
* Attention: On WebWorkers, this will always return a value,
* as we are asking for a result synchronously. I.e.
* the caller can't rely on checking whether this is null or not.
*/
abstract nextSibling(node: any): 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;
}
增加一个style
this.renderer2.setStyle(this.processBar.nativeElement, "width", this.skillMeta.percentage);
增加很多style,使用setAttribute
this.renderer2.setAttribute(this.content.nativeElement, "style","left: 0;top: 0" );
this.renderer.setAttribute(detailModal, "style",
`width: ${modalProps.width}px;
height: ${modalProps.height}px;
top: ${modalProps.offsetTop}px;
left: ${modalProps.left}px`
);
更多用法更新于 github