1
votes

I am trying to implement same pipe for two-level json data, that I am parsing in html in two, nested ngFor loops.

home.component.html:

<div class="drop-type">
<div class="label">Multi-select two levels</div>

<div class="search-input-box">
  <input [(ngModel)]="searchValue" class="search-input" placeholder="Search...">

  <div *ngIf="searchValue && searchValue.length > 1" class="ac-list">
    <ul>
      <ng-container *ngFor="let value of values | value: searchValue">
        <li class="drop-item" (click)="selectMSTLItem(value)">
          <div class="check" [ngClass]="{'selected': value.selected}"></div>
          <div class="name">{{value.name}}</div>
        </li>
        <ng-container *ngFor="let subvalue of value.subitems | value: searchValue">
          <li class="drop-item sub-item" (click)="selectMSTLItem(subvalue)">
            <div class="check" [ngClass]="{'selected': subvalue.selected}"></div>
            <div class="name">{{subvalue.name}}</div>
          </li>
        </ng-container>
      </ng-container>
    </ul>
  </div>
</div>

<ng-container *ngFor="let selectedItem of values">
  <div class="selected-drop-item">
    <div *ngIf="selectedItem.selected" (click)="deselectMSTLItem(selectedItem)">{{selectedItem.name}}</div>
  </div>
  <ng-container *ngFor="let selectedSubItem of selectedItem.subitems">
    <div class="selected-drop-item">
      <div *ngIf="selectedSubItem.selected" (click)="deselectMSTLItem(selectedSubItem)">{{selectedSubItem.name}}</div>
    </div>
  </ng-container>
</ng-container></div>

home.component.ts:

import { Component, OnInit } from '@angular/core';
import {ValuesService} from './values.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.less']
})
export class HomeComponent implements OnInit {
  values: any[] = [];
  searchValue: string;

  constructor(private valuesService: ValuesService) {
  }

  ngOnInit() {
    this.valuesService.getValues()
      .subscribe(
        (response) => {
          this.values = response.results;
        },
        (error) => {
          console.log(error);
        }
      );
  }

  selectMSTLItem(selectedItem) {
    selectedItem.selected = !selectedItem.selected;
  }

  deselectMSTLItem(selectedItem) {
    selectedItem.selected = false;
  }
}

search.pipe.ts:

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

@Pipe({name: 'value'})
export class SearchPipe implements PipeTransform {
  transform(values: any, searchValue: any): any {
    if (searchValue == null) { return values; }

    return values.filter(function(value){
      return value.name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1
        || value.subitems.name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
    });
  }
}

values.service.ts:

import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class ValuesService {
  private _url = '../assets/values.json';

  constructor(private _http: Http) {}

  getValues () {
    return this._http.get(this._url)
      .map((response: Response) => response.json());
  }
}

values.json:

    {
  "results": [
    {
      "name": "Value 1.0",
      "selected": false,
      "subitems": [
        {
          "name": "Value 1.1",
          "selected": false
        },
        {
          "name": "Value 1.2",
          "selected": false
        },
        {
          "name": "Value 1.3",
          "selected": false
        }
      ]
    },
    {
      "name": "Value 2.0",
      "subitems": [
        {
          "name": "Value 2.1",
          "selected": false
        },
        {
          "name": "Value 2.2",
          "selected": false
        },
        {
          "name": "Value 2.3",
          "selected": false
        }
      ]
    },
    {
      "name": "Value 3.0",
      "subitems": [
        {
          "name": "Value 3.1",
          "selected": false
        },
        {
          "name": "Value 3.2",
          "selected": false
        },
        {
          "name": "Value 3.3",
          "selected": false
        }
      ]
    }
  ]
}

What I am trying to accomplish is when, for example, user enters string "Va" it lists all level 1 and level 2 name values that contains string "va", which it does now. But, when user wants to search for level 2 elements and it enters string "Value 1." (searching for Value 1.1, or Value 1.2 etc) I got an error

TypeError: Cannot read property 'toLowerCase' of undefined

in pipe on second return value (value.subitems.name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1)

My guess is that I need to somehow, in pipe, loop through ALL level 2 elements, and filter out those who contain search value, and then return those as well as all level 1 elements of course. Can someone help me with this problem please?

2

2 Answers

1
votes

value.subitems is an array you cannot access value.subitems.name try this one:

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

@Pipe({name: 'value'})
export class SearchPipe implements PipeTransform {
  transform(values: any[], searchValue: any): any {
    if (searchValue == null) { return values; }

    return values.filter(v => this.filterFn(v, searchValue));
  }

  filterFn(value: any, searchValue) {
    let flag = this.testSearchCondition(value.name, searchValue);
    if (!flag && value.subitems) {
      flag = value.subitems.filter(v => this.testSearchCondition(v.name, searchValue)).length > 0;
    }
    return flag;
  }
  testSearchCondition(name: string, searchValue: string): boolean {
    return name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
  }
}
1
votes

Nested array Search Use below code Here data.sub_categories is nested array

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

@Pipe({
    name: 'category'
})
export class CategoryPipe implements PipeTransform {

    transform(value: any, args?: any): any {
        if (args === undefined || args === '') { return value; }
        return value.filter(data => { 
          return  data.category_name.toLowerCase().includes(args.toLowerCase())  || data.sub_categories.find(item => item.category_name.toLowerCase().includes(args.toLowerCase()));

        });
    }

}