1

I have the following custom pipe in my Angular 8 app:

import { Pipe, PipeTransform } from '@angular/core';
import { MyService} from '../_services/my.service';

@Pipe({
    name: 'myPipe'
})
export class MyPipe implements PipeTransform {
    constructor(private myService: MyService) {}

    transform(value: string): any {
        return this.myService.loadForID(value);
    }
}

I am trying to transform a value dynamically in a component code, by having the string name only: 'myPipe' but i cannot find anyway to inject/load a pipe dynamically for a string. an online post that i found suggests the following code, but even this seem to require a type (MyService) and not a string

constructor(private injector:Injector) {
  injector.get(MyService);
}

is there a way to achieve this in Angular?

kob490
  • 3,177
  • 4
  • 25
  • 40
  • what's your intent. what are you trying to achieve? Maybe there is an another alternative. do you know about dynamic imports will that help you? If you want the pipe to be used in your component you can just create a new instance of it in your component by new `MyPipe()` – joyBlanks Sep 24 '19 at 02:06
  • I'm trying to create a dynamic datatable component of which both metadata and source data is coming from an API. Some of the columns in it are IDs and I am trying to load their respective data prior to rendering the datatable. Since I already have a bunch of services and pipes in the system and in order to keep it DRY I wanted to use the same architecture for loading the data. – kob490 Sep 24 '19 at 02:53
  • so you can use dynamic imports then – joyBlanks Sep 24 '19 at 02:56
  • AFAIK, dynamic imports are for loading scripts dynamically, right? Not sure that I understand how this applies to my problem. Can you share an example please? – kob490 Sep 24 '19 at 03:02

1 Answers1

1

In you app.module you can use a string:

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent ],
  bootstrap:    [ AppComponent ],
  providers: [
    MyPipe,
    { provide: 'myPipe', useClass: MyPipe }
  ]
})
export class AppModule { }

Then, you can inject it into your component:

import { Component, Injector } from '@angular/core';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular';

  constructor(private injector:Injector){
    var myInterface = injector.get('myPipe');
    myInterface.transform('test');
  }  
}

Here's a stackblitz showing the example

EDIT

If you want to avoid the deprecated call to injector.get but still be able to access the service from the injector with a string, you can use a service to figure out which injection token should be used. I don't think this is the prettiest way to do it, but I think it meets OPs needs.

First create a Service to map a string to the expected injection token:

@Injectable()
export class TokenService {
  static MY_PIPE_TOKEN = new InjectionToken<MyPipe>('myPipe');

  getToken(name: string): InjectionToken<any> {
    if (name === 'myPipe') {
      return TokenService.MY_PIPE_TOKEN;
    } else {
      throw `No token with name ${name} found`;
    }
  }
}

Set up your providers, using the Injection Token from the Service:

@NgModule({
  imports:      [ BrowserModule, FormsModule ],
  declarations: [ AppComponent, HelloComponent ],
  bootstrap:    [ AppComponent ],
  providers: [
    TokenService,
    MyPipe,
    { provide: TokenService.MY_PIPE_TOKEN, useClass: MyPipe }
  ]
})
export class AppModule { }

In the component, use the service to get the right token:

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  name = 'Angular';

  constructor(private injector:Injector, private tokenService: TokenService){    
    let myInterface = injector.get(tokenService.getToken('myPipe'));
    myInterface.transform('test');    
  }  
}

Here's a stackblitz showing the updated example.

spots
  • 2,483
  • 5
  • 23
  • 38
  • Thank you. this seem to work OK. however, TSLint throws a 'Deprecated' warning for "injector.get('myPipe')" as follows: "get is deprecated: from v4.0.0 use Type or InjectionToken". any idea how to let this line pass TSLint validation? – kob490 Sep 24 '19 at 19:39
  • I've updated the answer with an example. Another way to make it pass the TSLint validation would be to suppress the warning, but that really just masks the problem and may cause you pain later. Everything said, this seems like a code smell to me. You have a service you need to access dynamically, but to do anything useful with it you have to assume some interface. Depending on what your real requirements are, there's likely a better way to set up your dependencies. – spots Sep 24 '19 at 21:35