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();
}
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:
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();
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];
}
}
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 😀