Ion-range not updating with ChangeDetectionStrategy.OnPush

I am using ngrx/store to handle application state, so I have an AllLightsPage that displays a list of Light bulbs that it gets from the store.

// In all-lights.page.ts  
ngOnInit() {
  this.lights = this.store.let(getAllLights());
}

// In all-lights.page.html
<ion-list no-lines>
  <ion-item *ngFor="let lightItem of lights | async">
    <light-card
      [header]="lightItem.name"
      [light]="lightItem"
      (toggleLight)="toggleLight($event)"
      (updateBrightness)="updateBrightness($event)"
      (updateColour)="updateColour($event)"
    >
    </light-card>
</ion-item>

And then I have a LightCard component that displays all the information for each light.

// In light-card.component.html
<ion-card-content>
  <ion-row>
    <ion-col width-20>
      <button [clear]="!light.isOn" (click)="toggleLight.emit({ light: light, value: $event.value })">
        <ion-icon name="bulb"></ion-icon>
      </button>
    </ion-col>

    <ion-col>
      <ion-item>
        <ion-range [(ngModel)]="light.brightness" min="0" max="254" (ionChange)="updateBrightness.emit({ light: light, value: $event.value })">
          <ion-icon range-left small name="sunny"></ion-icon>
          <ion-icon range-right name="sunny"></ion-icon>
        </ion-range>
      </ion-item>
    </ion-col>
  </ion-row>

  <ion-row>
    <ion-col>
      <input type="color" [value]="light.hexColour" (change)="updateColour.emit({ light: light, value: $event.target.value })">
    </ion-col>
  </ion-row>
</ion-card-content>

So my light-card component has one main input, the light object, which sets the card’s name, hexColour, brightness, and whether the light is on or off. When I use ChangeDetectionStrategy.OnPush, all the values in the template correctly reflect the light object’s current state EXCEPT the ion-range that is used to represent the light’s brightness. Even manually selecting a value by moving the ion-range’s knobs has no effect, it always goes back to 0.

I am guessing that this is something to do with the two-way data binding using [(ngModel)]. But I can’t find any other way to set the ion-range’s value. Its value does not appear to be an input, so I can’t just go <ion-range [value]="light.brightness" ...> to avoid using two-way data binding.

Is this a bug with ion-range? How can I get its state to update correctly using ChangeDetectionStrategy.OnPush? How can I set the range’s value without using two-way data binding?

I have the same problem, I created a plunker to demonstrate the problem:


When removing changeDetection: ChangeDetectionStrategy.OnPush from home.ts, moving the slider works, whereas the interface does not update when OnPush is used
Calling ChangeDetectorRef.detectChanges() manually does not work, too, see here: http://stackoverflow.com/questions/39687560/angular-2-forms-onpush/39689388?noredirect=1#comment66696297_39689388

did you tried:
this.changeDetectionRef.markForCheck();

if you changed the value of the slider?

Thank you, this works!

constructor(private cdr: ChangeDetectionRef) {}

this.form.valueChanges
      .subscribe(() => {
        this.cdr.markForCheck();
        window.setTimeout(() => {
          // this is for hiding the pin
          this.cdr.markForCheck();
        }, 100);
      })
1 Like

observables does not return anything the change detection can listen on in onPush mode. So you have to say --> hey here are some changes please check my path in the change detection component tree.

I did try manually calling markForCheck() but it didn’t seem to work for me. I’m not using a form, so maybe it has something to do with that? Where would I place the markForCheck call? I tried doing <ion-range (ngModelChange)="updateTemplate()"> where updateTemplate simply called this.cdr.markForCheck(). The method was executing correctly, but still not updating the view.

My all-lights-page uses an observable, but the input that it passes to light-card is just an object, not an observable, so it should be able to detect the changes automatically still.

Simply putting the the ion-range in a form worked for me. I didn’t even have to call markForCheck().

    // light-card.component.html
    <form ion-item [formGroup]="form">
      <ion-range formControlName="brightness" [(ngModel)]="light.brightness" min="0" max="254" (ionChange)="updateBrightness.emit({ light: light, value: $event.value })">
        <ion-icon range-left small name="sunny"></ion-icon>
        <ion-icon range-right name="sunny"></ion-icon>
      </ion-range>
    </form>

    // light-card.component.ts
    ngOnInit() {
      this.form = this.formBuilder.group({
        brightness: this.light.brightness,
      });
    }

So there must be something wrong with the ion-range component.

Any luck on this? Looks like I am stuck with similar issue!

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ngIf: true'. Current value: 'ngIf: false'