zoukankan      html  css  js  c++  java
  • 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
    来源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
  • 相关阅读:
    C# 工厂
    sql server 数据库 数据DateTime 转mysql
    java 快捷工具,如何清理不需要的引用
    动态调用webservice 接口
    动态调用wcf接口服务
    下载图片出现问题
    jQuery Validation Engine 表单验证
    mvc5 知识点01
    mvc5 @RenderSection("scripts", required: false) 什么意思
    mvc5 _ViewStart.cshtml 模板页如何定义
  • 原文地址:https://www.cnblogs.com/rjjs/p/11275960.html
Copyright © 2011-2022 走看看