refactoring code from ChangeDetectionStrategy.Default to ChangeDetectionStrategy.OnPush

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*/
    }
}

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 !

Tags:

Angular