2

The page shows a list of dives, it has an "add new dive", "clear dives" and a search box, which filters the displayed list as you type into it.

This is the template:

<div class="container-fluid">

  <h1>My Latest Dives (Angular/TypeScript)</h1>
  <div class="row">
    <div class="col-sm-5">
      <button class="btn btn-primary btn-lg"
        [disabled]="!enableAdd()"
        (click)="addDive()">
          Add new dive
      </button>
      <button class="btn btn-danger btn-lg"
        (click)="clearDives()">
          Clear dives
      </button>
    </div>
    <div class="col-sm-4 col-sm-offset-3">
      <input #searchBox class="form-control input-lg"
        placeholder="Search"
        (keyup)="0" />
    </div>
  </div>
  <div class="row">
    <div class="col-sm-4"
      *ngFor="let dive of dives | contentFilter:searchBox.value">
      <h3>{{dive.site}}</h3>
      <h4>{{dive.location}}</h4>
      <h2>{{dive.depth}} feet | {{dive.time}} min</h2>
    </div>
  </div>
</div>

This is the component code:

import {Component} from '@angular/core';

@Component({
  selector: 'divelog',
  templateUrl: 'app/dive-log.template.html'
})

export class DiveLogComponent {
  public dives = [];
  private _index = 0;
  private _stockDives = [
  {
    site: 'Abu Gotta Ramada',
    location: 'Hurghada, Egypt',
    depth: 72,
    time: 54
  },
  {
    site: 'Ponte Mahoon',
    location: 'Maehbourg, Mauritius',
    depth: 54,
    time: 38
  },
  {
    site: 'Molnar Cave',
    location: 'Budapest, Hungary',
    depth: 98,
    time: 62
  }];

  public enableAdd() {
    return this._index < this._stockDives.length;
  }

  public addDive() {
    if (this.enableAdd()) {
      this.dives.push(this._stockDives[this._index++]);
    }
  }

  public clearDives() {
    this.dives = [];
    this._index = 0;
  } 
}

This is the filter code:

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

@Pipe({name: 'contentFilter'})
export class ContentFilterPipe implements PipeTransform {

  transform(value: any[], searchFor: string) : any[] {
    if (!searchFor) return value;

    searchFor = searchFor.toLowerCase();
    return value.filter(dive => 
      dive.site.toLowerCase().indexOf(searchFor) >= 0 ||
      dive.location.toLowerCase().indexOf(searchFor) >= 0 ||
      dive.depth.toString().indexOf(searchFor) >= 0 ||
      dive.time.toString().indexOf(searchFor) >= 0);
  }
}

The filter is getting invoked and the list is getting rerendered whenever I type into the search box, but not when I click "add" button. If I have something in the search box, "add" button does not result in the change of the dive list even though the content of the search box allows the new items to be displayed. How do I change the code so that clicking the "add" button would cause rerendering of the displayed list of dives?

1 Answers1

5

You have a pure pipe so

its method transform will be executed only when it detects a pure change to the input value.

For your case

*ngFor="let dive of dives | contentFilter:searchBox.value"

the input value will be dives and searchBox.value

According to the angular2 guide on pipes:

A pure change is either a change to a primitive input value (String, Number, Boolean, Symbol) or a changed object reference (Date, Array, Function, Object).

  • When a dive is added, the array reference (dives) isn't changed – hence transform method is not executed.
  • When something is typed into the filter input, searchBox.value does change - hence transform is executed.

So one of possibles solutions is to have always a new reference array each time a div is added:

Just replace:

this.dives.push(this._stockDives[this._index++]);

with:

this.dives = this.dives.concat(this._stockDives[this._index++]);

or:

this.dives = [...this.dives, this._stockDives[this._index++]];

Second way to do it working is use impure pipe:

@Pipe({name: 'contentFilter', pure: false })
yurzui
  • 205,937
  • 32
  • 433
  • 399