#Waiting for a resource to be completed in sync or async context

21 messages · Page 1 of 1 (latest)

floral schooner
#

im wondering how we can wait on a reosurce to be loaded (because its loaded in a resolver)
I simplified the example and added comments for use cases.
The example shows getBySerialNumber being called from a resolver

I havent testet it yet, but I added a while loop, to wait for the resource to complete. And was hoping someone might has a better idea.
Another idea I have, which I would like feedback on is: make the function async, and use the http client directy, and then set the resource manually. However I im not sure thats a good approach. I think its better than the while loop tho

machine.api.ts

private readonly machineResource: ResourceRef<Machine[]> = rxResource({
    stream: () => {
      // use-case: we need them eagerly loaded (compared to toSignal())
      return (this.httpClient.get<MachineResponseDto[]>(this.baseUrl + 'GetAllMachines'));
    },
    defaultValue: [],
  });

reloadMachines(): void {
    if (!this.machineResource.reload())
      console.warn('unnecessary or unsupported reload call on get all machines skipped.');
  }

// this method is called in a resolver, so we can make it async or use Obersavles if needed
  getBySerialNumber(serialNumber: string): Machine | null {
    const getBySerialNo = () =>
      untracked(this.machineResource.value).find(
        (m) => m.serialNumber === serialNumber,
      ) ?? null;

    const machine = getBySerialNo();

    if (machine) return machine; // hot path first

    this.reloadMachines(); // unlikely usecase, because to view a machine you'd have to click on it on the dashboard, where it was already loaded
    while (this.machineResource.isLoading()) {
      // wait for the resource to be loaded
      // blocking the ui is fine, because its white currently anyways, if this is called from the resolver
    }

    return getBySerialNo();
  }
loud spoke
#
effect(() => {
  if (this.machineResource.isLoading()) return;
  // do something
});
floral schooner
#

Can you elaborate on this? I dont know how this is helpful for my use case, when called from within a resolver

loud spoke
#

If resolver:

getBySerialNumber(serialNumber: string): Machine | null cannot work, as it async. Can you check ResolverFn there maybe Signal<T> or Observable<T>

#

resolver still no signal yet v20, use above

#
getBySerialNumber(serialNumber: string): Observable<Machine|null> {
  const getBySerialNo = ...;
  return new Observable((subscriber) => {
    const effectRef = effect(() => {
     if (this.machineResource.isLoading()) return;
     subscriber.next(getBySerialNo());
     subscriber.complete();
    });
    return effectRef.destroy;
  });
}
floral schooner
#
export const machineRedirectResolver: ResolveFn<Machine> = async (route, _state) => {
  const router = inject(Router);
  const service = inject(MachineApi);

  const serialNumber = route.paramMap.get('serialNumber')!; // safe to ignore nullable, because the route will not match if there is non given

  const machine = await service.getBySerialNumber(serialNumber);

  if (!machine) {
    console.warn('TODO implement 404 view');
    return new RedirectCommand(router.parseUrl('/404'));
  }

  const type = getType(machine);

  const urlTree = router.createUrlTree(['/', type, serialNumber], {
    preserveFragment: true,
  });

  if (isDevMode())
    console.debug(
      'Old url detected, prefetched machine and redirecting to ' +
        urlTree.toString(),
    );

  return new RedirectCommand(urlTree, {
    skipLocationChange: false,
    state: machine,
  });
};
loud spoke
#
getBySerialNumber(serialNumber: string): Observable<Machine> {
    const getBySerialNo = () =>
      untracked(this.machineResource.value).find(
        (m) => m.serialNumber === serialNumber,
      ) ?? null;

    const machine = getBySerialNo();
    if (machine) return of(machine);
    return new Observable((subscriber) => {
      this.reloadMachines();
      const effectRef = effect(() => {
        if (this.machineResource.isLoading()) return;
        subscriber.next(getBySerialNo());
        subscriber.complete();
      });
      return effectRef.destroy;
    });
  }
#

You use await ? Ok. Wait

#
getBySerialNumber(serialNumber: string): Promise<Machine> {
    const getBySerialNo = () =>
      untracked(this.machineResource.value).find(
        (m) => m.serialNumber === serialNumber,
      ) ?? null;

    const machine = getBySerialNo();
    if (machine) return of(machine);
    return new Promise((resolve, reject) => {
      this.reloadMachines();
      const effectRef = effect(() => {
        if (this.machineResource.isLoading()) return;
        if (this.machineResource.ERROR)
         reject(???);
        else
         resolve(getBySerialNo());

      });
      return effectRef.destroy;
    });
  }
keen marsh
#

For a resolver just use normal observables, not a resource.

floral schooner
#

the whole app depends on the resource, the resolver is just an edge case

keen marsh
#

I am not sure this should be a resource then, to be honest.

floral schooner
#

the resource is used in many html templates

keen marsh
#

Sure, you can keep exposing it as a resource, if you want to. But you should also expose the underlying Observable, so that the resolver can use it.

floral schooner
#

not sure how that would look like, since th obseravble is recreated for each request (HttpClient)

flint bramble
#

You'd like the resolver to re-use the data loaded ?

floral schooner
#

Can you rephrase that question? I dont understand

flint bramble
#

Do you want to use a resource because you might want to re-use a value that was already loaded ?

floral schooner
#

we decided to go with this approach, for now
(just in case your wondering, the API does not offer a endpoint we could use to read a single machine by serial no)

async getBySerialNumber(serialNumber: string): Promise<Machine | null> {
    const getBySerialNo = () =>
      untracked(this.machineResource.value).find(
        (m) => m.serialNumber === serialNumber,
      ) ?? null;

    const machine = getBySerialNo();

    if (machine) return machine; // hot path first

    const machines = await firstValueFrom(this.getAllMachines());

    this.machineResource.set(machines);

    return getBySerialNo();
  }