0
votes

I have a table that has a slide toggle inside of it that loads as toggled/not toggled based on data I am getting back from an API. This works as expected. The user can then toggle this switch, which causes a POST request to be sent to an API. If this POST fails, I want to reset the toggle switch to whatever position it originally loaded in. I am attempting this by emitting the original observable again on failure. I have verified through the console in the browser that this gives the toggle the value I expect, however the slider does not move accordingly.

At this point I am questioning if I am going about this the right way, as the toggle position does not seem to be affected by the value it's bound to if that value changes. I noticed that the mat slide toggle has a built in toggle() method, should I be calling this instead? I want to know the proper way to manage the slide toggle's position as I cannot find any documentation on it.

Component.ts

ngOnInit() {
    const lastItems$ = new ReplaySubject(1);

    const getItemsAndCache = () => {
      return this.myService
        .getItems(this.accountId)
        .pipe(
          tap(itemsRes => lastItems$.next([...itemsRes])
          )
        )
    };

    geItemsAndCache().subscribe(items => lastItems$.next(items));

    const updatedItems$ = this.itemChangeSubject.asObservable().pipe(
        exhaustMap(itemChange => 
        this.myService.changeItemToggle(
            this.accountId,
            itemChange.item.id
        )
        .pipe(
            mergeMap(() => getItemsAndCache()),
            catchError(() => lastItems$.pipe(first())
            ))
        )
    )

    this.items$ = concat(lastItems$.pipe(first()), updatedItems$) as Observable<MyItem[]>;

}

onToggleChange(item: MyItem) {
   this.itemChangeSubject.next({ item });

}

Component.html

<my--custom-table
[items]="items$ | async"
[displayedColumns]="displayedColumns"
[paginationOptions]="paginationOptions"
>
<ng-container matColumnDef="status">
  <mat-header-cell *matHeaderCellDef>Status</mat-header-cell>
  <mat-cell *matCellDef="let item">
    {{ item.status }}
  </mat-cell>
</ng-container>
<ng-container matColumnDef="toggled">
  <mat-header-cell *matHeaderCellDef>Toggled?</mat-header-cell>
  <mat-cell *matCellDef="let item">
    <span>{{item.toggled}}</span>
    <mat-slide-toggle
      [(ngModel)]="item.toggled"
      [disabled]="clicked"
      (change)="onToggleChange(item); clicked = true"
    ></mat-slide-toggle>
  </mat-cell>
</ng-container>
</my-custom-table>
1
I don't see the issue, but I also don't see where "onLockChange" is defined, and the issue could be there since that is presumably what is doing the POST call and the potential togglingKevin
Missed that line by mistake when I copy/pasted, it's been addedJosh
Unfortunately that really just pushes the issue back by one layer, I'm not sure what "itemChangeSubject" does, and I don't see where you're setting item.toggled, so I can't comment on what could be going wrong. I would suggest that you start from a very basic set of code (like what I included in my answer), and build the rest of your component functionality back in piece by piece to make sure you don't mess up the toggleKevin
Ah sorry, it's just a ReplaySubject containing the item (just a row in the table) that the toggle was clicked for. private lockChangeSubject: ReplaySubject<{ item: MyItem; }> = new ReplaySubject(); I didn't think I needed to explicitly set item.toggled, as I am setting this.items$ instead. My initial GET API call (getItemsAndCache()) returns $items which is then passed to my table to iterate over, and one of the props on each item is toggled. On failure of my POST, I catch the error and lastItems$.pipe(first()), which should emit the original observableJosh
Then doing this here this.items$ = concat(lastItems$.pipe(first()), updatedItems$) as Observable<MyItem[]>; should change $items, which should then update the table, and the toggle, accordingly since I am using an async pipe.Josh

1 Answers

2
votes

This answer is based on an example from angular.material.io: https://material.angular.io/components/slide-toggle/examples

If you bind a variable from your component to the [(ngModel)] property of the slide-toggle then you can simply change the value of that variable back to its previous state, which will update the UI accordingly.

In your HTML template you can set the slide-toggle like this:

<mat-slide-toggle [(ngModel)]="checked" (change)="postChangeToServer()">
    Slide me!
</mat-slide-toggle>

And set up your component typescript like this:

@Component({
  selector: 'slide-toggle-example',
  templateUrl: 'slide-toggle-example.html',
})
export class SlideToggleExample {
  checked = false;

  postChangeToServer(): {
    //Do your POST call here and handle the negative response with the line below
    //For the sake of demonstration I've just set a one second timeout instead of making a server call

    setTimeout(() => {
      this.checked = !this.checked;
    }, 1000);
  }
}