Angular-styleguide: Good practice for accessing parent controller properties

Created on 28 Jul 2015  ·  13Comments  ·  Source: johnpapa/angular-styleguide

Using the controllerAs syntax, what is a good practice to access/modify simple properties of a parent controller? Please see the example below. I do not like accessing them via $scope.$parent.parent. How do you guys go about similar situations without creating a service for such a trivial logic.

<div ng-controller="Parent as parent">

  <div ng-if="parent.showMessage">
    Some simple message.
  </div>

  <div ng-controller="ChildOne as childOne"></div>

</div>
app.controller('Parent', function () {
    var self = this;
    self.showMessage = true;
});

app.controller('ChildOne', function ($scope) {
    var self = this;
    self.foo = 'bar';
    // Some simple logic.
    if (self.foo === 'bar') {
        $scope.$parent.parent.showMessage = false;
    }
});
question

Most helpful comment

Using a more component-oriented approach:

<html ng-app="myApp">
  <body>
    <div ng-controller="ParentCtrl as vm">
      <my-directive on-foo-eq-bar="vm.updateMessage(msg)"></my-directive>
    </div>

    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
    <script>
      angular.module('myApp', [])
        .controller('ParentCtrl', function($window) {
          var vm = this;
          vm.updateMessage = function(msg) {
            $window.alert(msg);
          };
        })
        .directive('myDirective', function() {
          return {
            scope: {
              onFooEqBar:'&'
            },
            controllerAs:'vm',
            controller: function($scope) {
              var self = this;
              self.foo = 'bar';
              // Some simple logic.
              if (self.foo === 'bar') {
                $scope.onFooEqBar({msg: false});
              }
            }
          };
        });
    </script>
  </body>
</html>

All 13 comments

Can you give a real example? I can only think of situations where the parent already know the object the child controllers will use/modify, and that you give that object to the child controllers through data-ng-model. If you do this, the parent controller can access the object. This way, your controllers don't need to know each other, or each others' scope.

Or are you doing something different than what I think it is?

@dietergeerts thanks for your input. In your example, passing a primitive value down to a child using ng-model does not have any affect when the child modifies that value.

I basically need to show the first div if some simple logic is true in the child.

(ChildOne is attached to a route, so I can't just stick the first div in the child)

@Bekt, it does can have that effect, as data-ng-if could be ="parent.object.property == 'some_value'", of course depending on what the logic is and how complicated that logic needs to be.

If they are set through routes with ui-router, and they are child routes, then they both can use the object.

Generally, it's a very bad idea to have to ask the parent for something, as that means that the controller isn't self-contained, which they need to be. There usually is a far better way to do such 'communication'. That's why I was asking for the actual situation you have, as there could be better ways to do what you want.

i like to keep things decoupled. BUT there are few absolutes ... in general my directives, my controllers, my services ... I like them to be self contained and if they need external aspects, I inject them or bind them. I don't like to rely on something to be used in a specific context. Make sense?

I would solve this uisng a diretive to encapsulate the child controller, like this:

<div ng-controller="Parent as parent">

  <div ng-if="parent.showMessage">
    Some simple message.
  </div>

  <div child-one parent="parent"></div>

</div>
app.controller('Parent', function () {
    var self = this;
    self.showMessage = true;
});

app.directive('childOne', function() {
    return {
        controllerAs: 'childOne',
        controller: function($scope, $attrs) {
            var parent = $scope.$eval($attrs.parent);

            var self = this;
            self.foo = 'bar';
            // Some simple logic.
            if (self.foo === 'bar') {
                parent.showMessage = false;
            }
        }
    };
});

Or to use a ng-init on the second contoller.

<div ng-controller="ChildOne as childOne" ng-init="childOne.setParent(parent)"></div>

Using a more component-oriented approach:

<html ng-app="myApp">
  <body>
    <div ng-controller="ParentCtrl as vm">
      <my-directive on-foo-eq-bar="vm.updateMessage(msg)"></my-directive>
    </div>

    <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
    <script>
      angular.module('myApp', [])
        .controller('ParentCtrl', function($window) {
          var vm = this;
          vm.updateMessage = function(msg) {
            $window.alert(msg);
          };
        })
        .directive('myDirective', function() {
          return {
            scope: {
              onFooEqBar:'&'
            },
            controllerAs:'vm',
            controller: function($scope) {
              var self = this;
              self.foo = 'bar';
              // Some simple logic.
              if (self.foo === 'bar') {
                $scope.onFooEqBar({msg: false});
              }
            }
          };
        });
    </script>
  </body>
</html>

@dbabaioff @leandrocosta: thanks, interesting suggestions.

I ended up going with $on on the parent and $broadcast on the children.

good conversation, but nothing to add here to the guide, IMO

:+1:

in your parent controller subscribe to an event on $rootScope

$rootScope.$on('myEvent', function(data){/*do something*/})

then in the child, fire the event using $rootScope

$rootScope.$emit('myEvent', someData);

Imho, it's far better to have 2 components instead of controllers, and inject properties into the child component if needed, and if you want to set them, output something from the child to the parent. Far more performant and easier to reason about + easier to unit test.

Using $rootScope and eventing is not done anymore nowadays.

You can check if the property exists and then assign the value as shown below..

if ($scope.$parent.showMessage){
var parent = $scope.$parent;
while(!parent.hasOwnProperty('showMessage ')){
parent = parent['$parent'];
}
parent.showMessage = false;
}

Sorry to necro this, but just in case anyone else lands here, my solution would be to convert the child controller directly to a component, then bind an update function from the parent to the child, as follows:

<div ng-controller="Parent as parent">

  <div ng-if="parent.showMessage">
    Some simple message.
  </div>

  <child-one update-parent="parent.showMessage = value"></child-one>

</div>
app.controller('Parent', function () {
    var self = this;
    self.showMessage = true;
});

app.component('childOne', /* component def stuff including updateParent: '&' in bindings */);

function childOneController() {
    var self = this;
    self.foo = 'bar';
    // Some simple logic.
    if (self.foo === 'bar') {
        self.updateParent({value: false});
    }
});
Was this page helpful?
0 / 5 - 0 ratings

Related issues

samithaf picture samithaf  ·  12Comments

jusefb picture jusefb  ·  9Comments

kdekooter picture kdekooter  ·  8Comments

andreshg112 picture andreshg112  ·  3Comments

sgbeal picture sgbeal  ·  7Comments