美文网首页Angular框架专题
Angular中的TemplateRef与ViewContain

Angular中的TemplateRef与ViewContain

作者: 听书先生 | 来源:发表于2021-11-18 23:19 被阅读0次

在HTML5标准中引入了template模板元素,模板元素的处理方式是浏览器的一种机制,允许在加载页面的时候对模板中的内容不进行渲染,但是开发人员可以通过js进行实例化客户端内容。可以想象为模板就是暂存在页面之后的一块内容,当js对他进行处理了,他便会加载到页面中去。

Angular中提出的ng-template又是另一种比较有趣的处理方式

前言.png
1、HTML5中的模板元素:

HTML5的写法:

<template id="temp">
  <span>模板元素中span标签</span>
</template>

这一段内容一般编译运行后是无法显示在页面中的,如果进行代码的修改

// 先定义一个容器
<div  id="container"></div>

<template id="temp">
  <span>模板元素中span标签</span>
</template>

// 获取容器以及模板元素的内容
let container = document.querySelector('#container'); 
let temp = document.querySelector('#temp');

let tempNode = document.importNode(temp.content, true);

//将模板元素的内容追加到容器中去
container.appendChild(tempNode);

这样浏览器即可显示模板元素中的内容

2、Angular中的模板元素:

Angular中, 模板元素主要应用在结构指令中,但是需要注意的是,在NG4.X版本之后就不推荐直接写template了,官方文档推荐使用ng-template

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

@Component({
  selector: 'my-app',
  template: `
    <ng-template #temp>
      <span>ng模板在的span标签的内容</span>
    </ng-template>
  `,
})
export class AppComponent {

  // 获取模板元素
  @ViewChild('temp') temp!: TemplateRef<any>;

  ngAfterViewInit() {
    console.dir(this.temp);
  }
}

打印结果.png img.png

Angular中ng-template中定义的 模板元素,渲染后会被替换成 comment 元素,其内容被 "container"替换掉了 。此外我们通过 @ViewChild 获取的模板元素,是 TemplateRef 类的实例。

3、templateRef类部分源码:

Angular2.x的源码:

// @angular/core/src/linker/template_ref.d.ts
// 用于表示内嵌的template模板,能够用于创建内嵌视图(Embedded Views)
export declare abstract class TemplateRef<C> {
    elementRef: ElementRef;
    abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;
}

Angular8.x的源码

export abstract class TemplateRef<C> {
  //此嵌入视图的父视图中的锚元素。从该“TemplateRef”创建的嵌入式视图的数据绑定和注入上下文从该位置的上下文继承。通常,新的嵌入视图附着到此位置的视图容器,但在在高级用例中,视图可以附加到不同的容器,同时保持来自原始位置的数据绑定和注入上下文。
  abstract get elementRef(): ElementRef;

  // 基于此模板实例化嵌入式视图,并将其附着到视图容器。嵌入视图的数据绑定上下文,如声明的那样在`<ng template>`中使用。返回新的嵌入视图对象。
  abstract createEmbeddedView(context: C): EmbeddedViewRef<C>;

  static __NG_ELEMENT_ID__:
      () => TemplateRef<any>| null = () => SWITCH_TEMPLATE_REF_FACTORY(TemplateRef, ElementRef)
}

抽象类与普通类的区别是抽象类有包含抽象方法,不能直接实例化抽象类,只能实例化该抽象类的子类。

  • 尝试调取源码中的抽象类方法:
    console.dir(this.temp.createEmbeddedView(null));
在编辑器中可以看到有该方法.png
ViewRef.png
可以看到rootNodes中包含了模板元素的内容。

TemplateRef抽象类中定义的 createEmbeddedView抽象方法,该方法的返回值是EmbeddedViewRef对象,当调用 createEmbeddedView 方法后返回了 ViewRef_ 新的嵌入视图对象。该视图对象的 rootNodes 属性包含了 模板中的内容。TemplateRef 实例对象中的 elementRef 属性封装了我们的 comment 元素。

4、根据源码的思路,我们也可以尝试模板元素的步骤过程
    // 初始化页面的<!-- container --> 占位
    let elementRef = this.temp.elementRef.nativeElement;
    console.log(elementRef.parentNode);
    const container = elementRef.parentNode
    
    // 创建内嵌视图
    let temp = this.temp.createEmbeddedView(null);
    console.log(temp);
    
    // 获取内嵌视图中的rootNodes数组节点
    temp.rootNodes.forEach(node => {
      container.appendChild(node);//追加node节点到容器中去
    })
遍历rootNodes数组中的节点.png

总结下来angular内部完成ng-template的过程就是创建内嵌视图(embedded view),再去遍历内嵌视图中的 rootNodes,动态的插入node。

5、ViewContainerRef的作用
  • ViewContainerRef抽象类的源码:
export abstract class ViewContainerRef {

  abstract get element(): ElementRef;
  abstract get injector(): Injector;

  /** @deprecated No replacement */
  abstract get parentInjector(): Injector;
  /**
   * Destroys all views in this container.
   */
  abstract clear(): void;
  abstract get(index: number): ViewRef|null;
  abstract get length(): number; // 内嵌视图插入的位置

  //  基于TemplateRef创建内嵌视图,并自动添加到视图容器中,可通过index设置
  abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
      EmbeddedViewRef<C>;
 
  // 创建组件视图
  abstract createComponent<C>(
      componentFactory: ComponentFactory<C>, index?: number, injector?: Injector,
      projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;

  // 相关的操作方法
  abstract insert(viewRef: ViewRef, index?: number): ViewRef;

  abstract move(viewRef: ViewRef, currentIndex: number): ViewRef;

  abstract indexOf(viewRef: ViewRef): number;

  abstract remove(index?: number): void;

  abstract detach(index?: number): ViewRef|null;

  /**
   * @internal
   * @nocollapse
   */
  static __NG_ELEMENT_ID__:
      () => ViewContainerRef = () => SWITCH_VIEW_CONTAINER_REF_FACTORY(ViewContainerRef, ElementRef)
}

ViewContainerRef:表示的是一个容器,可以添加视图,通过ViewContainerRef实例,可以基于TemplateRef实例创建内嵌视图,并能指定内嵌视图的插入位置,便于对已有的视图进行管理。

也就是说可以通过ViewContainerRef实例也就是tempVcRef直接创建一个内嵌视图,把获取到的TemplateRef实例也就是temp插入内嵌视图中。实际上步骤与上述等同。

    console.dir(this.tempVcRef);
    this.tempVcRef.createEmbeddedView(this.temp)
image.png
6、Angular中的视图类型

ng中视图的类型存在两类,一种就是现在的这种Embedded Views也就是Template 模板元素还有一种是Host Views也就是Component 组件。

创建Embedded Views也就是内嵌视图的创建过程:
TemplateRef抽象类的实例调取内部的createEmbeddedView()方法。
而Host Views的创建过程:

constructor(private injector: Injector,
    private r: ComponentFactoryResolver) {
    let factory = this.r.resolveComponentFactory(AppComponent);
    let componentRef = factory.create(injector);
    let view = componentRef.hostView;
}

相关文章