adapt-dev adapt-dev - 5 months ago 17
Javascript Question

In angular js, How do I pass data from a parent controller to a child controller?

I have a little widget I'd like to use over and over on a single page. It has its own controller. Problem is it needs a piece of data to operate (basically a key), and each key is contained in the parent controller.

Here is an example (which is obviously wrong)

http://plnkr.co/edit/VajgOr1LqpLDnbEJcvor?p=preview

script:

angular.module('myApp', [])
.controller('ParentCtrl', ['$scope',
function($scope) {
$scope.keyForChartABC = "somekey1";
$scope.keyForChartXYZ = "somekey2";
$scope.keyForChartLALA = "somekey3";

}
])
.controller('ChartCtrl', ['$scope',
function($scope) {
//todo: have $scope.key assigned from parent somehow

//not shown: use $scope.key to pull data and format chart data
}
])


index:


<!-- ng-init like this is quite wrong -->
<div ng-init="key = keyForChartABC"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartXYZ"
ng-include="'chartwidget.html'"></div>
<hr>
<div ng-init="key = keyForChartLALA"
ng-include="'chartwidget.html'"></div>



chartwidget:

<div ng-controller="ChartCtrl">
<p>Drawing chart for data: {{key}}</p>
<p>some chart directive here</p>
</div>


As you can see in the plunker, what I tried here with ng-init doesn't work - key for all the sub-controllers end up with the same value.

I've gotten this to work with ng-repeat and an array of data in the parent, somehow $index gets set in each child to the right index and stays that one value. But I'd like to avoid using ng-repeat in this case so I can have more control of the layout.

Answer

Creating re-usable widgets is exactly the purpose of Directives. You can create a directive which handles the output of your widget quite easily.

I forked your plunker and modified it to change it to use a directive.

Here are a few highlights:

First, your template no longer needs the controller defined within it.

<div>
    <p>Drawing chart for data: {{key}}</p>
    <p>some chart directive here</p>
</div>

Next, the directive is defined, with an isolate scope which is unique to each instance of the directive:

.directive('chartWidget', function(){
    return {
      restrict: 'E',
      scope: {
        key: '='
      },
      templateUrl : 'chartwidget.html'
    }
})

Lastly, the directive is declared in the HTML. Note the camel-case name of the directive in the JavaScript, but the hyphenated name in the HTML:

<div>
    <chart-widget key="keyForChartABC"></chart-widget>
    <hr>
    <chart-widget key="keyForChartXYZ"></chart-widget>
    <hr>
    <chart-widget key="keyForChartLALA"></chart-widget>
</div>

Edit

I updated the plunker to show binding the directive property to an inner controller. This method uses the ControllerAs syntax to define the controller, and binds the directive's scope to the controller scope.

Relevant changes:

.directive('chartWidget', function(){
    return {
      restrict: 'E',
      scope: {
        key: '='
      },
      templateUrl : 'chartwidget.html',
      controller: 'chartWidgetController',
      controllerAs: 'ctrl',
      bindToController: true
    }
  })
  .controller('chartWidgetController', function(){
    console.log(this.key);
  })

And a small change to the template to support ControllerAs:

<div>
    <p>Drawing chart for data: {{ctrl.key}}</p>
    <p>some chart directive here</p>
</div>

Note that trying to use ng-controller= in the template will cause the template to have a different scope object from the scope object created for the directive, and the controller would not have access to the properties defined on the directive.

Also note, bindToController is a feature of angular 1.3.x or higher. in angular 1.2.x or earlier, your only option was to use $scope.$watch to monitor the isolate scope for changes.