lsieting
lsieting

Reputation: 95

Angular dataSource not loading in HTML template

I am trying to load data from an Excel file into an angular application. I can load the Excel data, parse it to get only the columns I need and store this in an array. This is done in my service.ts file.

The service is called from the component.ts file as a return. I verify here that I am getting my data using a console.log().

I am using @Output() to send the data to the child component to display the data as a MatTableDataSource.

I know the html template works as I have put the data into a json-server and called it from there using a service to return the data.

I have tried several different mods to see why the datasource is not being displayed in the html. My html screen does show that I have a number of records, but no data rows show up.

service.ts

import { Injectable, EventEmitter, Output } from '@angular/core';
import * as XLSX from 'xlsx';
import { BoMItem } from '../_models/bomItem';
import { Observable, of } from 'rxjs';

const BOM_DATA: BoMItem[] = [];

@Injectable({
  providedIn: 'root'
})
export class ExcelService {

  fileUploaded: File;
  storeData: any;
  worksheet: any;
  jsonData: any;

  myData = {} as BoMItem;

  constructor() { }

  readExcel(filesUploaded: FileList): Observable<any> {
    // console.log(filesUploaded);
    for (const file in filesUploaded) {
      if (Object.prototype.hasOwnProperty.call(filesUploaded, file)) {
        let importFile: File = filesUploaded[file];
        // console.log('File: ', importFile);
        let readFile = new FileReader();
        readFile.onload = (e) => {
          this.storeData = readFile.result;
          let data = new Uint8Array(this.storeData);
          let arr = new Array();
          for (let i = 0; i !== data.length; ++i) {
            arr[i] = String.fromCharCode(data[i]);
          }
          let bstr = arr.join('');
          let workbook = XLSX.read(bstr, { type: 'binary' });
          let firstSheetName = workbook.SheetNames[0];
          this.worksheet = workbook.Sheets[firstSheetName];
          this.createJsonData();
        };
        readFile.readAsArrayBuffer(importFile);
      }
    }
    return of(BOM_DATA);
  }

  createJsonData() {
    // : Observable<BoMItem[]>
    this.jsonData = XLSX.utils.sheet_to_json(this.worksheet, { raw: false });

    this.jsonData.forEach(element => {
      this.myData = {} as BoMItem;
      for (const key in element) {
        if (Object.prototype.hasOwnProperty.call(element, key)) {
          const item = element[key];
          if (key === 'DESC' || key === 'Description' || key === 'Disc') {
            this.myData.desc = item;
          }
          if (key === 'PART NUMBER' || key === 'MFG PART NUMBER' || key === 'Catalog') {
            this.myData.partNumber = item;
          }
          if (key === 'MFG' || key === 'Manufacturer' || key === 'mfg') {
            this.myData.mfg = item;
          }
          if (key === 'QTY' || key === 'qty' || key === 'Quantity') {
            this.myData.qty = item;
          }
        }
      }
      BOM_DATA.push(this.myData);
    });
    // console.log('BOM_DATA: ', BOM_DATA);
    return of(BOM_DATA);
  }

model.ts

export interface BoMItem {
    desc: string;
    partNumber: string;
    mfg: string;
    qty: string;
    supplierName?: string;
  }

parent component.ts

import { Component, OnInit, Output } from '@angular/core';
import { ExcelService } from 'src/app/_services/excel.service';

@Component({
  selector: 'app-bom-load',
  templateUrl: './bom-load.component.html',
  styleUrls: ['./bom-load.component.css']
})
export class BomLoadComponent implements OnInit {
  @Output()
  data: any;

  constructor(
    private excelService: ExcelService
  ) { }

  ngOnInit(): void {
  }

  uploadedFile(evt) {
    return this.excelService.readExcel(evt.target.files).subscribe((response: any) => {
      this.data = response;
      console.log('bom-load: ', this.data);
    });
  }
}

child component.ts

import { Component, ViewChild, OnInit, Input } from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatTableDataSource } from '@angular/material/table';
import { NgForm } from '@angular/forms';
import * as _ from 'lodash';
import { BoMItem } from 'src/app/_models/bomItem';
import { HttpClient } from '@angular/common/http';
import { BomService } from 'src/app/_services/bom.service';
import { MatSort } from '@angular/material/sort';



@Component({
  selector: 'app-bom-list',
  templateUrl: './bom-list.component.html',
  styleUrls: ['./bom-list.component.css']
})
export class BomListComponent implements OnInit {
  @ViewChild('bomForm', { static: false})
  bomForm: NgForm;

  bomData: BoMItem;

  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, {static: true}) sort: MatSort;
  displayedColumns: string[] = ['desc', 'partNumber', 'mfg', 'qty', 'supName', 'actions'];
  // displayedColumns: string[] = ['desc', 'partNumber', 'mfg', 'qty', 'actions'];

  @Input()
  data: any[];

  dataSource: MatTableDataSource<BoMItem>;

  isEditMode = false;

  constructor(private http: HttpClient, private bomService: BomService) {
    this.bomData = {} as BoMItem;
  }

  ngOnInit() {
    this.dataSource = new MatTableDataSource(this.data);
    // this.getAllBomItems();
    console.log('bom-list: ', this.dataSource);
    // this.dataSource.paginator = this.paginator;
    // this.dataSource.sort = this.sort;
  }

  getAllBomItems() {
    this.bomService.getList().subscribe((response: any) => {
      this.dataSource.data = response;
    });
  }

  editItem(element) {
    this.bomData = _.cloneDeep(element);
    this.isEditMode = true;
  }

  cancelEdit() {
    this.isEditMode = false;
    this.bomForm.resetForm();
  }

  onSubmit() {
    if (this.bomForm.form.valid) {
      if (this.isEditMode) {
        this.updateBomItem();
      }
      else {
        this.addBomItem();
      }
    } else {
      console.log('Enter valid data!');
    }
  }

  addBomItem() {
    throw new Error('Method not implemented.');
  }

  updateBomItem() {
    this.bomService.updateItem(this.bomData.partNumber, this.bomData).subscribe((response: any) => {
      this.dataSource.data = this.dataSource.data.map((o: BoMItem) => {
        if (o.partNumber === response.partNumber) {
          o = response;
        }
        return o;
      });
      this.getAllBomItems();
    });
  }

}

parent component.html

<mat-toolbar>
    <input type="file" class="form-control" (change)="uploadedFile($event)" placeholder="Upload file"
        accept=".xls,.xlsx" multiple>
</mat-toolbar>

<div *ngIf="data != null">
    <app-bom-list [data]="data"></app-bom-list>
</div>

child component.html

<div class="container">
   
        <ng-container *ngIf="isEditMode;">
            <button mat-button color="warn">Update</button>
            <a mat-button color="warn" (click)="cancelEdit()">Cancel</a>
        </ng-container>
    </form> -->

    <table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
        <ng-container matColumnDef="desc">
            <th mat-header-cell *matHeaderCellDef>Description</th>
            <td mat-cell *matCellDef="let element">{{element.desc }}</td>
        </ng-container>

        <ng-container matColumnDef="partNumber">
            <th mat-header-cell *matHeaderCellDef>Part Number</th>
            <td mat-cell *matCellDef="let element"> {{element.partNumber}} </td>
        </ng-container>

        <ng-container matColumnDef="mfg">
            <th mat-header-cell *matHeaderCellDef>Manufacturer</th>
            <td mat-cell *matCellDef="let element"> {{element.mfg}} </td>
        </ng-container>

        <ng-container matColumnDef="qty">
            <th mat-header-cell *matHeaderCellDef> Quantity </th>
            <td mat-cell *matCellDef="let element"> {{element.qty}} </td>
        </ng-container>

        <ng-container matColumnDef="supName">
            <th mat-header-cell *matHeaderCellDef> Supplier Name </th>
            <td mat-cell *matCellDef="let element"> {{element.supplierName}} </td>
        </ng-container>

        <ng-container matColumnDef="actions">
            <mat-header-cell *matHeaderCellDef>
                <!-- <button mat-icon-button color="primary">
                    <mat-icon aria-label="Example icon-button with a heart icon">add</mat-icon>
                </button> -->
            </mat-header-cell>

            <mat-cell *matCellDef="let element; let i=index;">
                <button mat-icon-button color="accent">
                    <mat-icon aria-label="Edit" (click)="editItem(element)">edit</mat-icon>
                </button>

                <button mat-icon-button color="accent">
                    <mat-icon aria-label="Delete">delete</mat-icon>
                </button>
            </mat-cell>
        </ng-container>

        <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
        <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>

    </table>

    <mat-paginator [length]="dataSource?.data.length" [pageSize]="5" [pageSizeOptions]="[5, 10, 25, 100, 500]">
    </mat-paginator>

</div>

Console.Log information:

bom-load:  
[]
0: {qty: "6", mfg: "Mencom", partNumber: "1321-3R8-C", desc: "480V 3-PHASE AC DRIVE REACTORS, OPEN TYPE, 8A, 3 HP Drive"}
1: {qty: "1", mfg: "AB", partNumber: "1756-EN2T", desc: "1756 CONTROL LOGIX ETHERNET / IP BRIDGE MODULE (128 TCP/IP CONNECTIONS)"}
2: {qty: "1", mfg: "AB", partNumber: "1756-EN2TR", desc: "1756 CONTROL LOGIX ETHERNET / IP BRIDGE MODULE (128 TCP/IP CONNECTIONS) REDUNDANT"}
3: {qty: "1", mfg: "AB", partNumber: "1756-L81E", desc: "1756 CONTROL LOGIX CONTROLLER, USER MEMORY 3MB, 1Gb ENET PORT"}
4: {qty: "6", mfg: "AB", partNumber: "25B-D6P0N104", desc: "POWERFLEX 525 VARIABLE FREQUENCY DRIVE 3 HP, 480VAC 3 PH, FRAME A"}
5: {qty: "6", mfg: "AB", partNumber: "25-COMM-E2P", desc: "POWERFLEX 525 DUAL-PORT ETHERNET/IP MODULE"}
length: 6
__proto__: Array(0)
bom-list.component.ts:43 bom-list:  
MatTableDataSource {_renderData: BehaviorSubject, _filter: BehaviorSubject, _internalPageChanges: Subject, _renderChangesSubscription: Subscriber, sortingDataAccessor: ƒ, …}
filterPredicate: (data, filter) => {…}
filteredData: (6) [{…}, {…}, {…}, {…}, {…}, {…}]
sortData: (data, sort) => {…}
sortingDataAccessor: (data, sortHeaderId) => {…}
_data: BehaviorSubject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
_filter: BehaviorSubject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
_internalPageChanges: Subject {_isScalar: false, observers: Array(0), closed: false, isStopped: false, hasError: false, …}
_renderChangesSubscription: Subscriber {closed: false, _parentOrParents: null, _subscriptions: Array(1), syncErrorValue: null, syncErrorThrown: false, …}
_renderData: BehaviorSubject {_isScalar: false, observers: Array(1), closed: false, isStopped: false, hasError: false, …}
data: Array(6)
0: {qty: "6", mfg: "Mencom", partNumber: "1321-3R8-C", desc: "480V 3-PHASE AC DRIVE REACTORS, OPEN TYPE, 8A, 3 HP Drive"}
1: {qty: "1", mfg: "AB", partNumber: "1756-EN2T", desc: "1756 CONTROL LOGIX ETHERNET / IP BRIDGE MODULE (128 TCP/IP CONNECTIONS)"}
2: {qty: "1", mfg: "AB", partNumber: "1756-EN2TR", desc: "1756 CONTROL LOGIX ETHERNET / IP BRIDGE MODULE (128 TCP/IP CONNECTIONS) REDUNDANT"}
3: {qty: "1", mfg: "AB", partNumber: "1756-L81E", desc: "1756 CONTROL LOGIX CONTROLLER, USER MEMORY 3MB, 1Gb ENET PORT"}
4: {qty: "6", mfg: "AB", partNumber: "25B-D6P0N104", desc: "POWERFLEX 525 VARIABLE FREQUENCY DRIVE 3 HP, 480VAC 3 PH, FRAME A"}
5: {qty: "6", mfg: "AB", partNumber: "25-COMM-E2P", desc: "POWERFLEX 525 DUAL-PORT ETHERNET/IP MODULE"}
length: 6
__proto__: Array(0)
filter: (...)
paginator: (...)
sort: (...)
__proto__: DataSource

Image of html table trying to use data loaded from Excel Screen image of table showing pagination count but no rows

Image of static data using this.getAllBomItems Image of static data using this.getAllBomItems()

Interesting development:

by adding; as suggested; ngOnChanges(), I now get data, but ONLY after clicking the pagination buttons.

  ngOnChanges() {
    this.dataSource = new MatTableDataSource(this.data);
    this.dataSource.paginator = this.paginator;
    console.log('bom-list: ', this.dataSource);
    console.log('this.data: ', this.data);
    this.dataSource.paginator = this.paginator;
  }

Upvotes: 0

Views: 1425

Answers (2)

andsilver
andsilver

Reputation: 5972

Update

Instead of using ngOnInit, try to use ngOnChanges in your child component.

And try to use ChangeDetectorRef.

constructor (
  ...
  private cdr: ChangeDetectorRef
) {}

ngOnChanges() {
    this.dataSource = new MatTableDataSource(this.data);
    this.dataSource.paginator = this.paginator;
    this.cdr.detectChanges();
}

Note

<div *ngIf="data != null">

Change the above line into:

<div *ngIf="data">

You data value is undefined so *ngIf="data != null" this expression will be truthy. So the ngOnInit of your child component will be called without the data.

Also you need to update the below code too:

getAllBomItems() {
    this.bomService.getList().subscribe((response: any) => {
      this.dataSource = new MatTableDataSource(response);
      // this.dataSource.data = response; <- your table will not detect change with this line, you must replace the whole dataSource object.
    });
  }

Upvotes: 1

Lucas Sartori
Lucas Sartori

Reputation: 41

try inject ChangeDetectorRef in your component with contains your datatable and call like this:

  constructor(private cdRef: ChangeDetectorRef) {
  }

//after add new data to your table
this.cdRef.detectChanges();

Upvotes: 0

Related Questions