Are Angular Resolvers on Life Support ?

jdgamble555

Jonathan Gamble

Posted on November 15, 2024

Are Angular Resolvers on Life Support ?

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 of async. 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 a derivedAsync to do this for you, and Angular 19 will have resource() 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$ and server$ for preloading data. Qwik "resumes," which doesn't require hydration and only runes once.
  • SolidStart - uses a query function and with preload 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.

  1. 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 course file based routing. Read any of my previous posts for work arounds on these things.
  2. 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

Async Fetch in Angular

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);
Enter fullscreen mode Exit fullscreen mode

Resolver Version

  todo = injectRouteData<Todo>('data');

  idNumber = computed(() => this.todo()!.id);

  prevId = computed(() => Math.max(this.idNumber() - 1, 1));
  nextId = computed(() => this.idNumber() + 1);
Enter fullscreen mode Exit fullscreen mode

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();
};
Enter fullscreen mode Exit fullscreen mode

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

💖 💪 🙅 🚩
jdgamble555
Jonathan Gamble

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