Tips for conducting a worldwide search in Angular 2?

I'm currently navigating my way through angular2 development and I am aiming to conduct a comprehensive search within an array of JSON objects. To illustrate, consider this sample array:

invoiceList = 
  [
    {
      invoiceNumber: 1234, 
      invoiceSupplier: "test", 
      invoiceStatus: "Import error", 
      invoiceCategory: "invoice with GR", 
      date: "22/01/2017", 
      amount : 134527
    },
    ...
  ];

I envision carrying out my search in the following manner: https://i.sstatic.net/YxoBo.png

The crux of the issue lies in:

  • I wish to only search based on certain values (e.g., status, supplier name, number...) while displaying OTHER fields (such as date, net amount etc..).
  • I aim to sort the final results based on specific criteria (e.g., number, supplier, date and amount). However, I find myself at a loss on how to actualize this in angular2.
  • And lastly, is there an equivalent of ng-show in angular2? Essentially, I desire to exhibit the table solely upon clicking the search button, and have it disappear when cancel is clicked.

In angular 1, accomplishing these tasks was simpler using filters, 'orderBy', among other constructs. Yet, with angular2, it seems less straightforward and has left me feeling quite bewildered. Could you please assist me in resolving these issues???

Here is the code snippet for my component :

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

  @Component({
    selector: 'app-search',
    templateUrl: './search.component.html'
  })
  export class SearchComponent implements OnInit {

  invoiceList = [{invoiceNumber:  1234, invoiceSupplier : "test", invoiceStatus : "Import error", invoiceCategory: "invoice with GR", date : "22/01/2017", amount : 134527}, 
  {invoiceNumber:  15672, invoiceSupplier : "test11", invoiceStatus : "Import error", invoiceCategory: "invoice without PO", date : "02/01/2017", amount : 134.3},
  {invoiceNumber:  99863, invoiceSupplier : "test22", invoiceStatus : "Other Document", invoiceCategory: "invoice with GR", date : "10/12/2016", amount : 189},
  {invoiceNumber:  24561, invoiceSupplier : "test50", invoiceStatus : "Other Document", invoiceCategory: "invoice without PO", date : "15/01/2017", amount : -134527},
  ];


    constructor() { }

    ngOnInit() {
    }

  }

and my html code :

  <form>
    <table>
      <tr>
        <td>Invoice Number :</td>
        <td>
          <input name="invoiceNumber">
        </td>

        <td>Invoice Supplier Name :</td>
        <td>
          <input name="invoiceSupplier">
        </td>
      </tr>

      <tr>
        <td>Invoice Status :</td>
        <td>
          <select name="invoiceStatus">

            <option></option>
            <option> Import error </option>
            <option> Invoice control required </option>
            <option>Rejected from SAP </option>
            <option>Other Document </option>
            <option> Invoice created manually in SAP </option>
            <option>To be integrated in SAP </option>
            <option> Integrated in SAP </option>
            <option> Booked in SAP </option>
            <option>Paid in SAP</option>
          </select>
        </td>

        <td>Invoice Category :</td>
        <td>

          <select name="invoiceCategory">
            <option></option>
            <option>Invoice with GR</option>
            <option>Invoice without GR</option>
            <option>Invoice without PO</option>

          </select>
        </td>
      </tr>

      <tr>
        <td>Order :</td>
        <td>
          <select name="order">

            <option> Number </option>
            <option> Supplier </option>
            <option> Date </option>
            <option> Net Amount </option>
          </select>
        </td>
      </tr>

      <tr>
        <td>
          <button type="submit">Search</button>
        </td>
        <td>
          <div class="detail">
            <button type="reset">Cancel</button>
          </div>
        </td>
      </tr>

    </table>
  </form>

Despite not having made significant progress yet, I am really finding my footing in angular2 to be challenging. Any guidance or assistance would be greatly appreciated!

Many thanks in advance!

Answer №1

Completed Successfully!! - View on PLUNKER

@Component({
  selector: 'app-search',
  templateUrl: 'src/search.component.html'
})
export class SearchComponent{

 invoiceList = [
  {invoiceNumber:  1234, invoiceSupplier : "test", invoiceStatus : "Import error", invoiceCategory: "invoice with GR", date : "22/01/2017", amount : 134527}, 
  {invoiceNumber:  15672, invoiceSupplier : "test11", invoiceStatus : "Import error", invoiceCategory: "invoice without PO", date : "02/01/2017", amount : 134.3},
  {invoiceNumber:  99863, invoiceSupplier : "test22", invoiceStatus : "Other Document", invoiceCategory: "invoice with GR", date : "10/12/2016", amount : 189},
  {invoiceNumber:  24561, invoiceSupplier : "test50", invoiceStatus : "Other Document", invoiceCategory: "invoice without PO", date : "15/01/2017", amount : -134527},
 ];

 invoiceCats = [];
 invoiceStatuses = [];
 invoiceNumbers = [];
 invoiceFields = Object.keys(this.invoiceList[0]);

 invoiceStatus = "";
 invoiceCategory = "";
 invoiceNumber;
 invoiceSupplier;
 order = this.invoiceFields[0];

 searchResults = [];

 constructor() { 
   this.invoiceList.forEach(i => {
     if(this.invoiceCats.indexOf(i.invoiceCategory) === -1) {
      this.invoiceCats.push(i.invoiceCategory);
     }

     if(this.invoiceStatuses.indexOf(i.invoiceStatus) === -1) {
      this.invoiceStatuses.push(i.invoiceStatus);
     }

     this.invoiceNumbers.push(i.invoiceNumber);
   })
 }

 ngOnInit() {
 }

 searchNow(e) {
   e.preventDefault();
   this.searchResults = this.invoiceList.filter(i => {
     return (this.invoiceStatus ? i.invoiceStatus === this.invoiceStatus : true) 
        &&  (this.invoiceNumber ? i.invoiceNumber === this.invoiceNumber : true)
        &&  (this.invoiceSupplier ? i.invoiceSupplier === this.invoiceSupplier : true)
        &&  (this.invoiceCategory ? i.invoiceCategory === this.invoiceCategory : true)
   }).sort((a, b) => {
      if(['invoiceNumber', 'amount'].indexOf(this.order) > -1) return a[this.order] - b[this.order];
      if(['invoiceSupplier', 'invoiceStatus', 'invoiceCategory'].indexOf(this.order) > -1) return (a[this.order] == [a[this.order], b[this.order]].sort()[1] ? 1 : -1);
      if(this.order === 'date') {
        const x = new Date(a.date.split('/').reverse().join('/'));
        const y = new Date(b.date.split('/').reverse().join('/'));
        return x.getTime() - y.getTime();
      }
   })
 }

}

Note: The code inside constructor is just for generating metadata.

<form (submit)="searchNow($event)">
    <table>
      <tr>
        <td>Invoice Number :</td>
        <td>
          <input name="invoiceNumber" [(ngModel)]="invoiceNumber" type="number">
        </td>

        <td>Invoice Supplier Name :</td>
        <td>
          <input name="invoiceSupplier" [(ngModel)]="invoiceSupplier" type="text">
        </td>
      </tr>

      <tr>
        <td>Invoice Status :</td>
        <td>
          <select name="invoiceStatus" [(ngModel)]="invoiceStatus">
            <option value="">Any</option>
            <option *ngFor="let iS of invoiceStatuses" [value]="iS">{{iS}}</option>
          </select>
        </td>

        <td>Invoice Category :</td>
        <td>

          <select name="invoiceCategory" [(ngModel)]="invoiceCategory">
            <option value="">Any</option>
            <option *ngFor="let iC of invoiceCats" [value]="iC">{{iC}}</option>
          </select>
        </td>
      </tr>

      <tr>
        <td>Order By:</td>
        <td>
          <select name="order" [(ngModel)]="order">
            <option *ngFor="let iF of invoiceFields" [value]="iF">{{iF}}</option>
          </select>
        </td>
      </tr>

      <tr>
        <td>
          <button type="submit">Search</button>
        </td>
        <td>
          <div class="detail">
            <button type="reset">Cancel</button>
          </div>
        </td>
      </tr>

    </table>
  </form>

  <div>
    <ul>
      <li *ngFor="let i of searchResults">Number: {{i.invoiceNumber}},  Supplier: {{i.invoiceSupplier}}, Date: {{i.date}}</li>
    </ul>
  </div>

Note: There are several ways to work with forms in Angular2, feel free to explore and utilize the one that suits your needs best.

Answer №2

Providing a foundational framework...

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

export class Invoice {
    invoiceNumber: number;
    invoiceSupplier: string;
    invoiceStatus: string;
    invoiceCategory: string;
    date: string;
    amount: number
}

@Component({
    moduleId: module.id,
    selector: 'my-app',
    template: `
        <div></div>
    `,
    styleUrls: []
})

export class AppComponent implements OnInit {

    ngOnInit(): void {

        this.invoiceList = [ {invoiceNumber: 1231, invoiceSupplier: "test1", invoiceStatus: "Import error3", invoiceCategory: "invoice with GR1", date: "22/01/2017", amount : 134527 },
                             {invoiceNumber: 1232, invoiceSupplier: "test1", invoiceStatus: "Import error2", invoiceCategory: "invoice with GR2", date: "22/01/2017", amount : 134527 },
                             {invoiceNumber: 1233, invoiceSupplier: "test2", invoiceStatus: "Import error1", invoiceCategory: "invoice with GR1", date: "22/01/2017", amount : 134527 },
                             {invoiceNumber: 1234, invoiceSupplier: "test3", invoiceStatus: "Import error3", invoiceCategory: "invoice with GR3", date: "22/01/2017", amount : 134527 },
                          ];

        //this.invoiceFilter.invoiceNumber = 1234;
        //this.invoiceFilter.invoiceSupplier = "test2";
        this.invoiceFilter.invoiceCategory = "invoice with GR2";

        let filterdeInvoices = this.filterInvoices(this.invoiceList, this.invoiceFilter);
        console.log(filterdeInvoices);
        this.fieldToSortBy = "invoiceNumber";
        let sortedInvoices = this.sortInvoices(filterdeInvoices, this.fieldToSortBy);
        console.log(sortedInvoices);
    }

    invoiceFilter = new Invoice();
    fieldToSortBy: string;
    invoiceList: Invoice[];

    filterInvoices(invoiceList:Invoice[], invoiceFilter: Invoice): Invoice[]         {        
        return invoiceList.filter((invoice) => (invoiceFilter.invoiceNumber ? invoiceFilter.invoiceNumber === invoice.invoiceNumber : true) &&
                                               (invoiceFilter.invoiceSupplier ? invoiceFilter.invoiceSupplier === invoice.invoiceSupplier : true) &&
                                               (invoiceFilter.invoiceStatus ? invoiceFilter.invoiceStatus === invoice.invoiceStatus : true) &&
                                               (invoiceFilter.invoiceCategory ? invoiceFilter.invoiceCategory === invoice.invoiceCategory : true));
    }

    sortInvoices(invoiceList:Invoice[], fieldToSortBy: string): Invoice[] {
        return invoiceList.sort((inv1, inv2) => (inv1[fieldToSortBy] > inv2[fieldToSortBy] ? 1 : -1));
    }
}

Answer №3

If you want to efficiently filter data, I suggest utilizing a custom Pipe.

Define your custom Pipe as follows:

  1. Inspect incoming variables
  2. Filter out unwanted invoices
  3. Sort them if needed
@Pipe({
  name: 'filter'
})
export class FilterPipe implements PipeTransform {
  public transform(invoices: Invoice[], searchOptions: any): Invoice[] {
    if (!invoices || !invoices.length) return [];
    if (!searchOptions) return [];

    let filtered = invoices.filter(i => {
      return i &&
             (!searchOptions.number || i.invoiceNumber.toString().indexOf(searchOptions.number) >= 0) &&
             (!searchOptions.name || i.invoiceSupplier.toLowerCase().indexOf(searchOptions.name.toLowerCase()) >= 0) &&
             (!searchOptions.status || i.invoiceStatus === searchOptions.status) &&
             (!searchOptions.category || i.invoiceCategory === searchOptions.category);
    });

    let orderBy = searchOptions.orderBy;
    if (!orderBy) return filtered;

    /* kudos for sorting goes to: 'Ankit Singh' ! :) */
    return filtered.sort((a, b) => {

      switch (orderBy) {
        default: return 0;

        case 'invoiceNumber':
        case 'amount':
          return a[orderBy] - b[orderBy];
        case 'invoiceSupplier':
        case 'invoiceStatus':
        case 'invoiceCategory':
          return (a[orderBy] == [a[orderBy], b[orderBy]].sort()[1] ? 1 : -1);
        case 'date': {
          const x = new Date(a.date.split('/').reverse().join('/'));
          const y = new Date(b.date.split('/').reverse().join('/'));
          return x.getTime() - y.getTime();
        }
      }
   });
  }
}

To utilize this Pipe, follow this format:

<div class="row" *ngFor="let invoice of invoiceList | filter:searchOptions">
  <div>{{ invoice.invoiceNumber }}</div>
  <div>{{ invoice.invoiceSupplier }}</div>
  <div>{{ invoice.date }}</div>
  <div>{{ invoice.amount }}</div>
</div>

The searchOptions object is obtained from our form inputs:

<md-input-container>
  <input mdInput ngModel name="name" placeholder="Invoice supplier name">
</md-input-container>

<md-select placeholder="Invoice status" ngModel name="status">
  <md-option *ngFor="let status of statusArray" [value]="status.val">
    {{ status.name}}
  </md-option>
</md-select>

<md-select placeholder="Invoice category" ngModel name="category">
  <md-option *ngFor="let category of categoryArray" [value]="category.val">
    {{ category.name}}
  </md-option>
</md-select>

<md-select placeholder="order by" ngModel name="orderBy">
  <md-option *ngFor="let key of invoiceKeyArray" [value]="key.val">
    {{ key.name }}
  </md-option>
</md-select>

<button md-raised-button color="primary" (click)="searchOptions = f.value">search</button>
<button md-raised-button color="accent" (click)="resetForm(f);">reset</button>

Check out the live demonstration here: http://plnkr.co/edit/VPTnIOpieKt3YLqsEncr?p=preview

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

When using ngModel, the Tinymce Angular 2+ templates do not initially replace values, whereas they do when templates are inserted by the user

In my Angular 7 app, I am utilizing the TinyMCE editor component. When I insert a template variable into the editor: <span class="data-variable">${variable_name}</span> The variable gets replaced successfully with a value from the `template_r ...

Remove multiselect label in PrimeNG

I am attempting to change the multiselect label of selected items and replace it with a static default label. Here is what it currently shows: This is what I have tried: .p-multiselect-label { visibility: collapse; overflow: hidden; white ...

Issue NG8002: Unable to link to 'FormGroup' as it is not recognized as a property of 'form' in Angular 9

I recently created a brand new Angular 9 application with a submodule. Here are my app.modules.ts: import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppRoutingModule } from & ...

Updating form fields within nested forms using the FormBuilder array

The recommended method to change nested values according to the API documentation is using patchValue. For example, myForm.patchValue({'key': {'subKey': 'newValue'}}); But what if we need to change values in a nested array, ...

Is Angular capable of determining which module to load for the frontend, or does it always need to load the entire application with all modules?

Is it possible for Angular 2 to selectively load specific modules for the frontend, or does it always have to load the entire application with all modules included? ...

Guide on customizing VS Code's Explorer to display angular js icons

How can you customize the icons displayed in Explorer within VS Code by adding a specific addon procedure? ...

Exploring the World of Geometry with Three.js and TypeScript

How can I correctly define types for Mesh Vertices and Faces? In my initial attempt, I decided to create a new Mesh object. However, when attempting to access Vertices and Faces from the geometry property, I encountered a few errors: const material = new ...

The button values fail to adhere to the specified input length in the Point of Sale application built with Angular

Having an issue with my Point of Sale app. When I enter values using the keyboard, it respects the maxLength limit, but when I enter values using the buttons, it does not respect the limit. Any ideas on how to solve this? <div mat-dialog-content> < ...

Typescript is unable to access the global variables of Node.js

After using Typescript 1.8 with typings, I made the decision to upgrade to typescript 2.8 with @types. When I downloaded @types/node, my console started showing errors: error TS2304: Cannot find name 'require'. The project structure is as foll ...

What is the reason behind the restriction on creating circular dependencies in Ionic applications?

Working with Ionic, I have created 2 services to provide data: export class DocumentService { constructor(favorisService: FavorisService) { } async GetById(id: number): Promise<DocumentModel>{ let path = this.favorisService.getPath(); ...

Error in Redux reducer file caused by an incorrect data type in action.payload

I have encountered a type error in my reducers' file where it says that my 'Property 'address' does not exist on type 'number | { connection: boolean; address: string; }'. This issue is arising in my React application while us ...

Whenever I try to update my list of products, I encounter an error message stating that the property 'title' cannot be read because it is null

I am encountering an issue with editing data stored in the database. When attempting to edit the data, it is not displaying correctly and I am receiving a "cannot read property" error. HTML admin-products.component.html <p> <a routerLink="/ad ...

Modify FrameColor of Material UI Inputs when Reset button is clicked

When using Angular Material UI in the Registermenu, I am facing an issue where clicking on the reset button deletes the content but leaves the red frames unchanged. Can anyone provide assistance with this problem? Thank you. Screenshot Here is the code f ...

Create the correct structure for an AWS S3 bucket

My list consists of paths or locations that mirror the contents found in an AWS S3 bucket: const keysS3 = [ 'platform-tests/', 'platform-tests/datasets/', 'platform-tests/datasets/ra ...

Troubleshooting Docker Angular+Nginx: Decoding the Mystery Behind Error Codes 403 and

Backend of my .net application is running on mssql and frontend is built with Angular. I have successfully configured Docker files and nginx.conf, but I am facing an issue specifically with Angular 17 within Docker. The main problem is that, when accessing ...

The Angular router is directed to an empty URL path instead of the specified URL

I have encountered a strange issue while developing my angular app which involves the router functionality. All routes were working perfectly fine until I discovered that after a successful login, instead of navigating to the '/admin' path as int ...

This TypeScript error occurs when trying to assign a value of type 'null' to a parameter that expects a type of 'Error | PromiseLike<Error | undefined> | undefined'

Currently, I am making use of the Mobx Persist Store plugin which allows me to store MobX Store data locally. Although the documentation does not provide a TypeScript version, I made modifications to 2 lines of code (one in the readStore function and anot ...

Stopping all child events upon clicking (when navigating to a different tab) in RxJS

Currently, I am fetching all the designs (let's say 100) from the database and iterating through them. For each design, I am fetching the design image using a child component. <div *ngFor="let design of designs"> <div (click)="sho ...

Utilizing Angular 2's Routerlink with *ngIf and Parameters

Currently, I am facing an issue with a routerlink that includes a parameter: http://localhost:4200/item/1 I am trying to figure out how to implement an *ngIf statement with a parameter.... Here is what I have attempted so far: <div *ngIf="router.url ...

Exploring the use of file loaders with TypeScript

Currently, I have configured a file loader for .png files using esbuild. Additionally, I have the following in my index.d.ts: declare module "*.png" { const value: string; export default value; } One issue I am facing is that my code editor ...