angularsignalsangular-resourcefacadeangular-signals

How to provide two different instances of an angular injectable in the same component with the new angular resource


I have an angular service as shown below:

@Injectable({ providedIn: 'root' })
export class SelectItemGroupsResourceFacade {
  $assetPath = signal<string>('')
  $appName = signal<string>('')

  #selectItemGroupsResource: ResourceRef<TSelectItemGroup[] | undefined> =
    resource<TSelectItemGroup[], TSelectItemGroupsLoaderRequest>({
      request: () => ({
        appName: this.$appName(),
        assetPath: this.$assetPath()
      }),
      loader: selectItemGroupsLoader()
    })

  configureResource = (
    appName: string,
    assetPath: string
  ): ResourceRef<TSelectItemGroup[] | undefined> => {
    this.$appName.set(appName)
    this.$assetPath.set(assetPath)

    return this.#selectItemGroupsResource
  }
}

The issue is that when the two calls below are made, the last one wins.

  #resrcFacade = inject(SelectItemGroupsResourceFacade)

  $titlesResrc: ResourceRef<TSelectItemGroup[] | undefined> =
    this.#resrcFacade.configureResource('registration', 'titles.json')

  $suffixResrc: ResourceRef<TSelectItemGroup[] | undefined> =
    this.#resrcFacade.configureResource('registration', 'suffixes.json')

What I actually need is two different instances of SelectItemGroupsResourceFacade, instead of a single one. Using the providers decorator would give the same result in the same component


Solution

  • The answer to your question is to use InjectionToken to provide the same class, but with different instance.

    We define the injection tokens:

    export const titlesToken = new InjectionToken('titlesToken');
    
    export const suffixesToken = new InjectionToken('suffixesToken');
    

    Then these tokens are served as separate providers:

    @Component({
      selector: 'app-root',
      template: `
        {{$titlesResrc.value()}} | {{$suffixResrc.value()}}
      `,
      providers: [
        { provide: titlesToken, useClass: SelectItemGroupsResourceFacade },
        { provide: suffixesToken, useClass: SelectItemGroupsResourceFacade },
      ],
    })
    

    We then get these providers using these tokens:

    export class App {
      #titlesResrcFacade = inject<SelectItemGroupsResourceFacade>(titlesToken);
      $titlesResrc: ResourceRef<any[] | undefined> =
        this.#titlesResrcFacade.configureResource('registration', 'titles.json');
    
      #suffixesResrcFacade = inject<SelectItemGroupsResourceFacade>(suffixesToken);
      $suffixResrc: ResourceRef<any[] | undefined> =
        this.#suffixesResrcFacade.configureResource(
          'registration',
          'suffixes.json'
        );
    }
    

    Full Code:

    import {
      Component,
      Injectable,
      signal,
      ResourceRef,
      resource,
      ResourceLoader,
      InjectionToken,
      inject,
    } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    
    @Injectable({ providedIn: 'root' })
    export class SelectItemGroupsResourceFacade {
      $assetPath = signal<string>('');
      $appName = signal<string>('');
    
      selectItemGroupsLoader(): ResourceLoader<any, any> {
        return () => Promise.resolve(Math.random());
      }
    
      #selectItemGroupsResource: ResourceRef<any[] | undefined> = resource<
        any[],
        any
      >({
        request: () => ({
          appName: this.$appName(),
          assetPath: this.$assetPath(),
        }),
        loader: this.selectItemGroupsLoader(),
      });
    
      configureResource = (
        appName: string,
        assetPath: string
      ): ResourceRef<any[] | undefined> => {
        this.$appName.set(appName);
        this.$assetPath.set(assetPath);
    
        return this.#selectItemGroupsResource;
      };
    }
    
    export const titlesToken = new InjectionToken('titlesToken');
    
    export const suffixesToken = new InjectionToken('suffixesToken');
    
    @Component({
      selector: 'app-root',
      template: `
        {{$titlesResrc.value()}} | {{$suffixResrc.value()}}
      `,
      providers: [
        { provide: titlesToken, useClass: SelectItemGroupsResourceFacade },
        { provide: suffixesToken, useClass: SelectItemGroupsResourceFacade },
      ],
    })
    export class App {
      #titlesResrcFacade = inject<SelectItemGroupsResourceFacade>(titlesToken);
      $titlesResrc: ResourceRef<any[] | undefined> =
        this.#titlesResrcFacade.configureResource('registration', 'titles.json');
    
      #suffixesResrcFacade = inject<SelectItemGroupsResourceFacade>(suffixesToken);
      $suffixResrc: ResourceRef<any[] | undefined> =
        this.#suffixesResrcFacade.configureResource(
          'registration',
          'suffixes.json'
        );
    }
    
    bootstrapApplication(App);
    

    Stackblitz Demo