Last active
August 31, 2023 08:45
-
-
Save jsonberry/496a024e63b86b4581807bbabd038986 to your computer and use it in GitHub Desktop.
Angular: RxJS + Lodash for getting deeply nested data out of an Observable stream and into presentational components
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// .js extension for syntax highlighting - all files are actually .ts | |
// A general service that makes an HTTP call to an API | |
import { Injectable } from '@angular/core'; | |
import { HttpClient } from '@angular/common/http' | |
@Injectable() | |
export class ApiService { | |
constructor ( | |
private http: HttpClient, | |
) { } | |
get data$ () { | |
return this.http.get('/some/api') // Returning an Observable | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Presentational List Component | |
import { Component, OnInit, Input } from '@angular/core'; | |
@Component({ | |
selector: 'my-list-item', | |
template: ` | |
<p>{{ message }}</p> // "an orginal message string hamburgers" | |
<p>{{ date | date }}</p> // "Oct 4, 2017" | |
`, | |
styleUrls: ['./something-cool-data.component.scss'] | |
}) | |
export class MyListItemComponent implements OnInit { | |
public date // This just helpes to make the template a little bit cleaner | |
public message | |
@Input() set props(props) { // Just showing off a way to do some stuff in an @Input if you need, like data checking | |
const { date, message }: { date: Date, message: string } = props | |
Object.assign(this, { | |
date, | |
message | |
}) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Presentational List Component | |
import { Component, OnInit, Input } from '@angular/core'; | |
@Component({ | |
selector: 'my-presentational-list', | |
template: ` | |
<my-list-item *ngFor="let prop of props" | |
[props]="item"> | |
</my-list-item> | |
`, | |
styleUrls: ['./something-cool-data.component.scss'] | |
}) | |
export class MyPresentationalListComponent implements OnInit { | |
@Input() props | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { filter, flow, getOr, map, pick } from 'lodash/fp' | |
export { | |
mySelector | |
} | |
// Assume we have a data structure like this: | |
interface MyCoolInterface { | |
whateverIDontNeedIt: object[] | |
id: string | |
somethingElseIDonotCareAbout: whocares[] | |
events: Event[] // We care about these | |
} | |
interface Event { | |
type: 'tweet' | 'email' | 'snailMail' | 'chat' | |
data: { | |
blob: { // just to represent some deeply nested data | |
id: string | |
message: string | |
dateTime: Date | |
read: boolean | |
} | |
} | |
} | |
/* | |
This is the data structure that will be fed into the presentational component | |
example: https://runkit.com/jsonberry/59d5bdf05acdf30012d339c2 | |
[{ | |
dateTime: "2017-10-04T05:54:45.743Z" | |
message: "an orginal message string hamburgers" | |
}] | |
*/ | |
function mySelector (data$): Observable { | |
return data$.map(() => { // This is still returning an Observable, but we have manipulated the output for presentation | |
return transformData(data$) // The async pipe in the Data component is handling the Observable subscription and teardown | |
}) // ---> IMPORTANT NOTE ---> this example is not fully fleshed out, when I have some more time I'll update it, but this is "basically" how it works | |
} | |
/* | |
The secret sauce for data transforming and getting deeply nested values | |
Some reading on the techniques: | |
https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba | |
https://blog.pragmatists.com/higher-order-functions-in-lodash-3283b7625175 | |
*/ | |
const unreadChatMessages = obj => obj.type === 'chat' && !obj.data.blob.read | |
const formatDate = obj => ({ ...obj, dateTime: obj.dateTime.toISOString() }) | |
const addHamburgers = obj => ({ ...obj, message: `${obj.message} hamburgers` }) | |
const transformData = flow( | |
getOr([], 'events'), | |
filter(unreadChatMessages), | |
map( | |
flow( | |
getOr({}, 'data.blob'), | |
pick(['message', 'dateTime']), | |
formatDate, | |
addHamburgers | |
) | |
) | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// A data component, repsonsible for connecting to Angular services, and providing data to presentational components | |
import { Component, OnInit } from '@angular/core'; | |
import { Observable } from 'rxjs/Observable'; | |
import { ApiService } from '../api.service'; // Our majesteic API service | |
import { mySelector } from './myselector'; // A magical place full of wonder and joy | |
@Component({ | |
selector: 'my-cool-data', | |
template: ` | |
<my-presentional-list | |
[props]="data$ | async"> // Using the async pipe helps with all the Observable cleanup mumbojumbo | |
</my-presentional-list> | |
`, | |
styleUrls: ['./something-cool-data.component.scss'] | |
}) | |
export class MyCoolDataComponent implements OnInit { | |
public data$: Observable<any> | |
constructor ( | |
private apiService: ApiService | |
) { } | |
ngOnInit () { | |
this.data$ = this.apiService.data$ | |
.let(mySelector) // https://www.learnrxjs.io/operators/utility/let.html | |
} // We'll use the `let` Observable method to pass the Observable stream through a transform | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment