相关文章推荐
寂寞的登山鞋  ·  Linux下Appium+Python+he ...·  1 年前    · 
另类的开水瓶  ·  在 ASP.NET Core ...·  1 年前    · 
温柔的红薯  ·  TiDB 5.0.3 Release ...·  1 年前    · 
有情有义的菠萝  ·  python - ...·  1 年前    · 
Collectives™ on Stack Overflow

Find centralized, trusted content and collaborate around the technologies you use most.

Learn more about Collectives

Teams

Q&A for work

Connect and share knowledge within a single location that is structured and easy to search.

Learn more about Teams

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>
                It's not provide, useValue but provide, useClass : values are for instanciated values, and you're giving it a class, not an instance of your class.
– user4676340
                Nov 15, 2018 at 10:22
                Could you make a minimal reproducible example on stackblitz.com ? That would help in resolving the issue
– user4676340
                Nov 15, 2018 at 10:25
                And also, could you test this : {       useValue: new InjectionToken<ISearchable>('CustomerService'),        provide: CustomerService     } (the opposite of what you have written)
– user4676340
                Nov 15, 2018 at 10:27

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(...);
                This creates a dependency and if you are in 2 separate modules, you really need them completely independent. So passing the original token, would not really fix the issue. .get('TOKEN_NAME'), should not really be deprecated it this is going to be the solution now. I hope angular team realize that.
– Mohy Eldeen
                Jan 14, 2019 at 18:49
                I just built an example here stackblitz.com/edit/… I will open a ticket that they should not remove .get('name'), without fixing this.
– Mohy Eldeen
                Jan 14, 2019 at 19:47
                If it make sense, please vote for the git issue I created, because they closed it and I am hoping it will re-open github.com/angular/angular/issues/28136
– Mohy Eldeen
                Jan 16, 2019 at 19:12
                This is actually not a bad answer - I've hit this numerous times. Sometimes the Angular compiler doesn't pick up changes in modules in serve mode and you need to restart the compiler.
– jiroch
                Feb 16, 2022 at 14:19

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 { }
                Using {   providedIn: 'root', } declares a singleton, which isn't what the OP seems to need.
– user4676340
                Nov 15, 2018 at 11:54
                Yes but I want to use it inside a component without having to hard-code it, meaning any ISearchable service should be able to be fetched from the dependency injector, like a simple key-value lookup which doesn't seem to work with InjectionTokens but works fine if I pass a string (which is somehow deprecated)
– Paradoxis
                Nov 15, 2018 at 12:07
                @NikolaosGrammatikos that, yet again, just moves the problem, I don't want my service to be hard-coded in my any of my components, it has to be simply queried via a string in my tag (see 'hello.component.ts' and 'search-bar.component.ts')
– Paradoxis
                Nov 15, 2018 at 14:29

You are mixing it all up.

Tokens are made to be plain objects. By using a service with a dependency injection, you can't declare it as a token.

To create services on demand, you will have to use a factory. A factory is a function that will create instances of your class, with given properties.

In my example, I have added the endpoint as the given propety, but you can do whatever you want in your factory.

Stackblitz : https://stackblitz.com/edit/angular-dg1hut?file=src/app/search-bar/search-bar.component.ts

const factory = (http: HttpClient) => new CustomerService(http, '/api/v1/customer')
// ...
providers: [{
  provide: CustomerService,
  useFactory: factory,
  deps: [HttpClient]
export class SearchBarComponent implements OnInit {
  @Input() source: string;
  constructor(private searcher: CustomerService) { }
  ngOnInit() { console.log(this.searcher); }
  search() { return this.searcher.search(''); }
                This doesn't solve my problem though, I want to dynamically resolve a service to query the backend service with (like you could do in AngularJS with $injector.get('MyServiceName');), this simply hard-codes the service in the constructor which is exactly what I don't want as the component no longer is modular
– Paradoxis
                Nov 15, 2018 at 11:20
                The service is hard-coded but it's value isn't : it depends on the factory. This means you can create several factories, each returning a custom instance of the service, with different properties. And the injector is only there to fetch you a dependency injected into your module, both in Angular and AngularJS : it's not there to dynamically resolve a service. If you wish to keep your token, you will have to declare it as a service configuration, not as a whole service, because as said, tokens aren't made to be class instances with dependencies but only plain objects.
– user4676340
                Nov 15, 2018 at 11:23
                I get that, but I simply want to do a simple key-value lookup in wherever my services are defined, that's it; the commented out examples work but they're deprecated and I simply can't seem to port them to Angular's new format
– Paradoxis
                Nov 15, 2018 at 12:10
                And again, it works because it's very simple; it just fetches your service. That's not different from using the constructor to inject a dependency. If you want to override a property in your service, you will have to use a factory or a token. If you want to override your whole service, you will have to use a factory or a provider override. What you do now is using a token to override your whole service. It won't work, no matter how hard you try. If your need is to override your whole service, you simply can't use an injection token.
– user4676340
                Nov 15, 2018 at 12:13

for Angular 15 with standalone components in 2023 we should define injection tokens in routes, for my situation with mfe and libs in case when it needs to have injection token we need to put them to entryRoutes.ts array

export const remoteRoutes: Route[] = [
    path: '',
    component: RemoteEntryComponent,
    children: uiRoutesChildren,
    providers: [
        provide: 'API_URL',
        useValue: API_URL,

source: https://angular.io/guide/standalone-components

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.