Raúl Jiménez
elecash@gmail.com
@elecash
Using Elm's Json Decoders in Angular
about me

Byte
Default

What's
Elm?
Definition
Elm is a programming language for declaratively creating web browser-based graphical user interfaces. Elm is purely functional, and is developed with emphasis on usability, performance, and robustness. It advertises "no runtime exceptions in practice".
Source: wikipedia
NO
RUNTIME
EXCEPTIONS
Features
- Purely functional
No OOP here
- Elm Public Library
Can detect changes in your dependencies
- Immutability
Enforced by the compiler
- Statically typed
With type inference like TypeScript
Elm's
Decoders
Why?
- Web apps load a lot endpoints
- How can we protect against those changes?
- Front-end doesn't know if the endpoint changed
- Web apps uses a lot of third party APIs
For example
We load this data
[ { "title": "Beers", "count": 12, "tags": [ "beer", "Żywiec", "Okocim", "Tyskie" ], "available": true }, { "title": "Nutella", "count": 1, "tags": [ "nutella", "chocolate", "cream", "gluttony" ], "available": true } ]
Later in your code...
We read the tags in the HTML
<ul> <li *ngFor="let item of (shoppingList$ | async).items"> {{ item.title }} <p *ngIf="item.tags.length"> <span *ngFor="let tag of sortedTags(item)">{{ tag }}, </span> </p> </li> </ul>
Or TypeScript code
sortedTags(item: ShoppingItem) { return item.tags.sort(); }
The API changes...
We don't get noticed or a third-party lib publish a fix instead of a breaking change
[ { "title": "Beers", "count": 12, "meta": { "tags": [ "beer", "Żywiec", "Okocim", "Tyskie" ], }, "available": true }, { "title": "Nutella", "count": 1, "meta": { "tags": [ "nutella", "chocolate", "cream", "gluttony" ], }, "available": true } ]
Result
We have a runtime exception in our template
<ul> <li *ngFor="let item of (shoppingList$ | async).items"> {{ item.title }} <p *ngIf="item.tags.length"> <span *ngFor="let tag of sortedTags(item)">{{ tag }}, </span> </p> </li> </ul>
We have a runtime exception in TS code
sortedTags(item: ShoppingItem) { return item.tags.sort(); }
It could be worse...
If they change "tags" to an array of Objects
[ { "title": "Beers", "count": 12, "tags": [ { "name": "beer"}, { "name": "Żywiec"}, { "name": "Okocim"}, { "name": "Tyskie"} ], "available": true }, { "title": "Nutella", "count": 1, "tags": [ { "name": "nutella" }, { "name": "chocolate" }, { "name": "cream" }, { "name": "gluttony" } ], "available": true } ]
Result
If we're not sorting the list it will display
[object Object]
<ul> <li *ngFor="let item of (shoppingList$ | async).items"> {{ item.title }} <p *ngIf="item.tags.length"> <span *ngFor="let tag of item.tags">{{ tag }}, </span> </p> </li> </ul>
The
Elm's Decoders
Definition
A decoder is a function that can take a piece of JSON and decode it into an Elm value, with a type that matches a type that Elm knows about.
Source: javascriptplayground
Decoders in Elm
Taking as example our JSON file:
import Json.Decode as Decoder shoppingItemDecoder : Decoder.Decoder ShoppingItem shoppingItemDecoder = Decoder.map4 (\title count tags available -> ShoppingItem title count tags available) (Decoder.field "title" Decoder.string) (Decoder.field "count" Decoder.int) (Decoder.field "tags" (Decoder.list Decoder.string)) (Decoder.field "available" Decoder.bool) shoppingListDecoder : Decoder.Decoder ShoppingList shoppingListDecoder = Decoder.list shoppingItemDecoder
We can transform our JSON file to an Elm value with the "shoppingListDecoder" function
Decoders in Angular
Introducing ts.data.json
Source: joanllenas/ts.data.json
- TypeScript library
- Works perfectly with NGRX
- Framework agnostic
- There are others but this is ours
Features
- String
- Number
- Boolean
- Objects and strict Objects
- Arrays and Dictionaries
- Optional values
- Failovers
- and more...
decoder.ts
Create a data decoder
export const ShoppingItemDecoder = JsonDecoder.object<ShoppingItem>({ title: JsonDecoder.string, count: JsonDecoder.number, tags: JsonDecoder.array<string>(JsonDecoder.string, 'ShoppingItemTags[]'), available: JsonDecoder.boolean }, 'ShoppingItemDecoder'); export const ShoppingListDecoder = JsonDecoder.array<ShoppingItem>( ShoppingItemDecoder, 'ShoppingItemDecoder[]' );
We can nest decoders to create complex objects or reuse decoders
app.component.ts
Execute action to load the data from the view
ngOnInit() { this.subscriptions.push( this.actions$.pipe( filter(action => action.type === ShoppingListActions.LOAD_SHOPPING_LIST_FAILURE) ).subscribe( action => console.log(action) ) ); this.store.dispatch(loadShoppingList()); }
If we want to react programmatically to the actions we can subscribe to them
service.ts
Load the data from the service
@Injectable() export class ShoppingListService { constructor(public http: HttpClient) {} getShoppingList() { return this.http.get<ShoppingItem[]>('assets/mocks/list.json').pipe( concatMap(p => fromPromise(ShoppingListDecoder .decodePromise(p) .catch(e => { throw new Error(e); }) )) ); } }
And decode the result...
or catch the errors
effects.ts
Process the response in the effects
loadShoppingList$ = createEffect(() => this.actions$.pipe( ofType(ShoppingListActions.LOAD_SHOPPING_LIST), mergeMap(() => this.shoppingListService.getShoppingList() .pipe( map(items => ({ type: ShoppingListActions.LOAD_SHOPPING_LIST_SUCCESS, payload: items })), catchError(error => of({ type: ShoppingListActions.LOAD_SHOPPING_LIST_FAILURE, payload: error.message })) ) ) ));
And return a success...
or an error
Demo using NGRX
Print the data in the template
<span *ngIf="(shoppingList$ | async).isLoading">loading...</span> <ul> <li *ngFor="let item of (shoppingList$ | async).items"> {{ item.title }} <p *ngIf="item.tags.length"> <span *ngFor="let tag of sortedTags(item)">{{ tag }}, </span> </p> </li> </ul> <div *ngIf="(shoppingList$ | async).error"> {{ (shoppingList$ | async).error }} </div>
Demo
Summary
Summary
- Decoders protects our UI against API changes
- Some API changes may be hard to detect
- We can nest decoders to reuse code
- It's easy to integrate into an NGRX workflow
- We can react to decoding errors easily
Resources
Resources
- Elm Language
elm-lang.org
- Angular
angular.io
- NGRX
ngrx.io
- TypeScript JSON decoding library
github/ts.data.json
GRACIAS
Using Elm's Json Decoders in Angular
By Raúl Jiménez
Using Elm's Json Decoders in Angular
Angular Elements slides for my talk at ng-poland 2019
- 1,976