Joe Joe - 15 days ago 4
HTML Question

angular 2 when adding new item to list, custom pipe not reordering list

I implemented a orderby pipe that works fine when the DOM loads. But when I add a new item to the list, my pipe is not being activated and the new item that has been added to the list, is not order - but added to the last place in the list.

The pipe:

import { PipeTransform, Pipe } from '@angular/core';

import { ITeam } from './team';

@Pipe({
name: "LeagueFilter"
})

export class LeagueFilterPipe implements PipeTransform {
transform(value: any[], filterBy: string): any[] {
filterBy = filterBy ? filterBy.toLocaleLowerCase() : null

for (let i = 0; i < value.length - 1; i++) {
for (let j = 0; j < value.length - 1; j++) {
if (value[j][filterBy] < value[j + 1][filterBy]) {
let temp = value[j]
value[j] = value[j + 1]
value[j + 1] = temp
}
}
}
return value;
}
}


The Html:

<div class="container">
<div class="row">
<div class="col-sm-offset-1 col-sm-4">
<h2>
{{leagueTable}}
</h2>

<table class="table table-responsive">
<thead>
<tr>
<th>#</th>
<th>Team</th>
<th>Points</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let team of teams | LeagueFilter:'points'; let i = index">
<td>{{i+1}}</td>
<td>{{team.name}}</td>
<td>{{team.points}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<fieldset class="col-sm-offset-1 col-lg-3">
<legend>Add Team</legend>
<label for="name">Name&nbsp;&nbsp;</label>
<input type="text" name="" value="" [(ngModel)] = "newTeam.name" />
<br/>
<label for="name">Points</label>
<input type="text" name="" value="" [(ngModel)] = "newTeam.points" />
<button type="submit" (click)="addTeam()">Add</button>
</fieldset>
</div>
</div>


The component:

import { Component } from '@angular/core';
import { ITeam } from './team';

@Component({
selector: 'league-list',
moduleId:module.id,
templateUrl: 'league-list.component.html',
styleUrls: ['league-list.component.css']
})
export class LeagueListComponent {
leagueTable: string = "La Liga"
teams: ITeam[] = [
{ name: "Barcelona", points: 84 },
{ name: "Real Madrid", points: 85 },
{ name: "Valencia", points: 78 },
{ name: "Sevilla", points: 80 },
{ name: "Villareal", points: 62 },
]
newTeam:ITeam = { name:"", points:null };
addTeam():void{
this.teams.push(this.newTeam)

}
}

Answer

The pipe is only executed by Angular if the value changes (or the parameter fo a pipe if there are any)

There are no parameters in your example and the value doesn't change. Angulars change detection doesn't check the content of an array, only if the reference changed (to a different array instance, same for objects btw.)

What you can do

  • make the pipe non-pure
@Pipe({
    name: "LeagueFilter",
    pure: false
})

This way the pipe will be executed with every change detection run, which can become expensive. The pipe should take care to not do unnecessary work by for example caching the result and use IterableDiffer (like used in NgFor) to check the array for changes explicitly.

  • create a copy of the array after the modification
this.teams.push(this.newTeam);
this.teams = this.teams.slice();

This also can become expensive if the array is large and changes happen often

  • introduce an artifical pipe parameter
this.teams.push(this.newTeam);
this.teamsChanged++;
transform(value: any[], filterBy: string, teamsChanged:any /*ignored*/): any[] {
<tr *ngFor="let team of teams | LeagueFilter:'points':teamsChanged; let i = index">

This additional parameter will cause the pipe to be called every time teamsChanged was modified.