Are Angular Resolvers on Life Support ?
Jonathan Gamble
Posted on November 15, 2024
I was scrolling through Tech Twitter a few months back, when I saw this tweet by the infamous Brandon:
If you don't know, Brandon created AnalogJS, the NextJS-like meta framework for Angular. I'm a huge fan of what he does for the Angular community, so I had to respond. He will be the first to tell you I want to solve everything with resolvers.
And...
Not one... single... like or response.
I don't post a lot on Twitter, nor do I have a following, so I didn't think anything of it.
However, I came across this post again randomly and read the comments, and I realized no one agrees with me! I honestly wonder if they even understand what I'm talking about.
Two Ways to Load Data
There are actually two popular paradigms in JavaScript to load data.
1. Inside the Component
This was the first way I ever learned in Angular. When I first took Fireship's Original Angular Course, I never even learned about resolvers. Resolvers are not popular, and I think extremely misunderstood.
Brandon's example above shows the data being loaded AFTER the component has rendered. This is the same pattern for other frameworks:
-
React Query - Tanstack Query uses
useEffect
under the hood. Perhaps the first fetch pattern was created in React. -
Vue recommends
watch
-
SolidJS - uses
createResource
which returns a signal -
Qwik - has
useResource$
, which returns a signal -
Svelte - no recommended usage on GH for pure Svelte, although you would need to use
$effect
with.then()
instead ofasync
. Svelte 4 uses stores, which would follow the same unrecommended pattern inside the store. See Svelte 5 with Firebase -
Angular - Angular has always recommended the Observable / Behavioral Subject pattern, and now you can just use
effect()
. However, ngxtension has aderivedAsync
to do this for you, and Angular 19 will haveresource()
built in. In reality, RxJS is still too very intertwined with Angular, interceptors, and old school http client.
2. Inside a Load Function
-
NextJS - what used to be
getServerSideProps
is now just an async function inside a Server Component. You're server only, or an outside package like React Query. -
Nuxt - Nuxt has built in fetch functions
$fetch
which handle fetching once on the client and hydrating to the browser. You can also fetch inside a Server Component like in NextJS. - SvelteKit - the only recommended way in Svelte or SvelteKit, is inside a load function. This runs BEFORE the component is mounted, and can run on the server or client. They are NOT just for the server.
-
QwikCity - QwikCity has
routeLoader$
andserver$
for preloading data. Qwik "resumes," which doesn't require hydration and only runes once. -
SolidStart - uses a
query
function and withpreload
that runs on every route. - Angular - Angular has resolvers, which are perfect for this use case. However, no one seems to use them anymore.
What's your point?
Did you notice a pattern here? Server Side frameworks prefer load functions (resolver), while client frameworks fetch data in a signal reactively. But...
Angular is NOT a Server Side Framework!
The problem isn't that Angular is not an SSR frameworks, the problem is that it pretends to be.
- Adding
@angular/ssr
does not actually add any server features outside of hydration and automatic transfer state (except in the resolver of course). Nevertheless, technically React has Server Components, while NextJS takes advantage of them. Missing features include but not limited to.env
support,endpoints
,server components
,form actions
,server caching
,pre-loading data from server only
,bun, deno, cloudflare, non-nodejs support
, and of coursefile based routing
. Read any of my previous posts for work arounds on these things. - Notice Firebase App Hosting only supports Angular and NextJS, but NOT Analog, which is the actual Angular Server Side framework!
Now I don't expect the Angular Team to add all of my feature requests. However, it would be nice to have basic .env
support in the main builder, and the ability to make endpoints with the Angular Router. Brandon can handle the rest.
It is also still crazy to me that I can't deploy a basic Angular SSR application to Vercel.
Why Not Fetch Reactively?
I read an article about resolvers from 2019 that says the use case for resolvers is "very rare." Basically, you should only use them when you're fetching data that can load quickly. Ok, agreed. In reality, you would only load slow data in rare use cases. You want your site or application to be quick.
🤷 What the heck man...
What would Josh Morony say?
You should not use RxJS in Angular unless you need to handle asynchronous events with race conditions or coordinate complex data flow.
He was referring to Signals VS Observables there, so I have no idea. Nevertheless, I like to think you should just fetch in the resolver by default UNTIL you have these advanced use cases.
You really don't have a choice...
If you're building a professional SSR application, you will need SEO generated from a database. You MUST use a resolver, or manually pause the component from loading with PendingTask, which is extremly funky.
In Analog, I suspect people are fetching only inside the file based endpoints, or they are generating static pages where it doesn't matter.
Svelte VS Angular
The programming patterns for my two favorite frameworks are polar opposites.
- Huntabyte is going to show you the recommended way --- there is no other way --- of loading data using page load functions.
- Meanwhile the Angular and Analog community dismisses resolvers and shows their arguably more complicated reactive way.
HTTP Streaming
One popular answer to slow loading in the resolver, is HTTP Streaming. NextJS and SvelteKit support this, but it was turned down for Angular.
😔
Reflecting on this... TL;DR
- Angular is not a complete SSR framework
- The community rarely uses resolvers for loading async data
- Angular Team members often point to Analog as a reason NOT to change things
- This is not necessarily a bad thing, just polar opposite of the Svelte community
- Handling race conditions, abort controllers, observable actions, or any complex fetching is always better in the component
- Pre-loading data for SEO is always better in a resolver
- Svelte can use a little bit of RxJS sometimes
- Resolvers should have the ability to use signals like
input()
- Is the Angular SSR community small?
- Do most people just build enterprise applications while fetching in another language?
- Does SEO not matter to MOST Angular SSR users, or is it just an after thought?
- HTTP Streaming would be cool in Angular, along with Resumability when Wiz is combined.
State
Currently, anything in the resolver will get fetched twice (server + client). This needs to be handled in the future as well. 🤔 Resolvers should pass the state automatically... use my useAsyncTrasferState function in a resolver for that.
Comparing the Two Approaches
I used ngxtension for the demo for brevity, but the result is the same.
Effect Version
id = injectParams('id');
idNumber = computed(() => Number(this.id()));
todo = derivedAsync<Todo>(() =>
fetch(`https://jsonplaceholder.typicode.com/todos/${this.id()}`).then(
(response) => response.json()
)
);
prevId = computed(() => Math.max(this.idNumber() - 1, 1));
nextId = computed(() => this.idNumber() + 1);
Resolver Version
todo = injectRouteData<Todo>('data');
idNumber = computed(() => this.todo()!.id);
prevId = computed(() => Math.max(this.idNumber() - 1, 1));
nextId = computed(() => this.idNumber() + 1);
This is loaded from the resolver.
import { ResolveFn } from '@angular/router';
export const routeResolverResolver: ResolveFn<boolean> = async (route) => {
const todoId = route.paramMap.get('id');
if (!todoId) {
throw new Error('Todo ID is missing in the route!');
}
// Fetch the todo from the API
const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${todoId}`);
if (!response.ok) {
throw new Error('Failed to fetch the todo');
}
return await response.json();
};
Which is better?
In this particular demo, there is a "flicker" in the effect version, while there is no flicker in the resolver version. I believe the resolver is better in this use case.
What do you think?
📝 Because Vercel doesn't support SSR Deployment, the demo is loading the resolver on the client only. This means routing only works from the home page.
Answer
I would say that it is on life support for asynchronous fetches. In reality, Angular SSR users should consider resolvers more for this use case and SvelteKit users should consider loading in $effect()
for more use cases. But maybe that is the point? The userbase is different.
I'm still learning, but these questions fascinate me. Hopefully we see more shake ups in both ecosystems.
J
Posted on November 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 24, 2024