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,752