Ngx-datatable: Async Problem when using @Input as data source

Created on 4 Nov 2016  路  37Comments  路  Source: swimlane/ngx-datatable

I'm submitting a ... (check one with "x")

[ x ] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here

Current behavior

After upgrading to 1.1 I am getting the error error_handler.js:47 EXCEPTION: Error in ./DataTableBodyComponent class DataTableBodyComponent - inline template:14:8 caused by: Cannot read property 'length' of null

This happens when I pass the data via Input from another component.

My dumb component has:
class="material"
[rows]="rowData"
[columns]="columns"
[columnMode]="'force'"
[headerHeight]="50"
[footerHeight]="50"
[rowHeight]="'auto'">

where "rowData" is passed via @Input
@Input() rowData: Array = [];

I can see my data in the component if I output it {{ rowData | json }} (I should mention that I am using ngrx and that I just updated to angular 2.2.0-rc.0

Expected behavior

It should display the data correctly as it did on v 0.11

Reproduction of the problem

Reproducible plunkr http://plnkr.co/edit/PrGJLIVC3FMGa3BqOTxw?p=preview

What is the motivation / use case for changing the behavior?

Please tell us about your environment:


Mac OS, angular-cli beta 19.3

  • Table version: 1.1

  • Angular version: 2.2.0-rc.0

  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]

  • Language: [all | TypeScript X.X | ES6/7 | ES5]

Investigate

Most helpful comment

This PR should take care of the mutations that were causing store freeze to not work. https://github.com/swimlane/ngx-datatable/pull/623

It will be landing with v8.0.0 this week!

All 37 comments

I think it just means that your row data is null. Are you sure it is never null? This happened to me too so I just made sure to replace null or undefined row data with an empty array.

That being said, this component should probably be robust to null or undefined rows.

You can actually see it reproduced here now http://plnkr.co/edit/PrGJLIVC3FMGa3BqOTxw?p=preview

That's not really a fair reproduction is it? You have an error that is unrelated to datatable when you try to call push on an undefined property called rows. Line 50 of app.ts. Also, since this.rows is undefined, it also leads to it not having a member called length. If you declare and initialize this.rows as an empty array, it might work.

My bad on the undefined rows, however, I edited the plunkr, defined the rows, then passed it and there's no error now but the data does not display... am I missing something? The code was actually running on 0.11...

Now it seems to be displaying for me when I view your plunkr with Chrome on Windows. However, there is the ExpressionChangedAfterItHasBeenCheckedError. This is caused because datatable modifies your provided rows and {{ rows | json }} really doesn't like it when datatable side-swipes the rows array, but that's a different problem. If you remove the {{ rows | json }}, does it work in your browser?

It does work at the plunkr... However, I've revisited my other code and I am still getting the error. This may have to do with the observable not returning the data by the time the component is initialized and so I tend to think the internal handling of datatable still has some kind of async issue...Again, the very same code works in 0.12 or 0.11
The only difference I see in my code is that I am using ngrx to retrieve the data in the parent component as in:

public rowData: Array<Workaround> = [];
....
      this.store.let(getIssueRelatedWorkarounds()).subscribe(workarounds => {

        console.log('----workarounds: ', workarounds); // this is null at this point
        this.rowData = <Workaround[]>workarounds; // I'be tried this as well
        console.log('-----: ', this.rowData); // but still null here so maybe datatable should account for this?....

      });
      this.store.dispatch(this.issueWorkaroundActions.loadIssueRelatedWorkarounds(this.issueId));

Why not do this?

<datatable [rows]='rows | async'></datatable>

I have tried that with no luck.

Ok, my workaround for the time being is:

  this.store.let(getIssueRelatedWorkarounds())
    .map(data => data || []) // prevent datatable null error
    .subscribe(workarounds => {
    this.rowData = <Workaround[]>workarounds;
  });
  this.store.dispatch(this.issueWorkaroundActions.loadIssueRelatedWorkarounds(this.issueId));`

`
I still feel this should be handled internally by datatables...

Revisiting this after a while I also noticed the following error when I enabled storefreeze in ngRx

error_handler.js:47 EXCEPTION: Error in ./DatatableComponent class DatatableComponent - inline template:25:8 caused by: Can't add property $$index, object is not extensible

This is because Datatables is mutating the data being passed to it. Perhaps this could also be addressed by the the component as well.

My workaround for the time being is:

  store.let(getIssueRelatedWorkarounds()).subscribe(workarounds => {

        // prevent datatables from mutating the data
        this.rowData = JSON.parse(JSON.stringify(<Workaround[]>workarounds));

  });

Fixed in 1.7.0

I am not sure what was fixed but this continues to be an issue with ngrx store freeze. I am forced to pass a copy of the data to prevent mutation.

@Codermar - see my rx demo...are u using it differently? Can u help us repo this in a demo project?

I am also having this issue. I'm using ngrx/store and ngrx-store-freeze. I followed your rx example and just passed the observable in the rows input. Still not working. No errors, but the data isnt showing up

I'll share my workaround:

In my scenario, I have a smart component that calls a dumb component to render the datatable

Smart component:

...
export class EngneedsListComponent implements OnInit {

  searchQuery$: Observable<string>;
  rowData$: Observable<EngineeringNeed[]>;
  rowData: Array<EngineeringNeed>; // need this extra var to deep copy the data
  loading$: Observable<boolean>;
  itemRoute: string = '';

  constructor( private router: Router,
               private store: Store<fromRoot.State>,
               private service: EngneedsService ) {
    this.searchQuery$ = store.let(fromRoot.getEngNeedSearchQuery).take(1);
    this.rowData$ = store.let(fromRoot.getEngNeedSearchResults);
    this.loading$ = store.let(fromRoot.getEngNeedSearchLoading);
  }

  ngOnInit() {
   // I need this extra step so I can pass this.rowData (deep copy) to the dumb component
    this.rowData$.subscribe(engNeeds => {
      // prevent datatables from mutating the data
      this.rowData = JSON.parse(JSON.stringify(<EngineeringNeed[]>engNeeds)); 
    });
    this.store.dispatch(new action.LoadAllAction());
  }
}

In the template:

          <aui-engneed-table-list
            [rowData]="rowData" // note in here I could not do [rowData]="rowData$ | async"
            (onEdit)="onEdit($event)"
            (onDelete)="onDelete($event)"></aui-engneed-table-list>

In the dumb component: (aui-engneed-table-list)

... 
@Input() rowData: Array<EngineeringNeed> = [];
...
        <datatable
          class='material datatable fixed-header fixed-row scroll-vertical'
          [rows]='rowData'
          [columnMode]="'force'"
          [headerHeight]="50"
          [footerHeight]="0"
          [rowHeight]="50"
          [scrollbarV]="true"
          [scrollbarH]="true">
         .... my columns here ...
</datatable>

@amcdnl The issue has to do with data mutation of the passed rows object. I'll try to come up with a plunker but it requires quite a bit of work to reproduce it exactly.

Note that if we disable storeFreeze passing [rowData]="rowData$ | async" works without the deep copy workaround, but once enabled storeFreeze will throw "Can't add property $$index, object is not extensible"
Also: as @ethanve mentioned. Even with the workaround the data takes a while to show or it does not show until you click anywhere or resize the screen so I am disabling storeFreeze for the time being.

So I feel datatables should not touch the data coming in as a final solution. StoreFreeze is supposed to help you during development to stay true to the redux pattern.

I think it would be great if we kept the row data immutable and have some other internal array holding on to the meta data (such as the $$index)?

I'm working on this, hoping to have a patch today.

Fixed in 2.0

Thank you for the 2.0 release but I am afraid the storeFreeze issue is still there... I just updated, enabled storeFreeze for ngrx and got the same error. As I mentioned before, my workaround is disabling storeFreeze for local but it would be nice if they could play together. (perhaps I should actually open a new issue specific to this)

Yes plz 馃

@MikeRyan52 any thoughts on the above? ^^

I have the same problem with async data but I just wrapped the table with an ngIf

<div *ngIf="playlist.tracks">
  <swui-datatable [rows]="playlist.tracks" ...></swui-datatable>
</div>

I'll add to this. Using "@swimlane/angular-data-table 4.0.0" I am experiencing the same issue with storeFreeze. Once I disable it, I get data loaded in the table. Disabling storeFreeze is a much less than ideal solution, however.

Although not ideal, an alternative to not disabling storeFreeze is what I mentioned in https://github.com/swimlane/ngx-datatable/issues/253#issuecomment-264022087
I have a "half-there" plunker to reproduce the issue for the datatable team. I'll try to finalize it soon.

I gave your workaround a try. I thought about doing this myself, and didn't want to initially, because I consider it a hack. BUT, it keeps me moving forward on this until something changes. THANKS!

@Codermar thanks for the workaround. I see that this issue has been closed, is there another open one which discusses a "native" fix?

I moved on. I found PrimeNG which provides a datatable that is compatible with storeFreeze.

@pchristou I was going to open a new issue for that. I just haven't had the chance to get the plunker ready to reproduce it, in the meantime there's a new version of datatable which I have not tried but it seems to have issues of its own so I am taking a few days to decide which course of action to take. @gregkopp: i do agree that was a "hack" and that a native solution is in order. Thanks for the tip on PrimeNG, it looks promising...

@gregkopp - Sorry to hear you moved on, best of luck. I've looked at Prime before, sadly they didn't have virtualized dom at the time.

Sadly, I tried a all on-push setup in 2.0 but many others had trouble. After lengthy discussion with @MikeRyan52 we decided its probably best to do it like this. I did learn that if your parent component implements on push it transcends all the way down.

If you have a fully working example, it would be much easier to help resolve the problem.

This PR should take care of the mutations that were causing store freeze to not work. https://github.com/swimlane/ngx-datatable/pull/623

It will be landing with v8.0.0 this week!

@amcdnl Don't mean to rush but just wondering when this is going to be released as I have the same use case, storeFreeze is causing the object is not extensible issue.

Edit: Just read the other issue with your response. Hopefully we can figure out a way that doesn't impact performance, meanwhile I simply just created a deepClone of my rows.

Sadly, I had to back this change out due to regressions. I'll be on vaca next week but this is on my list as I'm moving to ngrx-store soon.

I use the latest version 9.2.0 with Redux (ngrx) and ngrx-store-freeze module. I run into the same issue. It is easy producible since the original JSON object has been modified with index$$: ... and other keys/values.

Is there a timeline on when this feature will be landed?
Is there a hack that I can use without removing freeze store function check?

It seems the problem still here in 9.3.0

Should we reopen this issue and track the right status?

So weird, i have freeze but didn't get a freeze error... plus, this kind of breaks the nice template convention of
[rows]="someRows$ | async"
there's no good way to copy data at this point.

you must not enable the freeze somehow. are you sure that you register the freeze via metaReducer. Anyway, the issue still exsits. See my concern thinking of using angular-material/cdk instead. :( if the support is there, that would be great.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

JanStock picture JanStock  路  3Comments

mmrath picture mmrath  路  3Comments

TakhirMamirov picture TakhirMamirov  路  3Comments

paritosh64ce picture paritosh64ce  路  3Comments

WA-WilliamKrieg picture WA-WilliamKrieg  路  3Comments