9

I'm trying to create custom pipe on asynchronous pipe, I tried many solutions, but still not working. Here is the snippet of code.

product.sort.ts - custom pipe

import { PipeTransform, Pipe } from '@angular/core';
import { Observable } from 'rxjs/Observable';

@Pipe({
    name: 'sortByName'
})
export class ProductPipe implements PipeTransform{
    /*transform(values: Array<any>, term:string){
        return values.filter(obj => obj.pname.startsWith(term))
    }*/

    //CODE NOT WORKING >>>>>
    transform($value: Observable<Array<any>>, term:string){
        if($value){
            $value.subscribe(
                (obj) => {
                    return obj.filter(obj => obj.pname.startsWith(term))
                }
            )
        }
    }
}

products.component.ts - main component

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import { AppService } from '../app.service/app.service';
import { ProductPipe } from '../products.sort/products.sort';

@Component({
    selector: 'products-pg',
    template: `
        Products List:
        <ul>
            <li *ngFor = 'let product of $productList | async | sortByName:"A"'>{{product.pname}}</li>
        </ul>
    `
})
export class ProductsComponent implements OnInit{
    private $productList:Observable<Array<any>>;

    constructor(private _service: AppService, private _store: Store<Array<any>>){}

    ngOnInit(){
        this._service.setProductList();
        this.$productList = this._store.select('products');
    }
}

Here, I'm using store for state management, I'm trying to sort by name, so passing "A" as first letter. Since $productList is observable, how to write pipe which handles asynchronous behavior like this, plase help me to solve this.

br.julien
  • 3,420
  • 2
  • 23
  • 44
Sagar Ganesh
  • 2,454
  • 3
  • 20
  • 32
  • `async` resolves the observable, so `sortByName` just gets the resulting array. Did your commented-out implementation not work? What precisely is going wrong? – jonrsharpe Jan 09 '17 at 12:28
  • its showing error link "$value.subscribe is not a function" – Sagar Ganesh Jan 09 '17 at 12:31
  • Presumably because (due to the fact that you're already using the AsyncPipe), by the time it gets to your pipe it's just an array, and arrays don't have a subscribe function. The [mcve] is `[].subscribe()`. – jonrsharpe Jan 09 '17 at 12:32
  • yap, im trying to use Observable but not able to figure out the proper way of implementing it in 'transform()' method. – Sagar Ganesh Jan 09 '17 at 12:41
  • 1
    Your pipe **is not getting an observable**. That's the whole point of putting `| async` before calling your own pipe. It resolves the observable and returns the result, an array. Why have you commented out the code that takes an array? What was the initial problem that led to your trying to rewrite the pipe? – jonrsharpe Jan 09 '17 at 12:42
  • or do i need to dispatch event for store to change the application state?? is it right way to do it?? :( – Sagar Ganesh Jan 09 '17 at 12:43
  • Do *what?* Please give some context. One obvious issue is that your current code only ever matches by `'A'`; do you want that to be changed at some point? – jonrsharpe Jan 09 '17 at 12:44
  • I'll provide user input field for 'A', so user can control the input parameter. – Sagar Ganesh Jan 09 '17 at 12:48
  • ...OK? I've still no idea what you're asking, then; please [edit] to clarify. **What is the *problem* you're trying to solve?** – jonrsharpe Jan 09 '17 at 12:49
  • if i go with the first transform method(see the commented transform method) it will show error like - Cannot read property 'filter' of undefined. – Sagar Ganesh Jan 09 '17 at 12:51
  • Well that's easy; you added checking for a missing input into the second version, why not add it to the first? – jonrsharpe Jan 09 '17 at 13:01
  • thanks jonrsharpe, as you said | async resolves the observable and returns the result that is an array. so I used the first transform method (which is commented above) and added 'if(values)' condition in it, and its working!!! – Sagar Ganesh Jan 10 '17 at 05:40

3 Answers3

7

I had the same issue as well the way I fixed it, was like this:

<li class="list-group-item" *ngFor='let alert of _alerts$ | ipwrAlertFilter:seeAll | async'>

look how I had my custom filter before the async pipe, this way my custom pipe gets an observable and then in my pipe I have this:

return value.map(data => data.filter(x => x.originalHasBeenSeen === false));

this way my custom pipe still returns something that I can still apply async pipe on. and so with each new item in stream I still get a hit on my custom pipe and my view gets updated. hope this helps.

Aran Dekar
  • 461
  • 5
  • 12
  • Thanks, also for the last line in my case I had to use the map operator `return value.pipe(map(el => el.value));` – Ultranuke Jun 26 '19 at 19:49
5

The best way to go about it is to still stick with your async and after the async call your custom pipes , like the one in the question, just that your pipe code will now change to not act on the to be loaded array since the record is still loading, so we tell our custom pipe not to do anything again or return empty array. e.g

transform(items: any[], field: string, format?: string) { 
    if (items == null) //since the async is still working
      return [];
//do our normal pipe logic or function
    return items.sort((a: any, b: any) => {
      let value1 = a[field];
      let value2 = b[field];

      if (value1 > value2) {
        return 1;
      } else if (value1 < value2) {
        return -1;
      } else {
        return 0;
      }
    });

then your template still retains

*ngFor = 'let product of $productList | async | sortByName:"A"'
Theophilus Omoregbee
  • 2,463
  • 1
  • 22
  • 33
3

In case this isn't clear for everyone, it's as easy as...

<div *ngFor="let product of (products$ | async | searchFilter: (query$ | async) )">

Notice the brackets (query$ | async) already in an async pipe operation.

Daniel Q
  • 31
  • 2