您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

Angular Renderer使用案例分享

2024/5/1 21:01:09发布90次查看
这次给大家带来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) 等。
相信看了本文案例你已经掌握了方法,更多精彩请关注其它相关文章!
推荐阅读:
使用js判断字符串中包含内容方法总结
js+html5实绑定鼠标事件的粒子动画
以上就是angular renderer使用案例分享的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product