ViewContainerRef 动态创建视图

Angular DOM 操作

相关的APIs和类:

  • 查询DOM节点
    • template variable ref: 模版变量引用,相当于react中的ref
    • ViewChild: 查询DOM,返回单个元素引用
    • ViewChildren: 返回一个QueryList对象,包含一系列元素
  • ElementRef: 元素引用
    • 查询的方式获取,比如 @ViewChild('myInput') inputElm: ElementRef
    • 依赖注入的方式,获取宿主元素,比如 constructor(private elem: ElementRef){}
  • TemplateRef: 模板引用
    • 查询的方式, 比如 <ng-template #tpl></ng-template>
  • ViewContainerRef: 视图容器,包含创建angular视图的方法和操作视图的apis
  • ViewRef: 视图引用,angular最小的UI单元,创建的视图的返回类型就是ViewRef
  • angular中的2种类型的视图
    • 插入式视图Embedded Views
    • 宿主视图 component instance views,即组件实例视图
  • EmbeddedViewRef: 插入式视图引用,上面创建插入式视图防护的类型
  • ComponentRef<C>: 组件视图引用,创建hostView时返回的类型 <C> 表示组件名

示例

创建视图并插入

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

@Component({
  selector: 'app-sample', template: ` <span>我是第一个span标签</span> <ng-container #vc></ng-container> <span>我是第二个span标签</span> <ng-template #tpl> <div>我是模版里面的div标签</div> </ng-template> ` }) export class SampleComponent implements AfterViewInit { // 查询元素, {read: ViewContainerRef} 不能省略,因为angular无法推断出它是一个容器 @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; // {read: TemplateRef} 可以省略 @ViewChild('tpl') tpl: TemplateRef<any>; constructor() {} ngAfterViewInit() { // 创建一个插入式视图, 一般插入式视图都对应的是模版视图 const tplView: ViewRef = this.tpl.createEmbeddedView(null); // 插入到容器当中 使用视图容器操作视图的方法insert this.vc.insert(tplView); } } 

最后得到的结果是

<app-sample>
    <span>我是第一个span标签</span> <!--template bindings={}--> <div>我是模版里面的div标签</div> <span>我是第二个span标签</span> <!--template bindings={}--> </app-sample> 

可以看出 ng-containerng-template 最后编译后都变为了注释

<!--template bindings={}-->

使用 ngTemplateOutlet 指令

angular为创建插入式视图提供了 ngTemplateOutlet 指令,这个和 router-outlet 功能类似,为模版提供了一个进入的入口,上面的例子可以改写为

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

@Component({
  selector: 'app-sample',
  template: `
    <span>我是第一个span标签</span>
    <ng-container [ngTemplateOutlet]="tpl"></ng-container> <span>我是第二个span标签</span> <ng-template #tpl> <div>我是模版里面的div标签</div> </ng-template> ` }) export class SampleComponent { } 

可以看出这种十分的方便,在饿了吗angular tooltip组件中就使用到了这个指令


@Component({
  selector: 'el-tooltip',
  template: `
    <div style="position: relative; display: inline-block;">
      <div [class]="'el-tooltip__popper is-' + effect + ' ' + popperClass" style="left: -20000px; top: 0; position: absolute;" [@fadeAnimation]="!showPopper" [attr.x-placement]="xPlacement" #popperContent> <div x-arrow class="popper__arrow" [hidden]="!visibleArrow"></div> <ng-template [ngTemplateOutlet]="tip"></ng-template> # 此处使用到了ngTemplateOutlet指令 </div> <ng-content></ng-content> </div> `, animations: [fadeAnimation], }) export class ElTooltip implements AfterContentInit { @ContentChild('tip') tip: TemplateRef<any> } 

使用时:

<el-tooltip>
  <ng-template #tip>我是将要插入的模版内容<ng-template> </el-tooltip> 

ngComponentOutlet

这个指令和上面的 ngTemplateOutlet类似,但是它将创建一个 host view(组件的实例),而不是插入式视图,使用方式

<ng-container *ngComponentOutlet="ColorComponent"></ng-container> 

ViewContainerRef源码

import { Injector } from '../di/injector';
import { ComponentFactory, ComponentRef } from './component_factory'; import { ElementRef } from './element_ref'; import { NgModuleRef } from './ng_module_factory'; import { TemplateRef } from './template_ref'; import { EmbeddedViewRef, ViewRef } from './view_ref'; /** * Represents a container where one or more Views can be attached. * 表示能够被一个或者多个视图附着的容器 * The container can contain two kinds of Views. Host Views, created by instantiating a * {@link Component} via {@link #createComponent}, and Embedded Views, created by instantiating an * {@link TemplateRef Embedded Template} via {@link #createEmbeddedView}. * 容器能够包含2种类型的视图: 宿主视图(组件实例) 和 插入式视图(使用模版创建的视图) * The location of the View Container within the containing View is specified by the Anchor * `element`. Each View Container can have only one Anchor Element and each Anchor Element can only * have a single View Container. * * Root elements of Views attached to this container become siblings of the Anchor Element in * the Rendered View. * 插入的视图的根元素会成为视图容器的兄弟节点,而不是之间插入到容器中,这点和router-outlet插入组件的方式一致 * * To access a `ViewContainerRef` of an Element, you can either place a {@link Directive} injected * with `ViewContainerRef` on the Element, or you obtain it via a {@link ViewChild} query. * 可以通过注入的方式范围viewContainerRef 和 通过 ViewChild 查询的方式获取viewContainerRef * @stable */ export declare abstract class ViewContainerRef { /** * Anchor element that specifies the location of this container in the containing View. * <!-- TODO: rename to anchorElement --> */ readonly abstract element: ElementRef; readonly abstract injector: Injector; // 注入器, 用于动态创建组件中 readonly abstract parentInjector: Injector; // 父注入器, 如果组件自身没有提供注入器,使用父注入器 /** * Destroys all Views in this container. 销毁容器内的所有视图 */ abstract clear(): void; /** * Returns the {@link ViewRef} for the View located in this container at the specified index. * 返回视图引用索引 */ abstract get(index: number): ViewRef | null; /** * 获取视图容器的数量 * Returns the number of Views currently attached to this container. */ readonly abstract length: number; /** * 实例化一个插入式视图,可以插入到指定的索引位置,如果不指定索引,将放到最后面 * Instantiates an Embedded View based on the {@link TemplateRef `templateRef`} and inserts it * into this container at the specified `index`. * * If `index` is not specified, the new View will be inserted as the last View in the container. * * Returns the {@link ViewRef} for the newly created View. 返回一个视图引用 */ abstract createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number): EmbeddedViewRef<C>; /** * 实例化单个组件,插入到宿主视图中,可以插入到指定的索引位置,如果不指定索引,将放到最后面 * Instantiates a single {@link Component} and inserts its Host View into this container at the * specified `index`. * * The component is instantiated using its {@link ComponentFactory} which can be * obtained via {@link ComponentFactoryResolver#resolveComponentFactory}. * 组件通过 ComponentFactory 实例化, 而组件工厂可以通过 ComponentFactoryResolver来创建 * If `index` is not specified, the new View will be inserted as the last View in the container. * * You can optionally specify the {@link Injector} that will be used as parent for the Component. * * Returns the {@link ComponentRef} of the Host View created for the newly instantiated Component. */ abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>; /** * 插入视图 * Inserts a View identified by a {@link ViewRef} into the container at the specified `index`. * * If `index` is not specified, the new View will be inserted as the last View in the container. * * Returns the inserted {@link ViewRef}. */ abstract insert(viewRef: ViewRef, index?: number): ViewRef; /** * 依据索引移动视图 * Moves a View identified by a {@link ViewRef} into the container at the specified `index`. * * Returns the inserted {@link ViewRef}. */ abstract move(viewRef: ViewRef, currentIndex: number): ViewRef; /** * 返回视图的索引位置 * Returns the index of the View, specified via {@link ViewRef}, within the current container or * `-1` if this container doesn't contain the View. */ abstract indexOf(viewRef: ViewRef): number; /** * 移除视图 * Destroys a View attached to this container at the specified `index`. * * If `index` is not specified, the last View in the container will be removed. */ abstract remove(index?: number): void; /** * 将视图从当前容器中分离 * Use along with {@link #insert} to move a View within the current container. * * If the `index` param is omitted, the last {@link ViewRef} is detached. */ abstract detach(index?: number): ViewRef | null; } 

2种视图

模版视图

也称之为插入式视图

<ng-template #tpl></ng-template>
<ng-container #vc><ng-container>

class SampleComponent implments AfterViewInit { @ViewChild('tpl') tpl: Template<any>; @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; ngAfterViewInit() { let embeddedView: ViewRef = this.tpl.createEmbeddedView(null); this.vc.insert(embeddedView); } } 

宿主视图

和组件相关

<ng-container #vc><ng-container>

class SampleComponent implments AfterViewInit { @ViewChild('tpl') tpl: Template<any>; @ViewChild('vc', {read: ViewContainerRef}) vc: ViewContainerRef; componentRef: ComponentRef<ColorComponent>; constructor(private injector: Injector, provate cfr: ComponentFactoryResolver) {} ngAfterViewInit() { const factory = this.cfr.rosolveComponentFactory(ColorComponent); // 创建组件工厂 this.componentRef = this.vc.createComponent(factory); // 创建组件引用 // this.componentRef = factory.create(this.injector); // 创建注入器 // let view: ViewRef = componentRef.hostView; // 创建宿主视图 // this.vc.insert(view); } } 

最后动态创建的组件需要添加到 entryComponent

示例:

文章参考:



作者:JamesSawyer
链接:https://www.jianshu.com/p/40dec12a278c
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

转载于:https://www.cnblogs.com/rjjs/p/11275960.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值