Search code examples
angularjsselectscopeparentng-options

Parent scope not updating from select with ng-change from directive


I've seen a couple of articles on this but still can't figure it out.

I am unable to update the parent scope from the within a directive. I have read the articles saying the scope value should not be primitive and instead it should be an object but still can't figure out why this is not working.

angular.module('moduleMihai').controller('someController',
    ['$scope', '$http', function ($scope, $http) {

            $scope.durations = [{
                field: 'yearly',
                title: 'Yearly'
            }, {
                field: 'monthly',
                title: 'Monthly'
            }, {
                field: 'weekly',
                title: 'Weekly'
            }];
            $scope.selectedDuration = $scope.durations[0];

            $scope.handleDurationSelection = function () {
                console.log($scope.selectedDuration.field + ' selected');
                $scope.someData.title[0] = "SOMETHING ELSE!!";
            };


            $scope.someData= {
                title: ['Value1', 'Value2', 'Value3'] };
}]);

the directive doesn't have any stuff in it:

angular.module("awaCommon").directive("durationSelection", [
    function () {
        return { 
            scope: {}, // tried removing this as well as seen in some articles 
            restrict: "E",
            templateUrl: "duration-selection.html",
            link: function ($scope, $element, $attr) {
            }
        }
    }
]);

below the duration-selection.html which contains the select:

<div ng-controller="someController">
<div>
    Child: {{someData.title[0]}}
    <select
        ng-options="item.title for item in durations"
        ng-model="selectedDuration"
        ng-change="handleDurationSelection()">
    </select>
</div>

So this value above in the Child: {{someData.title[0]}} - gets updated properly when value is selected. But the one in here - Parent: {{someData.title[0]}}, in the main route is not:

<div ng-controller="someController">
<div>
    Parent: {{someData.title[0]}}
    <duration-selection></duration-selection>
</div>

I need the parent scope to be updated in order to update different directives


Solution

  • The way to interact and update your parent scope from your directive is to use

    • event handling (emit and broadcast) Todd about events $emit and $broadcast : so here we alert the parent when there is a change from the child directive, then the parent listens for the event. I suggest minimal usage due to some bad sides
    • directive attribute to pass the function: we pass our function to be processed to our directive to handle or call it when needed from the directive ( for me the best method)
    • inside the directive to update the $scope.$parent.lngBusinessUnit, no need to pass the function to the directive again, not necessary. since the directive is the one handling the logic. we just directly update the parent straight up.
    • the use of $watch on the parent directive to help check for changes of the selectedDuration $watch read more: this is quite easy since we map the ngModel to the passed param of our directive using two way binding in our return->scope "=" from directive setup

    Example For each of the above possibilities

    • Event Handling

    angular.module("eventTest", [])
    .controller("mainCtrl", function ($scope){
      console.log("am here");
    $scope.parentValue = "test";
    $scope.valueToPass = ["Male", "Female"];
    
    
    //let's catch the updated content
    $scope.$on('childUpdated', function(event, value){
      $scope.parentValue = value;
      console.log("updated from child directive", value);
    });
    
    
    })
    .directive("child", function(){
    return {
      restrict:'E',
      scope: {
      valueToPass:"="
      },
      templateUrl:"child.html",
      controller: function ($scope){
      //this is method is triggered when the select of our valueToPass is changed
      $scope.childChanges = function (value){
          $scope.$emit('childUpdated', value);
          console.log("child emitted this:", value);
       }
      }
    }
    });
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
    <body ng-app="eventTest"> 
    <div ng-controller="mainCtrl">
    <h1>Event Test Just for your case, advise you read up</h1>
    Parent: <b>{{parentValue}}</b>
    <br>
    <child value-to-pass="valueToPass"></child>
    </div>
    
    
    <script type='text/ng-template' id="child.html">
    Child value : <b>{{menu}}<b> <br>
    <select ng-model="menu" ng-change="childChanges(menu)">
      <option ng-repeat="item in valueToPass">{{item}}</option>
    </select>
    </script>
    
    </body>

    • directive attribute , using function

    angular.module("eventTest", [])
        .controller("mainCtrl", function ($scope){
          console.log("am here");
        $scope.parentValue = "test";
        $scope.primaryVariable = "Male";
        
        $scope.onChange = function (){
         $scope.parentValue = $scope.primaryVariable;
        }
       
    
        })
        .directive("child", function(){
        return {
          restrict:'E',
          scope: {
          primaryVariable:"=",
          callMe:"&"//note this syntax, check angular directive doc
          },
          templateUrl:"child.html",
          controller: function ($scope){
           $scope.valueToPass = ["Male", "Female"];
          //this is method is triggered when the select of our primaryVarible is changed
          $scope.childChanges = function (){
            $scope.callMe();
           }
          }
        }
        });
     <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
        <body ng-app="eventTest"> 
        <div ng-controller="mainCtrl">
        <h1>Directive Function Passing</h1>
        Parent: <b>{{parentValue}}</b>
        <br>
        <child primary-variable="primaryVariable" call-me="onChange()"></child>
        </div>
    
    
        <script type='text/ng-template' id="child.html">
        Child value : <b>{{primaryVariable}}<b> <br>
        <select ng-model="primaryVariable" ng-change="childChanges()">
          <option ng-repeat="item in valueToPass">{{item}}</option>
        </select>
        </script>
    
        </body>

    • using scope.$parent

     angular.module("eventTest", [])
            .controller("mainCtrl", function ($scope){
              console.log("am here");
            $scope.parentValue = "test";
            $scope.primaryVariable = "Male";
             
    
            })
            .directive("child", function(){
            return {
              restrict:'E',
              scope: {
              primaryVariable:"="
              },
              templateUrl:"child.html",
              controller: function ($scope){
               $scope.valueToPass = ["Male", "Female"];
              //this is method is triggered when the select of our primaryVarible is changed
              $scope.childChanges = function (){
                $scope.$parent.parentValue = $scope.primaryVariable;
               }
              }
            }
            });
         <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
            <body ng-app="eventTest"> 
            <div ng-controller="mainCtrl">
            <h1>Using $parent</h1>
            Parent: <b>{{parentValue}}</b>
            <br>
            <child primary-variable="primaryVariable"></child>
            </div>
    
    
            <script type='text/ng-template' id="child.html">
            Child value : <b>{{primaryVariable}}<b> <br>
            <select ng-model="primaryVariable" ng-change="childChanges()">
              <option ng-repeat="item in valueToPass">{{item}}</option>
            </select>
            </script>
    
            </body>

    • Using the $watch

    angular.module("eventTest", [])
            .controller("mainCtrl", function ($scope){
              console.log("am here");
            $scope.parentValue = "test";
            $scope.primaryVariable = "Male";
             
             $scope.$watch('primaryVariable', function(){
             $scope.parentValue = $scope.primaryVariable;
             
             });
             
            })
            .directive("child", function(){
            return {
              restrict:'E',
              scope: {
              primaryVariable:"="
              },
              templateUrl:"child.html",
              controller: function ($scope){
               $scope.valueToPass = ["Male", "Female"];
              }
            }
            });
         <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
    
            <body ng-app="eventTest"> 
            <div ng-controller="mainCtrl">
            <h1>using $watch</h1>
            Parent: <b>{{parentValue}}</b>
            <br>
            <child primary-variable="primaryVariable"></child>
            </div>
    
    
            <script type='text/ng-template' id="child.html">
            Child value : <b>{{primaryVariable}}<b> <br>
            <select ng-model="primaryVariable" ng-change="childChanges()">
              <option ng-repeat="item in valueToPass">{{item}}</option>
            </select>
            </script>
    
            </body>

    Hope this helps