2

I have a custom pipe I use in the HTML part of a component. It is declared in the module:

  declarations: [  I18nPipe ],

I want to be able to call a method on it from the component code (not the transform method).

I was hoping that the pipe instance is living somewhere in the dependency injection context so that I can grab it. But I was wrong. If I inject it in the constructor of the component (like any normal service, for example):

  constructor(private i18nPipe: I18nPipe)  

then I got an error: no providers. So I include it in the providers section of the same module:

  providers: [ I18nPipe ]

then I will have access to it in the component code but there will be two instances of my custom pipe.

  1. created by providers, available in DI context. I will get this instance when injected in the constructor, so I will work with this instance in my component code.

  2. the instance that is used in the HTML. Where does it live? I want access to this instance in my component code, not to the "provided" one; how can I obtain it?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Dacian
  • 678
  • 6
  • 12
  • 1
    Declarations are available in templates, providers via DI. Why does it matter that they are separate instances? If the pipe has state, it might be better to move that elsewhere. What methods does it have other than the transform? More context is needed to give a useful answer. – jonrsharpe Aug 30 '17 at 21:31
  • I have a method that try to configure a bit the pipe. Instead of input parameter (that need to be present in all pipe appearances in html template) I was thinking to try to set the configuration params from the code. For example setting the language. Of course, a service keeping the state and injected in the pipe would be more clean, I just tried to shortcut this and see if it is possible. And discovered this - which I was not expected. Anyhow: components are also present in the declarations and in the DI tree too. Why pipes not ? Generally speaking is about better understanding angular. – Dacian Aug 30 '17 at 21:59
  • 1
    *"components are also present in the declarations and in the DI tree too"* - I don't think you can inject a component, or a directive, either. – jonrsharpe Aug 30 '17 at 22:09
  • Consider using a service instead for your configuration information. Then any desired components or your pipe can reference that service. – DeborahK Aug 30 '17 at 22:13

1 Answers1

6

Every Angular component is compiled into the View with nodes. And pipes are one of the nodes types. And you can't inject view nodes except for parent components and directives defined on the host element of the component.

Suppose you have the following template for a component:

<div>{{3|mypipe}}</div>

You will have the following view nodes:

PipeElement
HTMLDivElement
HTMLTextElement

Then during change detection Angular runs through each node and performs change detection specific action - dom update, bindings update or transform method call.

If you wanted, you could access the instance of the pipe through the View instance (which is not public API) like this:

class I18nPipe {
   sayHello() {}
}

class ComponentWithPipe {

  constructor(cd: ChangeDetectorRef) {
    setTimeout(() => {
      const pipeNode = cd._view.nodes.find((n) => {
        return n.instance && n.instance instanceof I18nPipe
      });
      const pipeInstance = pipeNode.instance;
      pipeInstance.sayHello();
    })
  }

But this is for educational purposes only. You don't want to use this approach in production.

Here are some of the articles that will help you understand:

Max Koretskyi
  • 101,079
  • 60
  • 333
  • 488
  • Thank You Maxim. Very useful. Right on my approach direction. Still one question: if I have the pipe several times used in my HTML, there would be several pipe nodes but just one pipe instance ? I checked and there is only one pipe instance. OBS: in my angular 4 there is no _view on cd so they restricted access to internal stuff maybe. Anyhow it was just for understanding, as you said. – Dacian Aug 31 '17 at 08:00
  • @Dacian, you're welcome. There should be as many instances as you used pipes. Pipes instances are stateful, for example, `async` pipe holds rxjs subscription which can't be shared between all pipes. How did you check? Also, the `_view` should be available. It's not exposed on the `ChangeDetectorRef` interface but the ChangeDetectorRef is just a wrapper around the underlying view – Max Koretskyi Aug 31 '17 at 08:06
  • I was expecting also one instance per usage. I retested now and indeed I see now several instances (I randomly generate in constructor an id and print it - so an id for each created instance). This is very important: framework create one instance per usage - should be stated in the pipe documentation IMO . Like in Spring framework: when a bean scope = prototype is stated clearly about a bean (I can't stop thinking to Spring as the main framework I know which rely on DI - there everything created by the framework is available in DI context - in Angular is not the case ). – Dacian Aug 31 '17 at 10:48
  • Again I see only one pipe instance for two pipe usage in same html. Strange. Transform is called two times but on the same instance. Seems non deterministic. – Dacian Aug 31 '17 at 11:38
  • 1
    Woau !!! What a discovery :) Pure pipes - one instance only (I had several usages in a component HTML template). Impure pipes - one instance per usage (usage one after another in HTML template). – Dacian Aug 31 '17 at 11:58
  • @Dacian, that's a great discovery! Good job) That's exactly what pure means - no side effects, no state! Now I think [this answer](https://stackoverflow.com/a/39285608/2545680) is actually incomplete. I would say that the multiple/single instances is the biggest differentiator. I think you should post your answer explaining the discovery and drawing a parallel with functional programming. Drop a link here and I'll definitely upvote – Max Koretskyi Aug 31 '17 at 12:23
  • 1
    Thanks, here is the [answer](https://stackoverflow.com/questions/39285550/what-is-impure-pipe-in-angular2/45986420#45986420) – Dacian Aug 31 '17 at 16:41
  • @Dacian, I've written a related article, you may want to read it [The essential difference between pure and impure pipes and why that matters](https://medium.com/@maximus.koretskyi/the-essential-difference-between-pure-and-impure-pipes-and-why-that-matters-999818aa068) – Max Koretskyi Sep 01 '17 at 09:06
  • is this still true with Angular9/10, I see pure pipe getting instantiated for each usage in single component? – kaleshanagineni Nov 13 '20 at 19:56
  • @kaleshanagineni, I haven't looked under the hood of newer versions of Angular – Max Koretskyi Nov 17 '20 at 15:58
  • I have tried pure pipe in Angular 9, its indeed instantiating for each usage in single component. – kaleshanagineni Nov 17 '20 at 16:10
  • @kaleshanagineni, interesting, maybe you can write an article about it – Max Koretskyi Nov 18 '20 at 14:00