angular11源碼探索十一【ViewContainerRef動態組件】

ViewContainerRef

錨元素,指定該容器在包含視圖中的位置。在渲染好的視圖中會變成錨點元素的兄弟。能夠在元素上放置注入了 ViewContainerRefDirective 來訪問元素的 ViewContainerRefapi

是否是聽着有點矇蔽,沒事讓我慢慢幫你理解本質app

angular-master\angular-master\packages\core\test\acceptance\view_insertion_spec.tsdom

插入的參數this

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();
  }
}

子代的插入問題spa

<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);
  }
}

插入的時候特殊的問題點,報錯的解決方案code

<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);
  }
}

在動態組件視圖前插入

指令component

@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);
  }
}

忽然疑惑一點爲啥我加入一個新的變量的時候,就會報錯找不到viblog

@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);
  }
}

試不試感受模模糊糊的,那咱們從新編寫讓你們看的更清晰些three

案例

<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;
相關文章
相關標籤/搜索