Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filter array in template Angular2

I'm trying to make simple template but I came across little issue. I want to print the number of uncompleted task in my todo list, but can't filter them inside template. I have this:

<span class="todo-count"><strong>{{todos.length}}</strong> left</span>

But I want to filter this todos to count just ones that have status completed set to false:

<span class="todo-count"><strong>{{todos.filter(t => !t.complete).length}}</strong> left</span>

But this isn't working. How can I achieve that?

like image 947
freshbm Avatar asked Jan 16 '17 11:01

freshbm


3 Answers

The most correct way to do this is with an Angular pipe:

template

<span class="todo-count"><strong>{{ (todos | filter : filterFunction).length }}</strong> left</span>

pipe

import {
    Injector,
    Pipe,
    PipeTransform
} from '@angular/core';
@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {

    public constructor(private readonly injector: Injector) {
    }

    transform(value: Array<any>, callback: any): any {
        return value.filter(callback);
    }
}

filterFunction in the component controller

filterFunction(t): boolean {
  return !t.complete;
}

you can see why not use methods in angular templates here Don't Use Functions Inside Angular Templates and What to Use Instead. For short This is because of the change detection mechanism in Angular. Angular cannot detect whether the result of a function is changed until it runs the function function.

The thing to consider is that the function will be run even though we have the same input data

like image 54
Ernesto Alfonso Avatar answered Nov 10 '22 21:11

Ernesto Alfonso


I have written a simple Angular pipe that allows you to pass an object or a callback function as a filter.

Please look at the following example:

home.component.ts

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
})
export class HomeComponent {
  array = [
    {type: 'log', name: 'Log 1'},
    {type: 'log', name: 'Log 2'},
    {type: 'log', name: 'Log 3'},
    {type: 'log', name: 'Log 4'},
    {type: 'event', name: 'Event 1'},
    {type: 'event', name: 'Event 2'},
    {type: 'event', name: 'Event 3'},
    {type: 'event', name: 'Event 4'}
  ];

  complexFilter = (item, index) => item.type === 'event' && index % 2 !== 0;
}

home.component.html

<h1>All items</h1>
<div *ngFor="let item of array">
  {{item.name}}
</div>

<h1>Only logs</h1>
<div *ngFor="let item of array | dynamicFilter: { type: 'log'}">
{{item.name}}
</div>

<h1>Complex filter</h1>
<div *ngFor="let item of array | dynamicFilter: complexFilter">
  {{item.name}}
</div>

The output will be

All items
Log 1
Log 2
Log 3
Log 4
Event 1
Event 2
Event 3
Event 4
   
Only logs
Log 1
Log 2
Log 3
Log 4

Complex filter
Event 2
Event 4

As you see, you can provide a simple filter object in the template or a complex filter callback function.

You can find the source code of dynamicFilter pipe here.

like image 3
Mir Milad Hosseiny Avatar answered Nov 10 '22 21:11

Mir Milad Hosseiny


Make it a function which applies you criteria:

<span class="todo-count"><strong>{{filterValue()}}</strong> left</span>

And in your controller

      filterValue() : number 
      { 
          return todos.filter(...);
       }
like image 1
brewsky Avatar answered Nov 10 '22 20:11

brewsky