14

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?

4 Answers 4

18

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

Sign up to request clarification or add additional context in comments.

5 Comments

angular.io/guide/pipes#no-filter-pipe Maybe I'm wrong (I'm no expert), but don't the angular docs say that the "most correct way" to do this is to do it in the component itself? That pipe will still execute on most ChangeDetection cycles as I understand it...
O yes, but there are times you need to do this on the template.
the pipes are execute on property change if they are pure pipes
Yes. But be aware that when one of the arguments to the pipe is an array, you have more hoops to jump through. It wont detect the change if you just push onto the array. You will need to reassign the whole array. The link above is no longer working, but this explains it angular.io/guide/…
Good explanation. Also reading the following link is will be useful. medium.com/showpad-engineering/…
3

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.

1 Comment

Great pipe solution Mir Milad!!!
1

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(...);
       }

2 Comments

Thanks, I already did it this way. I just vandering if it's posible to do it directly in template. Probably not.
It's a bad practice to call a method in a template in that way, because angular do not notice the value changes, if you add a console.log(...) to filterValue function your will notice as the function is called several times and not one or two
-5

I use lodash for these sorts of problems.

You can install it like this... https://medium.com/@gtsopour/importing-lodash-into-angular-2-typescript-application-94590365f46d#.bsepr3agd

Then use its filter function to filter the array in the view... https://lodash.com/docs/4.17.4#filter

1 Comment

Using another library for simple task whenever you can do it natively is one of the worst things you could do

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.