Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create custom pipe that is async by itself

Tags:

I've created a custom pipe that retrieves bits of textual content from an API, like this:

@Pipe({ name: 'apiText' }) export class ApiTextPipe implements PipeTransform {   constructor(private myApiService: MyApiService) {   }    transform(key: string): Observable<string> {     return this.myApiService.getText(key);   } } 

I have to use it like this:

<strong>{{'some-key' | apiText | async}}</strong> 

But effectively I will always want to combine the apiText and async pipes. I'd prefer to write this:

<strong>{{'some-key' | apiTextAsync}}</strong> 

Can I do that somehow, and make things a bit more DRY by combining the two pipes?


UPDATE 1: I've opened a GitHub issue as a feature request for a solution for this.

UPDATE 2: The issue was closed because the async pipe "is not special" and "nothing prevents you from writing one that is similar 🤷‍♂️

like image 942
Jeroen Avatar asked Jul 23 '18 14:07

Jeroen


People also ask

Can we create custom pipe?

Steps Involved In Creating a Custom Pipe In Angular are: 1) Create a Pipe Class and decorate it with the decorator @Pipe. 2) Supply a name property to be used as template code name. 3) Register your Pipe in the module under declarations. 4) Finally, implement PipeTransform and write transformation logic.

Is async a pure pipe?

Async pipe is an example of an Impure pipe.

Does pipe automatically subscribe?

Yes every time when it subscribe or value changes it will unsubscribe automatically this is the magic of async pipe.


1 Answers

Several commenters pointed out (rightfully so) that this violates SRP and might hurt readability. But even though that'll make me reconsider whether I want this, I still for sure wanted to know how to do it if you want it. I found two solutions (with help from commenters, again).

Composition (not recommended)

Create your own internal AsyncPipe and use it. Here's a base setup that should work:

@Pipe({ name: 'apiText', pure: false }) export class ApiTextPipe implements PipeTransform, OnDestroy {   private asyncPipe: AsyncPipe;    constructor(private myApiService: MyApiService, private injector: Injector) {     this.asyncPipe = new AsyncPipe(injector.get(ChangeDetectorRef));   }    ngOnDestroy() {      this.asyncPipe.ngOnDestroy();   }    transform(key: string): string {     return this.asyncPipe.transform(this.myApiService.getText(key));   } } 

In addition to the abovementioned downsides (noted by commenters), I see other issues:

  • If ever the AsyncPipe changes, e.g. it starts implementing OnInit, then our own pipe also needs to change. And you will likely miss this. It's not good that our pipe's implementation is coupled in this way to the AsyncPipe.

  • I couldn't seem to get AsyncPipe injected because it relies on the special ChangeDetectorRef, so I used this suggested approach asking it directly from the injector. (There's likely a better way to do this, but I'm not digging further for now...)

Inheritance (not recommended)

You could also try to extends AsyncPipe in your own pipe, but that route lies even more awkward code that tightly couples your pipe to the async pipe. Some problems with that approach when I tried it:

  • You again need a ChangeDetectorRef to pass to the super(...) call
  • You need to tightly couple to the signature of the transform method from the AsyncPipe
  • The transform gets super complicated as it no longer just takes a string (see the previous point)
  • Unknown to me if this properly makes Angular call the super class's ngOnDestroy method

Plus whatever else I forgot. The code I had felt so icky that it doesn't seem wise to even share it.

Duplicate the Source (not recommended?)

As one of the commenters suggested, the source for AsyncPipe is open. So you could take that and build your own pipe from that.

This is not a wise decision in my opinion and I'd recommend against it, but it is the solution recommended by the Angular team on GitHub.

Stick with double pipes (the only decent option?)

So doing {{'some-key' | apiText | async}} and always using two pipes, seems to be the only somewhat reasonable option currently.

like image 110
Jeroen Avatar answered Oct 30 '22 14:10

Jeroen