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>
–
–
–
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(...);
–
–
–
–
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 { }
–
–
–
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(''); }
–
–
–
–
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.