Best way to implement list with toggles and sending it back to server


#1

What is the best way to implement following:

I have a list of items with a toggle for each item respectively.

  1. The user can enable/disable an item in the list by using the toggle for corresponding list item.

  2. As and when the user is changing/toggling an item, I want to update that item in the list in my TS file.

  3. And when the user clicks on Done button, I want to send that list to my server to be reflected in the database.

The way I plan to do is capture the toggle change using “ionChange” and pass in the “$event” and my “item” objects. As in the small code snippet below:

HTML

<ion-content padding>
    <ion-list >
        <ion-item *ngFor="let level of levelDetails">
            <ion-label >
                {{level.name}}
            </ion-label>
            <ion-toggle item-end color="secondary" [checked]="level.isAssigned" (ionChange)="setLevel($event, level)" ></ion-toggle>
        </ion-item>
    </ion-list>
    <button block ion-button>
        Done
    </button>
</ion-content>

TypeScript

setHierarchyAsSelected(event: any, level: LevelInfo) {
        this.levels
            .filter(l => l.levelId == level.levelId)
            .map(e => {
                e.isAssigned = event.target.value;
                e.hasChanged = true;
            });
}

Haven’t included the TS method that will send updated list back to server.

Any inputs any/or suggestions?

Thanks!


#2

I don’t see the need for such a complicated setup with change handlers and all the traversing. Why not just:

<ion-toggle [(ngModel)]="level.isAssigned">

#3

Aah…can do that? That came to my mind but wasn’t sure because that “level” is going to be an object in the list. Thanks!

The other reason I didn’t give it much thought or did not try it was because I wanted to set another flag “hasChanged” in that “level” object. This is because, it can be a big list and I don’t want to send across the whole list back. Using the “hasChanged” flag I can just filter the list and send the filtered list back to server.
How can I achieve it if I go your route?

Thanks.


#4

Unless we’re talking at least tens of thousands of items, I think worrying about this is premature optimization.

That being said, if you insist, you could use something like lodash’s clone() method to prepare a “before” version of levelDetails, and then compare it to the “after” version modified by user actions at submission time to see which levels have changed.


#5

@rapropos - there could be few hundreds.

Since you suggested the lodash approach, I am taking it for granted that my way of implementing it will be inefficient as compared to maintaining a copy of the original array using lodash’s clone().

Thanks.


#6

I think so, especially if the list is big. The way you had it, you had to walk the entire list on every toggle flip. With clone-and-compare, that only has to happen once, after all toggles have been modified.


#7

Oh right, map and filter methods will keep on comparing and creating new arrays.

Thanks again, very much appreciated!


#8

@rapropos - one more question.

So the above functionality is on of the pages in my app. On the click of “Done” button (as in point #3 in my original post), instead of submitting/sending the list to server I want to go back to the previous page which I can do using “pop()” method, however, I don’t know how to carry this changed list back to the previous page.

Any help will be highly appreciated.


#9

I would stash it in a service provider injected by all relevant pages. You could also move the comparison and upload logic into there as well. Something with an API like:

class LevelProvider {
  public levels: Level[];
  private _changedLevels: Level[];

  checkout(): Level[] {
    return _.clone(this.levels);
  }

  checkin(newlevels: Level[]): void {
    this._changedLevels = this.compareTo(newlevels);
    this.levels = newlevels;
  }

  upload(): Observable<Response> {
    return this._http.post(api, this._changedLevels);
  }
}

Previous page could just inject this and access its levels property.


#10

@rapropos - that is a great solution! Thanks a lot!

How dumb of me not to come up with something similar. Maybe because I was thinking there has to be some cleaner and Ionic specific way. :slight_smile: This is not to say your way is not cleaner, it’s just more work, so to say.

Apparently, after reading through forums and tutorial websites and documentation, Ionic doesn’t have a pop() method, like push(), in which we can pass objects/values. And nothing in the plans too, it looks like.

From a best practices perspective, what do you think of this solution?

Your input will be greatly appreciated.

Thanks again!


#11

Not a huge fan. I’m skeptical of explicit Promise construction in general, and I think that idea creates overly tight coupling between pages. Say a third page gets involved. It has to be initiated into the web of communication. When you let service providers do all the data handling, adding additional consumers (pages) is trivial and does not introduce any additional code complexity that must be tested and can introduce subtle bugs.


#12

Hmmm…right, got your point(s).

Regarding Promise, should be able to replace it with RxJS Observable. But yes, this way of implementation will certainly introduce tight coupling.

Thanks @rapropos - appreciated!