ElementRef的应用_AngularJS_脚本之家,的具体使用_AngularJS_脚本之家www.js8331.com

Angular 此中的二个企划指标是使浏览器与 DOM 独立。DOM
是复杂的,由此使组件与它分离,会让大家的应用程序,更便于测验与重构。别的的收益是,由于这种解耦,使得我们的接受能够运营在此外平台
(举例:Node.js、WebWorkers、NativeScript 等卡塔尔(قطر‎。

Angular 的口号是 – “一套框架,多样阳台。同有时候适用手提式有线电话机与桌面 (One
framework.Mobile & desktop.卡塔尔”,即 Angular
是支撑开拓跨平台的施用,比如:Web 应用、移动 Web
应用、原生移动使用和原生桌面应用等。

为了能够协理跨平台,Angular
通过架空层封装了分化平台的异样。举个例子定义了抽象类 Renderer、Renderer2
、抽象类 RootRenderer
等。别的还定义了以下引述类型:ElementRef、TemplateRef、ViewRef
、ComponentRef 和 ViewContainerRef 等。

为了能够帮衬跨平台,Angular 通过架空层封装了差别平台的异样,统一了 API
接口。如定义了抽象类 Renderer 、抽象类 RootRenderer
等。此外还定义了以下引述类型:ElementRef、TemplateRef、ViewRef
、ComponentRef 和 ViewContainerRef 等。上面我们就来剖析一下 ElementRef
类:

正文的重大内容是分析 Angular 中 Renderer
,可是在张开具体深入分析前,我们先来介绍一下平台的概念。

ElementRef的作用

平台

在应用层直接操作
DOM,就能够产生应用层与渲染层之间强耦合,致使我们的使用不恐怕运维在分歧蒙受,如
web worker 中,因为在 web worker 情形中,是不可能平昔操作
DOM。有意思味的读者,能够阅读一下 Web Workers 中援救的类和方式那篇小说。通过 ElementRef 大家就能够打包区别平台下视图层中的 native 元素(在浏览器碰着中,native 成分通常是指 DOM 成分卡塔尔,最终仰仗 Angular
提供的刚劲的信赖性注入天性,我们就足以轻易地访谈到 native 成分。

怎么样是平台

ElementRef的定义

阳台是应用程序运营的条件。它是一组服务,能够用来拜见你的应用程序和
Angular 框架自个儿的放手作用。由于Angular 首即使四个 UI
框架,平台提供的最入眼的职能之一正是页面渲染。

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

平台和指点应用程序

ElementRef的应用

在我们最早构建二个自定义渲染器早先,咱们来看一下哪些设置平台,以致指引应用程序。

大家先来介绍一下完整供给,我们想在页面成功渲染后,获取页面中的 div
成分,并更改该 div 成分的背景颜色。接下来我们来一步步,达成那几个需要。

import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';import {BrowserModule} from '@angular/platform-browser';@NgModule({ imports: [BrowserModule], bootstrap: [AppCmp]})class AppModule {}platformBrowserDynamic().bootstrapModule;

首先大家要先获得 div 成分,在文中 “ElementRef 的功效”
部分,大家早就涉嫌可以接收 Angular
提供的精锐的信任注入性子,获取封装后的 native 成分。在浏览器中 native
成分正是 DOM 成分,大家只要先得到 my-app成分,然后选用 querySelector API
就能够博取页面中 div 成分。具体代码如下:

ElementRef的应用_AngularJS_脚本之家,的具体使用_AngularJS_脚本之家www.js8331.com。如你所见,引导进程由两有些组成:创造平台和指导模块。在此个事例中,大家导入
BrowserModule
模块,它是浏览器平台的一局地。应用中一定要有一个激活的阳台,可是大家能够使用它来指导迷津多个模块,如下所示:

import { Component, ElementRef } from '@angular/core'; @Component({ selector: 'my-app', template: ` Welcome to Angular World Hello {{ name }} `,}) export class AppComponent { name: string = 'Semlinker'; constructor(private elementRef: ElementRef) { let divEle = this.elementRef.nativeElement.querySelector; console.dir; }}
const platformRef: PlatformRef = platformBrowserDynamic();platformRef.bootstrapModule;platformRef.bootstrapModule;

运营方面代码,在调整桃园没有现身非凡,但是出口的结果却是 null 。什么状态
? 未有抛出极度,我们能够测算 this.elementRef.nativeElement
那一个目的是存在,但却找不到它的子成分,那应该是在调用布局函数的时候,my-app
成分下的子元素还没创制。那怎么消除那几个主题材料呢 ?沉思中… ,不是有
setTimeout 么,大家在多少更改一下:

鉴于应用中只好有三个激活的阳台,单例的劳必须得在该平嘉义注册。比方,浏览器独有一个地址栏,对应的服务对象正是单例。其它如何让大家自定义的
UI 分界面,能够在浏览器中突显出来吧,这就须求利用 Angular
为大家提供的渲染器。

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

渲染器

主题素材消逝了,但感到不是很温婉 ?有未有越来越好的方案,答案是迟早的。Angular
不是有提供组件生命周期的钩子,大家得以筛选三个体面的机缘,然后拿走大家想要的
div 成分。

什么样是渲染器

 import { Component, ElementRef, AfterViewInit } from '@angular/core'; @Component({ selector: 'my-app', template: ` Welcome to Angular World Hello {{ name }} `,}) export class AppComponent { name: string = 'Semlinker'; // 在构造函数中 this.elementRef = elementRef 是可选的,编译时会自动赋值 // function AppComponent { this.elementRef = elementRef; } constructor(private elementRef: ElementRef) { } ngAfterViewInit() { // 模板中的元素已创建完成 console.dir(this.elementRef.nativeElement.querySelector; // let greetDiv: HTMLElement = this.elementRef.nativeElement.querySelector; // greetDiv.style.backgroundColor = 'red'; }}

渲染器是 Angular 为大家提供的一种内置服务,用于推行 UI
渲染操作。在浏览器中,渲染是将模型映射到视图的进程。模型的值能够是
JavaScript
中的原始数据类型、对象、数组或其余的多寡对象。可是视图能够是页面中的段落、表单、开关等其它因素,那些页面成分内部使用
DOM (Document Object Model卡塔尔 来代表。

运营一下下面的代码,我们看出了意料中的 div 成分。大家直接接纳ngAfterViewInit
这一个钩子,不要问笔者何以,因为它看得最顺眼咯。可是大家后边也可能有特地的稿子,详细解析一下
Angular 组件的生命周期。成功取到 div
成分,就剩下的事体就好办了,直接通过 style 对象设置成分的背景颜色。

Angular Renderer

功用即使已经完结了,但还应该有优化的空间么?

www.js8331.com,RootRenderer

import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';@Component({ selector: 'my-app', template: ` Welcome to Angular World Hello {{ name }} `,}) export class AppComponent { name: string = 'Semlinker'; @ViewChild greetDiv: ElementRef; ngAfterViewInit() { this.greetDiv.nativeElement.style.backgroundColor = 'red'; }}
export abstract class RootRenderer { abstract renderComponent(componentType: RenderComponentType): Renderer;}

/** * @deprecated Use the `Renderer2` instead. */export abstract class Renderer { abstract createElement(parentElement: any, name: string, debugInfo?: RenderDebugInfo): any; abstract createText(parentElement: any, value: string, debugInfo?: RenderDebugInfo): any; 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; // ...}

export abstract class Renderer2 { abstract createElement(name: string, namespace?: string|null): any; abstract createComment: any; abstract createText: 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:  => boolean | void): () => void;}

是否感觉弹指间伟大上了,然则先等等,上边的代码是还是不是还会有更为的优化空间呢
?我们看出设置 div
成分的背景,大家是暗中认可使用的运转条件在是浏览器中。前面已经介绍了,大家要尽量减弱应用层与渲染层之间强耦合关系,进而让大家使用能够灵活地运维在差别条件。最终我们来看一下,最终优化后的代码:

需求小心的是在 Angular 4.x+ 版本,大家应用 Renderer2 替代
Renderer。通过观看 Renderer 相关的抽象类
,大家开采抽象类中定义了广大硕大而无当方法,用来成立成分、文本、设置属性、增添样式和装置事件监听等。

import { Component, ElementRef, ViewChild, AfterViewInit, Renderer } from '@angular/core'; @Component({ selector: 'my-app', template: ` Welcome to Angular World Hello {{ name }} `,}) export class AppComponent { name: string = 'Semlinker'; @ViewChild greetDiv: ElementRef; constructor(private elementRef: ElementRef, private renderer: Renderer) { } ngAfterViewInit() { // this.greetDiv.nativeElement.style.backgroundColor = 'red'; this.renderer.setElementStyle(this.greetDiv.nativeElement, 'backgroundColor', 'red'); }}

渲染器如何是好事

1.Renderer API 还应该有哪些常用的艺术 ?

在实例化一个零器件时,Angular 会调用 renderComponent()
方法并将其得到的渲染器与该零部件实例相关联。Angular
将会在渲染组件时通过渲染器实践相应相关的操作,举例,创制作而成分、设置属性、增多样式和订阅事件等。

export abstract class Renderer { // 创建元素 abstract createElement(parentElement: any, name: string, debugInfo?: RenderDebugInfo): any; // 创建文本元素 abstract createText(parentElement: any, value: string, debugInfo?: RenderDebugInfo): any; // 设置文本 abstract setText(renderNode: any, text: string): void; // 设置元素Property abstract setElementProperty(renderElement: any, propertyName: string, propertyValue: any): void; // 设置元素Attribute abstract setElementAttribute(renderElement: any, attributeName: string, attributeValue: string): void; // 设置元素的Class abstract setElementClass(renderElement: any, className: string, isAdd: boolean): void; // 设置元素的样式 abstract setElementStyle(renderElement: any, styleName: string, styleValue: string): void; }

使用 Renderer

内需潜心的是在 Angular 4.x+ 本子,大家应用 Renderer2 代替 Renderer 。

@Component({ selector: 'exe-cmp', template: ` Exe Component `})export class ExeComponent { constructor(private renderer: Renderer2, elRef: ElementRef) { this.renderer.setProperty(elRef.nativeElement, 'author', 'semlinker'); }}

2.Renderer2 API 还会有哪些常用的办法 ?

上述代码中,大家选取构造注入的措施,注入 Renderer2 和 ElementRef
实例。有个别读者或然会问,注入的实例对象是怎么变卦的。这里大家只是稍稍介绍一下连锁知识,并不会详细张开。具体代码如下:

export abstract class Renderer2 { abstract createElement(name: string, namespace?: string|null): any; abstract createComment: any; abstract createText: 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:  => boolean | void): () => void;}
// packages/core/src/view/util.tsconst _tokenKeyCache = new Map();export function tokenKey: string { let key = _tokenKeyCache.get { key = stringify + '_' + _tokenKeyCache.size; _tokenKeyCache.set; } return key;}// packages/core/src/view/provider.tsconst RendererV1TokenKey = tokenKey;const Renderer2TokenKey = tokenKey;const ElementRefTokenKey = tokenKey;const ViewContainerRefTokenKey = tokenKey;const TemplateRefTokenKey = tokenKey;const ChangeDetectorRefTokenKey = tokenKey;const InjectorRefTokenKey = tokenKey;

上述正是本文的全体内容,希望对我们的上学抱有助于,也盼望大家多多指教脚本之家。

resolveDep()

export function resolveDep( view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef, notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any { const tokenKey = depDef.tokenKey; // ... while  { switch  { case RendererV1TokenKey: { // tokenKey const compView = findCompView(view, elDef, allowPrivateServices); return createRendererV1; } case Renderer2TokenKey: { // tokenKey const compView = findCompView(view, elDef, allowPrivateServices); return compView.renderer; } case ElementRefTokenKey: // tokenKey return new ElementRef(asElementData.renderElement); // ... 此外还包括:ViewContainerRefTokenKey、TemplateRefTokenKey、 // ChangeDetectorRefTokenKey 等 } } } // ...}

经过上述代码,我们开掘当我们在组件类的构造函数中扬言相应的依附对象时,如
Renderer2 和 ElementRef,Angular 内部会调用 resolveDep() 方法,实例化
Token 对应依据对象。

在超越50%场馆下,大家付出的 Angular
应用程序是运营在浏览器平台,接下去我们来领悟一下该平台下的暗许渲染器 –
DefaultDomRenderer2。

DefaultDomRenderer2

在浏览器平台下,大家能够通过调用 DomRendererFactory2
工厂,遵照差别的视图封装方案,创制对应渲染器。

DomRendererFactory2

// packages/platform-browser/src/dom/dom_renderer.ts@Injectable()export class DomRendererFactory2 implements RendererFactory2 { private rendererByCompId = new Map(); private defaultRenderer: Renderer2; constructor( private eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost) { // 创建默认的DOM渲染器 this.defaultRenderer = new DefaultDomRenderer2; }; createRenderer(element: any, type: RendererType2|null): Renderer2 { if  { return this.defaultRenderer; } // 根据不同的视图封装方案,创建不同的渲染器 switch  { // 无 Shadow DOM,但是通过 Angular 提供的样式包装机制来封装组件, // 使得组件的样式不受外部影响,这是 Angular 的默认设置。 case ViewEncapsulation.Emulated: { let renderer = this.rendererByCompId.get; if  { renderer = new EmulatedEncapsulationDomRenderer2(this.eventManager, this.sharedStylesHost, type); this.rendererByCompId.set; } (renderer).applyToHost; return renderer; } // 使用原生的 Shadow DOM 特性 case ViewEncapsulation.Native: return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type); // 无 Shadow DOM,并且也无样式包装 default: { // ... return this.defaultRenderer; } } }}

地点代码中的 EmulatedEncapsulationDomRenderer2ShadowDomRenderer
类都持续于 DefaultDomRenderer2 类,接下去大家再来看一下
DefaultDomRenderer2 类的里边得以达成:

class DefaultDomRenderer2 implements Renderer2 { constructor(private eventManager: EventManager) {} // 省略 Renderer2 抽象类中定义的其它方法 createElement(name: string, namespace?: string): any { if  { return document.createElementNS(NAMESPACE_URIS[namespace], name); } return document.createElement; } createComment: any { return document.createComment; } createText: any { return document.createTextNode; } addClass(el: any, name: string): void { el.classList.add; } setStyle(el: any, style: string, value: any, flags: RendererStyleFlags2): void { if (flags & RendererStyleFlags2.DashCase) { el.style.setProperty( style, value, !!(flags & RendererStyleFlags2.Important) ? 'important' : ''); } else { el.style[style] = value; } } listen( target: 'window'|'document'|'body'|any, event: string, callback:  => boolean): () => void { checkNoSyntheticProp; if (typeof target === 'string') { return  void>this.eventManager.addGlobalEventListener( target, event, decoratePreventDefault; } return  void>this.eventManager.addEventListener( target, event, decoratePreventDefault => void; }}

介绍完 DomRendererFactory2DefaultDomRenderer2
类,最终大家来看一下 Angular 内部如何利用它们。

DomRendererFactory2 内部使用

BrowserModule

// packages/platform-browser/src/browser.ts@NgModule({ providers: [ // 配置 DomRendererFactory2 和 RendererFactory2 provider DomRendererFactory2, {provide: RendererFactory2, useExisting: DomRendererFactory2}, // ... ], exports: [CommonModule, ApplicationModule]})export class BrowserModule { constructor @SkipSelf() parentModule: BrowserModule) { // 用于判断应用中是否已经导入BrowserModule模块 if  { throw new Error( `BrowserModule has already been loaded. If you need access to common directives such as NgIf and NgFor from a lazy loaded module, import CommonModule instead.`); } }}

createComponentView()

// packages/core/src/view/view.tsexport function createComponentView( parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData { const rendererType = nodeDef.element !.componentRendererType; // 步骤一 let compRenderer: Renderer2; if  { // 步骤二 compRenderer = parentView.root.renderer; } else { compRenderer = parentView.root.rendererFactory .createRenderer(hostElement, rendererType); } return createView( parentView.root, compRenderer, parentView, nodeDef.element !.componentProvider, viewDef);}

步骤一

当 Angular 在创设组件视图时,会基于 nodeDef.element 对象的
componentRendererType 属性值,来创建组件的渲染器。接下来大家先来看一下
NodeDefElementDefRendererType2 接口定义:

// packages/core/src/view/types.ts// 视图中节点的定义export interface NodeDef { bindingIndex: number; bindings: BindingDef[]; bindingFlags: BindingFlags; outputs: OutputDef[]; element: ElementDef|null; // nodeDef.element provider: ProviderDef|null; // ...}// 元素的定义export interface ElementDef { name: string|null; attrs: [string, string, string][]|null; template: ViewDefinition|null; componentProvider: NodeDef|null; // 设置组件渲染器的类型 componentRendererType: RendererType2|null; // nodeDef.element.componentRendererType componentView: ViewDefinitionFactory|null; handleEvent: ElementHandleEventFn|null; // ...}// packages/core/src/render/api.ts// RendererType2 接口定义export interface RendererType2 { id: string; encapsulation: ViewEncapsulation; // Emulated、Native、None styles: []; data: {[kind: string]: any};}

步骤二

获取 componentRendererType 的属性值后,假若该值为 null
的话,则直接利用 parentView.root 属性值对应的 renderer
对象。若该值不为空,则调用 parentView.root 对象的 rendererFactory()
方法创制 renderer 对象。

经过地点分析,我们发掘无论是走哪条分支,大家都急需动用 parentView.root
对象,不过该目的是哪些非凡对象?大家发掘 parentView 的数据类型是
ViewData ,该多少接口定义如下:

// packages/core/src/view/types.tsexport interface ViewData { def: ViewDefinition; root: RootData; renderer: Renderer2; nodes: {[key: number]: NodeData}; state: ViewState; oldValues: any[]; disposables: DisposableFn[]|null; // ...}

通过 ViewData 的接口定义,我们究竟意识了 parentView.root
的属性类型,即 RootData

// packages/core/src/view/types.tsexport interface RootData { injector: Injector; ngModule: NgModuleRef; projectableNodes: any[][]; selectorOrNode: any; renderer: Renderer2; rendererFactory: RendererFactory2; errorHandler: ErrorHandler; sanitizer: Sanitizer;}

如何时候创设 RootData 对象? 怎么开创 RootData 对象?

如何时候创制 RootData 对象?

当创设根视图的时候会创立 RootData,在付出景况会调用
debugCreateRootView() 方法创设 RootView,而在生养遭遇会调用
createProdRootView() 方法创立 RootView。轻易起见,大家只剖判
createProdRootView() 方法:

function createProdRootView( elInjector: Injector, projectableNodes: any[][], rootSelectorOrNode: string | any, def: ViewDefinition, ngModule: NgModuleRef, context?: any): ViewData { /** RendererFactory2 Provider 配置 * DomRendererFactory2, * {provide: RendererFactory2, useExisting: DomRendererFactory2}, */ const rendererFactory: RendererFactory2 = ngModule.injector.get; return createRootView( createRootData(elInjector, ngModule, rendererFactory, projectableNodes, rootSelectorOrNode), def, context);}// 创建根视图export function createRootView(root: RootData, def: ViewDefinition, context?: any): ViewData { // 创建ViewData对象 const view = createView(root, root.renderer, null, null, def); initView(view, context, context); createViewNodes; return view;}

地点代码中,当创设 RootView 的时候,会调用 createRootData() 方法成立
RootData 对象。最后一步便是深入分析 createRootData() 方法。

怎么开创 RootData 对象?

透过上边分析,我们知晓通过 createRootData() 方法,来创建 RootData
对象。createRootData() 方法具体落到实处如下:

function createRootData( elInjector: Injector, ngModule: NgModuleRef, rendererFactory: RendererFactory2, projectableNodes: any[][], rootSelectorOrNode: any): RootData { const sanitizer = ngModule.injector.get; const errorHandler = ngModule.injector.get; // 创建RootRenderer const renderer = rendererFactory.createRenderer; return { ngModule, injector: elInjector, projectableNodes, selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer, errorHandler };}

这个时候浏览器平台下, Renderer
渲染器的连带基本功知识已介绍达成。接下来,我们做一个轻易易行总结:

Angular 应用程序运转时会创设 RootView (生产条件下通过调用
createProdRootView 创造 RootView 的长河中,会创立 RootData
对象,该指标足以经过 ViewData 的 root 属性访谈到。基于 RootData
对象,我们能够透过 renderer 访谈到默许的渲染器,即 DefaultDomRenderer2
实例,别的也足以由此 rendererFactory 访问到 RendererFactory2 实例。
在开立组件视图 时,会基于 componentRendererType
的属性值,来设置组件关联的 renderer 渲染器。
当渲染组件视图的时候,Angular 会利用该器件关联的 renderer 提供的
API,创造该视图中的节点或举办视图的连锁操作,比方创制成分 、创制文本
、设置样式 和 设置事件监听 等。

背后要是不经常间的话,大家会介绍怎么着自定义渲染器,风乐趣的读者,能够先查阅
“参照他事他说加以考察能源” 中的链接。

上述正是本文的全体内容,希望对大家的读书抱有助于,也盼望我们多都赐教脚本之家。

发表评论

电子邮件地址不会被公开。 必填项已用*标注