Search code examples
angulardependency-injectionangular15

Angular 15: Appending to provided values when same token is provided in scope


For example, parent scope provides the string "parent" with SOME_TOKEN, then when the child provides the same token (lets say with "child"), it appends to a list of values provided by that same token, making it ["child", "parent"]. I want this because I need to work with a library code that I can not change.

I tried the following function to provide the string but it throws Circular dependency error.

export const provideToken = (val: string) => {
  return {
    provide: SOME_TOKEN,
    useFactory: () => {
      const token = inject(SOME_TOKEN)
      return token ? [...token, val] : val
    }
  }
}

Basically I want to get all the values that are provided, but angular only gives the most closest value in the default way.

I can achieve this with a service. Modules or components could append to the list in their constructor and remove the string in their ngOnDestroy, but that seems error prone.

Update

@Anton's answer works but I need to be able to use this concept with shared modules as well. For example:

@NgModule({
  declarations: [SharedDirective],
  imports: [],
  exports: [SharedDirective],
  providers: [provideToken("shared")]
})
export class SharedModule {}


@NgModule({
  declarations: [SomeComponent],
  imports: [SharedModule],
  exports: [],
  providers: [provideToken("some")]
})
export class SomeModule {}

When skipSelf is used, only "some" token is provided, and the "shared" token is ignored.

Update 2

When multi: true is used in the provider, no token is ignored but the resulting array becomes a nested array, so the injecting service/directive or etc needs to be able to work with it. So, I extended the directive from the library to flatten the array and remove duplicates in its constructor, solving my issue.

export const provideToken = (val: string) => {
  return {
    provide: SOME_TOKEN,
    useFactory: () => {
      const token = inject(SOME_TOKEN, {skipSelf: true, optional: true})
      return token ? [...token, val] : val
    },
    multi: true
  }
}
// constructor of the extended directive

constructor(
    ...,
    @Optional()
    @Inject(SOME_TOKEN)
    providedToken: MaybeArray<string>,
    ...
) {

   if (Array.isArray(providedToken)) {
            providedToken = [...new Set(providedToken.flat(Infinity))]
   }
   super(..., providedToken, ...)
}

Solution

  • You can try to inject token with option skipSelf. It works:

    export const provideToken = (val: string) => {
      return {
        provide: SOME_TOKEN,
        useFactory: () => {
          const token = inject(SOME_TOKEN, { skipSelf: true })
          return token ? [token, val] : val
        }
      }
    }