• angular11源码探索十一【ViewContainerRef动态组件】


    ViewContainerRef

    锚元素,指定该容器在包含视图中的位置。在渲染好的视图中会变成锚点元素的兄弟。可以在元素上放置注入了 ViewContainerRefDirective 来访问元素的 ViewContainerRef

    是不是听着有点蒙蔽,没事让我慢慢帮你理解本质

    angular-masterangular-masterpackagescore estacceptanceview_insertion_spec.ts

    插入的参数

     createEmbeddedView<C>(templateRef: TemplateRef<C>, context?: C, index?: number):
          EmbeddedViewRef<C> {
        const viewRef = templateRef.createEmbeddedView(context || <any>{});
        this.insert(viewRef, index);
        return viewRef;
      }
    index未指定插入最后,我们可以知道,索引默认从0开始,填写第几个就是在哪个位置插入
    
    <ng-template #simple>
      <app-a></app-a>
    </ng-template>
    <div #container></div>
    export class TwoComponent implements OnInit, AfterViewInit{
     @ViewChild('container', {read: ViewContainerRef, static: true})
      container: ViewContainerRef = null!;
      @ViewChild('simple', {read: TemplateRef, static: true})
      simple: TemplateRef<any> = null!;
        
      constructor(private changeDetector: ChangeDetectorRef) {}
      ngAfterViewInit() {
        //直接插入
        this.container.createEmbeddedView(this.simple)
        // 指定特定的插入位置  
        this.container.createEmbeddedView(this.simple, {}, 2)
          // 如果插入的是组件需要运行变更检测,如果插入的是dom,就不需要,主要看是否有值传入里面
        this.changeDetector.detectChanges();
      }
    }
    

    子代的插入问题

    <app-a>
      <div>xxx</div>
      <app-b></app-b>
    </app-a>
    
    app-a
    
    <ng-template #projection>
      <ng-content></ng-content>
    </ng-template>
    <div #container>
      <h1>dddd</h1>
    </div>
    
    export class AComponent implements OnInit, AfterViewInit, AfterContentInit, OnDestroy, AfterContentChecked {
      @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
      @ViewChild('projection',{read:TemplateRef}) projection: TemplateRef<any> = null!;
    
      ngAfterViewInit() {
        // 默认插入的位置
        this.container.createEmbeddedView(this.projection);
        // 修改位置,我们发现dom在下面了,组件在上面,因为ng-content只能有一个,所以重新插入会替换掉之前的  
        this.container.createEmbeddedView(this.projection,{},0);
      }
    }    
    

    插入的时候特殊的问题点,报错的解决方案

    <ng-template #subContainer>
      <div class="dynamic" *ngIf="true">test</div>
    </ng-template>
    <div #container></div>
    
    export class TwoComponent implements OnInit, AfterViewInit, AfterViewChecked, AfterContentInit, AfterContentChecked {
      constructor(private changeDetector: ChangeDetectorRef) {
      }
      @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef = null!;
      @ViewChild('subContainer', {read: TemplateRef}) subContainer: TemplateRef<any> = null!;
         ngAfterViewInit() {
            this.view1 = this.container.createEmbeddedView(this.subContainer);
            this.view1 = this.container.createEmbeddedView(this.subContainer, null, 0);
          //如果不写下面的可以会报错,在这里运行变更检测来避免 因为值被传递给ngIf
            this.changeDetector.detectChanges();
         }
    } 
    

    指令的插入问题

    <ng-template #insert>
      <div>insertA</div>
    </ng-template>
    <ng-template #before>
      <div>insertB</div>
    </ng-template>
    
    <div>
          // 介绍下为什么要 #vi="vi"  
          // #vi 是描点定义的变量 'vi' 是别名的使用,就类似 #a="vi" 
          // vi的别名复制给 #a
          // 页面页面上面的其他元素通过 vi.方法 使用指令里面的方法
          // 第二点,在ts中我们使用@ViewChild 也需要#vi 指定的变量拿到其中的位置 
      <ng-template #vi="vi" viewInserter>
      </ng-template>
    </div>
    <button (click)="insertA()">Click</button>
    ----------
    
      @ViewChild('before', {static: true}) beforeTpl!: TemplateRef<{}>;
      @ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
      @ViewChild('vi', {static: true}) viewInsertingDir!: ViewInsertingDir;
      insertA() {
        const beforeView = this.beforeTpl.createEmbeddedView({});
        // 变更检测“before视图”来创建所有子视图
        beforeView.detectChanges();
        this.viewInsertingDir.insert(beforeView, this.insertTpl);
      }
    -----------
    指令
    @Directive({
      selector: '[viewInserter]',
      exportAs:'vi'
    })
    export class ViewInserterDirective {
    
      constructor(private _vcRef:ViewContainerRef) { }
    
      insert(beforeView: ViewRef, insertTpl: TemplateRef<{}>) {
        this._vcRef.insert(beforeView, 0);
        this._vcRef.createEmbeddedView(insertTpl, {}, 0);
      }
    }
    

    在动态组件视图前插入

    指令

    @Directive({
      selector: '[viewInserter]',
      exportAs:'vi'
    })
    export class ViewInserterDirective {
    
      constructor(private _vcRef:ViewContainerRef) { }
    
      insert(beforeView: ViewRef, insertTpl: TemplateRef<{}>) {
        this._vcRef.insert(beforeView, 0);
        this._vcRef.createEmbeddedView(insertTpl, {}, 0);
      }
    }
    
    <ng-template #insert>insert</ng-template>
    <div><ng-template #vi="vi" viewInserter></ng-template></div>
    
    <button (click)="insertA()">Click</button>
    
    export class TwoComponent implements OnInit{
      constructor(private _cfr: ComponentFactoryResolver, private _injector: Injector) {}
      @ViewChild('insert', {static: true}) insertTpl!: TemplateRef<{}>;
      @ViewChild('vi', {static: true}) viewInserterDirective!: ViewInserterDirective;
    
      insertA() {
        // 创建一个动态组件视图,作为“insert before”视图
        const componentFactory = this._cfr.resolveComponentFactory(AComponent);
          // 添加服务
        const beforeView = componentFactory.create(this._injector).hostView;
        // 变更检测“before视图”来创建所有子视图
        beforeView.detectChanges();
        this.viewInserterDirective.insert(beforeView, this.insertTpl);
      }
    }
    

    突然疑惑一点为啥我加入一个新的变量的时候,就会报错找不到vi

     @ViewChild('vi', {static: true}) 
    其实我们把{static:true} 去掉或者改成false就可以啦
    

    组件的增删改查

    <ng-template #tpl1>
      <div #foo>Foo 1</div>
    </ng-template>
    <div #foo>Betwean tpl _definitions_</div>
    <ng-template #tpl2 let-idx="idx">
      <div #foo>Foo 2</div>
    </ng-template>
    
    <ng-template viewInserter #vi="vi"></ng-template>
    
    <hr>
    
    <button (click)="vi.insert(tpl1)">Insert Foo1</button>
    <button (click)="vi.insert(tpl2)">Insert Foo2</button>
    <button (click)="vi.clear()">clear</button>
    <button (click)="vi.remove()">删除第4个</button>
    <!--另一种使用方式-->
    <button (click)="clickMode()">click</button>
    
      @ViewChild(ViewInserterDirective) vc: ViewInserterDirective;
      // 这也是一种方式
     clickMode(){
        this.vc.insert(this.tpl2)
      }
    

    指令中

    @Directive({
      selector: '[viewInserter]',
      exportAs:'vi'
    })
    export class ViewInserterDirective {
    
      constructor(private _vcRef: ViewContainerRef,private changeDetector: ChangeDetectorRef) {}
    
      insert(tpl: TemplateRef) {
        this._vcRef.createEmbeddedView(tpl);
      }
    
      clear() {
        this._vcRef.clear();
      }
      //默认出现报错记得检测更新
      remove(index?: number) {
        this._vcRef.remove(index);
      }
      //可以还不懂
      move(viewRef: ViewRef, index: number) {
        this._vcRef.move(viewRef, index);
      }
    }
    

    试不试感觉模模糊糊的,那我们重新编写让大家看的更清晰些

    案例

    <ng-template #one>
      <div>one</div>
    </ng-template>
    <ng-template #two>
      <div>two</div>
    </ng-template>
    <ng-template #three>
      <div>three</div>
    </ng-template>
    <h1>--------</h1>
    <div viewInserter></div>
    <button (click)="increase()">增加one</button>
    <button (click)="increaseT(two)">增加two另一种模式</button>
    <button (click)="increaseT(three)">增加two另一种模式</button>
    <button (click)="remove()">删除1</button>
    <button (click)="clickMove(0,3)">移动0,3</button>
    <button (click)="clear()">删除全部</button>
    
    export class TwoComponent implements OnInit, AfterViewInit {
      constructor(private _cfr: ComponentFactoryResolver, private _injector: Injector) {
      }
    
      @ViewChild('one') one!: TemplateRef<any>;
      @ViewChild('two') two!: TemplateRef<any>;
      @ViewChild('three') three!: TemplateRef<any>;
      @ViewChild(ViewInserterDirective) vd: ViewContainerRef;
      // 移动
      clickMove(start, end) {
        // 查询
        this.vd.move(start,end)
      }
    
      ngOnInit(): void {
      }
    
      //增
      increase() {
        this.vd.insert(this.one)
      }
      //增
      increaseT(tpl: Comp) {
        this.vd.insert(tpl)
      }
      //删除
      remove(){
        this.vd.remove(1)
      }
      //删除全部
      clear(){
        this.vd.clear()
      }
    }
    

    指令

    @Directive({
      selector: '[viewInserter]',
    })
    export class ViewInserterDirective {
    
      constructor(private _vcRef: ViewContainerRef, private changeDetector: ChangeDetectorRef) {
      }
    
      insert(tpl: TemplateRef<unknown>) {
        this._vcRef.createEmbeddedView(tpl);
      }
    
      clear() {
        this._vcRef.clear();
      }
    
      //默认出现报错记得检测更新
      remove(index?: number) {
        this._vcRef.remove(index);
      }
    
      move(start, end) {
        this._vcRef.move(this._vcRef.get(start), end);
      }
    }
    

    突然想想那默认探究下ViewContainerRef 具体有哪些api呢

     explore(){
        // 查找找不到范围-1
       // this._vcRef.indexOf(this._vcRef.get(0));
        // 拿到当前指令的dom
        // console.log(this._vcRef.element.nativeElement);
        // 插入
          // insert(viewRef: ViewRef, index?: number): ViewRef
        //从这个容器中分离视图而不销毁它。
        // *与' insert() '一起使用来移动当前容器中的视图。
        // * @param index要分离的视图基于0的索引。
        // *如果不指定,容器中的最后一个视图将被分离。
        this._vcRef.detach(3)
      }
    

    动态组件插入

    @NgModule({
      declarations: [ AComponent],
      entryComponents:[AComponent],// 动态组件需要在模块中引入
    
    <ng-container #container></ng-container>
    <button (click)="createComp()">++</button>
    export class TwoComponent implements OnInit, AfterViewInit {
      constructor(private _cfr: ComponentFactoryResolver) { }
      @ViewChild('container', {read: ViewContainerRef}) vcRef!: ViewContainerRef;
      createComp() {
        const factory = this._cfr.resolveComponentFactory(AComponent);
        this.vcRef.createComponent(factory)
      }
    }
    

    templateRef和ElementRef

    搞混

    ElementRef DOM

    templateRef 就是ng-template上的

      <ng-template #foo></ng-template>
      @ViewChild('foo', {static: true}) foo!: TemplateRef<any>;
    
       <span #foo></span>
      @ViewChild('foo', {static: true}) foo!: ElementRef;
    
  • 相关阅读:
    【代码笔记】iOS-字符串的分割
    【代码笔记】iOS-柱状图
    【代码笔记】iOS-UILable电子表显示
    【代码笔记】iOS-UILable高度自适应(sizeWithFont)
    【代码笔记】iOS-中国地图
    【代码笔记】iOS-正在加载
    【代码笔记】iOS-账号,密码记住
    【代码笔记】iOS-由身份证号码返回性别
    【代码笔记】iOS-用户发布后能保存崩溃
    【代码笔记】iOS-一个tableView,两个section
  • 原文地址:https://www.cnblogs.com/fangdongdemao/p/14195298.html
Copyright © 2020-2023  润新知