Slaying a UI Antipattern with Angular
[Update Dec 17, 2018: Check the new ngx-remotedata library]
Kris Jenkins How Elm slays a UI antipattern blog post about fixing the UI states when loading remote data has been around for almost a couple of years now and its popularity has transcended the Elm ecosystem. There are a few blog posts explaining how to achieve the same results with other languages / frameworks but none of them are based on Angular2+. In this article I’ll try to explain how to achieve something similar with Angular and TypeScript.
The example is an application that gives you the sunrise and sunset times of the current day given the latitude and longitude of any place in the world.
The UI displays a Loading message while the data is being loaded and it also supports displaying an error in case an error occurs.
The Boolean approach
Before giving you the final solution let’s see an intermediate working version of the application in which we handle the remote data UI states using booleans.
What illustrates the boolean approach in this example is the
SunriseState in the
sunriseSunset.service.ts file, which we use to express all possible UI states.
( actually it should have been called SunriseSunsetService ¯\_(ツ)_/¯ )
Let’s see what each property means:
- isLoading: It‘s
truewhile the remote data is being fetched.
- error: It‘s either
null(no errors) or any string (there are errors).
- data: It’s either
null(no data) or an object (there is data).
There are a few problems with this approach but the main one is that it is possible to create invalid states such:
error: 'Fatal error',
As we can see our template also uses complex
*ngIf statements in order to make sure that we are displaying exactly what we should.
The RemoteData approach
The final solution uses an adaptation of Kris Jenkins’ RemoteData library. I have done it in a way that is effective but it is far from being as type safe as it would be if we were using Elm.
The key part that illustrates this approach is the RemoteData Union Type declaration:
Using this approach we are able to refactor our service to use one single property to describe the current UI state, effectively making it impossible to represent invalid states.
state: RemoteData<string, ServiceResult> = new NotAsked();
We’ve also added a new initial UI state not asked that tells the UI that it is our first visit and we haven’t even attempted to load any data yet.
Our template has also been simplified. Instead of using combinations of boolean checks in our
*ngIf statements we now use a single pipe for each state.