Angular Renderer使用案例分享
这次给大家带来Angular Renderer使用案例分享,Angular Renderer使用的注意事项有哪些,下面就是实战案例,一起来看一下。
Angular 其中的一个设计目标是使浏览器与 DOM 独立。DOM 是复杂的,因此使组件与它分离,会让我们的应用程序,更容易测试与重构。另外的好处是,由于这种解耦,使得我们的应用能够运行在其它平台 (比如:Node.js、WebWorkers、NativeScript 等)。
为了能够支持跨平台,Angular 通过抽象层封装了不同平台的差异。比如定义了抽象类 Renderer、Renderer2 、抽象类 RootRenderer 等。此外还定义了以下引用类型:ElementRef、TemplateRef、ViewRef 、ComponentRef 和 ViewContainerRef 等。
本文的主要内容是分析 Angular 中 Renderer (渲染器),不过在进行具体分析前,我们先来介绍一下平台的概念。
平台
什么是平台
平台是应用程序运行的环境。它是一组服务,可以用来访问你的应用程序和 Angular 框架本身的内置功能。由于Angular 主要是一个 UI 框架,平台提供的最重要的功能之一就是页面渲染。
平台和引导应用程序
在我们开始构建一个自定义渲染器之前,我们来看一下如何设置平台,以及引导应用程序。
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {BrowserModule} from '@angular/platform-browser'; @NgModule({ imports: [BrowserModule], bootstrap: [AppCmp] }) class AppModule {} platformBrowserDynamic().bootstrapModule(AppModule);
如你所见,引导过程由两部分组成:创建平台和引导模块。在这个例子中,我们导入 BrowserModule 模块,它是浏览器平台的一部分。应用中只能有一个激活的平台,但是我们可以利用它来引导多个模块,如下所示:
const platformRef: PlatformRef = platformBrowserDynamic(); platformRef.bootstrapModule(AppModule1); platformRef.bootstrapModule(AppModule2);
由于应用中只能有一个激活的平台,单例的服务必须在该平台中注册。比如,浏览器只有一个地址栏,对应的服务对象就是单例。此外如何让我们自定义的 UI 界面,能够在浏览器中显示出来呢,这就需要使用 Angular 为我们提供的渲染器。
渲染器
什么是渲染器
渲染器是 Angular 为我们提供的一种内置服务,用于执行 UI 渲染操作。在浏览器中,渲染是将模型映射到视图的过程。模型的值可以是 JavaScript 中的原始数据类型、对象、数组或其它的数据对象。然而视图可以是页面中的段落、表单、按钮等其他元素,这些页面元素内部使用 DOM (Document Object Model) 来表示。
Angular Renderer
RootRenderer
export abstract class RootRenderer { abstract renderComponent(componentType: RenderComponentType): Renderer; }
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; // ... }
Renderer2
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; }
需要注意的是在 Angular 4.x+ 版本,我们使用 Renderer2
替代 Renderer
。通过观察 Renderer 相关的抽象类 (Renderer、Renderer2),我们发现抽象类中定义了很多抽象方法,用来创建元素、文本、设置属性、添加样式和设置事件监听等。
渲染器如何工作
在实例化一个组件时,Angular 会调用 renderComponent()
方法并将其获取的渲染器与该组件实例相关联。Angular 将会在渲染组件时通过渲染器执行对应相关的操作,比如,创建元素、设置属性、添加样式和订阅事件等。
使用 Renderer
@Component({ selector: 'exe-cmp', template: ` <h3>Exe Component</h3> ` }) export class ExeComponent { constructor(private renderer: Renderer2, elRef: ElementRef) { this.renderer.setProperty(elRef.nativeElement, 'author', 'semlinker'); } }
以上代码中,我们利用构造注入的方式,注入 Renderer2 和 ElementRef 实例。有些读者可能会问,注入的实例对象是怎么生成的。这里我们只是稍微介绍一下相关知识,并不会详细展开。具体代码如下:
TokenKey
// packages/core/src/view/util.ts const _tokenKeyCache = new Map<any, string>(); export function tokenKey(token: any): string { let key = _tokenKeyCache.get(token); if (!key) { key = stringify(token) + '_' + _tokenKeyCache.size; _tokenKeyCache.set(token, key); } return key; } // packages/core/src/view/provider.ts const RendererV1TokenKey = tokenKey(RendererV1); const Renderer2TokenKey = tokenKey(Renderer2); const ElementRefTokenKey = tokenKey(ElementRef); const ViewContainerRefTokenKey = tokenKey(ViewContainerRef); const TemplateRefTokenKey = tokenKey(TemplateRef); const ChangeDetectorRefTokenKey = tokenKey(ChangeDetectorRef); const InjectorRefTokenKey = tokenKey(Injector);
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 (view) { if (elDef) { switch (tokenKey) { case RendererV1TokenKey: { // tokenKey(RendererV1) const compView = findCompView(view, elDef, allowPrivateServices); return createRendererV1(compView); } case Renderer2TokenKey: { // tokenKey(Renderer2) const compView = findCompView(view, elDef, allowPrivateServices); return compView.renderer; } case ElementRefTokenKey: // tokenKey(ElementRef) return new ElementRef(asElementData(view, elDef.index).renderElement); // ... 此外还包括:ViewContainerRefTokenKey、TemplateRefTokenKey、 // ChangeDetectorRefTokenKey 等 } } } // ... }
通过以上代码,我们发现当我们在组件类的构造函数中声明相应的依赖对象时,如 Renderer2 和 ElementRef,Angular 内部会调用 resolveDep()
方法,实例化 Token 对应依赖对象。
在大多数情况下,我们开发的 Angular 应用程序是运行在浏览器平台,接下来我们来了解一下该平台下的默认渲染器 - DefaultDomRenderer2。
DefaultDomRenderer2
在浏览器平台下,我们可以通过调用 DomRendererFactory2
工厂,根据不同的视图封装方案,创建对应渲染器。
DomRendererFactory2
// packages/platform-browser/src/dom/dom_renderer.ts @Injectable() export class DomRendererFactory2 implements RendererFactory2 { private rendererByCompId = new Map<string, Renderer2>(); private defaultRenderer: Renderer2; constructor( private eventManager: EventManager, private sharedStylesHost: DomSharedStylesHost) { // 创建默认的DOM渲染器 this.defaultRenderer = new DefaultDomRenderer2(eventManager); }; createRenderer(element: any, type: RendererType2|null): Renderer2 { if (!element || !type) { return this.defaultRenderer; } // 根据不同的视图封装方案,创建不同的渲染器 switch (type.encapsulation) { // 无 Shadow DOM,但是通过 Angular 提供的样式包装机制来封装组件, // 使得组件的样式不受外部影响,这是 Angular 的默认设置。 case ViewEncapsulation.Emulated: { let renderer = this.rendererByCompId.get(type.id); if (!renderer) { renderer = new EmulatedEncapsulationDomRenderer2(this.eventManager, this.sharedStylesHost, type); this.rendererByCompId.set(type.id, renderer); } (<EmulatedEncapsulationDomRenderer2>renderer).applyToHost(element); return renderer; } // 使用原生的 Shadow DOM 特性 case ViewEncapsulation.Native: return new ShadowDomRenderer(this.eventManager, this.sharedStylesHost, element, type); // 无 Shadow DOM,并且也无样式包装 default: { // ... return this.defaultRenderer; } } } }
上面代码中的 EmulatedEncapsulationDomRenderer2
和 ShadowDomRenderer
类都继承于 DefaultDomRenderer2
类,接下来我们再来看一下 DefaultDomRenderer2 类的内部实现:
class DefaultDomRenderer2 implements Renderer2 { constructor(private eventManager: EventManager) {} // 省略 Renderer2 抽象类中定义的其它方法 createElement(name: string, namespace?: string): any { if (namespace) { return document.createElementNS(NAMESPACE_URIS[namespace], name); } return document.createElement(name); } createComment(value: string): any { return document.createComment(value); } createText(value: string): any { return document.createTextNode(value); } addClass(el: any, name: string): void { el.classList.add(name); } 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: (event: any) => boolean): () => void { checkNoSyntheticProp(event, 'listener'); if (typeof target === 'string') { return <() => void>this.eventManager.addGlobalEventListener( target, event, decoratePreventDefault(callback)); } return <() => void>this.eventManager.addEventListener( target, event, decoratePreventDefault(callback)) as() => void; } }
介绍完 DomRendererFactory2
和 DefaultDomRenderer2
类,最后我们来看一下 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(@Optional() @SkipSelf() parentModule: BrowserModule) { // 用于判断应用中是否已经导入BrowserModule模块 if (parentModule) { 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.ts export function createComponentView( parentView: ViewData, nodeDef: NodeDef, viewDef: ViewDefinition, hostElement: any): ViewData { const rendererType = nodeDef.element !.componentRendererType; // 步骤一 let compRenderer: Renderer2; if (!rendererType) { // 步骤二 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
属性值,来创建组件的渲染器。接下来我们先来看一下 NodeDef
、 ElementDef
和 RendererType2
接口定义:
// 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: (string|any[])[]; data: {[kind: string]: any}; }
步骤二
获取 componentRendererType
的属性值后,如果该值为 null
的话,则直接使用 parentView.root
属性值对应的 renderer
对象。若该值不为空,则调用 parentView.root
对象的 rendererFactory()
方法创建 renderer
对象。
通过上面分析,我们发现不管走哪条分支,我们都需要使用 parentView.root
对象,然而该对象是什么特殊对象?我们发现 parentView
的数据类型是 ViewData
,该数据接口定义如下:
// packages/core/src/view/types.ts export 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.ts export interface RootData { injector: Injector; ngModule: NgModuleRef<any>; 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<any>, context?: any): ViewData { /** RendererFactory2 Provider 配置 * DomRendererFactory2, * {provide: RendererFactory2, useExisting: DomRendererFactory2}, */ const rendererFactory: RendererFactory2 = ngModule.injector.get(RendererFactory2); 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(view); return view; }
上面代码中,当创建 RootView
的时候,会调用 createRootData()
方法创建 RootData
对象。最后一步就是分析 createRootData()
方法。
怎么创建 RootData
对象?
通过上面分析,我们知道通过 createRootData()
方法,来创建 RootData
对象。createRootData()
方法具体实现如下:
function createRootData( elInjector: Injector, ngModule: NgModuleRef<any>, rendererFactory: RendererFactory2, projectableNodes: any[][], rootSelectorOrNode: any): RootData { const sanitizer = ngModule.injector.get(Sanitizer); const errorHandler = ngModule.injector.get(ErrorHandler); // 创建RootRenderer const renderer = rendererFactory.createRenderer(null, null); return { ngModule, injector: elInjector, projectableNodes, selectorOrNode: rootSelectorOrNode, sanitizer, rendererFactory, renderer, errorHandler }; }
此时浏览器平台下, Renderer
渲染器的相关基础知识已介绍完毕。接下来,我们做一个简单总结:
Angular 应用程序启动时会创建 RootView (生产环境下通过调用 createProdRootView() 方法)
创建 RootView 的过程中,会创建 RootData 对象,该对象可以通过 ViewData 的 root 属性访问到。基于 RootData 对象,我们可以通过
renderer
访问到默认的渲染器,即 DefaultDomRenderer2 实例,此外也可以通过rendererFactory
访问到RendererFactory2
实例。在创建组件视图 (ViewData) 时,会根据
componentRendererType
的属性值,来设置组件关联的renderer
渲染器。当渲染组件视图的时候,Angular 会利用该组件关联的
renderer
提供的 API,创建该视图中的节点或执行视图的相关操作,比如创建元素 (createElement)、创建文本 (createText)、设置样式 (setStyle) 和 设置事件监听 (listen) 等。
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
Atas ialah kandungan terperinci Angular Renderer使用案例分享. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas



Quark Netdisk dan Baidu Netdisk adalah kedua-dua alat storan yang sangat mudah Ramai pengguna bertanya sama ada kedua-dua perisian ini boleh dikendalikan? Bagaimana untuk berkongsi Quark Netdisk ke Baidu Netdisk? Biarkan laman web ini memperkenalkan kepada pengguna secara terperinci cara menyimpan fail Quark Network Disk ke Baidu Network Disk. Cara menyimpan fail dari Cakera Rangkaian Quark ke Cakera Rangkaian Baidu Kaedah 1. Jika anda ingin tahu cara memindahkan fail dari Cakera Rangkaian Quark ke Cakera Rangkaian Baidu, mula-mula muat turun fail yang perlu disimpan pada Cakera Rangkaian Quark, dan kemudian buka klien Cakera Rangkaian Baidu , pilih folder tempat fail yang dimampatkan akan disimpan dan klik dua kali untuk membuka folder. 2. Selepas membuka folder, klik "Muat naik" di penjuru kiri sebelah atas tetingkap. 3. Cari fail termampat yang perlu dimuat naik pada komputer anda dan klik untuk memilihnya.

1. Mula-mula, kami masukkan Muzik Awan NetEase, dan kemudian klik pada antara muka laman utama perisian untuk memasuki antara muka main balik lagu. 2. Kemudian dalam antara muka main balik lagu, cari butang fungsi perkongsian di bahagian atas sebelah kanan, seperti yang ditunjukkan dalam kotak merah dalam rajah di bawah, klik untuk memilih saluran perkongsian dalam saluran perkongsian, klik pilihan "Kongsi ke". bahagian bawah, dan kemudian pilih "WeChat Moments" yang pertama membolehkan anda berkongsi kandungan ke WeChat Moments.

Angular.js ialah platform JavaScript yang boleh diakses secara bebas untuk mencipta aplikasi dinamik. Ia membolehkan anda menyatakan pelbagai aspek aplikasi anda dengan cepat dan jelas dengan memanjangkan sintaks HTML sebagai bahasa templat. Angular.js menyediakan pelbagai alatan untuk membantu anda menulis, mengemas kini dan menguji kod anda. Selain itu, ia menyediakan banyak ciri seperti penghalaan dan pengurusan borang. Panduan ini akan membincangkan cara memasang Angular pada Ubuntu24. Mula-mula, anda perlu memasang Node.js. Node.js ialah persekitaran berjalan JavaScript berdasarkan enjin ChromeV8 yang membolehkan anda menjalankan kod JavaScript pada bahagian pelayan. Untuk berada di Ub

Baru-baru ini, klien Android Baidu Netdisk telah memperkenalkan versi baharu 8.0.0 Versi ini bukan sahaja membawa banyak perubahan, tetapi juga menambah banyak fungsi praktikal. Antaranya, yang paling menarik perhatian ialah peningkatan fungsi perkongsian folder. Kini, pengguna boleh dengan mudah menjemput rakan untuk menyertai dan berkongsi fail penting dalam kerja dan kehidupan, mencapai kerjasama dan perkongsian yang lebih mudah. Jadi bagaimana anda berkongsi fail yang anda perlukan untuk berkongsi dengan rakan-rakan anda Di bawah, editor laman web ini akan memberikan pengenalan terperinci kepada anda. 1) Buka Baidu Cloud APP, mula-mula klik untuk memilih folder yang berkaitan pada halaman utama, dan kemudian klik ikon [...] di penjuru kanan sebelah atas antara muka (seperti yang ditunjukkan di bawah) 2) Kemudian klik [+] masuk; lajur "Ahli Dikongsi" 】, dan akhirnya semak semua

Adakah anda tahu Angular Universal? Ia boleh membantu tapak web menyediakan sokongan SEO yang lebih baik!

Dengan perkembangan pesat Internet, teknologi pembangunan bahagian hadapan juga sentiasa bertambah baik dan berulang. PHP dan Angular ialah dua teknologi yang digunakan secara meluas dalam pembangunan bahagian hadapan. PHP ialah bahasa skrip sebelah pelayan yang boleh mengendalikan tugas seperti memproses borang, menjana halaman dinamik dan mengurus kebenaran akses. Angular ialah rangka kerja JavaScript yang boleh digunakan untuk membangunkan aplikasi satu halaman dan membina aplikasi web berkomponen. Artikel ini akan memperkenalkan cara menggunakan PHP dan Angular untuk pembangunan bahagian hadapan, dan cara menggabungkannya

Tingkah laku paparan lalai untuk komponen dalam rangka kerja Angular bukan untuk elemen peringkat blok. Pilihan reka bentuk ini menggalakkan pengkapsulan gaya komponen dan menggalakkan pembangun untuk secara sedar menentukan cara setiap komponen dipaparkan. Dengan menetapkan paparan sifat CSS secara eksplisit, paparan komponen Sudut boleh dikawal sepenuhnya untuk mencapai reka letak dan responsif yang diingini.

Mango TV mempunyai pelbagai jenis filem, siri TV, rancangan pelbagai dan sumber lain, dan pengguna bebas memilih untuk menontonnya. Ahli Mango TV bukan sahaja boleh menonton semua drama VIP, tetapi juga menetapkan kualiti gambar definisi tertinggi untuk membantu pengguna menonton drama dengan gembira Di bawah, editor akan membawakan anda beberapa akaun keahlian Mango TV percuma untuk digunakan oleh pengguna, cepat dan lihat. Cuba tengok. Perkongsian percuma akaun ahli terbaru Mango TV 2023: Nota: Ini adalah akaun ahli terkini yang dikumpul, anda boleh log masuk dan menggunakannya secara terus, jangan tukar kata laluan sesuka hati. Nombor akaun: 13842025699 Kata laluan: qds373 Nombor akaun: 15804882888 Kata laluan: evr6982 Nombor akaun: 13330925667 Kata laluan: jgqae Nombor akaun: 1703
