Search code examples
angulardartangular-dartangular2-changedetection

Who owns the TemplateRef refreshing cycle in angular dart?


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?


Solution

  • The view in which the TemplateRef is instantiated has ownership. Because your components use ChangeDetectionStrategy.OnPush, their views will only update whenever

    1. an event handler (e.g. (event)=handleEvent) bound in the component's view is triggered,
    2. an input's value (e.g. <card [input]="value">) changes, or
    3. the component has injected ChangeDetectorRef 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!