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 thenameas our first parameter, and the succeeding parameters will be theweight,height,wins, andlosses. Theformatmethods append a string to the numeric parameters likeheight,weight,wins, andlosses.
Fighter profile pipe on Stackblitz by the author.
Single fighter profile
To use thefighterProfilepipe 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 lossesAn 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 thelegacyFighterProfilesin 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 throughlegacyFighterProfilesListand 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 ourFighterProfileinterface.
export interface FighterProfile { name: string; weight: number; height: number; wins: number; losses: number;}Thetransformmethod in our pipe class will take a singlefighterProfileparameter and return the formatted string. The format methods append a string to ourfighterProfileinterface’s numeric fields.
Fighter profile pipe on StackBlitz by the author.
Single fighter profile
To use thisfighterProfilepipe, we can initialize a property with theFighterProfiletype.
this.singleProfile = { name: 'Khabib Nurmagomedov', wins: 29, losses: 0, weight: 155, height: 178};In our template, we will pass thesingleProfileproperty as a single parameter to ourfighterProfilepipe.
{{ singleProfile | fighterProfile }}Our output will be:
Khabib Nurmagomedov, 155 lbs, 178 cm, 29 wins, 0 lossesAn 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 theFighterProfiletype. The method will accept onelegacyFighterProfileparameter. It will use some of thelegacyFighterProfilefields to return theFighterProfiletype.
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 thelegacyFighterProfilesvalue to our component’slegacyFightersListproperty.
this.legacyFightersList = legacyFighterProfiles;Then iterate through the list in our template. Apply the pipe to eachfighterProfilearray 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 theFighterProfileobjectdirectly 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, andlossesvalues.
{ name: fighter.bio.name, wins: fighter.mmaRecord.wins, losses: fighter.mmaRecord.losses, weight: fighter.bio.weight, height: fighter.bio.height } | fighterProfileIf 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.heightThanks for reading.