I want to sort a list of things by an observable field, but can't wrap my head around observables to get this working. Has somebody an idea how to achieve this?
The initial situation is something like the following:
Thing[] things;
interface Thing {
name: Observable<string>
}
<ul>
<li *ngFor="const thing for things">
{{thing.name | async}}
</li>
</ul>
Since I obviously haven't described my problem properly: The field I want to sort the list of things on is an Observable, not a plain string. I want to keep the field updated via websockets so to detect the changes properly I have to use an Observable field on which I can subscribe.
Thanks for clarifying the question, Phosphoros. :)
Here's how you could do what you asked:
// Function to compare two objects by comparing their `unwrappedName` property.
const compareFn = (a, b) => {
if (a.unwrappedName < b.unwrappedName)
return -1;
if (a.unwrappedName > b.unwrappedName)
return 1;
return 0;
};
// Array of Thing objects wrapped in an observable.
// NB. The `thing.name` property is itself an observable.
const thingsObs = Observable.from([
{ id: 1, name: Observable.of('foo') },
{ id: 2, name: Observable.of('bar') },
{ id: 3, name: Observable.of('jazz') }
]);
// Now transform and subscribe to the observable.
thingsObs
// Unwrap `thing.name` for each object and store it under `thing.unwrappedName`.
.mergeMap(thing =>
thing.name.map(unwrappedName => Object.assign(thing, {unwrappedName: unwrappedName}))
)
// Gather all things in a SINGLE array to sort them.
.toArray()
// Sort the array of things by `unwrappedName`.
.map(things => things.sort(compareFn))
.subscribe();
Logging emitted values to the console will show an array of Thing objects sorted by their unwrappedName
property:
[
{ id: 2, name: ScalarObservable, unwrappedName: "bar" },
{ id: 1, name: ScalarObservable, unwrappedName: "foo" },
{ id: 3, name: ScalarObservable, unwrappedName: "jazz" }
]
Please let me know if you have questions about this code.
If I understand you correctly, you want to have an object that looks like this:
Thing {
name: string;
}
You then have want to have an Observable that holds on array of Thing
:
things$: Observable<Thing[]>;
You then want to sort your things in the thing array
by a property, in this case name
. That could be done like this:
...
let sorted$: Observable<Thing[]> = things$.map(items => items.sort(this.sortByName))
...
sortByName(a,b) {
if (a.name < b.name)
return -1;
if (a.name > b.name)
return 1;
return 0;
}
...
And then finally, like Toung Le showed in his answer, change your template like this:
<ul>
<li *ngFor="let thing of sorted$ | async">
{{thing.name}} <!--No need async pipe here. -->
</li>
</ul>
You can use Observable.map
. For example:
Observable<Thing[]> things;
sortedThings$ = things.map(items => items.sort()) // Use your own sort function here.
In your template:
<ul>
<li *ngFor="let thing of sortedThings$ | async">
{{thing.name}} <!--No need async pipe here. -->
</li>
</ul>
You can use Observable.map
then sort()
with localeCompare
which would look something like this :
.map(data => ({
label: data.name
}))
.sort((a, b) => a.label.localeCompare(b.label));
Use groupby
operator (play with it):
const $things = getThings();
$things.pipe(
groupBy(thing => thing.id),
mergeMap(group$ => group$.pipe(
reduce((acc, cur) =>[...acc, cur], [])
))
)
.subscribe(console.log)
Groupby docs.