Why I Prefer to Use the Parameter Object Pattern in Angular Pipes
Why I Prefer to Use the Parameter Object Pattern in Angular Pipes
Rather than directly passing around parameters.
Source: Unsplash
I like to use the parameter object pattern to minimize the number of arguments that I need to pass around to a function or method. I first learned about this pattern fromClean Code: A Handbook of Agile Software Craftsmanship. Clean Code even suggests that three or more parameters are too much for a function.
So it makes sense to encapsulate these parameters into an object before passing them to a function if there are more than three.
In this post, I will start by dissecting how we would normally implement an Angular pipe which is passing the parameters directly. We will then go through how we would implement it using the parameter object pattern.
An Example — Fighter Profile Pipe
We will use this example in the entire blog post. Given an MMA fighter profile with the following data, for example:
- Name: Khabib Nurmagomedov
- Weight: 155 lbs
- Height: 158 cm
- Wins: 29
- Losses: 0
We are implementing an angular pipe that will format the above data into this one-line text:
Khabib Nurmagomedov, 155 lbs, 178 cm, 29 wins, 0 losses
I will call this pipe —fighterProfile
. Let’s go through the examples.
Using parameters
Let’s start with our pipe’s transform method if we are using parameters. We have thename
as our first parameter, and the succeeding parameters will be theweight
,height
,wins
, andlosses
. Theformat
methods append a string to the numeric parameters likeheight
,weight
,wins
, andlosses
.
Fighter profile pipe on Stackblitz by the author.
Single fighter profile
To use thefighterProfile
pipe above, we will pass the parameters directly to our template like the following.
{{ 'Khabib Nurmagomedov' | fighterProfile: 29: 0: 155: 178 }}
The output will be:
Khabib Nurmagomedov, 155 lbs, 178 cm, 29 wins, 0 losses
An array of fighter profiles
Now let’s inspect a more realistic example, an array of fighter profiles like below:
An array of fighter profiles on Stackblitz by the author.
I kept thelegacyFighterProfiles
in a separate file in my sample code. So we need to assign it to our component’s property first.
this.legacyFightersList = legacyFighterProfiles;
We will iterate throughlegacyFighterProfilesList
and apply the pipe for each fighter profile.
<ul> <li *ngFor="let fighter of legacyFightersList"> {{ fighter.bio.name | fighterProfile: fighter.mmaRecord.wins: fighter.mmaRecord.losses: fighter.bio.weight: fighter.bio.height }} </li></ul>
The above code should output:
- Conor McGregor, 22 wins, 6 losses, 155 lbs, 175 cm
- Khabib, 29 wins, 0 losses, 155 lbs, 178 cm
- Dustin Poirier, 28 wins, 6 losses, 155 lbs, 175 cm
You can find the full running example in StackBlitz:
Running code on Stackblitz by the author.
Parameter Object Pattern Using an Interface
If we want to use the parameter object pattern, we will have to pass the entire fighter profile as a single parameter. Unlike in our previous example, where each fighter profile attribute is its parameter. We will start by declaring ourFighterProfile
interface.
export interface FighterProfile { name: string; weight: number; height: number; wins: number; losses: number;}
Thetransform
method in our pipe class will take a singlefighterProfile
parameter and return the formatted string. The format methods append a string to ourfighterProfile
interface’s numeric fields.
Fighter profile pipe on StackBlitz by the author.
Single fighter profile
To use thisfighterProfile
pipe, we can initialize a property with theFighterProfile
type.
this.singleProfile = { name: 'Khabib Nurmagomedov', wins: 29, losses: 0, weight: 155, height: 178};
In our template, we will pass thesingleProfile
property as a single parameter to ourfighterProfile
pipe.
{{ singleProfile | fighterProfile }}
Our output will be:
Khabib Nurmagomedov, 155 lbs, 178 cm, 29 wins, 0 losses
An array of fighter profiles
We will use the same array from our previous example.
An array of fighter profiles on StackBlitz by the author.
Let’s start by creating a method to convert the fighter profile fields into theFighterProfile
type. The method will accept onelegacyFighterProfile
parameter. It will use some of thelegacyFighterProfile
fields to return theFighterProfile
type.
fighterProfile(legacyFighterProfile: LegacyFighterProfile): FighterProfile { return { name: legacyFighterProfile.bio.name, weight: legacyFighterProfile.bio.weight, height: legacyFighterProfile.bio.height, wins: legacyFighterProfile.mmaRecord.wins, losses: legacyFighterProfile.mmaRecord.losses } as FighterProfile;}
To use this pipe in our template, assign thelegacyFighterProfiles
value to our component’slegacyFightersList
property.
this.legacyFightersList = legacyFighterProfiles;
Then iterate through the list in our template. Apply the pipe to eachfighterProfile
array element.
<ul> <li *ngFor="let fighter of legacyFightersList"> {{ fighterProfile(fighter) | fighterProfile}} </li></ul>
This is more readable than using parameters. However, due tohow Angular change detection works, this will unnecessarily call thefighterProfile()
method many times than it should be*.*
A better solution is to get rid of thefighterProfile()
method and declare theFighterProfile
objectdirectly in the template. We won’t need extra calls to the component’s method:
<ul> <li *ngFor="let fighter of legacyFightersList"> {{ { name: fighter.bio.name, wins: fighter.mmaRecord.wins, losses: fighter.mmaRecord.losses, weight: fighter.bio.weight, height: fighter.bio.height } | fighterProfile }} </li></ul>
You can find the full running example in StackBlitz:
Running code on Stackblitz by the author.
Conclusion
After we got rid of ourfighterProfile()
method in our example, it might look like we are implementing the parameter object pattern the same way as using parameters in our template.
However, the parameter object pattern implementation is slightly more readable. Because we are clear as to what parameters we are passing to our pipe i.e. we know if we are passingweight
,height
,wins
, andlosses
values.
{ name: fighter.bio.name, wins: fighter.mmaRecord.wins, losses: fighter.mmaRecord.losses, weight: fighter.bio.weight, height: fighter.bio.height } | fighterProfile
If you compare that with passing parameters directly. It feels unnaturalto pass these many parameters with colons in between. Not as easy to read as the previous example.
fighter.bio.name | fighterProfile: fighter.mmaRecord.wins: fighter.mmaRecord.losses: fighter.bio.weight: fighter.bio.height
Thanks for reading.