Angular: Tips. The importance of Pipes
(Or why we all tend to use Angular incorrectly)
What we all like in Angular primarily is that it all mostly about templates. Templates are smart, dynamic, they use change detection to update themselves on a moment’s notice when a model is updated, the can manage HTML elements and nested components, enrich the markup with directives and make on-the fly data transformations, and the template syntax is amazing. The tools provided are vast and rich, but is it up to us to use them wisely.
But what is a Pipe anyway?
If you have ever used a terminal on any Linux distributive (Ubuntu, Debian, Mint, there are tons of them), then the concept of a pipe should sound familiar to you. It is an operator that takes a stream of inputs, transforms it, and returns. The pipes may be chained to perform complex operations, or they can be directed with some input parameters (like an ordinary function; well, after all a pipe is a function). Pipes in Angular are not literally the same, but the essence lying beneath is similar: a pipe takes some data as in input, performs some operation on it (presumably transforming it to something else; for example, the built-in date pipe in Angular takes a Date object/ISO string and formats it in the specified way, so, say, Mon Nov 27 2017 18:08:50 GMT+0400 (Caucasus Standard Time) may become Monday, November 27, 2017), and returns the result of that operation. The result may be then used in any arbitrary way — rendered inside the template, passed as an argument to a function, binded to an @Input property, passed to another pipe, anything legal in the template microsyntax.
This sounds useful, but not as useful, as it really is. Some developers tend to think of them as fancy enhancement, rather than an everyday tool, but that assumption is far from truth. So let’s dive inside it to find out some use cases.
Performing operations that require state and change detection
Change detection in Angular is amazing. If you are not familiar with the intricacies of how it really does work under the hood, I suggest you read this article. It provides a good glance on the process without diving to deep into the actual implementation (what it does vs how it is actually done) and is useful, if you want to understand how you can boost your app’s performance.
But nothing good comes without some flaws, and sometimes flaws are the cause of allowing so much freedom for the end developers to enjoy. The angular markup syntax allows us to use method invocations as bindings inside it, and here is some bad stuff coming along with this freedom.
Suppose we have a list of users, represented by objects, each of which contains a field named age, representing, surprisingly, the user’s age. And there is a section in which we want to display a text notice saying
There are users older than 25 years, of course, only if such users are present. So we think ‘Let’s have a method, which returns a boolean flag indicating, whether the notice should be displayed’. Okay, let’s go with that. Here’s how the component may look like:
So, this will do. The field is displayed when there are users that meet our conditions, and if we somehow remove them, the notice will disappear, and if we add them back in, the notice will reappear, and so on…
Looks nice, right? Let’s do a magic trick, and add a
console.log('I am working') statement inside our
filterUsers method, right before the
return statement, and see what happens next.
So it happens that we have lots of method calls almost all the time. Why does that happen? Well, the problem lies within the Angular change detection mechanism. You see, it does look hierarchically for any models that have been binded in a template, and try to find out whether their value has been changed. In a case of a method, how would change detection mechanism know if its return value has changed?
Problem is, it cannot. A function’s return value may depend on some sort of a state (
this keyword, some global variables, etc…), which will make it an impure function, and there is no way to determine (by static analysis) whether a function is pure or not, because if there was a way, then it would solve the famous Halting problem, which has been proven unsolvable by Alan Turing back in 1936 (heck, no one even thought about Angular back in those days, right?). This leaves just one option — if the change detection mechanism wants to find out, whether a function’s return value has changed or not, it has to invoke it. How often is Angular change detection triggered? Well, in the article above it is explained that change detection is triggered any time an asynchronous event happens (user input, setTimeout, Promise being resolved/rejected, etc…), and… this is a lot. Any time you move a mouse, press a key or make an HTTP call, the change detection will run, and invoke your method, and if you put some complex logic inside that method, it may affect your app’s performance poorly.
So, we should use pipes whenever we want to perform some logic on data inside our templates. And this is way more beautiful and reusable, than performing logic inside our component (filtering users anytime they change inside a component and reassigning them, so that the template is updated; we can then even set the change detection strategy to
OnPush to increase the performance, but this is an ugly hack rather than a solution, and even with performance boost, we are here trying to keep the harmony between performance and code quality, and the latter is completely broke with this one; pipes are here to bring the harmony back).
Chaining pipes allows us even more: we can delegate complex tasks to several pipes to find easy solutions for them. Some time ago I stumbled across this stackoverflow question. The person who asked it wanted to make authorized calls for images, using HTTP headers. This is impossible using plain HTML; the
<img> tag can make calls to any domains without restriction, but you cannot add headers to these calls. The proposed solution was to make XHR calls for images, setting the headers manually, and this is what I eventually did, but rather than writing some complex login in a component or service, I created a Pipe. Here is how it works: take a string, which is an image URL, create an XHR BLOB call (setting the headers beforehand), take the data back, read it and transform to base64, and finally place it inside the
src attribute. The challenge was that both XHR call and conversion to base64 are asynchronous processes, so we need to wrap them in an Observable and pass it on to the built-in
async pipe, so it will unwrap the result. Here is the code for the pipe:
And this is just how simple it is to use inside a template:
As a conclusion: Angular is powerful and provides a huge amount of tools. Try not to ever look at any of them as not very useful.