Abdul Ahmad
Abdul Ahmad

Reputation: 10021

how angular controller (and scope) inheritance works

I'm trying to figure out how controller inheritance works. I have three controllers:

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

myApp.controller('MainController', ['$scope', function($scope) {
    $scope.name = 'main';
    $scope.getName = function() {
        return $scope.name;
    };
}]);
myApp.controller('Child1', ['$scope', function($scope) {
    $scope.name = 'child1';
}]);
myApp.controller('Child2', ['$scope', function($scope) {
    $scope.name = 'child2';
}]);

and my view

<div ng-app='app'>
    <div ng-controller='MainController'>
        <div ng-bind='getName()'></div>

        <div ng-controller='Child1'>
            <div ng-bind='getName()'></div>

            <div ng-controller='Child2'>
                <div ng-bind='getName()'></div>
            </div>
        </div>
    </div>
</div>

but they're all showing "main". How do I fix this?

here's a fiddle http://jsfiddle.net/g3xzh4ov/3/

Upvotes: 1

Views: 243

Answers (3)

Mustafa ASAN
Mustafa ASAN

Reputation: 3825

If you want to overwrite name property in child scopes ,convert your primitive name property into object.

$scope.user = {};
$scope.user.name='main';
    $scope.getName = function() {
        return $scope.user.name;
    };

And you should read https://github.com/angular/angular.js/wiki/Understanding-Scopes for detailed information.

Upvotes: 0

Estus Flask
Estus Flask

Reputation: 222548

Here's an example of how controllers can be extended in Angular.

myApp.service('baseCtrl', function () {
  this.name = 'base';
  this.getName = function() {
    return this.name;
  };
});

myApp.controller('MainController', ['baseCtrl', function (baseCtrl) {
  angular.extend(this, baseCtrl);
  this.name = 'main';
}]);
myApp.controller('Child1', ['baseCtrl', function (baseCtrl) {
  angular.extend(this, baseCtrl);
  this.name = 'child1';
}]);
myApp.controller('Child2', ['baseCtrl', function (baseCtrl) {
  angular.extend(this, baseCtrl);
  this.name = 'child2';
}]);

It obliges to use controllerAs, which replaces $scope with this, it is especially good for such cases.

Notice the usage of service instead of other Angular service types, it uses new under the hood, so this... statements can be brought right from a controller to separate service.

There are several ways of doing controller inheritance. Here is another approach.

Regarding the original code, there is no 'controller iheritance' in Angular. And $scope prototypical inheritance assumes that

$scope.getName = function() {
    return $scope.name;
};

returns $scope.name from the context where it was defined, it is MainController function in your case.

Upvotes: 2

DRobinson
DRobinson

Reputation: 4471

The problem that you're facing is actually based on core Javascript functionality.

You see, the confusion that you're facing stems from the mix of scoping, and prototypical inheritance. The properties are copied over, but the scoping remains the same, preventing you from accessing the variables that you expect to be able to access. To understand this better, perhaps instead of $scope, we can look at a simpler variable:

myApp.controller('MainController', ['$scope', function($scope) {
    var a = 1;
    $scope.getName = function() {
        console.log(a); // -> 1
        console.log(b); // Error! `Uncaught ReferenceError: b is not defined`
    };
}]);
myApp.controller('Child1', ['$scope', function($scope) {
    var b = 2;
}]);

Obviously, MainController doesn't know that Child1 defined some variable called b, so it errors. That variable is strictly out of lexical scope.

Likewise, if we renamed b to a, it won't turn the value in MainController to 2. This demonstrates exactly what's happening for you: you have three things called $scope, and only one is in the lexical scope.

Two options to fix it:

1) use this:

$scope.getName = function() {
    return this.name;
};

The this solution works because of how Javascript determines "this" based on context. Basically, since it's attached to a given $scope Object, that Object's a good candidate. But Mozilla can explain this better than I can, so view their page on the topic here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

2) Otherwise, you can simply pass the $scope:

$scope.getName = function(item) {
    return item.name;
};

Upvotes: 1

Related Questions