I am having a problem with TemplateRef refreshing cycle when used with ngTemplateOutlet.
Consider the HTML:
<card [value]="item" full>
<template #buttons let-obj="obj"> <!-- THE TEMPLATE -->
<button (click)="myBoolean = false"
*ngIf="myBoolean"> <!-- THIS IF -->
SET FALSE
</button>
<button (click)="myBoolean = true"
*ngIf="!myBoolean"> <!-- THIS IF -->
SET TRUE
</button>
</template>
</card>
<button (click)="myBoolean = !myBoolean">TOGGLE</button>
So a page-component has a card. The card has this property:
@ContentChild('buttons')
TemplateRef buttons;
The TemplateRef buttons is used by this code:
<template [ngTemplateOutlet]="buttons"
[ngTemplateOutletContext]="{ 'obj': value }"></template>
It works well and the button is shown based on myBoolean variable of the page component. Also, when you click the buttons inside the <template>
the cycle works and they change based on myBoolean variable.
The problem is when the myBoolean variable is changed by something outside the <template>
. In the HTML example above, when I click the TOGGLE button, the myBoolean variable changes however the <template>
does not refresh accordingly.
So, who owns the refresh of TemplateRef?
What can I do to make it refresh correctly?
The view in which the TemplateRef
is instantiated has ownership. Because your components use ChangeDetectionStrategy.OnPush
, their views will only update whenever
(event)=handleEvent
) bound in the
component's view is triggered, <card [input]="value">
) changes, orChangeDetectorRef
and invoked changeDetector.markForCheck()
.Note that all three of these conditions lead to the view being marked for check; the first two implicitly, and the last explicitly.
In your example, you're projecting a <template>
into your <card>
component's view. Presumably your <card>
component is then rendering that <template>
in its own view via NgTemplateOutlet
. So in order for the rendered <template>
to be updated when myBoolean
changes, the <card>
component has to be marked for check.
The reason the view updates properly when you click a button inside the <template>
is due to reason (1) above. The event handler marks the view it's rendered in (the <card>
) to be checked.
The same reason is why it doesn't work when you click the TOGGLE button in parent view: that only marks the parent view to be checked. So when the parent view is change detected as a result of being marked for check, the <card>
view is skipped because none of the three conditions have been met for the <card>
view.
So in order to have the rendered <template>
update when the TOGGLE button is clicked, you need the handler to somehow call markForCheck()
on the ChangeDetectorRef
of the view in which the <template>
is rendered.
The good news is this is possible, but unfortunately it's not very clean. There are a number of ways you could accomplish this, but here's an idea:
Add a method to <card>
to abstract the details of using ChangeDetectorRef
:
@Component(...)
class CardComponent {
CardComponent(this._changeDetector);
final ChangeDetectorRef _changeDetector;
void updateButtons() {
_changeDetector.markForCheck();
}
}
Query for the <card>
in your page component, and call the update method when toggle is pressed:
@Component(...)
class PageComponent {
@ViewChild(CardComponent)
CardComponent card;
void toggle() {
myBoolean = !myBoolean;
card.updateButtons();
}
}
<button (click)="toggle">TOGGLE</button>
Hope this was helpful, cheers!