analogjs / analog

The fullstack meta-framework for Angular. Powered by Vite and Nitro
https://analogjs.org
MIT License
2.56k stars 243 forks source link

Use Resolvers Instead of Async Imports #1033

Open jdgamble555 opened 6 months ago

jdgamble555 commented 6 months ago

Which scope/s are relevant/related to the feature request?

create-analog

Information

Now that we know Zoneless is coming sooner than later as experimental, I think Analog needs to prepare for it. I actually wrote the waitFor function that was later implemented in Analog trying to avoid resolvers, when I didn't really understand that we definitely need to use them instead. Get rid of it. Get rid of all importing directly into the component, as this is not how Angular works, or should work.

It will break Analog when we go Zoneless!

I think this is the problem I keep going back to with Analog from different directions. The markdown components have a bug keeping SSR working correctly. Importing routes doesn't work as expected. We need transfer state for resolvers (provideClientHydration) so that we don't double fetch. This all seems to be problems related to not using resolvers. I have a few solutions.

import { TransferState, inject, makeStateKey } from "@angular/core";
import { ActivatedRoute } from "@angular/router";
import { map } from "rxjs";

// put in resolver for async operations
export const useAsyncTransferState = async <T>(
    name: string,
    fn: () => T
) => {
    const state = inject(TransferState);
    const key = makeStateKey<T>(name);
    const cache = state.get(key, null);
    if (cache) {
        return cache;
    }
    const data = await fn() as T;
    state.set(key, data);
    return data;
};

// put in resolver for non-async operations
export const useTransferState = <T>(
    name: string,
    fn: () => T
) => {
    const state = inject(TransferState);
    const key = makeStateKey<T>(name);
    const cache = state.get(key, null);
    if (cache) {
        return cache;
    }
    const data = fn() as T;
    state.set(key, data);
    return data;
};

// idea accidently came from Brandon
export const injectResolver = <T>(name: string) =>
    inject(ActivatedRoute).data.pipe<T>(map(r => r[name]));

export const injectSnapResolver = <T>(name: string) =>
    inject(ActivatedRoute).snapshot.data[name] as T;

You can see an example of useAsyncTransferState() in my Analog Firebase Repo.

We can use resolvers easier thanks to Brandon's accidental idea trying to understand my though process. I use them, they work great!

Problems to Solve

I think my functions above could be added to the framework as utility functions anyone could use.

This has been the only thing I have seen that holds back Analog.

Thanks,

J

Describe any alternatives/workarounds you're currently using

Obviously if Angular adds a way to resolve things in the component with Zoneless, that way would be an option too.

I would be willing to submit a PR to fix this issue

atscott commented 6 months ago

Obviously if Angular adds a way to resolve things in the component with Zoneless, that way would be an option too.

This will happen and likely as soon as 18.1 or 18.2. We use the PendingTasks service internally and this is actually how everything contributes to application stability, including ZoneJS. Having a public api for this is a blocker for zoneless becoming stable and also likely a blocker for graduating from experimental to developer preview.

jdgamble555 commented 6 months ago

@atscott - Well when you guys do look at it, perhaps you could look at server only routes (APP_INITIALIZER, except similar to ROUTE_INITIALIZER --- if that existed)...

Glad to know these things are evolving so quickly!

J