l-lin / angular-datatables

DataTables with Angular
https://l-lin.github.io/angular-datatables/
MIT License
1.58k stars 487 forks source link

Angular 4 - Datatable isn't reloading data correctly. #1146

Closed iCrash91 closed 6 years ago

iCrash91 commented 6 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x ] Bug report  
[ ] Feature request
[ ] Documentation issue or request

What versions you are using?


- node version: 6.11.2
- angular version: 4
- angular-cli version: 1.2.7
- jquery version: 1.9.1
- datatables version: 1.10.16
- angular-datatables version: 4.4.1

Current behavior

When I load the datatable the first time, everything works propertly, but if I add / edit or delete a new row using http, the data changes but the table doesn't recognize it. For example, If the table is empty it shows "No data available in table", after I push a new row to my array, I can see the row but also the "no data available" message,in the footer still displays "showing 0 to 0 of 0 entries" and I can't use the search bar to find the new row because it says no results. I tried using the rerender method as shown in the documentation, but after the rerender the new data disappear.

First time the table is loaded

asd

After the data is addes

qweqwe

Expected behavior

The table should recognize the changes made to the data in the table.

Minimal reproduction of the problem with instructions

This is my component

export class DrillsComponent implements OnInit {
    public drills: Array<Drill>;
    public addDrill = new Drill();
    dtOptions: any = {};
    dtTrigger: Subject<any> = new Subject();

    constructor(private drillService: DrillService ) {
        this.drill = [];
        this.getDrills();
        this.dtOptions = {
            pagingType: 'full_numbers',
            retrieve: true,
        };
    }

//function to load data from API
getDrills() {
        this.drillService.getDrills()
        .subscribe(
            data => {
              for (let drill of data) {
                this.drills.push(new Drill(drill))
        this.dtTrigger.next();
              }
            },
            error => {
              console.log(error)
            }
        );
    }
//Functin to add new data
saveDrill(){
      this.drillService.saveDrill(this.addDrill).subscribe(
          data => {
              this.drills.push(new Drill(data))
          },
          error => {
            console.log(error)
          });
        }

Methods in my service to send and load the data

//Get data from api
getDrills() {
    return this.http.get(this.apiUrl).map(res => res.json());
  }
//Save data in API
  saveDrill(data) {
  return this.http.post(this.apiUrl,data).map(res => res.json());
}

Template

<table class="table" width="100%" [dtOptions]="dtOptions" [dtTrigger]="dtTrigger" datatable  id="drills-table">
                    <thead>
                        <tr>
                            <th> Code</th>
                            <th> Name</th>
                            <th> Location</th>
                            <th> Operation Time</th>
                            <th> Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr *ngFor="let drill of drills" >
                            <td >{{drill.code}}</td>
                            <td>{{drill.name}}</td>
                            <td>{{drill.location}}</td>
                            <td>{{drill.operation_time}}</td>
                <td>{{drill.status}}</td>
                        </tr>
                    </tbody>
                </table>
l-lin commented 6 years ago

Angular and DataTables do not work on the same "data", ie any changes on Angular won't be impacted on DataTables. After you add your row, you need to rerender your DataTable so that DataTables acknowledges the changes.

Unfortunately, it's not really smooth and performant as it will rerender the whole DataTables instead of just adding/removing rows... I don't have any alternative solution.

Korigoth commented 6 years ago

this is a problem for our project, many tables need to play with dynamic data, so we decided to stop using this. the glitch it's making while rerendering is not a good user experience.

so datatables must be used with static data only.

l-lin commented 6 years ago

Another approach is to not use the angular renderer, but using the DataTable's API to add rows. This way, your table will be fully updated.

s3rkan commented 6 years ago

you will need to destroy your datatable before you run this.dtTrigger.next(); when data is updated ->

this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
          // first destroy table
            dtInstance.destroy();
           // get your data function call here
    });

might do the trick...

update: i use it this way in my application works just fine when rows added or deleted the it will rerender table as written in above comments not that "beautiful" experience..

Korigoth commented 6 years ago

it doesnt work for me

dtTrigger: Subject = new Subject(); @ViewChild('lacuneTable') lacuneTable: DataTableDirective;

this.lacuneTable.dtInstance.then dtInstance is null each time

i have many datable in my component and i need to specify the one that is dynamic

how can i do it?

s3rkan commented 6 years ago

Could you post a plunker or something similar?

Korigoth commented 6 years ago

i don't really know how to build up a project for Angular5 + Datatables in plunkr sorry but here is the code

HTML

<table datatable [dtOptions]="options" [dtTrigger]="trigger" class="table table-sm table-bordered table-hover table-striped">
    <thead>
        <tr>
            <th>Numéro</th>
            <th>Description</th>
        </tr>
    </thead>

    <tbody>
        <tr *ngFor="let lacune of lacunes" class="table-row-clickable" (click)="voirLacune(lacune)">
            <td>{{lacune.numero}}</td>
            <td [innerHTML]="lacune.description"></td>
         </tr>
     </tbody>
</table>

Component

  trigger = new Subject<any>();

  ngOnInit(): void {
    this.route.params.subscribe(params => {
      forkJoin(
        this.appreciationsService.obtenirListe(),
        this.recommandationsService.obtenir(+params['id'])
      ).subscribe(reponse => {
        this.recommandation = reponse[1];
        this.lacunes = this.recommandation.lacunes;
        this.trigger.next(); // This doesnt render the datatables nor does it show sort arrow on table and it doesnt add datatables class to the table
      });
    });
  }
Korigoth commented 6 years ago

ngAfterViewInit doesn't work for us, because we are loading data async and the AfterViewInit were called before data were back from server.

the this.trigger.next) in ngOnInit doesn't work either, while debugging the datables pluggin doesnt receive the next() event.

so we ended replacing the this.trigger.next() to

        setTimeout(function() { 
            this.trigger.next();
        }.bind(this));

and now it's working as expected

l-lin commented 6 years ago

For information, you can use this plnkr template to demonstrate your issue.

silentFred commented 6 years ago

@s3rkan how did you get a reference to your dtElement?

sebastian-zarzycki-apzumi commented 6 years ago

I'm kind of confused. So what this library actually does "in Angular way"? It seems like nothing else than a glorified wrapper over DataTable's API, but what's the point if it cannot properly rerender the data angular ngFor is generating? That destroy/create technique is hack at best. Is there no other way?

Korigoth commented 6 years ago

it seems that the original library is using the same way to regenerate the data if you dont use the column data way.

l-lin commented 6 years ago

It seems like nothing else than a glorified wrapper over DataTable's API

It is just a wrapper over DataTable's API.

what's the point if it cannot properly rerender the data angular ngFor is generating? That destroy/create technique is hack at best.

Unfortunately, it's been quite a time since I "touched" the frontend development (because it's moving so fast and it's not really my "expertise" and I don't have much time this year), that's why there are no improvement in this wrapper for quite some time... But, pull requests are always welcome :)

Korigoth commented 6 years ago

And you can check the code it's pretty straight forward.

xts-velkumars commented 6 years ago

i'm also having the same problem, reloading the data is not binding properly. Its disappeared immediately

jagdishjadeja commented 6 years ago

if it can be used only with static data then it should be given with big heading on front page of it

pktron commented 6 years ago

Same here... my scenario: Im using datatables on a child component, and i have a change detection using DoCheck When i rerender the new row get deleted even from the dom...

@ViewChild(DataTableDirective)
  dtElement: DataTableDirective;

  differ: any;
  first: Boolean = true;

  @Input() labels: string[] = ['Label 1', 'Label 2', 'Label 3'];
  @Input() rows: string[][] = [
    ['Test 1', 'Test 2', 'Test 3'],
    ['Prueba 1', 'Prueba 2', 'Prueba 3']
  ];

  dtOptions: DataTables.Settings = {};
  dtTrigger: Subject<any> = new Subject();

  constructor(private _iterableDiffers: IterableDiffers ) {
    this.differ = this._iterableDiffers.find([]).create(null);
    this.dtOptions = {
      pageLength: 20
    };
  }
  ngDoCheck() {
    const change = this.differ.diff(this.rows);
    if (change && !this.first) {
        this.rerender();
    }
    this.first = false;
  }

  ngAfterViewInit(): void {
    this.dtTrigger.next();
  }

  ngOnDestroy(): void {
    this.dtTrigger.unsubscribe();
  }

  rerender(): void {
    this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
      // Destroy the table first
      dtInstance.destroy();
      // Call the dtTrigger to rerender again
      // this.dtTrigger.next();
    });
  }

It is weird because if i dont destroy the datatable the row shows, but i cant search or use the datatable but when i destroy it it the destroy the new data and change it to where the datatable was initialized.

Korigoth commented 6 years ago

@pktron I've found a way to make it work i think for you!

put everything that is in your rerender() method in a setTimeout


rerender(): void {
    setTimeout(() => {
       // put rest of the code here
    });
}
pktron commented 6 years ago

I solved it using that approach @Korigoth thanks a lot!

Korigoth commented 6 years ago

@pktron np ;) it doesn't solve all the problem but it help in 95% of the case !

brumiser1550 commented 4 years ago

So I have been running into the same issue but my solution is a little different. It seems that the rerender in the docs isn't quite good enough when loading from a remote source. The trick that I found is to remove the table from the dom and then put it back before calling the dtTrigger.next().

This seems to fix all the issues I was having so hopefully it can help someone else too.

My Code

    buildDtOptions() {
        this.dtOptions = {
            dom: '<"row"<"col-sm-12"B>>' +
                '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
                '<"row"<"col-sm-12 table-responsive"tr>>' +
                '<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
            buttons: [
                {
                    extend: 'colvis',
                },
                {
                    extend: 'csv',
                },
                {
                    extend: 'pdfHtml5',
                    orientation: 'landscape',
                    title: 'welcome-call',
                    filename: 'welcome-call',
                    extension: '.pdf',
                    exportOptions: {
                        columns: ':visible'
                    }
                }
            ]
        };
    }

    ngOnInit() {
        this.buildDtOptions();
        this.loadWelcomeCalls();
    }
    loadWelcomeCalls() {
        this.welcomeCallService.getParentWelcomeCalls(this.filters).subscribe(response => {
            this.welcome_calls = response.results;
            this.rerender();
        });
    }
    rerender(): void {
        if (this.dtElement && this.dtElement.dtInstance) {
            this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
                dtInstance.destroy();
            });
        }
        this.loading = true;
        this.loading = false;
        setTimeout(() => {
            this.dtTrigger.next();
        });

    }
JaegerCaiser commented 3 years ago

So I have been running into the same issue but my solution is a little different. It seems that the rerender in the docs isn't quite good enough when loading from a remote source. The trick that I found is to remove the table from the dom and then put it back before calling the dtTrigger.next().

This seems to fix all the issues I was having so hopefully it can help someone else too.

My Code

    buildDtOptions() {
        this.dtOptions = {
            dom: '<"row"<"col-sm-12"B>>' +
                '<"row"<"col-sm-12 col-md-6"l><"col-sm-12 col-md-6"f>>' +
                '<"row"<"col-sm-12 table-responsive"tr>>' +
                '<"row"<"col-sm-12 col-md-5"i><"col-sm-12 col-md-7"p>>',
            buttons: [
                {
                    extend: 'colvis',
                },
                {
                    extend: 'csv',
                },
                {
                    extend: 'pdfHtml5',
                    orientation: 'landscape',
                    title: 'welcome-call',
                    filename: 'welcome-call',
                    extension: '.pdf',
                    exportOptions: {
                        columns: ':visible'
                    }
                }
            ]
        };
    }

    ngOnInit() {
        this.buildDtOptions();
        this.loadWelcomeCalls();
    }
    loadWelcomeCalls() {
        this.welcomeCallService.getParentWelcomeCalls(this.filters).subscribe(response => {
            this.welcome_calls = response.results;
            this.rerender();
        });
    }
    rerender(): void {
        if (this.dtElement && this.dtElement.dtInstance) {
            this.dtElement.dtInstance.then((dtInstance: DataTables.Api) => {
                dtInstance.destroy();
            });
        }
        this.loading = true;
        this.loading = false;
        setTimeout(() => {
            this.dtTrigger.next();
        });

    }

This what was that help me, Thank you a lot man.

ghost commented 3 years ago

@Korigoth

setTimeout(function() { this.trigger.next(); }.bind(this));

this is worked for me Thanks for the suggestion.