Suppose that you have a custom component in Angular that you use for rendering select inputs.
Thi component accepts, as an input, the options and it works something like that.
<custom-select-input [options]="options"></custom-select-input>Let's say also that the options must be an array like this one.
// somewhere inside your parent component
this.options = [
{
name: 'First option',
value: 1
},
{
name: 'Second option',
value: 2
},
// etc.
]Going further, you want to retrieve the options from an API, but the endpoint returns more data than the ones that you need formatted in the wrong way.
// Example API response
[
{
id: 1,
firstName: 'John',
lastName: 'Do'
},
{
id: 2,
firstName: 'Jane',
lastName: 'Do'
},
// etc.
]As you can see in the example above the response is not compatible with the format needed by the select input.
So, how you can approach this problem?
The imperative way: subscribe.
First of all, we need to know that Angular retrieve data from an HTTP call as an observable, so we need to use a subscribe or the async pipe in our parent component to assign the options to the select input.
In an imperative way, we subscribe to the response, manipulate it as we need, and assign it to our options variable.
// inside our parent component
ngOnInit() {
this.http.get('/api/getOptions')
.subscribe(options => {
this.options = options.map(option => ({
name: `${option.firstName} ${option.lastName}`,
value: option.id
}));
});
}And in the HTML we pass it through to the select input as we see earlier.
<custom-select-input [options]="options"></custom-select-input>This approach works well, but it's pretty imperative and it doesn't unsubscribe from the observable (that is a bad practice).
Yes, we can add the unsubscribe and fix that problem, but it still remains imperative. A better solution is to use the async pipe that handles the unsubscribe for us and give the code a declarative attitude.
The declarative way: the async pipe.
We need to transform our options variable to an observable, so we postpone to it a dollar sign ($) as a best practice.
this.options$ = this.http.get('/api/getOptions');And we add the async pipe in the HTML.
<custom-select-input [options]="options$ | async"></custom-select-input>If we stopped here however it doesn't work because the response from the API was formatted in a different way, so we need to pipe our observable and apply some more logic.
this.options$ = this.http.get('/api/getOptions')
.pipe(
map(options => options.map(option => ({
name: `${option.firstName} ${options.lastName}`,
value: option.id
})))
);The first map inside the pipe operator cycles through the results given by the observable, the second one goes through the options and formats them as we see in the imperative example.
This way we get the same behavior, but we use a real declarative approach, and our code is more readable.