Reputation: 2136
I plan to have a service that can use pipes like templates. For this propose I need to get the registered pipes.
The code should look like this in the end:
@Injectable()
class MyService {
construct(private injector: Injector) {}
// text could be something like 'myDate | date' or 'aVariable | uppercase'
public interpolate(text: string, params: object = {}): string {
let p: number = text.lastIndexOf('|');
if (p > -1) {
let args = [this.interpolate(text.substring(0, p))];
let pipeName = text.substr(p+1).trim();
// how ever we get the rest of the args from pipeName
pipe = this.getPipe(pipeName)
pipe.transform.apply(pipe, args);
} else {
// how ever we interpolate the base
}
}
private pipeInstances: any = {}
private getPipe(pipeName) {
if (!this.pipeInstances[pipeName]) {
// how to get the pipe?
this.pipeInstances[pipeName] = this.injector.get(PipesContainer).get(pipeName);
}
return this.pipeInstances[pipeName];
}
}
The problem is that you can't get pipes from injector. You have to provide them first (once for directives and once of providers). I'm looking for a way to get them from angular (compiler, core - what ever. somewhere have to be a list - may be for the root module) instead of defining a new list.
Upvotes: 2
Views: 1165
Reputation: 222855
There's no clean idiomatic way in Angular to get a pipe this way. Just because they are used internally by compiler and not exposed for injection. If pipes are supposed to be injected with injector, they should be defined as providers.
Since pipes are injectables, getting their instances with injector is the only proper way to do this. This can be implemented with a map of available pipes.
export const pipesMap = {
some: SomePipe
}
export const pipesList = Object.values(pipesMap);
@Injectable();
export class Pipes {
protected pipesMap = pipesMap;
constructor(private injector: Injector) {}
get(pipeName) {
return this.injector.get(this.pipesMap[pipeName]);
}
}
...
providers: [pipesList, Pipes, ...],
...
Filling the map can be automated by specifying an array of pipe classes and using PipeResolver
to get their names:
import {PipeResolver} from '@angular/compiler';
const pipeResolver = new PipeResolver();
export const pipesList = [
SomePipe
];
export const pipesMap = pipesList.reduce((pipesMap, pipeClass) => {
const pipeName = pipeResolver.resolve(pipeClass, true).name;
pipesMap[pipeName] = pipeClass;
return pipesMap;
}, {});
...
Since pipes are supposed to be instantiated by the compiler once per binding, injector.get(...)
may not work for some pipes. AsyncPipe
is illustrative in this case, it is stateful and also uses ChangeDetectorRef
dependency, which isn't available for injection outside the compiler.
So in the end this should be solved on per pipe basis, depending on developer's needs and pipe's internals. Well-designed pipes are often thin wrappers for the relevant documented services, a good practice is to use these service directly if possible.
Upvotes: 3