Calling functions from Angular templates

So, your Angular UI logic is getting a little complex and to neaten things up you decide to wrap it all up in a function and pop it in the .component file instead. Your html looks cleaner and everything works great. Only problem is if you throw a console.log() in that function you’ll see it is getting called many times a minute or even second. Here’s an example from a project I am working on:


getDaysUntillExpiry() {
  console.log('here');
  const expires = this.challenge.expires;
  const now = moment();
  const duration = moment.duration(expires.diff(now));
  return duration.humanize();
}
A generic square placeholder image with rounded corners in a figure.
This was about 5 seconds of logs

On a desktop or laptop you might be happy with this but on a mobile device (clock the ion tag in the template file) I’m not happy with this. This is getting called repeatedly because of how Angular monitors state and state changes, it has no idea if the contents of that function have changed so it calls it often to make sure the UI is up to date.

You could move to OnPush change detection but that doesn’t always solve the problem depending on the size and scope of your component. There are a couple of better solutions to this:

  • we can just manually calculate the value since we know when it changes
  • we can use a pipe in the UI to calculate the value

Option 1

For the above example the value never changes so option 1 is the most sensible approach. Add a component property called expiryDate, call getDaysUntillExpiry() in init() once the data is ready and then bind the UI to the new property {{ expiryDate }}.

// add this property to the component
expiryDate = '';

// assign a value when ready
this.expiryDate = this.getDaysUntillExpiry();

Option 2

For more complex changes (or if you just fancy playing around with pipes like I did) then option 2 is an alternative. Here is a more complex example that searches through a sub-object based on a toggle selection in the UI and the user’s gender to determine the workout weights they should use for a CrossFit WOD.

Side note: CrossFit workouts have recommended weights for your level (RX, Scale 1 … Scale N = hardest, easier … easiest) for both male and female. A benchmark workout like Grace is 30 Clean and Jerks as quick as you can at 135 lbs for men and 95 lbs for women.

First (bad) approach:

getScaleWeight(movement: Movement) {
  console.log('here');
  const scaleKey = `${this.scale}_${this.user.gender}`;
  const foundScale = movement.scales.find(scaleVal => {
    return !!scaleVal[scaleKey];
  });
  if (foundScale) {
    return foundScale[scaleKey];
  }
}
A generic square placeholder image with rounded corners in a figure.
As expected, many logs

So, using a pipe we need to pass in the movement, the chosen scale and the user’s gender. Since I plan to use a pipe for the weight units (lbs or kgs) this seems like a good place to use a pipe.

// the pipe
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'movementScale'
})
export class MovementScalePipe implements PipeTransform {

  transform(movement: any, ...args: any[]): any {
    console.log('here');

    const scale: string = args[0];
    const gender: string = args[1];

    const scaleKey = `${scale}_${gender}`;

    // find scale
    const foundScale = movement.scales.find(scaleVal => {
      return !!scaleVal[scaleKey];
    });
    if (foundScale) {
      return foundScale[scaleKey];
    }
    return null;

  }
}

Don’t forget to include your pipe in your .module file and then:

{{ movement.name }} x {{ movement.reps }}: {{ movement | movementScale:scale:user.gender }} lbs

Simple as that we now just get log calls when the scale is changed in the UI 😀