4

I'm trying to create a angular component, which renders a html table using the provided object array.

I have implemented OnChanges to detect changes to the @Input, it doesn't seem to work though.

For testing, I'm using setInterval to add some data, but It's not getting reflected in the child component.

This it the code I have so far,

ng-table.component.ts:

export class NgTableComponent implements OnInit, OnChanges {

    @Input() data: any[];

    private columns: string[];
    private rows: string[][];

    constructor() {
        this.columns = [];
        this.rows = [];
    }

    ngOnInit() {
    }

    ngOnChanges(changes: SimpleChanges) {
        console.log(changes);
        if (changes.data) {
            this.extractRowsAndColumns(this.data);
        }
    }

    extractRowsAndColumns(objArray: any[]): void {
        const colSet = new Set<string>();
        for (let i = 0; i < objArray.length; i++) {
            const keys = Object.keys(objArray[i]);
            for (let j = 0; j < keys.length; j++) {
                colSet.add(keys[j]);
            }
        }
        this.columns = Array.from(colSet);

        for (let i = 0; i < objArray.length; i++) {
            const obj = objArray[i];
            const row = [];
            for (let j = 0; j < this.columns.length; j++) {
                if (obj.hasOwnProperty(this.columns[j])) {
                    row.push(obj[this.columns[j]]);
                } else {
                    row.push(null);
                }
            }
            this.rows.push(row);
        }
    }
}

ng-table.component.html:

<div>
    <table class="ngtable">
        <tr>
            <th *ngFor="let col of columns">
                {{col}}
            </th>
        </tr>
        <tr *ngFor="let row of rows">
            <td *ngFor="let cell of row">{{cell}}</td>
        </tr>
    </table>
</div>

I'm using the above component in app.component

app.component.ts:

export class AppComponent {
  timer: any;
  count = 0;
  constructor() {
    const o: any = {
      'id': 1,
      'name': 'Jeanette',
      'last_name': 'Penddreth',
      'email': 'jpenddreth0@census.gov',
      'gender': 'Female',
      'ip_address': '26.58.193.2'
    };
    this.timer = setInterval(() => {
      this.data.push(o);
      console.log(this.data.length);
      if ( this.count++ === 5) {
        clearInterval(this.timer);
      }
    }, 1000 * 1);
  }

  data = [{
    'id': 1,
    'name': 'Jeanette',
    'last_name': 'Penddreth',
    'email': 'jpenddreth0@census.gov',
    'gender': 'Female',
    'ip_address': '26.58.193.2'
  }, {
    'id': 2,
    'name': 'Giavani',
    'last_name': 'Frediani',
    'email': 'gfrediani1@senate.gov',
    'gender': 'Male',
    'ip_address': '229.179.4.212'
  }, {
    'id': 3,
    'name': 'Noell',
    'last_name': 'Bea',
    'email': 'nbea2@imageshack.us',
    'gender': 'Female',
    'ip_address': '180.66.162.255'
  }, {
    'id': 4,
    'name': 'Willard',
    'last_name': 'Valek',
    'email': 'wvalek3@vk.com',
    'gender': 'Male',
    'ip_address': '67.76.188.26'
  }];
}

app.component.html:

<app-ng-table [data]="data"></app-ng-table>

How can I make the component update when @Input changes?

UPDATE: I have created a plunkr demonstrating this: https://plnkr.co/edit/szz1SNooQ1vIZhnFgiys?p=preview

cyberpirate92
  • 3,076
  • 4
  • 28
  • 46
  • try an observable of input by referring to this [**answer**](https://stackoverflow.com/questions/44467336/observable-of-input-property/44467942#44467942) – Aravind Oct 19 '17 at 11:26

5 Answers5

3

Just make your @Input() two-way binding...

_data;
@Input() set data(val) {
  this._data = val;
}
get data() {
  return this._data;
}
Carsten
  • 4,005
  • 21
  • 28
2

I had the same problem before; the ngOnChanges method listens only to changes on datatypes ==! array.

Explanation (ngOnChanges not firing for nested object):

During change detection, when Angular checks components' input properties for change, it uses (essentially) === for dirty checking. For arrays, this means the array references (only) are dirty checked. Since the rawLapsData array reference isn't changing, ngOnChanges() will not be called.

Tim Hovius
  • 721
  • 6
  • 16
  • You are right, the data in the child is indeed getting updated, but the `ngOnChanges` event is not getting fired. I tried logging the data in the child component using `setInterval` and I could see it getting updated. – cyberpirate92 Oct 19 '17 at 13:15
1

as a simple solution to your problem , you can detect @Input changes in ngOnChanges like this :

ngOnChanges(changes: SimpleChanges) {   
   for (let propName in changes) {
      // when your @Input value is changed  
      if(propName === "yourInputName"){
         // update the component here 
      }
   }
}

Hope it helps :)

Mohamed Ali RACHID
  • 3,245
  • 11
  • 22
0

As TimHovius stated, angular is only doing a reference check. Since you are pushing to the same array, the ng-table.component is not detecting a change (the reference to the input is still the same). One thing you can do is to create a shallow copy of the array.

addItem(): void {
    this.data.push(this.data[this.data.length-1]);
    // create shallow copy of array, since this is a new array (and new reference) ngOnChanges hook of the ng-table.component will fire
    this.data = this.data.slice(0);
}

Now ng-table.component will detect that the input has changed and fire ngOnChanges

Here is a plunkr demoing this (https://plnkr.co/edit/2tdMEA9PLc2TEL4Gy4Lp?p=preview)

LLai
  • 13,128
  • 3
  • 41
  • 45
  • @cyberpirate92 forgot to add my plnkr – LLai Oct 19 '17 at 13:25
  • Thanks @LLai, shallow copying works. I was wondering if there is any other way where I don't have to shallow copy in the parent component? I wonder how `kendo-grid` does it without an explicit shallow copy. – cyberpirate92 Oct 19 '17 at 13:26
  • @cyberpirate92 I'm not familiar with kendo-grid, but it is probably not relying on the ngOnChanges hook. (https://plnkr.co/edit/G1dlSJqtcJ9NX7F44zaz?p=preview) In this plunkr you can see the child component does pick up changes in the ngFor. But in your case, since you have the `extractRowsAndColumns` method in your ngOnChanges hook, you are needing that to run. – LLai Oct 19 '17 at 13:38
0

You need to assign new object to it. Mostly can be done by Object.assign

In your example, you just need to change few line and rest will work smooth.

...
let data:[] = this.data;
data.push(o);
this.data = Object.assign([], data);
...
Sumit Ramteke
  • 1,487
  • 1
  • 16
  • 39