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': '[email protected]',
      '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': '[email protected]',
    'gender': 'Female',
    'ip_address': '26.58.193.2'
  }, {
    'id': 2,
    'name': 'Giavani',
    'last_name': 'Frediani',
    'email': '[email protected]',
    'gender': 'Male',
    'ip_address': '229.179.4.212'
  }, {
    'id': 3,
    'name': 'Noell',
    'last_name': 'Bea',
    'email': '[email protected]',
    'gender': 'Female',
    'ip_address': '180.66.162.255'
  }, {
    'id': 4,
    'name': 'Willard',
    'last_name': 'Valek',
    'email': '[email protected]',
    '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

1
  • try an observable of input by referring to this answer Commented Oct 19, 2017 at 11:26

5 Answers 5

4

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

_data;
@Input() set data(val) {
  this._data = val;
}
get data() {
  return this._data;
}
Sign up to request clarification or add additional context in comments.

Comments

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.

1 Comment

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.
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 :)

7 Comments

I have implemented OnChanges, but it's not working.
what u want : if data's value changed you update the component by calling the method extractRowsAndColumns with the new data , am i right ?
Yeah that's exactly what I want
is there any errors ? , what happens exactly when you implemented my answer ?
I couldn't find any errors in the debug console, the table doesn't get updated ( the new rows are not getting added).
|
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)

3 Comments

@cyberpirate92 forgot to add my plnkr
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 I'm not familiar with kendo-grid, but it is probably not relying on the ngOnChanges hook. (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.
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);
...

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.