Learn how to Optimize Template Expressions in Angular

Learn how to Optimize Template Expressions in Angular

Learn how to Optimize Template Expressions in Angular . In this post, we will look at template expressions in Angular and learn how to optimize them to speed-up performance Template expressions were

Template expressions were, for me, one of the most useful features when I started using Angular in 2017. Define your data /property in your component and render it on the component using your double curly braces — that was awesome. Though if found it very useful, I still needed to know the best practices around using template expressions in Angular.

In this post, we will look at template expressions in Angular, and how to optimize it to top up our speed.

Template Expression

Let’s start with an example:

@Component({
  selector: 'app-chat',
  template: `
    <div>
        {{viewRun}}
        <h1> Count Component {{count}} </h1>
        <button (click)="onClick($event)">Click Me</button>
    </div>
  `,
  style: []
})
export class ChatComponent implements OnInit/*, DoCheck, OnChanges*/ {
    count = 0
    onClick(evt) {
        this.count++
    }
  get viewRun() {
    console.log('view updated:', this.count)
    return true
  }
}

We have two bindings in our template: viewRun and count. This tells Angular that the field in the template is dynamic, that is, it will be dynamically updated during CD run by Angular.

count is a property of the ChatComponent and viewRun is a method of the ChatComponent. When a Cd is run on the ChatComponent, Angular gets the current value of the count property and replaces it with the {{count}} in the template. viewRun, being a function, is run by Angular and the returning value is replaced with the {{viewRun}} expression in the template.

You see why they are called bindings. They glue the properties/methods of the class component to the view. Each time the Click Me button is clicked, the count property is incremented by 1. The increment is now updated in the view {{count}} on every CD run.

CD run in Angular is triggered by:

  • async events:
  • DOM events e.g. click, changes, mouseover, etc.
  • setTimeout, setInterval, etc.
  • Implicit CD call by the component.

Performance Bottleneck on Template Expressions

Whenever a Cd is run on the ChatComponent, the bindings are run and updated. The bindings in the component, especially the function viewRun, are called and the result is rendered on the DOM during DOM interpolations.

We are actually calling a function on the template of the ChatComponent component. What will happen if the function ‘viewRun’ doesn’t finish quickly? The users will experience drag or slowdowns.

For demo purposes, let’s make the ‘viewRun’ function execute very slowly, like to take several seconds before it finishes.

function viewRun() {
    function wait(ms) {
        let now = Date.now()
        let end = now + ms
        while (end > now) {
            now = Date.now()
        }
    }
    wait(20000)
}

See, we made the viewRun run in 20 secs!! That’s a huge performance impairment. So the users will have to wait for> 20s to see something render on the screen.

Best practices in Angular warns us not to compute heavy operations in the template, we should only execute functions that finish immediately.

Optimize Template Expressions

Let’s look at another example:

function factorial(num: number) {
    if (num == 1)
        return 1
    return num * factorial(num - 1)
}
@Component({
  selector: 'app-users',
  template: `
      <div *ngFor="let user of users">
        {{user.name}}
        {{user.id}}
        Factorial of User ID {{factorial(user.id)}}
      </div>  
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
class Users {
  @Input() users: Array<User> = []
}
@Component({
  selector: 'app',
  template: `
    <app-user [users]="users"></app-user>
    <button (click)="addToUsers($event)">Add A User</button>
  `
})
class App {
  users: Array<User> = [
    {
      id: 99,
      name: "Nnamdi"
    },
    {
      id: 88,
      name: "David"
    },
    {
      id: 77,
      name: "Philip"
    },
    {
      id: 66,
      name: "Chidume"
    }
  ]
  lastId = 66
  addToUsers() {
    this.users = this.users.concat([
      {
        id: this.lastId++,
        name: "User #ID: " + this.lastId 
      }
    ])
  }
}
interface User {
  id: number;
  name: string;
}

We have three components, Users and App components. In the Users component, it lists a users data stored in an array using the *ngFor directive. It displays the name, id and the factorial number of the user's id. The Users component is an OnPush component, it is a "pure" component in the sense that Angular updates the component when the input bindings in the component change. When we click the Add A User button a new user is added to the users array, this causes the Users component to re-render to display the new user.

Now, for every user added to the users array, the factorial template expression for all the users. Now, imagine we have about 1000 users in our array and displayed on the screen and we clicked to add another user to the array. The factorial template expression will be run for the 1001 users !!! We all know that calculating the factorial of numbers can become CPU intensive.

Although we have optimized our component not to run on unnecessary CD triggers yet we have issues in the template expressions. Yes, we can’t do away with the factorial template expressions but we will find a way of making execute less when a new user is added to the array.

We will use Caching/Memoization to make our factorial function execute less frequently. What does it mean?

Our factorial function is straight-forward and doesn’t have side-effects, what you give it, it produces an output based on the input. We can memoize it, by making it store the results of each input and return on subsequent calls when the factorial function is called with the same inputs.

Example:

In the factorial, to find a factorial of a number, it’s multiplied subsequently by its difference.

2! = 2 * 1
4! = 4 * 3 * 2 * 1
6! = 6 * 5 * 4 * 3 * 2 * 1
10! = 10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1

Look at 4! = 4 * 3 * 2 * 1, do you know we can only calculate for 4 * 3 and return 2! its prev calc. Instead of calculating it all over again whereas we have already done that earlier.

4! = 4 * 3 * 2!

That’s memoization, instead of calc a new value afresh we simply look it up to know whether it has already been calculated previously and get the result from there.

Lets memoize the factorial function:

function factorial(num) {
    if (!factorial.cache) {
        factorial.cache = {}
    }
    if (!factorial.cache[num]) {
        if (num == 1 || num == 0)
            return 1
        factorial.cache[num] = num * factorial(num - 1)
        return factorial.cache
    }
    return factorial.cache[num]
}

Wow!! lots of code. All we did here is just look a num in the cache factorial.cache object, if it's present the result is returned if not the factorial is performed and the result in the cache and returned.

Let’s test it. We will call 2! first followed by 4!. In 2! call, We would expect to have cache misses for 2 and 1 because it is the first time of calling with the inputs. Then, in 4! we would expect cache misses for 4, 3 but a cache hit for 2.

// ...
function factorial(num) {
    if (!factorial.cache) {
        factorial.cache = {}
    }
    if (!factorial.cache[num]) {
        c.log("cache miss: ", num)
        if (num == 1 || num == 0)
            return 1
        factorial.cache[num] = num * factorial(num - 1)
        return factorial.cache
    }
    c.log("cache hit: ", num)
    return factorial.cache[num]
}
c.log("2! call")
factorial(2)
c.log("\n")
c.log("4! call")
factorial(4)
[email protected] MINGW64 /c/wamp/www/developerse/projects/trash
$ node csl
2! call
cache miss:  2
cache miss:  1
4! call
cache miss:  4
cache miss:  3
cache hit:  2

Perfect!! like we expected. If we apply this to our Users component we will greatly minimize the rate of factorial deep calculations, don’t get it twisted, the factorial function will always be called when the input bindings changed and CD is triggered, we just optimized it not to do unnecessary calculations that it had previously done, only for the new user added.

function factorial(num: number) {
    if (!factorial.cache) {
        factorial.cache = {}
    }
    if (!factorial.cache[num]) {
        c.log("cache miss: ", num)
        if (num == 1 || num == 0)
            return 1
        factorial.cache[num] = num * factorial(num - 1)
        return factorial.cache
    }
    c.log("cache hit: ", num)
    return factorial.cache[num]
}
@Component({
  selector: 'app-users',
  template: `
      <div *ngFor="let user of users">
        {{user.name}}
        {{user.id}}
        Factorial of User ID {{factorial(user.id)}}
      </div>  
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
class Users {
  @Input() users: Array<User> = []
}
// ...

Using Pure Pipes

We can do the same using pure pipes. What are pure pipes? Pure pipes return the same output for particular inputs, they do not have side-effects on the program. Pure pipes evaluate a given expression only if they have been giving a different set of inputs from the prev. ones.

Examples of pure pipes in Angular are Decimal pipe, Number pipe, Percent pipe, Currency pipe, and Date pipe.

These pipes are pure because:

  • They do not have side-effects
  • They return a specific output for a specific input.

Taking the Date pipe as an example

{{ 10 June 1985 | date: 'shortDate'}}
6/10/85

Now, the first time we passed in 10 June 1985, Angular will evaluate the expression but on subsequent calls with the same input, the expression will not be evaluated but the prev. value will be returned because the arguments haven't changed.

Let’s make our factorial function into a pure pipe:

function factorial(num: number) {
    if (num == 1)
        return 1
    return num * factorial(num - 1)
}
@Pipe({
  name: 'factorial',
  pure: true
})
class Factorial {
  transform(val: number) {
    return factorial(val)
  }
}

See we moved the uncached version of our factorial function inside a pure pipe, we will no longer need our cached version Angular will provide it for us for free!! :)

We can now remove the {{ factorial(user.id) }} template expression and replace it with {{ user.id | factorial }}.

Using Decorators

Decorator is a new feature in TypeScript introduced to add additional info to a class, method, property during runtime. It is denoted using the @ symbol.

Angular made heavy use of Decorators:

  • @Component
  • @Directive
  • @Output
  • @Input
  • @Optional
  • @Self
  • @Injectable
  • @NgModule
  • @Pipe
  • … etc

We can use the decorator to add caching mechanisms in our app.

function memoize(fn: any) {
    return function () {
        var args =
Array.prototype.slice.call(arguments)
        fn.cache = fn.cache || {};
        return fn.cache[args] ? fn.cache[args] : (fn.cache[args] = fn.apply(this,args))
    }
}
function purify () {
  return function(target: any, key: any, descriptor: any) {
    const oldFunc = descriptor.value
    const newFunc = memoize(oldFunc)
    descriptor.value=function() {
        return newFunc.apply(this,arguments)
    }
  }
}

We have our method decorator purify, it returns a function that takes three params: target, the class of the method in which the class will be decorated with; key, the name of the method which is decorated; descriptor, an object containing the function/method in its value property which is decorated.

In the implementation we save the original function in the oldFunc variable, then we memoize it by passing the oldFunc to the memoize function. The memoize function is a general function used to memoize functions. The returned memoized function is saved in the newFunc variable. Next, we assign a new function to the descriptor.value property, the function implementation calls the newFunc (the memoized version) and returns the value.

To test it out, we would create a mock class with a factorial method and decorate it with the purity decorator.

class PureClass {
    @purify()
    factorial(num: number): number {
        console.log("cache miss: ", num)
        if (num == 1)
            return 1
        return num * this.factorial(num - 1)
    }
}

See we decorated the factorial method with @purify(). Let's run it:

const p = new PureClass()
console.log(p.factorial(2))
console.log(p.factorial(4))
[email protected] MINGW64 /c/wamp/www/developerse/projects/trash/di-ts
$ ts-node mem
cache miss:  2
cache miss:  1
2
cache miss:  4
cache miss:  3
24

See we have a cache miss for 2 because it is the first time it is called on subsequent times it will be returned from cache. For 4, we have a cache miss for 4 and 3 and a hit for 2 because 2 have been cached previously. If we call for 6 we will have a cache hit for 4,3,2 but not for 6 and 5 because it is the first time the function is seeing it.

Now, let’s apply it to our Angular class component:

@Component({
  selector: 'app-users',
  template: `
      <div *ngFor="let user of users">
        {{user.name}}
        {{user.id}}
        Factorial of User ID {{factorial(user.id)}}
      </div>  
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
class Users {
  @Input() users: Array<User> = []
  @purify()
  factorial(num: number) {
      if (num == 1)
          return 1
      return num * factorial(num - 1)
  }
}

Conclusion

We learned how to optimize template expressions in this post. We saw the slowdowns that may occur if we run a high performant function in our templates. Also, we looked at different ways we can highly optimize the template expressions by caching the functions:

  • memoization
  • pure pipes
  • decorators

In the next of our Angular runtime performance checklist series, we will be looking at how to optimize *ngFor directive in our template to avoid unnecessary DOM manipulations. Till then.

If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email or DM me.

Thanks !!!

angular javascript angularjs

What's new in Bootstrap 5 and when Bootstrap 5 release date?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

Deno Crash Course: Explore Deno and Create a full REST API with Deno

How to Build a Real-time Chat App with Deno and WebSockets

Convert HTML to Markdown Online

HTML entity encoder decoder Online

Random Password Generator Online

HTML Color Picker online | HEX Color Picker | RGB Color Picker

Basics of Angular: Part-1

What is Angular? What it does? How we implement it in a project? So, here are some basics of angular to let you learn more about angular. Angular is a Typesc

Migrating from AngularJS to Angular

Migrating from AngularJS to Angular a hybrid system architecture running both AngularJS and Angular

How to Build an Angular Application with Angular CLI

How to set up the Angular CLI and generate a Trivial App

What is the difference between JavaScript and AngularJS?

JavaScript is a client-side programming language used for creating dynamic websites and apps to run in the client's browser whereas AngularJS is a fully featured web app framework established on JavaScript and managed by Google.

What’s the difference between AngularJS and Angular?

Angular vs Angularjs - key differences, performance, and popularity