Eric B.
Eric B.

Reputation: 24481

AngularJS: revert form on modal cancel?

I'm trying to use AngularJS 1.3.0's new $rollbackViewValue() method in ngModelController to cancel changes to a form in a modal popup or persist them when I close the modal. I'm using BootstrapUI for the $modal service.

I think I'm on the right track, but there is something that isn't quite working properly:

In my controller, I've got:

   $scope.updateCharge = function (charge) {
        var scope = $scope.$new();
        scope.charge = charge;

        var modalInstance = $modal.open({
            templateUrl: 'client/app/views/providers/charges/updateCharge.html',
            scope: scope
        });

        modalInstance.result.then(function () {
            scope.charge.$save({providerId: providerId, chargeId: charge.id});
        });
    };

In my template, I have the following:

<form name="form" novalidate ng-model-options="{updateOn: 'save',  debounce: {'save': 0 }}" class="form-horizontal" role="form">
    <div class="modal-body">
        <div class="form-group">
            <label class="col-sm-3 control-label" for="chargeName">Name</label>

            <div class="col-sm-9">
                <input type="text" class="form-control" required ng-model="charge.code" id="chargeName"/>
            </div>
        </div>
     </div>
    <div class="modal-footer">
        <button class="btn btn-primary" ng-disabled="form.$invalid" ng-click="form.$broadcast('save'); $close()">Update</button>
        <button class="btn btn-warning" ng-click="form.$rollbackViewValue(); $dismiss('cancel')">Cancel</button>
    </div>

</form>

Generally speaking, this seems to work. When I click on cancel, my changes are reverted. When I click on Update, the modal closes but I do not see my updates in the scope.charge object.

I would have expected that my scope.charge object would be updated prior to the modal closing.

Am I using the ng-model-options incorrectly?

If I add a separate 'Apply' button that only does a form.$broadcast('save'), I see my scope object properly updated. So I am presuming that my $close() is being called prior to the event being processed by the ng-model-options. How can I avoid this race condition?

Upvotes: 4

Views: 11794

Answers (3)

frankclaassens
frankclaassens

Reputation: 1930

I did not end up using any of the options suggested as answers, because the reality is, angular 1x has no "proper" way of doing what I want. Angular uses 2way binding, and yes I can write fancy directives to make life easier but infact it just makes the html looks even more complicated.

I settled with the suggested way as per many threads on the forum and that is to use angular.copy and then use the cloned model in your html. When you submit changes, set the cloned model to the original model.

There was heaps of examples on here on how to use angular.copy. And it works well.

Upvotes: 0

AA.
AA.

Reputation: 4606

You can to use the $rollbackViewValue() method to revert changes but I think that is not the intention.

$rollbackViewValue(); Cancel an update and reset the input element's value to prevent an update to the $modelValue, which may be caused by a pending debounced event or because the input is waiting for a some future event.

If you have an input that uses ng-model-options to set up debounced events or events such as blur you can have a situation where there is a period when the $viewValue is out of synch with the ngModel's $modelValue.

In this case, you can run into difficulties if you try to update the ngModel's $modelValue programmatically before these debounced/future events have resolved/occurred, because Angular's dirty checking mechanism is not able to tell whether the model has actually changed or not.

The $rollbackViewValue() method should be called before programmatically changing the model of an input which may have such events pending. This is important in order to make sure that the input field will be updated with the new model value and any pending operations are cancelled.

The normal use case is to copy the model, optionally to persist the model and, if all is ok, refresh the model.

_this = this;
this.edit = function() {
    this.modelToEdit = angular.copy(this.originalModel);
}

this.save = function () {
    service.save(modelToEdit).then(function(savedModel) {
        _this.originalModel = savedModel;
    });
}

Or you can backup the model and restore when cancel

_this = this;
this.edit = function() {
    this.backupModel = angular.copy(originalModel);
}

this.cancel = function() {
    this.originalModel = this.backupModel;
}
this.save = function() {
    service.save(this.originalModel).then(function(data) {}, function(error) {
       _this.originalModel = _this.backupModel;})

}

Upvotes: 4

guzart
guzart

Reputation: 3730

I can see a few problems with your code:

  • ngModelOptions.updatedOn is meant to be a DOM event, i.e. click, blur, mouseenter, mouseover, etc,
  • The form controller does NOT have a $broadcast method, so it's never emitting an event.

I think the fact that it sort of works is because there is not type="button" on the <button> so they are considered as "submit" inside the form. And the model is updated because of that.

I suggest you use a simplified version, and

  • remove the ng-model-options from the form.
  • add a type="submit" to the Update button, and remove the form.$broadcast
  • add a type="button" to the Cancel button.

Am not sure how it would work with the modal, but here's a plunkr with ng-if: http://plnkr.co/edit/m37Fd0NybpnfslNkvJnO

Upvotes: 0

Related Questions