Disconnecting the UI from the data store

I’m having a weird problem editing some data in my application. I have a project page in my app, it’s setup as a modal page. I use the page to create a new project as well as to edit an existing project. When editing an existing project, I want to disconnect the edit page from the data model so changes the user makes to the project aren’t written back to the data source unless the user taps the Done button. I want changes discarded when the user taps the cancel button, but committed when the user taps the done button.

I had this issue a year or so ago and someone on slack, I think it was Mike Hartington, suggested using lodash to clone the project object before sending it to the project page for editing. That worked wonderfully in that older app, but in this Ionic V4 app, it’s not working. Even if the user taps Cancel the data’s written to the project array.

Here’s an example of the code that launches the project page with an existing project:

private async editProject(project: Project, idx: number) {
    const modal = await this.modalController.create({
      component: ProjectEditPage,
      // clone the project before passing it in,
      // to disconnect the project from the original
      componentProps: {
        project: _.clone(project),
        idx: idx
      }
    });
    // what happens when the user taps done or cancel
    modal.onDidDismiss().then((detail: OverlayEventDetail) => {
      // close the sliding item before you do anything else
      this.projectList.closeSlidingItems().then(() => {
        // Did we get a project back?
        if (detail.data !== null) {
          console.dir(detail.data);
          // Update the Projects array
          this.dataService.updateProject(detail.data, idx);
        }
      });
    });
    await modal.present();
  }

In this case, when the user taps cancel, null’s returned so the updateProject code doesn’t execute. I’ve confirmed that this is the case, but my project gets updated anyway.

Ignore the last sentence of my issue, that’s not correct. When the user taps cancel it works as expected. When the user taps Done, the updated data is written to the project - and I can’t figure out how.

Sorry, that wasn’t even clear. Sigh. Yes, I know how when tapping done the project data is written to the projects array. I’m using a sub-page to edit the category titles, when I tap done there, the data’s written to the project page as expected. When I tap cancel from there, the data’s written to the projects array which is not what I expect. The data’s written to the project as soon as I tap done from the category edit page, even though the logic in the onDidDismiss doesn’t save it.

I have code checking to see if the new button name is unique, and the button title value’s written to the button object as soon as the page closes, instead of through the logic in my code:

async buttonModify(title: string, idx: number) {
    // Edit an existing button Title
    console.log(`ProjectEditPage: buttonModify(button, ${idx})`);
    // Make a clone of the button so if we modify it later,
    // the changes won't automatically be passed back here
    // const tmpButton = _.clone(button);
    // let tmpTitle = title;

    // Create the modal, pass in the empty project
    const modal = await this.modalController.create({
      component: ButtonEditPage,
      // componentProps: { buttonTitle: tmpButton.title }
      componentProps: { buttonTitle: title }
    });
    // what happens when the user taps done or cancel
    modal.onDidDismiss().then((detail: OverlayEventDetail) => {
      // Did we get a result back?
      if (detail.data !== null) {
        console.log('We have data!');
        // console.dir(button);
        console.dir(detail.data);
        // Is the delete flag set?
        if (detail.data.doDelete) {
          console.log('Deleting button');
          // then delete the button
          this.buttonDelete(idx);
        } else {
          // Otherwise, update the button title with the edited value
          console.log('Saving button');
          this.buttonSave(detail.data.buttonTitle, idx);
        }
      } else {
        console.log('User tapped Cancel');
      }
    });
    await modal.present();
  }

OK, so I refactored the app to use Angular routing instead of ModalController to open the project edit page. I couldn’t find a way to send data back, so I use events to notify the calling page with the updated data. When the user taps done, the data is saved. When the user taps cancel, nothing’s sent to the calling page, but any edited or added buttons are written to the data store.

I even updated the Ionic project to the latest modules.

This shouldn’t be happening since there’s no direct connection between data store and the project being edited. I clone the project object first.

Can someone please help me?

Not sure how much has changed in the new version, how are you getting the object/data and passing it to the page? That can change how this could be resolved. It’s typically working with the object value, and not a direct reference.

Mike, I recorded a video showing the problem and illustrating the related code. Here’s a link: https://vimeo.com/336235751. You still have access to the repo I shared with you a couple of weeks ago

I’m passing the index of the project to the page the retrieving it from the store and cloning it. The cloned value is wired to the page UI.

John M. Wargo

So the way you tried to clone the object was not creating a new instance. It still made copies by references, not value. Specifically, the spread operator and Object.assign copy nested data by reference, not value.

In your project, I modified your getProjectClone method to use a clone function.

  getProjectClone(projectIdx: number) {
    return clone(this.projects[projectIdx]);
  }

And the clone function is based on some existing answers from StackOverflow

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Thanks, I’ll try this. I originally was just using lodash (_.clone) but then Josh Morony suggested the other approach so I tried that as well.

THANK YOU!!! It worked. I’m curious why the lodash clone didn’t work here. Oh well, app works not, thanks for your help.