Angular InjectionToken throws 'No provider for InjectionToken'

44,783

Solution 1

After asking on the official angular repository, it turns out to be a simple solution. Instead of passing the service name as a string, you'll have pass the tokens through the component into the view into another component.

Globally define the injection token

I did this alongside my service itself to make it easier to keep track of.

@Injectable()
export class CustomerService implements ISearchable { ... }

export const CUSTOMER_SERVICE = new InjectionToken<ISearchable>('CustomerService');

Register the injection token in your app providers

import {CUSTOMER_SERVICE, CustomerService} from "./services/customer/customer.service";


@NgModule({
  declarations: [ ... ],
  imports: [ ... ],
  providers: [
    {
      provide: CUSTOMER_SERVICE,  // That's the token we defined previously
      useClass: CustomerService,  // That's the actual service itself
    }
  ],
  bootstrap: [ ... ],
})
export class AppModule { }

Pass the token through the view to your other component

// In your component
import {CUSTOMER_SERVICE} from "./services/customer/customer.service";


@Component({
  selector: 'app-root',
  template: '<app-search-bar [source]="searcher"></app-search-bar>'
})
export class AppComponent
{
  searcher = CUSTOMER_SERVICE;
}

You can now import the service dynamically from your other component

@Component({
  selector: 'app-search-bar',
  templateUrl: './search-bar.component.html',
  styleUrls: ['./search-bar.component.sass'],
})
export class SearchBarComponent implements OnInit
{
  @Input()
  source: InjectionToken<ISearchable>;

  private searcher: ISearchable;

  constructor(private injector: Injector) {}

  ngOnInit()
  {
    this.searcher = this.injector.get<ISearchable>(this.source);
  }

  search(query: string)
  {
    this.searcher.search(query).subscribe(...);
  }
}

Solution 2

Something you can do us instead of implementing the service into the AppModule's providers' list, you could just add providedIn to root parameter to service's injectable annotation.

https://angular.io/guide/providers#providing-a-service

Example:

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

But, there is one more method, your method. Services can be used into the module's providers' list without requiring any IncpetionToken, because are already incpected, so you can just add it to providers list.

https://angular.io/guide/providers#provider-scope

Example:

@NgModule({
  declarations: [
    AppComponent,
    SearchBarComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    AppRoutingModule
  ],
  providers: [
    CustomerService // <-- You don't need to create any token.
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Share:
44,783
Paradoxis
Author by

Paradoxis

About me My name is Luke Paris, I'm a Dutch security expert working at Fox-IT. Contact and links If you wish to contact me you can do so via the contact form on my website or on my LinkedIn, you can also check out the code on Github. PGP publickey SSH publickey

Updated on February 11, 2022

Comments

  • Paradoxis
    Paradoxis over 2 years

    I'm currently learning the new Angular framework, and I'm trying to make a dynamic search bar which accepts a service name as an argument in order for it to dynamically resolve a service to query the backend service with.

    For this I'm using an Injector, and loading the the service during ngOnInit. This works fine when using a string based provider, however my IDE notes that it's deprecated and I should use an InjectionToken which I can't seem to wrap my head around.

    I expected the following code to work, as removing all instances of InjectionToken and replacing them with the direct string literal works:

    I tried looking at the following documentation, but I didn't quite understand it as I feel like I did exactly what it says, yet it keeps telling me it doesn't work: https://angular.io/guide/dependency-injection-providers

    Could someone tell me what I'm doing wrong?
    Thanks

    Module declaration

    // app.module.ts
    @NgModule({
      declarations: [
        AppComponent,
        SearchBarComponent
      ],
      imports: [
        BrowserModule,
        HttpClientModule,
        AppRoutingModule
      ],
      providers: [
        {
          provide: new InjectionToken<ISearchable>('CustomerService'), // <-- doesn't work;  'CustomerService' <-- works
          useValue: CustomerService
        }
      ],
      bootstrap: [AppComponent]
    })
    export class AppModule { }
    

    Search bar component:

    // search-bar.component.ts
    @Component({
      selector: 'search-bar',
      templateUrl: './search-bar.component.html',
      styleUrls: ['./search-bar.component.sass']
    })
    export class SearchBarComponent implements OnInit {
    
      @Input()
      source: string;
    
      private searcher: ISearchable;
    
      constructor(private injector: Injector) {}
    
      ngOnInit() {
        // error: Error: No provider for InjectionToken CustomerService!
        let token = new InjectionToken<ISearchable>(this.source);
        this.searcher = this.injector.get<ISearchable>(token);
    
        // this works, but it's deprecated and will probably break in the future
        // this.searcher = this.injector.get(this.source);
        console.log(this.searcher);
      }
    }
    

    Using the search bar:

    <!-- app.component.html -->
    <div class="row justify-content-center mb-2">
      <div class="col-8">
        <search-bar title="Customers" source="CustomerService"></search-bar>
      </div>
    </div>
    

    Edit: Here's an example with the error:

    https://stackblitz.com/edit/angular-3admbe