-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feedback (code structure): response to reddit post about enhancing this project #3
Comments
Hello @michael-small, |
@AIxHunter starting in Angular 19, there is a much better (but highly experimental) way to load things on demand: https://push-based.io/article/everything-you-need-to-know-about-the-resource-api I took this following code block from that article, for visual reference @Component()
export class MyComponent {
limit = signal(10);
todosResource = rxResource({
request: this.limit,
loader: (limit) => {
return this.http.get<Todo[]>(
`https://jsonplaceholder.typicode.com/todos?_limit=${limit}`
);
},
});
} The resource will call that API with the value of the TL;DR - a resource can do things like an HTTP get on initial load of a component and then when a signal it depends on changes. Some more important details about what resources add in value over just a
Where this ties in Because these resources can access their previous state to see when they were idling, and because they can manually be called to load, you can make this function function rxOnDemandResource<T>(
observableFn: Observable<T>
): ResourceRef<T | undefined> {
return rxResource({
loader: (param) => {
return param.previous.status === ResourceStatus.Idle
? of(undefined)
: observableFn;
},
});
} and use it like this response = rxOnDemandResource(
this.myService.callToAPI(
this.someSignalA(),
this.SomeSignalB()
)
); And then get the <label for="a">A</label>
<input [(ngModel)]="someSignalA" name="a" />
<label for="b">B</label>
<input [(ngModel)]="someSignalB" name="b" />
<button (click)="response.reload()" type="button">Load</button>
<div>
<b>"response" resource's states</b>
<p>isLoading(): {{ response.isLoading() }}</p>
<p>value(): {{ response.value() }}</p>
<p>error(): {{ response.error() }}</p>
</div> or in the TS someComputed = computed(() => {
return this.reponse.value() + 1
}) |
Hey, I am the person that responded to your reddit post.
TL;DR
app.component.ts
and how the files are fetched. The benefits of this refactor are spread out across the projects that I made which recreated the basic flow. Notes are in code comments in the main component file and a service defined above it, as well as some HTML unordered lists on each Stackblitz that you can read as you test out the demo.How I structured my examples of the code, and the overall benefits
I noticed that there is one method
getFiles()
that handles resetting and then setting assorted state, as well as the respective HTTP parameters. I think that with a few tweaks, that method and all of its responsibilities could be simplified or even removed.Before I summarize the three approaches, some overall similarities
delay(1000)
aka one second, but you could just put the param logic in thegetFormatedFiles
of yourDataService
isLoading
logic was moved into the service as well. This may be more of a personal preference, but in most projects I have been on or followed when it came to handling HTTP in services or stores that call them, the loading status tends to be put in services. I think it would be fine in the component as well though.finalize
to set the loading back to false. RXJS has plenty of specific operators, butfinalize
is really slick for scenarios like this since it handles when the observable terminates from both being completed or from an error. Pretty common to see it in the wild, but mostly for this scenario.finalize
convention.srcPaths
anddstPaths
were made to be "reactive" and "declarative". TheoriginalFiles
is also both of those things after the first example. You can find plenty of things if you search around with those terms, but the big benefits aresrcPaths
anddstPaths
can react to other changes and also have their whole state handled. This is done either by using an RXJS stream to map the values in relation tooriginalFiles$
, or in example number three with the relatively new Angular signals API.computed
values from some signal it depends on, rather than the terminology of RXJS with its pipes and some other fancy operators.Overall summary of the three approaches
takeUntilDestroyed
that is introduced in Angular 16 that is quite nice for scenarios like this.files$ = this.serviceName.getFiles()
, and once you need that in the template just usefiles$ | async
or in the componentfiles$.pipe(...)
to declare other values orfiles$.subscribe(...)
. No need to declare files in a function, or assign it in a constructor orngOnInit
.originalFiles
itself fully declarative, but does add some more RXJS understanding overhead.Subject
that stands in for the button click event happening. Then when that occurs, theswitchMap
operator "switches" into the HTTP observable and "maps" its value to the files observable.files$ = this.serviceName.getFiles()
, or in example 3,$files = toSignal(this.serviceName.getFiles()
. People normally do something like this in a method that is in a constructor or ngOnInit, but they are getting none of the reactive/declarative benefits@if
instead of*ngIf
and whatnot) introduced in v17. The current version of Angular is 18.2, and you could probably upgrade to it at your project's scale if you think it is worth it.toSignal
to allow events like thegetFilesEvent$
and the inner mapping of the HTTP call to be ultimately be handled in a synchronous manner. The src/dst paths don't need to be piped, don't need the async pipe, and otherwise are treated synchronously as something reacting to the changing of the original files. Also, theshareReplay(1)
is not needed since once the inner observable computes, the signal value for the files is purely synchronous and anything that depends on it isn't retriggering an observable stream.$files = toSignal(this.serviceName.getFiles()
in most other scenarios. Or if you want to be like scenario one where a button click manually subscribes, do that scenario but rather than.next
you.set
a signal.toSignal
ortoObservable
as needed.*ngIf/*ngSwitch/*ngFor
with@if/@switch/@for
, and has other benefits such as*<div ngIf="...; else loading">...</div> <ng-template #loading>...</ng-template>
to be able to do a mere else. With the new control flow, there is built in@else
and@else if (condition)
. There is similar wins in the other two types as well.The text was updated successfully, but these errors were encountered: