Mou
Mou

Reputation: 16282

Angular js Controllers inheritance issue

i just come across a sample code on Angular js Controllers inheritance from this url http://viralpatel.net/blogs/angularjs-controller-tutorial/

here is the code and i just do not understand one part.

<div ng-controller="BMWController">

    My name is {{ name }} and I am a {{ type }}

    <button ng-click="clickme()">Click Me</button> 

</div>


<script>
function CarController($scope) {

    $scope.name = 'Car';
    $scope.type = 'Car';

    $scope.clickme = function() {
        alert('This is parent controller "CarController" calling');
    }

}

function BMWController($scope, $injector) {

    $injector.invoke(CarController, this, {$scope: $scope});

    $scope.name = 'BMW';

}
</script>

1) i just do not understand this line of code $injector.invoke(CarController, this, {$scope: $scope});

2) which $scope is BMWController scope and which $scope is CarController scope ?

3) there is two scope {$scope: $scope} one in left and one in right which one is related with BMWController & CarController scope ?

4) why this keyword is used in invoke function ?

5) please help me to understand this line $injector.invoke(CarController, this, {$scope: $scope}); as much with easy explanation

thanks

Upvotes: 1

Views: 132

Answers (3)

Renan Ferreira
Renan Ferreira

Reputation: 2150

After reading the Doc, I think that these line means that you are:

  1. $injector.invoke(CarController... : invoking the CarController function;
  2. ... , this ... : a reference to the invoker;
  3. ...{$scope: $scope}) : the CarController has a dependency of $scope, so this line means that you are injecting th BmwController $scope into the CarController.

Besides all this, I really think that controller inheritance is a realy bad practice. If you need to share some logic between two controllers you should use Angular Services. They are singletons that exists during all lifecycle of your application and are meant to store logic or data, and, more importantly, make your controllers skin and elegant.

Take a look at this example:

https://jsfiddle.net/relferreira/2b5amcya/

HTML:

<div data-ng-app="app">

  <div data-ng-controller="MainController as mainVm">
    {{mainVm.name}}
  </div>

  <div data-ng-controller="DetailController as detailVm">
    {{detailVm.name}}
    {{detailVm.other}}
  </div>

</div>

JS:

angular.module('app', []);

angular.module('app')
    .controller('MainController', mainController);

mainController.$inject = ['UserService'];

function mainController(UserService){

    var vm = this;
  vm.name = UserService.getName();

}

angular.module('app')
    .controller('DetailController', detailController);

detailController.$inject = ['UserService'];

function detailController(UserService){

    var vm = this;
  vm.name = UserService.getName();
  vm.other = 'test';

}

angular.module('app')
    .factory('UserService', userService);

function userService(){
    var name = 'Renan'; 
  return{
    getName: getName
  }

  function getName(){
    return name;
  }
}

EDIT:

Looking at the AngularJs source code we can find the invoke method:

function invoke(fn, self, locals, serviceName) {
      if (typeof locals === 'string') {
        serviceName = locals;
        locals = null;
      }

      var args = injectionArgs(fn, locals, serviceName);
      if (isArray(fn)) {
        fn = fn[fn.length - 1];
      }

      if (!isClass(fn)) {
        // http://jsperf.com/angularjs-invoke-apply-vs-switch
        // #5388
        return fn.apply(self, args);
      } else {
        args.unshift(null);
        return new (Function.prototype.bind.apply(fn, args))();
      }
    }

As you can see, it is using the Function.prototype.apply() method. As stated by Alex, this method needs a context and it is most used by inheritance and/or override methods. Take a look at this answer:

https://stackoverflow.com/a/560952/1108979

it uses call method, that is similar to the apply.

Upvotes: 1

Lulylulu
Lulylulu

Reputation: 1264

The line $injector.invoke(CarController, this, {$scope: $scope}); calls the function CarController and passes the BMWController's $scope as a parameter.

You can see it like:

function CarController($scope) {
    // ...    
}
function BMWController($scope, $injector) {
    CarController($scope);
    $scope.name = 'BMW';
}

and you can also pass other parameters like:

function CarController($scope, foo) {
  // ...

}

function BMWController($scope, $injector) {
    // ...
    $injector.invoke(CarController, this, {$scope: $scope, foo: bar});
}

So, instead of calling the controller function directly, you use the dependency injector to call it. The dependency injection is a design pattern that has multiple advantages as explained here.

Edit

this an optional parameter to the invoke function $injector.invoke(fn, [self], [locals]); and is used to pass the current context: BMWController to the invoked controller function CarController.

In this example we define this.foo on the BMWController context and then we can access foo on the CarController context.

function CarController($http, $scope) {
  console.log(this.foo) // prints: bar
  // ....
}
function BMWController($scope, $injector) {
  this.foo = "bar";

  $injector.invoke(CarController, this, {  $scope: $scope });
  // ....
}

Upvotes: 2

AlexD
AlexD

Reputation: 4290

1) you run a function (CarController) and provide it arguments ($scope)

2) You pass your current scope (of BMWController) to the CarController, so they share scopes.

3) the line {$scope: $scope} creates an object with the key $scope and initializes it with the object $scope. Try to look at the first $scope just like any other object key, like name would be in {name: 'Mou'}

4) Invoke just runs the function with the given arguments: $injector documentation

You pass a function to the invoke function, but that function is without context. You need to tell it the where you want to run it. So by passing this which is the BMWController it runs it kind of inside the BMWController

Upvotes: 2

Related Questions