Reputation: 11490
This is driving me nuts:
Here is a STACKBLITZ that describes my problem best.
In a nutshell. I want "Stuff" inside of a CLOSED Expansion-Panel NOT to be checked by ChangeDetection (exlude from ChangeDetection).
cd-check-comp: Projected from Parent to Child
is STAMPED OUT from Parent
-View so it is checked when ever parent
is checked. That is the expected behaviour, but NOT DESIRED.
Question:
How can I put the projected ng-template
(in this example cdkPortal/TemplatePortal) into the same ViewContainer
as cd-check-comp: Im in Childs View
?
How can I change/ switch ViewContainerRef
of an ng-template
. I would like to "perform/code" the switch inside Child
-Component.
Stackblitz Save:
@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));
}
}
Upvotes: 1
Views: 1227
Reputation: 214175
I think you're trying to detach the wrong View
.
In ViewEngine if a template is declared in one view but inserted into a different view, change detection would also run when its declaration point was checked.
I would detach embedded inserted view. To do so you can get hold of ViewContainerRef
from the place where you're inserting view through portal
.
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();
}
...
}
Upvotes: 1