Controller inheritance with injection

2019-09-10 06:35发布

问题:

I'm having a hard time trying to understand the best way to achieve inheritance with my controllers. I've seen a few other posts here about these but I still don´t get some things.

Here's what I have: - 2 controllers which are 80% similar. I already have a factory which both use to get the data which will be displayed. - I use the controllerAs notation, with var vm = this - there's a mix of vars and functions which will be used in the view and therefore are created inside vm, and some other internal vars and functions which are not. - so I tried to create a single parent controller with all this and then use injection to create these 2 controllers, overwriting only what I need, but this is not working as I expected and also I'm not sure this is the right thing to do

Here is a simplified version of the code.

(function() {

    angular

    .controller('ParentController', ParentController)

    ParentController.$inject = [ '$scope', '$location' ];

    function ParentController($scope, $location) {

        var vm = this; // view model

        var URL = $location.url();      

        var isDataLoaded = false;

        vm.predicate = 'order';
        vm.reverse = false;

        vm.results;     

        vm.isDataReady = isDataReady;
        vm.setOrder = setOrder;

        function isDataReady() {
            return isDataLoaded;
        }

        function setOrder(p) {
            vm.reverse = (p === vm.predicate) ? !vm.reverse : false;
            vm.predicate = p;
        }

        $scope.$on('READ.FINISHED', function() {
            isDataLoaded = true;
        })
    }
})();

-

(function() {

    angular

    .controller('ChildController', ChildController)

    ChildController.$inject = ['$controller', '$scope', 'myFactory'];

    function ChildController($controller, $scope, myFactory) {

        $controller('ParentController', {$scope: $scope});

        var TEMPLATE = 'SCREEN';

        // ************** M A I N **************

        myFactory.getResults(URL, vm);
    }

})();

This is now working as I expected.

When I inject ChildController with ParentController, do I really need to inject the $scope? I'm actually using vm. Also, do I need to inject also $location? In this example when I execute my code I'm forced to use var URL = $location.url(); again in my ChildController, I expected to inherite the value from ParentController.

So the thing is, am I only getting values from $scope if I work like this? what about vm? and what about those vars/functions declared outside vm like var isDataLoaded? I'd appreciate some insight about this. Would this be the right way to do it? Many thanks.

EDIT: Ok, I found out how to use my ControllerAs syntax with this. Code in the child controller would be like this:

function ChildController($controller, $scope, myFactory) {

        $controller('ParentController as vm', {$scope: $scope});

        var vm = $scope.vm;

        var TEMPLATE = 'SCREEN';

        // ************** M A I N **************

        myFactory.getResults(URL, vm);
    }

But I still need to get a way to also recover the regular var/functions inside the parent controller. Any ideas? Can it be done cleanly?

回答1:

I recommend you to implement usual javascript's inheritance mechanism between two classes. At ChildController constructor you will execute ParentController constructor and pass to it injected parameters (without using $controller service). I created simple example (very far from your logic but consider it as patten). I have two controllers: ParentController and ChildController, that inherited from first one. I used only ChildController with $scope, $interval and custom service with name "myservice" (all of them needed only for example). You can see that I used methods and fields from parent and child controllers. Logic of my app is very simple: you can add new items to collection (by means of ParentController) and remove them(by means of ChildController) with logging.

At that case I recommend you use ChildController as ctrl for Data Binding instead of $scope, because it more in line inheritance paradigm (we inherite controllers(ctrl) not $scope).

P.S. If you be going to use inheritance very often I recommend you to use TypeScript - it gives very simple and flexible solution of this problem in c# style.

Controllers.js

function ParentController(myservice, $scope, $interval) {
  this.newOne={};
  this.lastacivity={};
  this.$interval = $interval;
  this.items = myservice();
}

ParentController.prototype.Log = function(item){
  var self = this;
  this.$interval(function(){
      console.log(item);
      self.lastacivity = item;
    }, 100, 1);
}
ParentController.prototype.AddNew = function (Name, Age) {
  var newitem = {
      name: this.newOne.Name,
      age: this.newOne.Age
    }
    this.items.push(newitem);
    this.Log(newitem);
}

function ChildController(myservice, $scope, $interval) {
    //Transfering myservice, $scope, $interval from ChildController to ParentController constructor,
    //also you can pass all what you want: $http, $location etc.
    ParentController.apply(this, [myservice, $scope, $interval]);
}        
ChildController.prototype = Object.create(ParentController.prototype)

//your ChildController's own methods
ChildController.prototype.Remove = function (item) {
    this.items.splice(this.items.indexOf(item), 1);
    this.Log(item);
}

script.js

(function(angular) {
  'use strict';
angular.module('scopeExample', []).factory('myservice', function() {
  var items = [
          {name:"Mike",age:21},
          {name:"Kate",age:22},
          {name:"Tom",age:11}
        ];
  return function(){
      return items;
  }
}).controller('ChildController', ['myservice', '$scope', '$interval', ChildController]);
})(window.angular);

HTML

<body ng-app="scopeExample">
  <div ng-controller="ChildController as ctrl">
    <label>Name</label>
    <input type='text' ng-model='ctrl.newOne.Name'/>
    <label>Age</label>
    <input type='text' ng-model='ctrl.newOne.Age'/>
    <input type='button' value='add' ng-click='ctrl.AddNew()' ng-disabled="!(ctrl.newOne.Name && ctrl.newOne.Age)"/>
    <br>
    <br>
    <table>
      <thead>
        <tr>
          <th>Name</th>
          <th>Age</th>
          <th></th>
        </tr>
      </thead>
      <tbody>
        <tr ng-repeat="item in ctrl.items">
          <td>{{item.name}}</td>
          <td>{{item.age}}</td>
          <td>
            <input type='button' value='X' ng-click='ctrl.Remove(item)'/>
          </td>
        </tr>
      </tbody>
    </table>
    <p>{{ctrl.lastacivity | json}}</p>
</div>
</body>