Let’s be honest, it’s pretty common that when your pipe isn’t running how you think it should in Angular, you can sometimes just tack on the “pure : false” tag like so
@Pipe({
name: 'myPipe',
pure : false
})
And you just hope for the best. But this actually has severe consequences for your application, and in some cases can grind your entire application to a halt. It’s also extremely easy to end up with infinite loops or memory issues as an impure pipe can easily end up running far more often than you realize. But let’s rewind a bit, what’s this “pure” business about anyway?
Pure Pipes
A “pure” pipe (Which I have to say, I don’t like the naming.. It’s not that intuitive…), is an Angular Pipe that only runs when the underlying variable value changes. So for example if I had the following
{{ myVariable | myPipe }}
Then myPipe would only run when myVariable changes value. This typically means on load, and then anytime that variable is touched. But there is a small caveat. Let’s say for example I have a variable that looks like so :
myVariable : any = { propertyA : "foo"};
And then I am piping my variable as normal
{{ myVariable | myPipe }}
What happens when I do the following?
myVariable.propertyA = "bar";
Does the pipe run again? The answer is actually no. The easiest way to understand this is that it’s only looking if the top level value of the variable changes. It does not look at child properties at all. The only way for this pipe to be run again is to change the entire instance of myVariable like :
myVariable = { propertyA : "bar"}
But that’s somewhat impractical. You can’t be rebuilding the entire object every time just so a pipe can run. And you may even be trying to pipe an object you have no control over how it’s being set. So that’s where impure pipes come in.
Impure Pipes
Introducing Impure pipes, you can make *any* Pipe impure just by setting the pure flag to false in your declaration :
@Pipe({
name: 'myPipe',
pure : false
})
Now instead of running every time the value changes, it runs often, *much* more often. Specifically it runs every digest cycle which is essentially Angular’s internal loop. Generally speaking, you may be running the pipe every time you move your mouse or use your keyboard. There’s a couple of issues this may cause :
- You are now running code irrespective of changes to the underlying data, meaning there is a lot of time wasted doing unneeded work.
- Depending on how heavy your pipe is, you could slow down your app trying to process work that doesn’t need to be done.
The second point is important. If your Pipe is doing something with a large piece of data, for example sorting a large array, it’s impractical to be doing this every digest cycle.
You’ll even notice this little tidbit from the Angular documentation around the lack or filter/orderBy pipes in the framework :
Angular doesn’t provide pipes for filtering or sorting lists. Developers familiar with AngularJS know these as filter and orderBy. There are no equivalents in Angular.
This isn’t an oversight. Angular doesn’t offer such pipes because they perform poorly and prevent aggressive minification. Both filter and orderBy require parameters that reference object properties. Earlier in this page, you learned that such pipes must be impure and that Angular calls impure pipes in almost every change-detection cycle.
Filtering and especially sorting are expensive operations. The user experience can degrade severely for even moderate-sized lists when Angular calls these pipe methods many times per second.
So as a framework, Angular has gone to pains to point out that even pure pipes should be lightweight and try and do as little heavy lifting as possible. Let alone if you are doing impure pipes.
A good trick to get into is to just add a simple console.log inside your pipe to see how often it’s being run, the results may blow your mind and make you think twice about switching to impure.
Good Examples Of Impure Pipes
For good examples of impure pipes, we can look at which pipes in the framework are marked as impure. These are :
- JsonPipe
- SlicePipe
- KeyValuePipe
Key to all 3 of these pipes are that they are working on objects, not primitive values (So require Impure pipes to detect “deep” changes), and all of them do a very simple process to output the data. For example the JsonPipe simply does a stringify on an object (Essentially a one liner). It doesn’t mean that these are particular performant and should be used everywhere, but they are a good example of “when you have to”.
As an example of the absolute worst thing you could do would be to make an HTTP Call from an Impure pipe. This will surely result in thousands of HTTP calls to an API and your page grinding to a halt.
Using Input Parameters Instead Of Impure Pipes
There may be times where you need to pipe a particular object, but within that object you are only using a couple of properties to derive the pipe output. You can get around having to use an impure pipe by instead using input parameters on your pipe. Using our object from earlier :
myVariable : any = { propertyA : "foo"};
We can then create a pipe that looks like so :
@Pipe({
name: 'myPipe'
})
export class myPipe implements PipeTransform {
transform(myVariable : any, inputParam : any): string {
return inputParam;
}
}
In my HTML I can then call the pipe like so :
{{ myVariable | myPipe:myVariable.propertyA }}
The pipe will run when myVariable changes *and* when propertyA changes. You don’t even have to use the parameter (Or the initial input variable) inside the pipe, but you can still use them to “trigger” the pipe. This can also be handy when you need the pipe to trigger based on another objects parameter. This can feel hacky at times and if your pipe is lightweight enough, it can be simpler to just to go impure, but it’s an option if you don’t want things firing multiple times per second.
💬 Leave a comment