8

Does it have a built in pipe to do so?

data = [
  {id: 5, name: 'Roger'},
  {id: 5, name: 'Mark'},
  {id: 5, name: 'Zach'},
  {id: 5, name: 'Mark'},
  {id: 5, name: 'Roger'},
];

<ul>
  <li *ngFor="let datum of data">
    {{datum.name}}
  </li>
</ul>

Output

  • Roger
  • Mark
  • Zach
  • Mark
  • Roger

Desired Output

  • Roger
  • Mark
  • Zach
anonym
  • 4,460
  • 12
  • 45
  • 75
  • Possible duplicate of [Angular2 removing duplicates from an array](http://stackoverflow.com/questions/38501434/angular2-removing-duplicates-from-an-array) – Akhter Al Amin Jan 26 '17 at 05:38
  • Thanks for the question and the answer. I have the same objective with one additional feature. Suppose each item have one property named mark, then filter unique names if duplicate exists display name with sum of marks. Any idea ? – Arj 1411 Mar 13 '19 at 17:34

2 Answers2

16

You can create your own pipe.

import { Pipe, PipeTransform } from '@angular/core';
import * as _ from 'lodash'; 

@Pipe({
  name: 'unique',
  pure: false
})

export class UniquePipe implements PipeTransform {
    transform(value: any): any{
        if(value!== undefined && value!== null){
            return _.uniqBy(value, 'name');
        }
        return value;
    }
}

You need to add in the component the UniquePipe and than add in the HTML file the pipe.

<ul>
  <li *ngFor="let datum of data | unique">
    {{datum.name}}
  </li>
</ul>
Yoav Schniederman
  • 5,253
  • 3
  • 28
  • 32
  • @anonym can you please update the answer with your modifications – Rashmi Pandit Mar 23 '17 at 03:57
  • 1
    FYI, this works but filtering is explicitly something the angular team recommends you DO NOT do in a pipe, along with sorting. Pipes are triggered on every change detection cycle and if this list is long, this will be a source of performance issues. You've used lodash here to obscure a lot of the list iteration, but the fact is the big O on this is n^2. Filtering should be done in code and trigger when it needs to be triggered. add in an ngChanges hook in your component and log to the console in it to see how often change detection is triggered. – bryan60 Sep 14 '17 at 19:36
  • Where should I create my own pipe and do I need to import it in app.module.ts or app.component.ts? – Yamur Oct 30 '17 at 09:10
  • i think you should create common module and create new pipes directory. and than you should import your common module to your app module. – Yoav Schniederman Oct 30 '17 at 09:21
11

This is a little old but I want to add an answer for future people that find this.

The accepted answer recommends using a pipe which achieves the goal, but it is not the recommended approach in this case. Here we are talking about a list filter, a very specific duplication filter. The worst case big O on this operation could be nearly n^2 or otherwise be fairly memory intensive (depending on your approach). This particular pipe would likely be marked as "impure"** (as it is in the accepted answer) due to how angular pipes run, and impure pipe operations execute on every change detection cycle, and change detection in Angular triggers very frequently. If this list is significantly long, or if you make a practice of this, then your app will grind to a halt and you'll experience noticeable performance issues.

**If you don't mark the pipe as impure, then if you ever mutate the array, the operation won't run, and while avoiding array mutation is GOOD practice, in this case, it's is a hidden dependency and generally bad practice.

Angular 1 actually included a "filter" pipe but the team opted to get rid of it in angular 2 because the abuse of the filter pipe led people to blame Angular 1 for performance issues that were the result of this bad practice. Pipes are for very small data transformations, like formatting numbers or strings.

The way you should do this is to filter your list in code, and build in the logic to trigger filtering yourself (eg when your list actually changes) rather than relying on change detection to do it for you. It's hard to say exactly how to do this in this case because it IS very use case specific, and it should be if you want your app to perform well. One fairly generic example would be if this list comes from an observable stream (as it should), then just do this in a map operator and use async like so:

dataSource = Observable.of(data); //this is just for example, the actual data source should be an http call or some service layer subject that you can actually update / trigger    
uniqueData$ = this.dataSource.map(data => _.uniqBy(data, 'name'));

<ul>
  <li *ngFor="let datum of uniqueData$ | async">
    {{datum.name}}
  </li>
</ul>

The advantage here is that the filter operation ONLY triggers when the observable stream triggers, so you have very high control over when the filtering occurs and is still very easy to reason about and very "sure to happen correctly"

bryan60
  • 28,215
  • 4
  • 48
  • 65