Manual change detection in Angular for performance

There are special use cases where you might need to customize change detection in Angular applications for performance reasons. The majority of the time, default change detection is the correct way to go. I’ve worked with several customers recently who were using very large datasets within Angular 8 components and this caused some interesting UI slowdowns as a side-effect.

Fortunately, and unsurprisingly, Angular does allow you modify change detection, for example ChangeDetectionStrategy.OnPush and ChangeDetectorRef. In both cases that I worked on, my recommendation was to lean towards ChangeDetectorRef in order to provide granular, explicit control over when a component updated to reduce performance impacts on the UI.

In one case, the change detection needed to be slowed down and it was suitable to run it on a timer loop. Here’s the pseudo-code, and there are plenty of similar examples on the internet including in the Angular documentation:

import { Component, ChangeDetectorRef, . . .  } from '@angular/core';

constructor(private changeDetector: ChangeDetectorRef) {
    changeDetector.detach();
    setInterval(() => {
      this. changeDetector.detectChanges();
    }, 5000);
  }

In the other use case, the change detection only needed to happen when an Observable from a service was updated. That implementation used a pattern similar to this pseudo-code:

import { Component, ChangeDetectorRef, OnInit } from '@angular/core';

constructor(private stateMgmtService: StateMgmtService, private changeDetector: ChangeDetectorRef) {}

public messages$: Observable<MySpecialArray[]>
public list: Subscription:
public data: any[] = [];

ngOnInit() {
   this.changeDetector.detach(); 
   this.messages$ = this.stateMgmtService.getSomeData();
   this.list = this.message$.subscribe({
      next: x => {
         // . . . do some calculations against x
         this.data = x;
         // Only detect changes on next
         this.changeDetector.detectChanges();
      }
   })

}

And here’s the component.html:

<!-- https://material.angular.io/cdk/scrolling/overview -->
<cdk-virtual-scroll-viewport [itemSize]="8" class="point-viewport">
  <div *cdkVirtualFor="let location of data”>
    {{location}}
  </div>
</cdk-virtual-scroll-viewport>

Caveat Emptor. There are technical debt issues when you implement manual control on Angular change detection. Instead of a nice, loosely coupled approach to handling UI updates, you step into potentially creating inconsistencies between how different components handle updates and this adds complexity to your application. This can also affect how you write unit tests and can introduce unforeseen bugs. With all that said, sometimes you have to make decisions based on your unique requirements and you have to take the best approach for your circumstances.

Performance comparison between readAsDataUrl and createObjectURL

If you work with applications that handle uploading images as blobs then you’ve most likely wondered if it’s faster to convert the image using FileReader.readAsDataUrl() or URL.createObjectURL(). For our implementations in the geographic mapping industry we often typically request dozens, hundreds or sometimes thousands of relatively tiny map tile images such as .png and .jpeg in a single user session. There’s always a question of loading and rendering performance.

I was working on a related customer question and I was curious which one is faster in our use cases, so I did some simple testing. A common example of an online web map contains tiles that are 256×256 and vary in size from around 2Kb to 15kb. I assumed for the file types and sizes we use the results would be different because I’d read that createObjectUrl() is typically faster.

TLDR;

The results were surprising to me. For our use cases with relatively small .png images I saw the following results:

  • Chrome: readAsDataUrl() was consistently faster uncached
  • Firefox: createObjectUrl() was consistently faster uncached
  • Safari: was inconsistent between the two.

Test appHere is the link to the test app.

Note that Safari had unpredictable performance in that sometimes readAsDataUrl() was faster than createObjectUrl(). I saw the same behavior for cached and uncached tests and didn’t have time to investigate further.

YMMV!

Just a caveat that since we use lots of small images, your mileage may vary if you use larger images. I hope someone reads this and devises a test for larger images and then shares the results.

The Tests

I tested basic performance of pulling a map tile image from a CDN then using performance.now() to determine the time to create the image and then append it to an HTML list element. I did try to build the code in a way that each loop of the test used a different image to avoid any unintentional optimizations, such as sharing an image in-memory, or in-browser caching. I also ran the comparative tests recursively to try and normalize for readAsDataUrl() being asynchronous. I didn’t have time to investigate memory usage between the two patterns.

Note that testing with the browser console closed will be significantly faster than testing with the console open.  I used a 2018 Macbook Pro, 16GB with DDR4 RAM. I cleared the browser cache before each loop and I used this test app. In the code, each pattern goes through 25 loops and is averaged.

Chrome 80
Test results averaged
Test 1
(ms)
Test 2
(ms)
Test 3
(ms)
Test 4
(ms)
readAsDataUrl0.7890.8270.8130.839
createObjectURL1.6841.6381.6411.544
Firefox 73Test 1
(ms)
Test 2
(ms)
Test 3
(ms)
Test 4
(ms)
readAsDataUrl1.481.281.281.2
createObjectURL1.120.841.041.0
Safari 13Test 1
(ms)
Test 2
(ms)
Test 3
(ms)
Test 4
(ms)
readAsDataUrl0.360.280.720.68
createObjectURL1.960.60.561.6

Conclusions

If you are only uploading a few smaller images, then wondering which approach is faster probably isn’t a good use of your time – either one is good. If you handle hundreds or thousands of smaller images per user session then it might be worth some testing. Based on these quick tests, and more testing is needed to be truly definitive, it really depends on which browsers your users prefer. For example, if you are building hybrid apps then you have control over which browser. In a pure web application you don’t typical have control over what users use in the wild.

I didn’t test larger sized images or images of a different type, such as .jpeg. I’m curious what type of test results those might produce.

Easily back up and restore your iPod to Mac Catalina

iTunes is gone but there are a few simple steps to backing up and restoring an iPod to MacOS Catalina without having to purchase software. You can complete the following steps by using tools that are included for free on your Mac.

In my case I had to back up a really old 1st generation iPod Nano that hadn’t been backed up in years, and restore the files to a 2015, 7th Generation Nano. NOTE: I haven’t tried the steps below with the latest generation iPods (post-7th Gen) or iPhone/iPad.

Also, if you tried to use old command line tricks for running your backups like I did, those won’t work out-of-the-box anymore because the latest MacOS updates include System Integrity Protection (SIP). You can disable SIP, but there’s an easier way! Read on…

Before you get started make sure your Mac is updated to the latest version.

Step 1: Connect iPod to your Mac

Make sure the iPod is on and when you connect it to the Mac by USB cable. Within a few seconds you should see two listings appear in the Finder’s sidebar with the name of your iPod under Locations. If the listings don’t appear the most likely culprit is a bad cable, so you’ll need to try another one. 

One of the listings provides an overview of iPod management options, the other listing will let you browse the iPod’s filesystem. In the management listing under the General section I chose to manually manage my music, movies and TV shows. This is important if you don’t want the iPod to automatically sync as soon as you connect it to the Mac.

Step 2: View hidden directories on the iPod

In Finder using the listing that lets you browse your iPod’s filesystem, if you don’t see the iPod_Control directory then you’ll need to unhide file folders. From your keyboard do command + shift + . (that last item is a period on your keyboard) and now you should be able to see all hidden directories including iPod_Control.

Step 3: Copy files from iPod to Mac

Now open another Finder window and create a new directory such as /Documents/iPod_music/

Copy the iPod_Control/Music/ sub-directory from the original Finder window to the new one. Depending on how many music files you have this could take a while.

Step 4: Sync the Music App

Open Music.app on your Mac. You should see your iPod listed under devices. If not then go back to Step 2.

In the top menu bar go to File > Import and select the directory that has your newly copied music files and then press the “open” button. This should import the files so that they are now managed by the Music app.

Step 5: Restore existing iPod or update new iPod

Once the file import in Step 4 is complete, then you can right click on your device name in the Music app and select sync. When the sync is complete, right click on the device name and choose eject. Once the device has finished ejecting you should now be able to browse and play your files!

Extra Credit

For extra credit, in Finder select the listing that lets you manage the iPod settings run Check for Update. I was surprised there was indeed an update for my Nano.