6

I created a Observable.interval with 500 ms somewhere in my component tree and subscribed to it. The component has no Input or Output properties. That interval triggers change detection starting from the root component each time it sends a tick. That causes a lot of overhead in my application which is not needed. I don't find any documentation about that behaviour.

Is it possible to switch change detection off caused by this Observable?

Edit: Adding code

The following code demonstrates what I want to do. I put the interval outside of Angular's zone as suggested by Günter, but now modifications on the array don't get published in the template. Is there any way to update the template without triggering a change detection?

import {NotificationList} from "./NotificationList";
import {Notification} from "./Notification";
import {Component, OnDestroy, ChangeDetectorRef, NgZone} from "@angular/core";
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/interval';

class TimedNotification {
    notification: Notification;
    remainingTime: number;
}

@Component({
    selector: "notifications",
    template: `
        <ul>
            <li *ngFor="let notification of notifications">notification.message</li>
        </ul>
    `
})
export class NotificationComponent implements OnDestroy {
    notifications: Array<TimedNotification> = [];
    private subscription: Subscription;
    private timer: Subscription = null;
    private delay: number = 2000;
    private tickDelay: number = 500;

    constructor(notificationQueue: NotificationList, private zone: NgZone) {
        this.subscription = notificationQueue.getObservable().subscribe(notification => this.onNotification(notification));
        this.zone.runOutsideAngular(() => {this.timer = Observable.interval(500).subscribe(x => this.onTimer())});
    }

    private onTimer(): void {
        if(this.notifications.length == 0) {
            return;
        }
        let remainingNotifications: Array<TimedNotification> = [];
        for(let index in this.notifications) {
            let timedNotification = this.notifications[index];
            timedNotification.remainingTime -= this.tickDelay;
            if(timedNotification.remainingTime <= 0) {
                continue;
            }
            remainingNotifications.push(timedNotification);
        }
        this.notifications = remainingNotifications;
    }

    private onNotification(notification: Notification): void {
        let timedNotification = new TimedNotification();
        timedNotification.notification = notification;
        timedNotification.remainingTime = this.delay;
        this.notifications.push(timedNotification);
    }

    ngOnDestroy(): void {
        this.subscription.unsubscribe();
        if(this.timer !== null) {
            this.timer.unsubscribe();
        }
    }
}
Normalo
  • 387
  • 4
  • 12

1 Answers1

0

You can switch it off for components using ChangeDetectionStrategy.OnPush.

Every event causes change detection to run (and setTimeout, and any other async API that is covered by NgZone).

If you use OnPush then only changes to inputs and events from observables subscribef to with | async cause change detection.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I think I need it the other way round. I want to prevent the Observable from causing change detection in any other component. This interval is only responsible for manipulating an array, it has no connection to the outside. – Normalo Sep 02 '16 at 10:47
  • You can run the observable outside Angulars zone. http://blog.thoughtram.io/angular/2016/02/01/zones-in-angular-2.html#running-code-outside-angulars-zone – Günter Zöchbauer Sep 02 '16 at 10:49
  • Wow, that's almost it. Can I perform change detection just for the component the Observable is created in? I will need to update the view in case the array is updated. – Normalo Sep 02 '16 at 10:54
  • I guess that will happen anyway when I change the array because the template is connected to it. I will give it a try tomorrow. I can't accept the answer with onPush, could you update it? – Normalo Sep 02 '16 at 11:02
  • If you use `*ngFor` then yes, it checks the array for chages when change detection runs and updates the view. Other bindings to array content will also be updated. – Günter Zöchbauer Sep 02 '16 at 11:20
  • I created an interval outside the zone, but now changes to the array are not noticed by `*ngFor`. I updated my question with the exact code. – Normalo Sep 03 '16 at 00:19
  • If you run code that you want to invoke change detection in `zone.run()` like `this.zone.run(() => this.notifications = remainingNotifications;)`. I didn't see from your question what exactly you want to cause change detection to run (`*ngFor` to update). – Günter Zöchbauer Sep 05 '16 at 09:59