Un contrôleur AngularJS peut-il hériter d'un autre contrôleur du même module?

198

Au sein d'un module, un contrôleur peut hériter des propriétés d'un contrôleur externe:

var app = angular.module('angularjs-starter', []);

var ParentCtrl = function ($scope, $location) {
};

app.controller('ChildCtrl', function($scope, $injector) {
  $injector.invoke(ParentCtrl, this, {$scope: $scope});
});

Exemple via: Lien mort : http://blog.omkarpatil.com/2013/02/controller-inheritance-in-angularjs.html

Un contrôleur à l'intérieur d'un module peut-il également hériter d'un frère?

var app = angular.module('angularjs-starter', []);

app.controller('ParentCtrl ', function($scope) {
  //I'm the sibling, but want to act as parent
});

app.controller('ChildCtrl', function($scope, $injector) {
  $injector.invoke(ParentCtrl, this, {$scope: $scope}); //This does not work
});

Le second code ne fonctionne pas car $injector.invokenécessite une fonction comme premier paramètre et ne trouve pas la référence à ParentCtrl.

Federico Elles
la source
Cela devrait aider: stackoverflow.com/questions/16828287/…
Bart
2
à part: cela ne ressemble pas à un héritage, mais plutôt à des méthodes de partage ou à une injection. Peut-être juste de la sémantique.
alockwood05
Le lien pour l'exemple n'est plus valide.
AlexS
Lien Google Cache: webcache.googleusercontent.com/… qui pointe vers ce violon intéressant: jsfiddle.net/mhevery/u6s88/12
Federico Elles

Réponses:

289

Oui, cela peut mais vous devez utiliser le $controllerservice pour instancier le contrôleur à la place: -

var app = angular.module('angularjs-starter', []);

app.controller('ParentCtrl', function($scope) {
  // I'm the sibling, but want to act as parent
});

app.controller('ChildCtrl', function($scope, $controller) {
  $controller('ParentCtrl', {$scope: $scope}); //This works
});
Salman von Abbas
la source
ParentCtrldevrait être un controllerou est-il possible d'utiliser un service?
gontard
@gontard: Dans ce cas, il doit s'agir d'un contrôleur, car $controlleril ne peut utiliser que des contrôleurs enregistrés.
ZeissS
10
C'est une très bonne solution. Je vous remercie. Mais comment pourrais-je le faire si j'utilise la syntaxe Controller As?
À Ka
1
Le violon ci-dessus a été posé comme une question. Il convient de noter que controllerAs affecte simplement le contrôleur à la portée - Vous devriez donc passer $scopeà this(en théorie)
Dan Pantry
4
Cela a fonctionné pour moi, mais j'essaie de le faire d'une manière que j'ai le contrôleur parent et le contrôleur enfant sur la même page. Cela provoque l'exécution de l'opération $ http dans le contrôleur parent deux fois. Lorsque le contrôleur enfant injecte la portée du contrôleur parent, mon tableau $ scope.AllMembers est rempli deux fois lorsque le contrôleur parent le fait fonctionner, puis le contrôleur enfant le fait réexécuter. Y a-t-il un moyen d'empêcher cela?
Ryan Mann
20

Dans le cas où vous utilisez la vmsyntaxe du contrôleur , voici ma solution:

.controller("BaseGenericCtrl", function ($scope) {

    var vm = this;
    vm.reload = reload;
    vm.items = [];

    function reload() {
        // this function will come from child controller scope - RESTDataService.getItemsA
        this.getItems();
    }
})

.controller("ChildCtrl", function ($scope, $controller, RESTDataService) {
    var vm = this;
    vm.getItems = RESTDataService.getItemsA;
    angular.extend(vm, $controller('BaseGenericCtrl', {$scope: $scope}));
})

Malheureusement, vous ne pouvez pas utiliser $controller.call(vm, 'BaseGenericCtrl'...), pour passer le contexte actuel dans la fonction de fermeture (pour reload()), donc une seule solution est d'utiliser la thisfonction héritée à l'intérieur afin de changer dynamiquement le contexte.

IProblemFactory
la source
Ne pourriez-vous pas simplement faire cela à la place? > $ controller ('BaseGenericControl', {vm: vm});
herringtown
vmest juste une variable à l'intérieur du contrôleur, je ne pense pas que Angular puisse l'utiliser comme prévu.
IProblemFactory
8

Je pense que vous devriez utiliser l'usine ou le service pour donner des fonctions ou des données accessibles aux deux contrôleurs.

voici une question similaire ---> Héritage du contrôleur AngularJS

LauroSkr
la source
Oui, c'est une façon, merci. Je suis tombé sur ce message lorsque je cherchais une solution. Je me demandais s'il y avait un moyen de charger la fonction du contrôleur et d'étendre "ceci" avec.
À Ka
Je voudrais avoir une loadingvariable universelle pour que lorsque les données se chargent, je fasse toujours la même chose, je ne pense pas que les usines puissent le faire. Mon contrôleur parent peut avoir une variable de chargement mais l'usine ne peut pas la manipuler ... non?!
PixMach
7

En réponse au problème soulevé dans cette réponse par gmontague , j'ai trouvé une méthode pour hériter d'un contrôleur en utilisant $ controller (), et toujours utiliser la syntaxe du contrôleur "as".

Tout d'abord, utilisez la syntaxe "as" lorsque vous héritez de l'appel de $ controller ():

    app.controller('ParentCtrl', function(etc...) {
        this.foo = 'bar';
    });
    app.controller('ChildCtrl', function($scope, $controller, etc...) {
        var ctrl = $controller('ParentCtrl as parent', {etc: etc, ...});
        angular.extend(this, ctrl);

    });

Ensuite, dans le modèle HTML, si la propriété est définie par le parent, utilisez-la parent.pour récupérer les propriétés héritées du parent; s'il est défini par l'enfant, utilisez-le child.pour le récupérer.

    <div ng-controller="ChildCtrl as child">{{ parent.foo }}</div>
gm2008
la source
5

Eh bien, je l'ai fait d'une autre manière. Dans mon cas, je voulais une fonction qui applique les mêmes fonctions et propriétés dans d'autres contrôleurs. Je l'ai aimé, sauf par paramètres. De cette façon, tous vos ChildCtrls doivent recevoir $ location.

var app = angular.module('angularjs-starter', []);

function BaseCtrl ($scope, $location) {
    $scope.myProp = 'Foo';
    $scope.myMethod = function bar(){ /* do magic */ };
}

app.controller('ChildCtrl', function($scope, $location) {
    BaseCtrl.call(this, $scope, $location);

    // it works
    $scope.myMethod();
});
Fabio Montefuscolo
la source
4

Pour ceux qui se demandent, vous pouvez étendre les contrôleurs de composants de la même manière, en utilisant la méthode dans la réponse acceptée.

Utilisez l'approche suivante:

Composant parent (à partir duquel):

/**
 * Module definition and dependencies
 */
angular.module('App.Parent', [])

/**
 * Component
 */
.component('parent', {
  templateUrl: 'parent.html',
  controller: 'ParentCtrl',
})

/**
 * Controller
 */
.controller('ParentCtrl', function($parentDep) {

  //Get controller
  const $ctrl = this;

  /**
   * On init
   */
  this.$onInit = function() {

    //Do stuff
    this.something = true;
  };
});

Composant enfant (celui qui s'étend):

/**
 * Module definition and dependencies
 */
angular.module('App.Child', [])

/**
 * Component
 */
.component('child', {
  templateUrl: 'child.html',
  controller: 'ChildCtrl',
})

/**
 * Controller
 */
.controller('ChildCtrl', function($controller) {

  //Get controllers
  const $ctrl = this;
  const $base = $controller('ParentCtrl', {});
  //NOTE: no need to pass $parentDep in here, it is resolved automatically
  //if it's a global service/dependency

  //Extend
  angular.extend($ctrl, $base);

  /**
   * On init
   */
  this.$onInit = function() {

    //Call parent init
    $base.$onInit.call(this);

    //Do other stuff
    this.somethingElse = true;
  };
});

L'astuce consiste à utiliser des contrôleurs nommés, au lieu de les définir dans la définition du composant.

Adam Reis
la source
2

Comme mentionné dans la réponse acceptée, vous pouvez "hériter" des modifications d'un contrôleur parent sur $ scope et d'autres services en appelant: $controller('ParentCtrl', {$scope: $scope, etc: etc});dans votre contrôleur enfant.

Cependant , cela échoue si vous êtes habitué à utiliser la syntaxe du contrôleur «as», par exemple dans

<div ng-controller="ChildCtrl as child">{{ child.foo }}</div>

Si a fooété défini dans le contrôleur parent (via this.foo = ...), le contrôleur enfant n'y aura pas accès.

Comme mentionné dans les commentaires, vous pouvez affecter le résultat de $ controller directement à la portée:

var app = angular.module('angularjs-starter', []);
app.controller('ParentCtrl ', function(etc...) {
    this.foo = 'bar';
});
app.controller('ChildCtrl', function($scope, $controller, etc...) {
    var inst = $controller('ParentCtrl', {etc: etc, ...});

    // Perform extensions to inst
    inst.baz = inst.foo + " extended";

    // Attach to the scope
    $scope.child = inst;
});

Remarque: Vous devez ensuite supprimer la partie «en tant que» deng-controller= , car vous spécifiez le nom de l'instance dans le code et non plus le modèle.

gmontague
la source
L'utilisation de la syntaxe «contrôleur en tant que» ne pose aucun problème. Voir ma réponse: stackoverflow.com/a/36549465/2197555
gm2008
2

J'utilisais la syntaxe "Controller as" avec vm = this et voulais hériter d'un contrôleur. J'ai eu des problèmes si mon contrôleur parent avait une fonction qui modifiait une variable.

En utilisant les réponses d'IProblemFactory et de Salman Abbas , j'ai fait ce qui suit pour avoir accès aux variables parents:

(function () {
  'use strict';
  angular
      .module('MyApp',[])
      .controller('AbstractController', AbstractController)
      .controller('ChildController', ChildController);

  function AbstractController(child) {
    var vm = child;
    vm.foo = 0;
    
    vm.addToFoo = function() {
      vm.foo+=1;
    }
  };
  
  function ChildController($controller) {
    var vm = this;
    angular.extend(vm, $controller('AbstractController', {child: vm}));
  };
})();
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="ChildController as childCtrl" layout="column" ng-cloak="" ng-app="MyApp">
  <button type="button" ng-click="childCtrl.addToFoo()">
    add
  </button>
  <span>
      -- {{childCtrl.foo}} --
  </span>
</div>

Dufaux
la source
0

Vous pouvez utiliser un mécanisme d'héritage JavaScript simple. N'oubliez pas non plus de passer un service angulaire nécessaire pour invoquer la méthode .call.

//simple function (js class)
function baseCtrl($http, $scope, $location, $rootScope, $routeParams, $log, $timeout, $window, modalService) {//any serrvices and your 2

   this.id = $routeParams.id;
   $scope.id = this.id;

   this.someFunc = function(){
      $http.get("url?id="+this.id)
      .then(success function(response){
        ....
       } ) 

   }
...
}

angular
        .module('app')
        .controller('childCtrl', childCtrl);

//angular controller function
function childCtrl($http, $scope, $location, $rootScope, $routeParams, $log, $timeout, $window, modalService) {      
   var ctrl = this;
   baseCtrl.call(this, $http, $scope, $location, $rootScope,  $routeParams, $log, $timeout, $window, modalService);

   var idCopy = ctrl.id;
   if($scope.id == ctrl.id){//just for sample
      ctrl.someFunc();
   }
}

//also you can copy prototype of the base controller
childCtrl.prototype = Object.create(baseCtrl.prototype);
trueboroda
la source