yaegerbomb
yaegerbomb

Reputation: 1188

ui-select2 inside directive isn't updating controller model

I have a directive that takes in a collection and builds out a dropdown.

.directive("lookupdropdown", function () {
    return {
        restrict: 'E',
        scope: {
            collectionset: '=',
            collectionchoice: '='
        },
        replace: true,
        template: '<select class="input-large" ui-select2 ng-model="collectionchoice" data-placeholder="">' +
                    '    <option ng-repeat="collection in repeatedCollection" value="{{collection.id}}">{{collection.description}}</option>' +
                    '</select>',
        controller: ["$scope", function ($scope) {
            $scope.repeatedCollection = new Array(); //declare our ng-repeat for the template
            $scope.$watch('collectionset', function () {
                if ($scope.collectionset.length > 0) {
                    angular.forEach($scope.collectionset, function (value, key) { //need to 'copy' these objects to our repeated collection array so we can template it out
                        $scope.repeatedCollection.push({ id: value[Object.keys(value)[0]], description: value[Object.keys(value)[1]] });
                    });
                }
            });

            $scope.$watch('collectionchoice', function (newValue, oldValue) {
                debugger;
                $scope.collectionchoice;
            });
        } ]
    }
});

This works fine. It builds out the drop down no problem. When I change the dropdown value, the second watch function gets called and I can see that it sets the value of collection choice to what I want. However, the collectionchoice that I have put into the directive doesn't bind to the new choice.

<lookupDropdown collectionset="SecurityLevels" collectionchoice="AddedSecurityLevel"></lookupDropdown>

That is the HTML markup.

This is the javascript:

$scope.SecurityLevels = new Array();
$scope.GetSecurityLevelData = function () {
    genericResource.setupResource('/SecurityLevel/:action/:id', { action: "@action", id: "@id" });
    genericResource.getResourecsList({ action: "GetAllSecurityLevels" }).then(function (data) {
        $scope.AddedSecurityLevel = data[0].SCRTY_LVL_CD;
        $scope.SecurityLevels = data;
        //have to get security levels first, then we can manipulate the rest of the page
        genericResource.setupResource('/UserRole/:action/:id', { action: "@action", id: "@id" });
        $scope.GetUserRoles(1, "");
    });
}
$scope.GetSecurityLevelData();

Then when I go to post my new user role, I set the user role field like this:

 NewUserRole.SCRTY_LVL_CD = $scope.AddedSecurityLevel;

but this remains to be the first item EVEN though I have updated the dropdown, which according the watch function, it has changed to the correct value. What am I missing here?

Upvotes: 0

Views: 481

Answers (2)

Mohammad Umair Khan
Mohammad Umair Khan

Reputation: 515

You faced this issue because of the prototypical nature inheritance in Javascript. Let me try and explain. Everything is an object in Javascript and once you create an object, it inherits all the Object.Prototype(s), which eventually leads to the ultimate object i.e. Object. That is why we are able to .toString() every object in javascript (even functions) because they are all inherited from Object.

This particular issue on directives arises due to the misunderstanding of the $scope in Angular JS. $scope is not the model but it is a container of the models. See below for the correct and incorrect way of defining models on the $scope:

...
$scope.Username = "[email protected]"; //Incorrect approach
$scope.Password = "thisisapassword";//Incorrect approach
...
$scope.Credentials = {
    Username: "[email protected]", //Correct approach
    Password: "thisisapassword" //Correct approach
}
...

The two declarations make a lot of difference. When your directive updated its scope (isolated scope of directive), it actually over-rid the reference completely with the new value rather then updating the actual reference to the parent scope hence it disconnected the scope of the directive and the controller.

Your approach is as follows:

<lookupDropdown collectionset="SecurityLevels" collectionchoice="$parent.AddedSecurityLevel"></lookupDropdown>

The problem with this approach is that although it works, but it not the recommended solution and here is why. What if your directive is placed inside another directive with another isolated scope between scope of your directive and the actual controller, in that case you would have to do $parent.$parent.AddedSecurityLevel and this could go on forever. Hence NOT a recommended solution.

Conclusion:

Always make sure there is a object which defines the model on the scope and whenever you make use of isolate scopes or use ng directives which make use of isolate scopes i.e. ng-model just see if there is a dot(.) somewhere, if it is missing, you are probably doing things wrong.

Upvotes: 1

yaegerbomb
yaegerbomb

Reputation: 1188

The issue here was that my directive was being transcluded into another directive. Making the scope im passing in a child of the directive it was in. So something like $parent -> $child -> $child. This of course was making changes to the third layer and second layer. But the first layer had no idea what was going on. This fixed it:

<lookupDropdown collectionset="SecurityLevels" collectionchoice="$parent.AddedSecurityLevel"></lookupDropdown>

Upvotes: 0

Related Questions