Raúl Jiménez
elecash@gmail.com
@elecash
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
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
}
]
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();
}
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
}
]
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();
}
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
}
]
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>
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
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
Introducing ts.data.json
Source: joanllenas/ts.data.json
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
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
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
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
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>