I'm trying to create a custom Pipe in Angular 2 that will sort an array of objects. I garnered a bit of help from this post. However, I can't seem to get this working.
My pipe looks like this:
@Pipe({
name: "orderByAsync",
pure: false
})
export class AsyncArrayOrderByPipe {
private _promise : Promise<Array<Object>>;
private _output: Array<Object>;
transform(promise: Promise<Array<Object>>, args: any): Array<Object>{
var _property : string = "";
var _descending : boolean = false;
this._property = args[0]["property"] || "";
this._descending = args[0]["descending"] || false;
if(!this._promise) {
this._promise = promise.then((result) => {
result.sort((a: any, b: any) => {
if (a[this._property] < b[this._property]) return (this._descending ? 1: -1);
else if (a[this._property] > b[this._property]) return (this._descending ? -1: 1);
else return 0;
});
this._output = result;
});
}
return this._output;
}
}
The use of the pipe would look like this:
<div *ngFor="#c of countries | orderByAsync">{{c.name}}</div>
It's like the view is never notified that the promise has resolved and data has been returned.
What am I missing?
The built in async
pipe injects a ChangeDetectorRef
and calls markForCheck()
on it when the promise resolves. To do it all in one pipe, you should follow that example. You can view the Typescript source for that here.
I would suggest, however, forgetting about handling async on your own and instead write a pure stateless sorting pipe and chain it with the built in async
pipe. For that, you would write your pipe to handle a bare Array
, not a promise, and use it like this:
<div *ngFor="#c of countries | async | orderBy">{{c.name}}</div>
Simply return a BehaviorSubject out of your pipe which then can get bound with angular async pipe.
Small example (put it in your transform method of your pipe) which should give you 'value' after 3 seconds:
const sub = new BehaviorSubject(null);
setTimeout(() => { sub.next('value'); }, 3000);
return sub;
Complete example:
import { IOption } from 'somewhere';
import { FormsReflector } from './../forms.reflector';
import { BehaviorSubject } from 'rxjs';
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({ name: 'getOptions' })
export class GetOptionsPipe implements PipeTransform {
public transform(value, ...args: any[]) {
const _subject = new BehaviorSubject('-');
if (args.length !== 2) {
throw `getOptions pipe needs 2 arguments, use it like this: {{ 2 | getOptions:contract:'contractType' | async }}`;
}
const model = args[0];
if (typeof model !== 'object') {
throw `First argument on getOptions pipe needs to be the model, use it like this: {{ 2 | getOptions:contract:'contractType' | async }}`;
}
const propertyName = args[1];
if (typeof propertyName !== 'string') {
throw `Second argument on getOptions pipe needs to be the property to look for, ` +
`use it like this: {{ 2 | getOptions:contract:'contractType' | async }}`;
}
const reflector = new FormsReflector(model);
reflector.resolveOption(propertyName, value)
.then((options: IOption) => {
_subject.next(options.label);
})
.catch((err) => {
throw 'getOptions pipe fail: ' + err;
});
return _subject;
}
}