dagda1
dagda1

Reputation: 28800

refactoring code from ChangeDetectionStrategy.Default to ChangeDetectionStrategy.OnPush

I'm new to angular after spending the last few years on react projects.

I have a component that is using changeDetection: ChangeDetectionStrategy.OnPush and I don't like my solution. The trouble is that I am finding it tough to find any good real world examples of ChangeDetectionStrategy.OnPush

For example, I have a component a bit like this:

  files: Uploads[] = [];

  get canUpload() {
    return this.files.length > 0l
  }

  get isUploading() {
    return this.files.length > 0 && this.files.some((f) => f.state === FileUpLoadState.uploading);
  }

  get activeFiles() {
    return this.files.filter((f) => f.state !== FileUpLoadState.success);
  }

  uploadFiles() {
    if (!this.files.length) {
      return;
    }

    const fileList: FileList = (event.target as HTMLInputElement).files;

    for (const uploadedFile of Array.prototype.slice.call(fileList)) {
      // do stuff
      this.files.push(new Upload(file));
    }

  }

I have these properties that are used in the template like this;

 <button (click)="uploadFiles()" [disabled]="!this.canUpload">Upload</button>

I really don't like this, using default change detection won't scale and when changes get propagated are outside of my control.

How can I refactor this code to use OnPush change detection?

Upvotes: 12

Views: 1011

Answers (4)

user4676340
user4676340

Reputation:

People answer you, but they don't explain you.

OnPush strategy is the most efficient strategy when it comes to change detection. Angular doesn't implement it by default, because newcomers are used to see magic (i.e. default strategy is more error-friendly and understandable when you start using Angular).

To detect changes, Angular listens to events on your view. With the default strategy, that can result in a lot of useless change detection.

In the push strategy, you control when the change detection is triggered.

In both cases, Angular uses memory references to know when your data has been updated. That's why object immutability is so important in Angular, and also why reactive programming works so well.

That being said, if you want to switch to the push strategy, you should use the following :

  // files: Uploads[] = [];
  files: BehaviorSubject<Uploads[]> = new BehaviorSubject([]);

  add(item) {
    this.files.pipe(first()).subscribe(files => this.files.next([...files, item]));
  }

  get canUpload() {
    // return this.files.length > 0l
    return this.files.pipe(
      map(files => files.length),
      map(size => !!size)
    );
  }

  get isUploading() {
    // return this.files.length > 0 && this.files.some((f) => f.state === FileUpLoadState.uploading);
    return this.files.pipe(
      startWith(false),
      filter(files => !!files.length),
      filter(files => files.some(f => f.state === FileUpLoadState.uploading)),
      map(() => true)
    );
  }

  get activeFiles() {
    // return this.files.filter((f) => f.state !== FileUpLoadState.success);
    return this.files.pipe(
      map(files => files.filter(f => f.state !== FileUpLoadState.success)),
    );
  }

  uploadFiles() {
    /*
    if (!this.files.length) {
      return;
    }

    const fileList: FileList = (event.target as HTMLInputElement).files;

    for (const uploadedFile of Array.prototype.slice.call(fileList)) {
      // do stuff
      this.files.push(new Upload(file));
    }
    */
    this.files.pipe(
      filter(files => !!files.length),
      map(files => (event.target as HTMLInputElement).files),
      first()
    ).subscribe(files => {
      for (const uploadedFile of Array.prototype.slice.call(fileList)) {
        // do stuff
        this.add(new Upload(file));
      }
    });
  }

This is one of many implementations you can do. I'm not sure this will work the way you expect, I just "translated" the code, so you might have one or two adjustements to make.

With this, your view gets updated automatically when your collection changes. You don't have to do anything. This complies with the push strategy, because the reactive programming triggers the change detection.

And because every getter depends on your BehaviorSubject, they all get updated at every change in that subject.

Just a "drawback" (which really, isn't), is that in your component template, you have to use the async pipe :

 <ng-container *ngIf="canUpload | async">...</ng-container>

if you have questions, feel free to ask them !

Upvotes: 3

OliverE
OliverE

Reputation: 499

If you want full control over change detection, using the OnPush-Strategy, get a changeDetectorReference

constructor(private changeDetector: ChangeDetectorRef){}

and trigger change detection when you want to, e.g. when your array changes or in your upload-method.

uploadFiles() { 
// ...
this.changeDetector.detectChanges()
}

Upvotes: -1

Dmitry S.
Dmitry S.

Reputation: 1678

According the template below and using ChangeDetectionStrategy.OnPush, ChangeDetection runs only after the button click. (It implements only to DOM events). Everything else won't affect and DetectChanges() function doesn't run (for example, the fetching some data from some resource won't affect on your code)

import { ChangeDetectionStrategy } from '@angular/core';

@Component({
  selector: 'app-root',
   template: `
    <button (click)="uploadFiles()" [disabled]="!this.canUpload">Upload</button>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
  styleUrls: ['./app.component.css']
})

export class AppComponent {
    uploadFiles() {
      .../*Here's function body*/
    }
}

Upvotes: 3

Todarmal
Todarmal

Reputation: 306

As Dmitry S wrote in his answer, we can set the ChangeDetectionStrategy of our component to ChangeDetectionStrategy.OnPush.

This tells Angular that the component only depends on its @inputs() (aka pure), and needs to be checked only in the following cases:

  1. The Input reference changes.
  2. An event originated from the component or one of its children.
  3. We run change detection explicitly.

Netanel Basal has published a very helpful article on ChangeDetectionStrategy.OnPush.

Upvotes: 0

Related Questions