Quelle est la méthode recommandée pour étendre les contrôleurs AngularJS?

193

J'ai trois contrôleurs assez similaires. Je veux avoir un contrôleur dont ces trois éléments étendent et partagent ses fonctions.

Vladexologija
la source

Réponses:

302

Peut-être que vous n'étendez pas un contrôleur, mais il est possible d'étendre un contrôleur ou de faire d'un seul contrôleur un mixin de plusieurs contrôleurs.

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    // Initialize the super class and extend it.
    angular.extend(this, $controller('CtrlImpl', {$scope: $scope}));
     Additional extensions to create a mixin.
}]);

Lorsque le contrôleur parent est créé, la logique qu'il contient est également exécutée. Voir $ controller () pour plus d'informations sur mais seule la $scopevaleur doit être transmise. Toutes les autres valeurs seront injectées normalement.

@mwarren , votre préoccupation est prise en charge automatiquement par injection de dépendance angulaire. Tout ce dont vous avez besoin est d'injecter $ scope, bien que vous puissiez remplacer les autres valeurs injectées si vous le souhaitez. Prenons l'exemple suivant:

(function(angular) {

	var module = angular.module('stackoverflow.example',[]);

	module.controller('simpleController', function($scope, $document) {
		this.getOrigin = function() {
			return $document[0].location.origin;
		};
	});

	module.controller('complexController', function($scope, $controller) {
		angular.extend(this, $controller('simpleController', {$scope: $scope}));
	});

})(angular);
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.3.15/angular.js"></script>

<div ng-app="stackoverflow.example">
    <div ng-controller="complexController as C">
        <span><b>Origin from Controller:</b> {{C.getOrigin()}}</span>
    </div>
</div>

Bien que $ document ne soit pas passé dans 'simpleController' lorsqu'il est créé par 'complexController', $ document est injecté pour nous.

Enzey
la source
1
De loin la solution la plus rapide, la plus propre et la plus simple pour cela! Merci!
MK
solution parfaite et géniale!
Kamil Lach
8
Je pense que vous n’avez pas besoin de cela $.extend(), vous pouvez simplement appeler$controller('CtrlImpl', {$scope: $scope});
tomraithel
5
@tomraithel n'utilise pas angular.extend(ou $.extend) signifie en fait étendre le $scopeseul, mais si votre contrôleur de base définit également certaines propriétés (par exemple this.myVar=5), vous n'avez accès qu'à this.myVardans le contrôleur d'extension lorsque vous utilisezangular.extend
schellmax
1
C'est bien! N'oubliez pas d'étendre toutes les fonctions nécessaires, c'est-à-dire que j'avais quelque chose comme: handleSubmitClickqui appellerait handleLoginqui à son tour avait un loginSuccesset loginFail. Ainsi, dans mon contrôleur étendu , je devais alors surcharger le handleSubmitClick, handleLoginet loginSucesspour la bonne loginSuccessfonction à utiliser.
Justin Kruse
52

Pour l'héritage, vous pouvez utiliser des modèles d'héritage JavaScript standard. Voici une démo qui utilise$injector

function Parent($scope) {
  $scope.name = 'Human';
  $scope.clickParent = function() {
    $scope.name = 'Clicked from base controller';
  }    
}

function Child($scope, $injector) {
  $injector.invoke(Parent, this, {$scope: $scope});
  $scope.name = 'Human Child';
  $scope.clickChild = function(){
    $scope.clickParent();
  }       
}

Child.prototype = Object.create(Parent.prototype);

Si vous utilisez la controllerAssyntaxe (que je recommande vivement), il est encore plus facile d'utiliser le modèle d'héritage classique:

function BaseCtrl() {
  this.name = 'foobar';
}
BaseCtrl.prototype.parentMethod = function () {
  //body
};

function ChildCtrl() {
  BaseCtrl.call(this);
  this.name = 'baz';
}
ChildCtrl.prototype = Object.create(BaseCtrl.prototype);
ChildCtrl.prototype.childMethod = function () {
  this.parentMethod();
  //body
};

app.controller('BaseCtrl', BaseCtrl);
app.controller('ChildCtrl', ChildCtrl);

Une autre façon pourrait être de créer simplement une fonction constructeur "abstraite" qui sera votre contrôleur de base:

function BaseController() {
  this.click = function () {
    //some actions here
  };
}

module.controller('ChildCtrl', ['$scope', function ($scope) {
  BaseController.call($scope);
  $scope.anotherClick = function () {
    //other actions
  };
}]);

Article de blog sur ce sujet

Minko Gechev
la source
16

Eh bien, je ne sais pas exactement ce que vous voulez atteindre, mais les services sont généralement la voie à suivre. Vous pouvez également utiliser les caractéristiques d'héritage de portée d'Angular pour partager du code entre les contrôleurs:

<body ng-controller="ParentCtrl">
 <div ng-controller="FirstChildCtrl"></div>
 <div ng-controller="SecondChildCtrl"></div>
</body>

function ParentCtrl($scope) {
 $scope.fx = function() {
   alert("Hello World");
 });
}

function FirstChildCtrl($scope) {
  // $scope.fx() is available here
}

function SecondChildCtrl($scope) {
  // $scope.fx() is available here
}
André Goncalves
la source
Partagez les mêmes variables et fonctions entre les contrôleurs qui font des choses similaires (l'un est pour l'édition, un autre pour la création, etc.). C'est certainement l'une des solutions ...
vladexologija
1
L'héritage de $ scope est de loin le meilleur moyen de le faire, bien mieux que la réponse acceptée.
snez le
En fin de compte, cela semblait la voie la plus anguleuse à suivre. J'ai trois parentsControllers très brefs sur trois pages différentes qui ne font que définir une valeur $ scope que le childController peut récupérer. Le childController, que j'ai initialement pensé à étendre, contient toute la logique du contrôleur.
mwarren
ne serait-il pas $scope.$parent.fx( ) une façon beaucoup plus propre de le faire, puisque c'est là que cela est réellement défini?
Akash le
15

Vous n'étendez pas les contrôleurs. S'ils exécutent les mêmes fonctions de base, ces fonctions doivent être déplacées vers un service. Ce service peut être injecté dans vos contrôleurs.

Bart
la source
3
Merci, mais j'ai 4 fonctions qui utilisent déjà des services (enregistrer, supprimer, etc.) et les mêmes existent dans les trois contrôleurs. Si l'extension n'est pas une option, y a-t-il une possibilité pour un 'mixin'?
vladexologija
4
@vladexologija Je suis d'accord avec Bart ici. Je pense que les services sont mixins. Vous devez essayer de déplacer autant de logique que possible hors du contrôleur (dans les services). Donc, si vous avez 3 contrôleurs qui doivent effectuer des tâches similaires, un service semble être la bonne approche à adopter. L'extension des contrôleurs ne semble pas naturelle dans Angular.
ganaraj
6
@vladexologija Voici un exemple de ce que je veux dire: jsfiddle.net/ERGU3 C'est très basique mais vous aurez l'idée.
Bart
3
La réponse est assez inutile sans aucun argument ni explication supplémentaire. Je pense aussi que vous manquez quelque peu le point du PO. Il existe déjà un service partagé. La seule chose que vous faites est d'exposer directement ce service. Je ne sais pas si c'est une bonne idée. Votre approche échoue également si l'accès à l'étendue est nécessaire. Mais en suivant votre raisonnement, j'exposerais explicitement la portée en tant que propriété de la portée à la vue afin qu'elle puisse être passée en argument.
un meilleur oliver
6
Un exemple classique serait lorsque vous avez deux rapports pilotés par formulaire sur votre site, dont chacun dépend d'un grand nombre des mêmes données, et qui utilisent chacun de nombreux services partagés. Vous pourriez théoriquement essayer de mettre tous vos services séparés dans un seul grand service avec des dizaines d'appels AJAX, puis avoir des méthodes publiques comme 'getEverythingINeedForReport1' et 'getEverythingINeedForReport2', et définir tout cela sur un objet de portée gigantesque, mais alors vous êtes mettre vraiment ce qui est essentiellement la logique du contrôleur à votre service. L'extension des contrôleurs a absolument un cas d'utilisation dans certaines circonstances.
tobylaroni
10

Encore une bonne solution tirée de cet article :

// base controller containing common functions for add/edit controllers
module.controller('Diary.BaseAddEditController', function ($scope, SomeService) {
    $scope.diaryEntry = {};

    $scope.saveDiaryEntry = function () {
        SomeService.SaveDiaryEntry($scope.diaryEntry);
    };

    // add any other shared functionality here.
}])

module.controller('Diary.AddDiaryController', function ($scope, $controller) {
    // instantiate base controller
    $controller('Diary.BaseAddEditController', { $scope: $scope });
}])

module.controller('Diary.EditDiaryController', function ($scope, $routeParams, DiaryService, $controller) {
    // instantiate base controller
    $controller('Diary.BaseAddEditController', { $scope: $scope });

    DiaryService.GetDiaryEntry($routeParams.id).success(function (data) {
        $scope.diaryEntry = data;
    });
}]);
Nikita Koksharov
la source
1
Cela a très bien fonctionné pour moi. Il a l'avantage de faciliter la refactorisation à partir d'une situation où vous avez commencé avec un contrôleur, en avez créé un autre très similaire, puis que vous vouliez créer le code DRYer. Vous n'avez pas besoin de changer le code, il suffit de le retirer et c'est terminé.
Eli Albert
7

Vous pouvez créer un service et hériter de son comportement dans n'importe quel contrôleur simplement en l'injectant.

app.service("reusableCode", function() {

    var reusableCode = {};

    reusableCode.commonMethod = function() {
        alert('Hello, World!');
    };

    return reusableCode;
});

Ensuite, dans votre contrôleur que vous souhaitez étendre à partir du service reusableCode ci-dessus:

app.controller('MainCtrl', function($scope, reusableCode) {

    angular.extend($scope, reusableCode);

    // now you can access all the properties of reusableCode in this $scope
    $scope.commonMethod()

});

DÉMO PLUNKER: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview

Raghavendra
la source
5

Vous pouvez essayer quelque chose comme ça (je n'ai pas testé):

function baseController(callback){
    return function($scope){
        $scope.baseMethod = function(){
            console.log('base method');
        }
        callback.apply(this, arguments);
    }
}

app.controller('childController', baseController(function(){

}));
Karaxuna
la source
1
Ouaip. Pas besoin de prolonger, il suffit d'appeler avec le contexte
TaylorMac
4

Vous pouvez étendre avec des services , des usines ou des fournisseurs . ils sont identiques mais avec un degré de flexibilité différent.

voici un exemple utilisant l'usine: http://jsfiddle.net/aaaflyvw/6KVtj/2/

angular.module('myApp',[])

.factory('myFactory', function() {
    var myFactory = {
        save: function () {
            // saving ...
        },
        store: function () {
            // storing ...
        }
    };
    return myFactory;
})

.controller('myController', function($scope, myFactory) {
    $scope.myFactory = myFactory;
    myFactory.save(); // here you can use the save function
});

Et ici, vous pouvez également utiliser la fonction de stockage:

<div ng-controller="myController">
    <input ng-blur="myFactory.store()" />
</div>
aaafly
la source
4

Vous pouvez utiliser directement $ controller ('ParentController', {$ scope: $ scope}) Exemple

module.controller('Parent', ['$scope', function ($scope) {
    //code
}])

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    //extend parent controller
    $controller('CtrlImpl', {$scope: $scope});
}]);
Anand Gargate
la source
1

J'ai écrit une fonction pour faire ceci:

function extendController(baseController, extension) {
    return [
        '$scope', '$injector',
        function($scope, $injector) {
            $injector.invoke(baseController, this, { $scope: $scope });
            $injector.invoke(extension, this, { $scope: $scope });
        }
    ]
}

Vous pouvez l'utiliser comme ceci:

function() {
    var BaseController = [
        '$scope', '$http', // etc.
        function($scope, $http, // etc.
            $scope.myFunction = function() {
                //
            }

            // etc.
        }
    ];

    app.controller('myController',
        extendController(BaseController,
            ['$scope', '$filter', // etc.
            function($scope, $filter /* etc. */)
                $scope.myOtherFunction = function() {
                    //
                }

                // etc.
            }]
        )
    );
}();

Avantages:

  1. Vous n'avez pas besoin d'enregistrer le contrôleur de base.
  2. Aucun des contrôleurs n'a besoin de connaître les services $ controller ou $ injector.
  3. Cela fonctionne bien avec la syntaxe d'injection de tableau d'angular - ce qui est essentiel si votre javascript va être minifié.
  4. Vous pouvez facilement ajouter des services injectables supplémentaires au contrôleur de base, sans avoir à vous rappeler de les ajouter et de les transmettre à tous vos contrôleurs enfants.

Les inconvénients:

  1. Le contrôleur de base doit être défini comme une variable, ce qui risque de polluer le périmètre global. J'ai évité cela dans mon exemple d'utilisation en enveloppant tout dans une fonction auto-exécutable anonyme, mais cela signifie que tous les contrôleurs enfants doivent être déclarés dans le même fichier.
  2. Ce modèle fonctionne bien pour les contrôleurs qui sont instanciés directement à partir de votre html, mais n'est pas si bon pour les contrôleurs que vous créez à partir de votre code via le service $ controller (), car sa dépendance à l'injecteur vous empêche d'injecter directement du supplément, non -service paramètres de votre code d'appel.
Dan King
la source
1

Je considère l'extension des contrôleurs comme une mauvaise pratique. Mettez plutôt votre logique partagée dans un service. Les objets étendus en javascript ont tendance à devenir plutôt complexes. Si vous souhaitez utiliser l'héritage, je recommanderais dactylographié. Pourtant, les contrôleurs minces sont la meilleure façon d'aller de mon point de vue.

Brecht Billiet
la source