28

AngularJS has a DOM based controller inheritance, as mentioned in the angular Docs.

<div ng-controller="BaseController">
    <p>Base Controller Value: {{value}}</p>
    <button ng-click="updateValue()">Update In Base</button>
    <div ng-controller="DerivedController">
        <p>Derived Controller Value: {{value}}</p>
        <button ng-click="updateValue()">Update In Derived</button>
    </div>
</div>

The scope variable "value" is present only in the BaseController. Based on this, while changing the value at a method in BaseController or DerivedController, I want both the values need to be updated. But only the individual scope variable gets updated.

Here is a fiddle demonstrating the same example: http://jsfiddle.net/6df6S/1/

How can changes at a level be made to propagate to the immediate child and immediate parent of the current scope.

We can implement this by using

$scope.$watch

Is there any way to do this without using it or any such watchers?

Edit 1:

By using $scope.$watch here is what I meant

$scope.$watch("value", function () {
   $scope.$broadcast("childChangeListener"); //Downwards to all children scopes
   $scope.$emit("parentChangeListener"); //Upwards to all parent scopes
});

I was looking for ways in which the value would get updated across all scopes without using such a mechanism.

Rod
  • 52,748
  • 3
  • 38
  • 55
Praveenram Balachandar
  • 1,587
  • 1
  • 13
  • 16

6 Answers6

50

When multiple controllers need access to the same data, a service should be used. You should not rely on scope inheritance, as it restricts how you can write your HTML. E.g., in the future, you made decide that DerivedController should not be a child of BaseController.

Your service should typically provide a public API, and hide the implementation. This makes it easier to refactor the internals.

HTML:

<div ng-controller="BaseController">
    <p>Base Controller Value: {{model.getValue()}}</p>
    <button ng-click="model.updateValue('Value updated from Base')">Update In Base</button>
    <div ng-controller="DerivedController">
        <p>Derived Controller Value: {{model.getValue()}}</p>
        <button ng-click="model.updateValue('Value updated from Derived')">Update In Derived</button>
    </div>
</div>

JavaScript:

app.factory('sharedModel', function () {
    // private stuff
    var model = {
        value: "initial Value"
    };
    // public API
    return {
        getValue:    function() { 
           return model.value; 
        },
        updateValue: function(value) {
           model.value = value;
        }
    };
});

function BaseController($scope, sharedModel) {
    $scope.model = sharedModel;
}

function DerivedController($scope, sharedModel) {
    $scope.model = sharedModel;
}

Fiddle.

Also, I do not recommend using $rootScope for sharing between controllers.

Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
  • I see your point, but this method seems to be very verbose. Can we not cause update to the model returned by service, without using the update method? – Chandermani Mar 14 '13 at 03:57
  • 1
    Just to say that you saved me :) – Ghislain Leveque Dec 11 '13 at 09:14
  • @Chandermani: I agree, I think that using a service here is inelegant. Take a look at this answer for a way to do controller inheritance in a way that doesn't depend on the HTML structure: http://stackoverflow.com/a/20230720/62571 – cdmckay Apr 08 '14 at 02:17
  • That first link for services 404s. I think this is the updated URL: https://docs.angularjs.org/guide/services – Matt Grande Mar 06 '15 at 15:03
  • @Chandermani, I updated the answer (2 years later, eh better late than never??) to explain that the verbosity -- i.e., use of methods -- is useful to encapsulate the internals of the service. – Mark Rajcok Apr 14 '15 at 01:49
  • @MarkRajcok what about case when you want use in parent controller some data from router 'resolve'? I'm leaning towards the use '$controller('ParentCtrl', {})' – maksimr Apr 28 '16 at 06:46
  • Instead of including the service everytime in the controller like `$scope.model = sharedModel;`, can I automate it somehow? So that, every controller includes this service by default. – Mr_Green Jan 19 '17 at 07:42
  • This answer is concerned with sharing **state** and not so much **behavior**. Could you please also show how to have a set of methods (like *getValue()*, *setValue()* above) acting on the state of controller they are used from? I believe that's what people have in mind when they talk about inheritance. – Piotr Dobrogost May 09 '18 at 14:37
8

The binding in your fiddle is directly on a property in scope. The inheritance that you need can be achieved by having the data binding on an object in the scope.

http://jsfiddle.net/2Dtyq/

scope.shared = {}    
scope.shared.value = "Something"

Instead of just

scope.value= "Something"

What is happening?
While DerivedController is constructed, the scope being passed in prototypically inherits from the scope of BaseController.
DerivedController's scope object still doesn't have its own property called "value". Therefore it still binds to the property that it got from parent's scope.
Now if you click on the Update In Base button, it will reflect in both the bindings. The moment you click on Update In Derived button, a new property named "value" is created on the DerviedController's scope. Therefore the binding to the parent's value property is broken. Any more clicks on Update In Base doesn't refresh the second data binding.
Putting it in a separate object prevents a new property from being created so the inheritance is still maintained.

San
  • 81
  • 1
  • This and @Chandermani's answer are the correct answers. The other solutions presented here are workarounds but do not identify the root cause of the behaviour. – do0g Aug 22 '14 at 21:35
5

You cannot directly refer to the string value in the child controller. Because of prototypal inheritance setting the property value on child scope (which gets created when you use a child controller) creates that property on the child scope rather than updating the parent scope property.

Your scope is not the model and therefore we should avoid polluting the $scope variable with lots of properties. Create a model object and create sub properties on it. This object can then be passed along the child controllers\scope.

I modified your jsfiddle to explain the above point.

Also I highly recommend you to go through this wiki page, to understand the nuisances of prototypal inheritance.

Chandermani
  • 42,589
  • 12
  • 85
  • 88
  • 2
    +1, but I recommend using a service to share data between controllers, rather than relying on a certain HTML structure to provide scope inheritance (see my answer). – Mark Rajcok Mar 13 '13 at 16:13
2

Take a look at the $broadcast method. Quoting the docs:

Dispatches an event name downwards to all child scopes (and their children) notifying the registered ng.$rootScope.Scope#$on listeners.

Bruno
  • 5,961
  • 6
  • 33
  • 52
  • Thanks Bruno. But I was looking for something else, since watching on "value" triggers every action even unrelated to this "value" to check if the "value" has changed, since as part of every digest cycle the watch variables are verified. – Praveenram Balachandar Mar 13 '13 at 13:29
  • Is there any particular reason for choosing service over `$rootscope`? – Aditya Ponkshe Jun 20 '14 at 01:50
0

Use objects instead of using primitive value. In your case define an object like this

$scope.obj = {"value":"base"}; 

and try(obj.value). it will work.

JSAddict
  • 12,407
  • 7
  • 29
  • 49
0

That happens because ngController create a new $scope and the reference for value is not the same. You can resolve that using the $parent property like that:

<div ng-controller="BaseController">
    <p>Base Controller Value: {{value}}</p>
    <div ng-controller="DerivedController">
        <p>Derived Controller Value: {{$parent.value}}</p>
    </div>
</div>

And in the DerivedController you can use something like that:$scope.$parent.value

But the best way to implement that it is to use the controllerAs syntax.

<div ng-controller="BaseController as BaseController ">
<p>Base Controller Value: {{BaseController.value}}</p>
<div ng-controller="DerivedController">
    <p>Derived Controller Value: {{BaseController.value}}</p>
</div>

And in the DerivedController you can use something like that:$scope.BaseController.value

That give to the code more readability, because you know what is the controller you are referencing. More about that in ngController Docs or Digging into Angular’s “Controller as” syntax by ToddMotto

Henrique Limas
  • 307
  • 1
  • 10