1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.
  2. Anuncie Aqui
    Anuncie aqui você Também: fdantas@4each.com.br

How to cache query results during re-computation in Angular (19+) Signals?

Discussão em 'Angular' iniciado por Peter T., Maio 28, 2025.

  1. Peter T.

    Peter T. Guest

    This is actually a follow-up question of Is there a way to dynamically compute rxResource() or resource() in Angular (19)

    We have a list of queries that is computed based on some component state. We want to fetch data for the queries only if the result has not been fetched previously (or when requested manually by the user).

    We have a this.dataService.loadQuary(query) that returns an Observable for the fetch request.

    I'm looking for a good and clean design how to model the caching of previous computation values when re-computing.

    @Component() class SomeComponent {
    private readonly dataQueries = computed<DataQuery[]>(() => {
    // compute queries from component state
    });

    private readonly dataResult: Signal<DataResult[]> = ????;
    }


    Options I considered so far:

    1. linkedSignal and rxResource


    Use a linkedSignal to have access to previous computation result and inside the computation return a rxResource for each request (that is created if not contained in previous result). This does not work, as we cannot create a rxResource during computation (in Angular 19 at least).

    2. computedRxCachedMap


    We could implement something like computedRxCachedMap that encapsulates the caching of Observable results and can be used like:

    private readonly loadedData = computedRxCachedMap({
    // uses effect to watch for key changes
    keys: this.dataQueries,
    // used to create the Observable for a key that is not yet in the cache
    getObservableForKey: key => this.dataService.loadQuery(key)
    // and maybe as well
    hashKey: key => JSON.stringify // or use some other hash or some stringify that ensures key order
    });
    private readonly dataResult = this.loadedData.values; // result Signal, see below


    that returns something like:

    {
    keys: Signal<K[]>,
    values: Signal<V[]>,
    map: Signal<Record<K,V>>,
    get(key: K): V,
    clearCache(), // remove cached values
    reload() // clear cache and re-create all observable for all keys
    }


    However, this would be some custom abstraction that we have to write and maintain, and which is not familiar for other developers. So we would prefer a solution built from "standard" Angular functionality.

    3. effect to update result values


    We could use an effect to monitor the queries and whenever they change, create the requests for new ones that update a result data signal. So something like this:

    @Component() class SomeComponent {
    private readonly dataQueries = computed<DataQuery[]>(() => {
    // compute queries from component state
    });

    private readonly dataObservables: {
    query: DataQuery;
    obs: Observables<DataResult>;
    sub: Subscription;
    lastValue;
    }[] = [];
    private readonly dataResult = signal<DataResult[]>([]);

    readonly updateEffect = effect(() => {
    const queries = this.dataQueries();
    // for each query:
    // - check if dataObservables already contains a request for this query
    // - if not: create one using this.dataService.loadQuery(query)
    // - write current lastValues to dataResult
    // - subscribe to new observables to update dataResult when they emit
    // - unscubscribe from observables that are no longer used.
    // - save current dataObservables
    })
    }

    4. One rxResource and a combined Observable


    If we use a top-level rxResource and combineLatest to combine an observable for each query. The resource loader could cache the previous list of observables & latestValues (similar to the effect version) and for observables that have completed already, include an of(lastValue). So something like:

    @Component() class SomeComponent {
    private readonly dataQueries = computed<DataQuery[]>(() => {
    // compute queries from component state
    });

    private readonly dataObservables: {
    query: DataQuery;
    obs: Observables<DataResult>;
    sub: Subscription;
    lastValue;
    }[] = [];
    private readonly dataResult = rxResource<DataQuery[], DataResult[]>({
    request: this.dataQueries,
    loader: ({request} => {
    const queries = request;
    // for each query:
    // - check if dataObservables already contains a request for this query
    // - if yes: reuse this observable and startWith(lastValue)
    // - if not: create one using this.dataService.loadQuery(query)
    // - unscubscribe from observables that are no longer used.
    // - save current dataObservables
    // - how do I update the lastValues in dataObservables?
    return combineLatest(observables)
    }
    });

    })
    }

    Other options to consider?


    Are there any other helpers around that are made for this case?

    I found connect and derived-async from the pretty cool ngxtension collection which might be interesting to consider.

    I really would ❤️ to hear what you think about these approaches, which one you would recommend or if there are better solutions. And what to consider to choose a good design.

    Continue reading...

Compartilhe esta Página