5

I've been reading a few articles, and haven't found the example that solves my issue.

My understanding is that:

  1. ng-if and ng-repeat create isolate scopes.
  2. Using $parent.someProperty is bad.
  3. Using $parent.$parent.someProperty would be an abomination.

So, with the given template markup, how can I properly bind the the controller so that the controller property updates?

Markup:
(note the nested ng-if and ng-repeat, creating a sort of $parent.$parent situation)

<div ng-app="MyApp" ng-controller="MyCtrl">
  <div ng-if="showOnCondition">
    <label ng-repeat="item in repeatingItems">
      {{item}}
      <setting item="item" />
    </label>
  </div>
  {{checkSetting()}}
</div>

JavaScript

var myApp = angular.module('MyApp', []);

myApp.controller('MyCtrl', function($scope) {
  $scope.settings = {
    someProperty: '',
    anotherProperty: 'Hello'
  }

  $scope.repeatingItems = [
    'One',
    'Two',
    'Three'
  ];

  $scope.showOnCondition = true;

  $scope.checkSetting = function() {
    // When calling checkSettings(), I would like to access the value of someProperty here
    return $scope.settings;
  }
});

myApp.directive('setting', function() {
  return {
    restrict: 'E',
    require: '^myCtrl',
    // HOW DO I SOLVE THIS?
    // $parent.$parent is bad.
    template: '<input type="radio" ng-model="$parent.$parent.settings.someProperty" name="mySetting" value="{{item}}" />',
    scope: {
      settings: '=',
      item: '='
    }
  };
});

Given the above example, how to I properly construct the directive and / or markup in order to access settings.someProperty in the controller? Or is there something entirely different I need to be doing?

Clarification
There seems to be some confusion around what I'm trying to do. The someProperty is available in the directive - that is working fine. Please note I'm trying to assign values to the controller's someProperty property from within the directive (using ng-model)

Update
I've revised the code above to known, working code, plus added a jsFiddle. Note that it works, but it uses $parent.$parent in the template. This is the problem I need to understand how to solve.

random_user_name
  • 25,694
  • 7
  • 76
  • 115
  • http://stackoverflow.com/questions/15672709/how-to-require-a-controller-in-an-angularjs-directive – The Reason Jan 26 '16 at 21:18
  • Take a look at this [example](http://jsfiddle.net/Blackhole/XyUGE/1/) Is that what you're trying to do? – Alon Eitan Jan 26 '16 at 21:21
  • No, that's a simple example that doesn't contain the nested isolate scopes. – random_user_name Jan 26 '16 at 21:22
  • First of all, scopes are not isolated so you can use scope inheritance here. Then controllerAs approach will always work in such cases. – dfsq Jan 26 '16 at 21:26
  • @dfsq - the child scopes / directive aren't "talking" to the controller. As far as "controllerAs" approach, thanks, but I have no idea what that means! I appreciate the help, but you're speaking in fairly big generalities, and I'm looking for some specific(s) to help get my head wrapped around this. – random_user_name Jan 26 '16 at 21:27
  • I don't think this is the recommended approach but I've used the compile method of the directive to go up the $parent chain till I find it and then preprocessed the template in the past. – Casey Jan 26 '16 at 21:49
  • While not what you are looking for, this is an interesting alternate way to arrange a $parent call: http://stackoverflow.com/questions/17561632/how-to-modify-scope-from-within-a-directive-in-angularjs – ryanm Jan 26 '16 at 22:41

4 Answers4

2

You can pass settings through to the directive, which I think is the simplest / most direct / cleanest way to do this.

Angular properly evaluates settings as MyCtrl.settings, then passes it through to the isolate scope of the setting directive. Inside the directive, you have two-way binding on settings, so you can update settings from there.

Modifying your example, pass in settings into the directive in the Markup:

<setting settings="settings" item="item" />

Then in the JavaScript, swap out this for the template:

template: '<input type="radio" ng-model="settings.someProperty" ng-value="item" name="mySetting" />',

Here's a working fiddle.

ryanm
  • 2,239
  • 21
  • 31
1

EDIT

I'm rewriting my answer completely, based on your fiddle and a simpler two-way data-binding approach. Note to future readers that the first couple of comments are no longer relevant to this updated answer.

HTML

<div ng-app="MyApp" ng-controller="MyCtrl">
  <div ng-if="showOnCondition">
    <label ng-repeat="item in repeatingItems">
      {{item}}
      <setting test="settings.someProperty" item="item" />
    </label>
  </div>
  {{checkSetting()}}
</div>

JS

var myApp = angular.module('MyApp', []);

myApp.controller('MyCtrl', function($scope) {
  $scope.settings = {
    someProperty: true,
    anotherProperty: 'Hello'
  }

  $scope.repeatingItems = [
    'One',
    'Two',
    'Three'
  ];

  $scope.showOnCondition = true;

  $scope.checkSetting = function() {
    // When calling checkSettings(), I would like to access the value of someProperty here
    return $scope.settings;
  }
});

myApp.directive('setting', function() {
  return {
    restrict: 'E',
        replace: true,
    template: '<input type="radio" ng-model="test" name="mySetting" value="{{item}}" />',
    scope: {
      test: '=',
      item: '='
    }
  };
});

This provides two-way data-binding between the parent controller property and the directive. With this approach, you would pass in each individual parent scope property to an attribute, and bind it in your isolate scope.

Daniel Nalbach
  • 1,033
  • 11
  • 17
0

One option when hierarchical relationships and data passing gets complicated is to decouple the parent/child relationships and just have a service to share the data.

I've seen some great explanations on this, for example passing state via services, with sample code. You could also adapt this idea for use with a generic cache service like Angular's $cacheFactory or, more sophisticated, this library: angular-cache.

ryanm
  • 2,239
  • 21
  • 31
  • I've heard of this concept, but it seems like a lot of overhead just to get an input's value on a template into the parent controller. I keep thinking there must be a simpler way? – random_user_name Jan 26 '16 at 22:13
  • You're right - I finally wrapped my head around it and provided a simpler answer http://stackoverflow.com/a/35025628/3232832, but will probably leave this in place as a data point for decoupled services. – ryanm Jan 26 '16 at 23:01
0

Why don't you just add a param 'someproperty' to pass into the settings directive = like so:

myApp.directive('setting', function() {
   return {
   restrict: 'E',
   template: '<input type="radio" ng-model="myProperty" 
   value="{{item}}" />',
   scope: {
      settings: '=',
      item: '=',
      myProperty: '='
   }
  };
});

And in your markup:

<label ng-repeat="item in repeatingItems">
  {{item}}
  <setting item="item" myProperty="settings.someProperty" />
</label>
brewsky
  • 607
  • 1
  • 9
  • 15
  • The myProperty param is 2 way data binding, right? So the new parm "myProperty=setting.someProperty" is bound to the controller $scope.settings. If the child directive makes changes to it, the controller will see those changes. And also In the template input element, the param is passed to ng-model. – brewsky Jan 26 '16 at 23:19