Home > Net >  angular material reactive data-table keeping old state whilst fetching data over the wire
angular material reactive data-table keeping old state whilst fetching data over the wire

Time:10-12

I have been following this tutorial (https://blog.angular-university.io/angular-material-data-table/) on how to implement an angular material data-table with pagination, sorting and filtering server side.

I wanted to expand from this by having a column to activate, de-activate a lesson with a checkbox, but only to do so when the user is ready. Only problem is that when user changes the state of a checbox, with either, search, sort or filter the old state is overwritten by what has been fetched over the wire. I can only think a few things to overcome this limitation.

  1. Send update of checkbox state to endpoint as soon as we toggle the checkbox. Downside here is user doesn't have the freedom to toggle more than one checkbox box before sending over the wire.
  2. Abandon server side implementation and load everything client side. Downside is first loading will take a while.

Ideally I would like to keep changes of toggling the active checkbox whilst still have the server-side feature that doesn't overwrite current state, this will allow the user to save whenever he intends to after performing multiple data-table actions. Code wise everything is already int the tutorial (https://github.com/angular-university/angular-material-course/tree/3-dialog-finished), all I added was an additional column called active, on the template, column table array definition, and on the server's db-data

template addition...

<ng-container matColumnDef="active">

            <mat-header-cell *matHeaderCellDef>Active</mat-header-cell>

            <mat-cell class="duration-cell"
                      *matCellDef="let lesson">
                      <mat-checkbox [(ngModel)]="lesson.active"></mat-checkbox>
                    </mat-cell>

        </ng-container>

Where I think we might need to intervene is in the method that is called to retrieve data I tried merging current lessons with what is being fetched without success:

 this.coursesService.findLessons(courseId, filter, sortDirection,
            pageIndex, pageSize).pipe(
                catchError(() => of([])),
                finalize(() => this.loadingSubject.next(false))
            )
            .subscribe(lessons => {
                const oldLessons = this.lessonsSubject.getValue();
                const newLessons = lessons;
                const mergedLessons = [
                    ...newLessons,
                    ...oldLessons
                ];
                console.log(mergedLessons);
                return this.lessonsSubject.next(lessons)
            });

CodePudding user response:

Did you try to copy only the active property that your old data has into the objects that come from the API? Try this:

this.coursesService.findLessons(courseId, filter, sortDirection,
            pageIndex, pageSize)
  .pipe(
    catchError(() => of([])),
    finalize(() => this.loadingSubject.next(false))
  ).subscribe(lessons => {
    const oldLessons = this.lessonsSubject.getValue();
    const newLessons = lessons;
    newLessons.forEach(lesson => {
      const oldLesson = oldLessons.find(l => l.id === lesson.id);
      if (oldLesson) {
        lesson.active = oldLesson.active;
      }
    });
    console.log(newLessons);
    this.lessonsSubject.next(newLessons);
  });

CodePudding user response:

Here is the solution I have come up with:

I created a simple solution with a service that keeps track of active lessons its key is the lesson id and value the checked status

import {Injectable} from "@angular/core";

@Injectable( {providedIn: 'root'})
export class ActiveTrackerService {
    public activeRules = {};
}

I attached a change event to each checbox that will be used to update the service with the changes.

 toggleActivateRule($event, lesson){
        this.activeTrackerService.activeRules = {
            ...this.activeTrackerService.activeRules,
            [lesson.id]: $event.checked
        } 
    } 

Finally, in the dataSource, whenever we retrive a list of lessons from the endpoint we use the activeTracker service again only this time it is to see the lessons that had their active status changed and modify the new list accordingly when we map over it.

  const newLessons = lessons.map( lesson => {
                    const isModifiedLesson = typeof this.activeTrackerService.activeRules[lesson.id] === 'boolean'
                    const hasValueChanged = this.activeTrackerService.activeRules[lesson.id] !== lesson.id
                    if(isModifiedLesson && hasValueChanged) {
                            lesson.active = this.activeTrackerService.activeRules[lesson.id];
                    }
                    return lesson;
                });
                return this.lessonsSubject.next(newLessons);

This may not be the most maintainable solution, what if there are mutliple tables with multiple values to track and only one datasource as source of truth, it will quickly start to become unmaintainable, would like your thoughhts on a better solution.

  • Related