Passing params to nested directives with isolated scope

10,062

Solution 1

I'll repeat what others before me said - that the link function of firstDirective is a post-link function that runs after the link function of secondDirective, and so scope.numbers is not yet assigned the object scope.dataFirst.numbers.

However, a solution that tightly couples two directives via require seems sub-optimal to me.

Instead, to make sure that a scope property is properly assigned in the parent before inner/child directives run (like secondDirective, in this case) is to use a pre-link function in the firstDirective (instead of a post-link)

link: {
  pre: function prelink(scope){
      console.log('first directive')
      console.log(scope)
      scope.numbers = scope.dataFirst.numbers;
    }
}

Demo

Solution 2

My solution to this would be to have the second directive inherit the data object attribute from the first directive.

angular.module('app', [])

.controller('MainCtrl', function($scope) {
  $scope.data = {
    numbers: {
      n1: 'one',
      n2: 'two'
    },
    letters: {
      a: 'A',
      b: 'B'
    }
  }
})

.directive('firstDirective', function() {
  return {
    template: '<div class="first-directive">\
      <h2>First Directive</h2>\
      {{dataFirst}}\
      <div second-directive></div>\
      <div second-directive></div>\
      </div>',
    replace: true,
    restrict: 'A',
    scope: {
      dataFirst: '=firstDirective'
    },
    controller: function firstDirectiveController($scope) {
      return $scope;
    },
    link: function postLink(scope, element, attrs) {
      console.log('first directive')
      console.log(scope)
      scope.numbers = scope.dataFirst.numbers;
    }
  };
})

.directive('secondDirective', function() {
  return {
    template: '<div class="second-directive">\
      <h2>Second Directive</h2>\
      {{dataSecond}}\
      <div class="is-obj">is an object: {{isObj}}</div>\
      </div>',
    replace: true,
    require: '^firstDirective',
    link: function postLink(scope, iElement, iAttrs, firstDirectiveController) {
      console.log('second directive');
      console.log(firstDirectiveController.dataFirst.numbers);
      
      scope.dataSecond = firstDirectiveController.dataFirst.numbers;

      scope.isObj = false;

      if (angular.isObject(scope.dataSecond)) {
        scope.isObj = true;
      }
    }
  };
});
  
h2 {
  padding: 0;
  margin: 0;
}

.first-directive {
  background: #98FFDA;
  color: black;
  padding: 10px;
}

.second-directive {
  background: #FFA763;
  color: white;
  padding: 10px;
}

.is-obj {
  background: blue;
}
  
<!DOCTYPE html>
<html ng-app="app">

  <head>
    <meta charset="utf-8" />
    <title>AngularJS Plunker</title>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
      <link rel="stylesheet" href="style.css">
    <script src="script.js"></script>
  </head>

  <body ng-controller="MainCtrl">
    <h2>MainCtrl</h2>
    {{data}}

    <div first-directive="data">
    </div>
  </body>

</html>
Share:
10,062
cespon
Author by

cespon

Updated on July 24, 2022

Comments

  • cespon
    cespon almost 2 years

    I have an object data in the MainCtrl. This object is used to pass data to directives first-directive and second-directive. Two-data binding is neccesary in both cases.

    For first-directive, I pass the complete object data but for second-directive I want to pass numbers object (scope.numbers = scope.dataFirst.numbers).

    The problem:

    When I do <div second-directive="dataFirst.numbers"></div>, and I check if dataSecond is an object, it returns true.

    But when I do <div second-directive="numbers"></div> and I check if dataSecond is an object, it returns false.

    In both cases if I do console.log(scope) the scope.dataSecond property is shown.

    The question:

    Why does this occur and what's the correct way to pass params to directives?

    EDIT: The idea is making reusable directives and this implies that they can't depend on others directives.

    angular.module('app',[])
    
    .controller('MainCtrl', function($scope) {
      $scope.data = {
        numbers: {
          n1: 'one',
          n2: 'two'
        },
        letters: {
          a: 'A',
          b: 'B'
        }
      }
    })
    
    .directive('firstDirective', function () {
      return {
        template: '<div class="first-directive">\
          <h2>First Directive</h2>\
          {{dataFirst}}\
          <div second-directive="dataFirst.numbers"></div>\
          <div second-directive="numbers"></div>\
          </div>',
        replace: true,
        restrict: 'A',
        scope: {
          dataFirst: '=firstDirective'
        },
        link: function postLink(scope, element, attrs) {
          console.log('first directive')
          console.log(scope)
          scope.numbers = scope.dataFirst.numbers;
        }
      };
    })
    
    .directive('secondDirective', function () {
      return {
        template: '<div class="second-directive">\
          <h2>Second Directive</h2>\
          {{dataSecond}}\
          <div class="is-obj">is an object: {{isObj}}</div>\
          </div>',
        replace: true,
        restrict: 'A',
        scope: {
          dataSecond: '=secondDirective'
        },
        link: function postLink(scope, element, attrs) {
          console.log('second directive');
          console.log(scope)
          
          // <div second-directive="XXXX"></div>
          // if 'numbers' returns undefined
          // if 'dataFirst.numbers' returns the object
          console.log(scope.dataSecond); 
          
          scope.isObj = false;
          
          if(angular.isObject(scope.dataSecond)){
            scope.isObj = true;
          }
        }
      };
    });
    h2 {
      padding: 0;
      margin: 0;
    }
    
    .first-directive {
      background: #98FFDA;
      color: black;
      padding: 10px;
    }
    
    .second-directive {
      background: #FFA763;
      color: white;
      padding: 10px;
    }
    
    .is-obj {
      background: blue;
    }
    <!DOCTYPE html>
    <html ng-app="app">
    
      <head>
        <meta charset="utf-8" />
        <title>AngularJS Plunker</title>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
      </head>
    
      <body ng-controller="MainCtrl">
        <h2>MainCtrl</h2>
        {{data}}
    
        <div first-directive="data">
        </div>
    
        <div second-directive="data">
        </div>
      </body>
    
    </html>