user6641203
user6641203

Reputation: 51

Angularjs: ngDialog only binds and modifies with objects; not basic variables.

I've already found a "solution" to this problem; I was just hoping someone might be able to provide a reason why it works.

This jsFiddle demonstrates the problem: http://jsfiddle.net/s1ca0h9x/137/

HTML

<div data-ng-app="myApplication">
    <div data-ng-controller="MainController">
<a href="" ng-click="ShowNgDialog()">Click Here</a>

        <input type="text" ng-model="accountNum" />
        <span>{{accountNum}}</span>

    </div>
</div>

ANGULARJS

var myApplication = angular.module('myApplication', ['ngDialog']);

myApplication.controller('MainController', function ($scope, ngDialog) {
    $scope.accountNum = 'test';
    $scope.ShowNgDialog = function () {
        ngDialog.open({            
            template: '<div><input type="text" ng-model="accountNum"/></div>',
            plain: true,
            scope:$scope

        });
    }    
});

When I try and manipulate a scope variable (in this case: $scope.accountNum = 'test') from the dialog, it doesn't bind/save it back to the model.

...However, when I change that variable into an object, things just magically work, as shown in this demo: http://jsfiddle.net/s1ca0h9x/138/

HTML

<div data-ng-app="myApplication">
    <div data-ng-controller="MainController">
<a href="" ng-click="ShowNgDialog()">Click Here</a>

        <input type="text" ng-model="FormData.accountNum" />
        <span>{{FormData.accountNum}}</span>

    </div>
</div>

ANGULARJS

var myApplication = angular.module('myApplication', ['ngDialog']);

myApplication.controller('MainController', function ($scope, ngDialog) {
    $scope.FormData={accountNum: ''};
    $scope.ShowNgDialog = function () {
        ngDialog.open({            
            template: '<div><input type="text" ng-model="FormData.accountNum"/></div>',
            plain: true,
            scope:$scope

        });
    }    
});

I also tested both options using a template linking to a file, and not using plain:true, in addition to trying ngDialog.openConfirm, etc. I essentially rebuilt the solution found here ngDialog $scope variables not being updated by ngModel fields in $dialog when using scope: $scope piece by piece, and finally the only change that seemed to work was using an object instead of a basic scope variable. Am I approaching this wrong, or missing some fundamental aspects of data binding?

Upvotes: 3

Views: 443

Answers (2)

Stefan Pintilie
Stefan Pintilie

Reputation: 715

For me what worked, is to create a function in the base controller, and call the function from ngDialog controller.

Ex:

myApplication.controller('MainController', function ($scope, ngDialog) {
    $scope.accountNum = 'test';
    $scope.ShowNgDialog = function () {
        ngDialog.open({            
            template: '<div><input type="text" ng-model="accountNum"/></div>',
            plain: true,
            scope:$scope,
            controller: ['$scope',
                function ($scope) {
                    $scope.updateVar();
                }]
        });
    };

    $scope.updateVar = function(){
        $scope.accountNum = "changed";
    }
});

Upvotes: 0

codtex
codtex

Reputation: 6558

I think this has nothing to do with the binding. I will explain what I did understood when I dig into the code of ngDialog and AngularJS.

I think the first case is not working as you expect, because $scope.accountNum = 'test'; is a simple string which is a primitive type and is not mutable(ref) or in other words is immutable:

Mutable is a type of variable that can be changed. In JavaScript, only objects and arrays are mutable, not primitive values. (You can make a variable name point to a new value, but the previous value is still held in memory. Hence the need for garbage collection.)

A mutable object is an object whose state can be modified after it is created.

Immutables are the objects whose state cannot be changed once the object is created.

String and Numbers are Immutable.

So, in short words, this was the reason why the first variant is not working as you want :)


Now let's have a look on this code of ngDialog, which is a part of open() method:

var scope;
scopes[dialogID] = scope = angular.isObject(options.scope) ? options.scope.$new() : $rootScope.$new();

in your case we are calling options.scope.$new(), because you specified scope in options when opening the dialog.

Now let's go and check this angular code:

$new: function (isolate, parent) {
    var child;  
    parent = parent || this;

    if (isolate) {
        child = new Scope();
        child.$root = this.$root;
    } else {
        if (!this.$$ChildScope) {
            this.$$ChildScope = createChildScopeClass(this); // <---- WE ARE COMING HERE NOW
        }
        child = new this.$$ChildScope();
    }
    ...

function createChildScopeClass looks like:

function createChildScopeClass(parent) {
    function ChildScope() {
        this.$$watchers = this.$$nextSibling =
                this.$$childHead = this.$$childTail = null;
        this.$$listeners = {};
        this.$$listenerCount = {};
        this.$$watchersCount = 0;
        this.$id = nextUid();
        this.$$ChildScope = null;
    }
    ChildScope.prototype = parent; /* <--- They simply assign the derived scope 
     as prototype of the new one (which is going to be the scope of the ngDialog) */
    return ChildScope;
}

We can see that function createChildScopeClass() simply assigns the parent scope's prototype to the new one (which is going to be the scope of the opened ngDialog)

And a sample that is demonstrating mutability and immutability:

var test = 'test'; 
var test2 = test;
test2 = 'new value';
console.log('test = ' + test + ' // test2 = ' + test2);

var testObj = {test: 'test'};
var test2Obj = testObj;
test2Obj.test = 'new value';
console.log('testObj.test = ' + testObj.test + ' // test2Obj.test = ' + test2Obj.test);

Conclusion

Use objects or arrays in your parent scope if you want binding to work in the derived scope. Sample using AngularJS:

var app = angular.module('sample', []);

app.controller('AppController', ['$scope', function($scope) {

  $scope.primitive = 'test';
  $scope.obj = {
    test: 'test initial'
  };

  $scope.newScope = $scope.$new();
  $scope.newScope.primitive = 'test 2';
  $scope.newScope.obj.test = 'updated value';

}]);

app.run();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="sample">
  <div ng-controller="AppController">
    <table>
      <thead><tr><th>Property</th><th>Value</th><th></th></tr></thead>
      <tbody>
        <tr>
          <td>primitive</td>
          <td>{{ primitive }}</td>
          <td><input type="text" ng-model="primitive"></td>
        </tr>
        <tr>
          <td>obj.test</td>
          <td>{{ obj.test }}</td>
          <td><input type="text" ng-model="obj.test"></td>
        </tr>
        <tr>
          <td>newScope.primitive</td>
          <td>{{ newScope.primitive }}</td>
          <td><input type="text" ng-model="newScope.primitive"></td>
        </tr>
        <tr>
          <td>newScope.obj.test</td>
          <td>{{ newScope.obj.test }}</td>
          <td><input type="text" ng-model="newScope.obj.test"></td>
        </tr>
      </tbody>
    </table>
  </div>
</div>

Upvotes: 2

Related Questions