如何更改 ng-template 的 ViewContainerRef

How can I change the ViewContainerRef of a ng-template

这让我抓狂:

这是最能描述我的问题的STACKBLITZ

简而言之。我希望 CLOSED 扩展面板 NOT 内的 "Stuff" 由 ChangeDetection 检查(从 ChangeDetection 中排除)。

cd-check-comp: Projected from Parent to ChildSTAMPED OUT 来自 Parent-View,所以只要检查 parent 就会检查它。这是预期的行为,但 不需要

问题:

如何将投影的 ng-template(在此示例中为 cdkPortal/TemplatePortal)放入与 cd-check-comp: Im in Childs View 相同的 ViewContainer

如何更改/切换 ng-templateViewContainerRef。我想 "perform/code" 里面的开关 Child-Component.


Stackblitz 保存:

@Component({
  selector: "parent",
  template: `
    <button (click)="tick()">Trigger app.tick()</button>
    <mat-expansion-panel #ep1>
      <mat-expansion-panel-header>
        <mat-panel-title>
          Stuff inside should only be checked if open
        </mat-panel-title>
      </mat-expansion-panel-header>

      <child [disableCD]="!ep1.expanded">

          <ng-template cdkPortal>
            <cd-check-comp name='Projected from Parent to Child'></cd-check-comp>
          </ng-template>

      </child>

    </mat-expansion-panel>

    <cd-check-comp name='Im in parents View.'></cd-check-comp>

    <p> Main Goal: <b>cd-check-comp: Projected from Parent to Child</b> should not be "checked" when the Panel is closed for the first time.</p>
  `,
})
export class Parent {
  tick() { setTimeout(() => {}); }
}

@Component({
  selector: 'child',
  template: `
    <ng-template [cdkPortalOutlet]="_portal"></ng-template>
    <cd-check-comp name="Im in Childs View"></cd-check-comp>
  `,
})
export class Child implements OnInit {

  @ContentChild(CdkPortal, {static: true}) _lazyPortal: CdkPortal;

  @Input() disableCD: boolean;

  _opened: BehaviorSubject<boolean>;

  _portal: TemplatePortal;

  constructor(
    private _changeDetectorRef: ChangeDetectorRef, private _vcr: ViewContainerRef
  ) {
  }

  ngOnInit() {
    this._opened = new BehaviorSubject(this.disableCD);
  }

  ngDoCheck() {
    console.log('Child checked')
  }

  ngOnChanges(sc: SimpleChanges) {
    // return;
    this.disableCD ? this._changeDetectorRef.detach() : this._changeDetectorRef.reattach();
    if (this._opened) { this._opened.next(this.disableCD); }
  }

  ngAfterContentInit() {

    if (this._lazyPortal) {
      this._opened.pipe(
        startWith(null!),
        filter(() => this._opened.value && !this._portal),
        take(1)
      ).subscribe(() => {
        this._portal = this._lazyPortal;
      });
    }
  }
}

@Component({
  selector: "cd-check-comp",
  template: "<p>cd-check-comp: <b>{{name ? name : instanceCounter}}</b></p>",
  styles: [':host { display: block; border: 1px dashed black}']

})
export class CdCheckComp implements DoCheck {
  static counter = 0;

  @Input() name: string;

  instanceCounter: number;

  constructor(private _vcr: ViewContainerRef) {
    this.instanceCounter = ++CdCheckComp.counter;
  }

  ngDoCheck() {
    console.log("checked:" + (this.name ? this.name : this.instanceCounter));
  }
}

我认为你试图分离错误 View

在 ViewEngine 中,如果模板在一个视图中声明但插入到另一个视图中,则更改检测也会 运行 在检查其声明点时。

我会分离嵌入的插入视图。为此,您可以通过 portal.

从要插入视图的位置获取 ViewContainerRef

child.html

<ng-template #portalContainer [cdkPortalOutlet]="_portal"></ng-template>
             ^^^^^^^^^^^^^^^^
                add this

child.ts

export class Child implements OnInit {
  @ViewChild('portalContainer', { read: ViewContainerRef, static: true }) 
  portalContainer: ViewContainerRef;

 ngOnChanges(sc: SimpleChanges) {
   if (this.portalContainer.length) {
     const view = this.portalContainer.get(0)!;
     this.disableCD ? view.detach() : view.reattach();
   }
   ...
 }

Forked Stackblitz