Qual è il modo consigliato per estendere i controller AngularJS?


193

Ho tre controller che sono abbastanza simili. Voglio avere un controller che questi tre estendono e condividano le sue funzioni.

Risposte:


302

Forse tu non estendere un controller, ma è possibile estendere un controller o fare un singolo controllore un mixin di più controller.

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.
}]);

Quando viene creato il controller principale, viene eseguita anche la logica al suo interno. Vedere $ controller () per ulteriori informazioni, ma è $scopenecessario passare solo il valore. Tutti gli altri valori verranno iniettati normalmente.

@mwarren , la tua preoccupazione viene risolta automaticamente dall'iniezione di dipendenza angolare. Tutto ciò di cui hai bisogno è iniettare $ scope, sebbene tu possa sovrascrivere gli altri valori iniettati se lo desideri. Prendi il seguente esempio:

(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>

Anche se $ document non viene passato in "simpleController" quando viene creato da "complexController", il documento $ viene iniettato per noi.


1
Di gran lunga la soluzione più rapida, pulita e semplice per questo! Grazie!
MK

soluzione perfetta e fantastica!
Kamil Lach,

8
Penso che non ti serva $.extend(), puoi semplicemente chiamare$controller('CtrlImpl', {$scope: $scope});
tomraithel il

5
@tomraithel non usare angular.extend(o $.extend) in realtà significa estendere il $scopesolo, ma se il controller di base definisce anche alcune proprietà (ad es. this.myVar=5), si ha accesso al this.myVarcontroller di estensione solo quando si utilizzaangular.extend
schellmax,

1
Questo è fantastico! Assicurati solo di ricordare di estendere tutte le funzioni necessarie, cioè avevo qualcosa del tipo: handleSubmitClickche chiamerebbe handleLoginche a sua volta aveva un loginSuccesse loginFail. Quindi, a mio controllo esteso Poi ho dovuto sovraccaricare il handleSubmitClick, handleLogine loginSucessper il corretto loginSuccessfunzione da utilizzare.
Justin Kruse,

52

Per l'ereditarietà è possibile utilizzare modelli di ereditarietà JavaScript standard. Ecco una demo che utilizza$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);

Nel caso in cui usi la controllerAssintassi (che consiglio vivamente), è ancora più semplice usare il modello di ereditarietà classico:

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);

Un altro modo potrebbe essere quello di creare una funzione di costruzione "astratta" che sarà il tuo controller di base:

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

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

Post di blog su questo argomento


16

Bene, non sono esattamente sicuro di ciò che vuoi ottenere, ma di solito i Servizi sono la strada da percorrere. È inoltre possibile utilizzare le caratteristiche di eredità Scope di Angular per condividere il codice tra controller:

<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
}

Condividi le stesse variabili e funzioni tra controller che fanno cose simili (una è per l'editing, un'altra per la creazione ecc ...). Questa è sicuramente una delle soluzioni ...
vladexologija

1
L'ereditarietà $ scope è di gran lunga il modo migliore per farlo, molto meglio della risposta accettata.
snez,

Alla fine, questo sembrava il modo più angolare di procedere. Ho tre parentController molto brevi su tre diverse pagine che hanno appena impostato un valore di $ scope che il childController può raccogliere. Il childController, che inizialmente pensavo di estendere, contiene tutta la logica del controller.
Mwarren,

non $scope.$parent.fx( ) sarebbe un modo molto più pulito per farlo, dal momento che è dove è effettivamente definito?
Akash,

15

Non estendi i controller. Se svolgono le stesse funzioni di base, tali funzioni devono essere spostate in un servizio. Tale servizio può essere iniettato nei controller.


3
Grazie, ma ho 4 funzioni che utilizzano già i servizi (salvataggio, eliminazione, ecc.) E lo stesso esiste in tutti e tre i controller. Se l'estensione non è un'opzione, esiste la possibilità di un "mixin"?
vladexologija,

4
@vladexologija Sono d'accordo con Bart qui. Penso che i servizi siano di mixin. Dovresti tentare di spostare la maggior parte della logica fuori dal controller (nei servizi). Quindi, se hai 3 controller che devono svolgere attività simili, un servizio sembra essere l'approccio giusto da adottare. L'estensione dei controller non è naturale in angolare.
Ganaraj,

6
@vladexologija Ecco un esempio di cosa intendo: jsfiddle.net/ERGU3 È molto semplice ma avrai l'idea.
Bart,

3
La risposta è piuttosto inutile senza argomenti e ulteriori spiegazioni. Penso anche che manchi un po 'il punto del PO. Esiste già un servizio condiviso. L'unica cosa che fai è esporre direttamente quel servizio. Non so se sia una buona idea. L'approccio fallisce anche se è necessario l'accesso all'ambito. Ma seguendo il tuo ragionamento, esponerei esplicitamente l'ambito come proprietà dell'ambito alla vista in modo che possa essere passato come argomento.
un oliver migliore il

6
Un classico esempio potrebbe essere quando nel tuo sito sono presenti due report basati su form, ognuno dei quali dipende da molti degli stessi dati e ognuno dei quali utilizza molti servizi condivisi. Potresti teoricamente provare a mettere tutti i tuoi servizi separati in un unico grande servizio con dozzine di chiamate AJAX e quindi avere metodi pubblici come 'getEverythingINeedForReport1' e 'getEverythingINeedForReport2', e impostare tutto su un oggetto ambito mammut, ma poi sei mettendo davvero ciò che è essenzialmente la logica del controller nel tuo servizio. L'estensione dei controller ha assolutamente un caso d'uso in alcune circostanze.
Tobylaroni,

10

Ancora un'altra buona soluzione presa da questo articolo :

// 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;
    });
}]);

1
Questo ha funzionato molto bene per me. Ha il vantaggio di un semplice refactoring da una situazione in cui hai iniziato con un controller, ne hai creato un altro molto simile e poi hai voluto creare il codice DRYer. Non è necessario modificare il codice, basta estrarlo ed essere fatto.
Eli Albert,

7

È possibile creare un servizio ed ereditare il suo comportamento in qualsiasi controller semplicemente iniettandolo.

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

    var reusableCode = {};

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

    return reusableCode;
});

Quindi nel controller che si desidera estendere dal servizio reusableCode sopra:

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

    angular.extend($scope, reusableCode);

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

});

DEMO PLUNKER: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview


5

Puoi provare qualcosa del genere (non ho testato):

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

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

}));

1
Sì. Non c'è bisogno di estenderlo, basta invocare il contesto
TaylorMac

4

È possibile estendere con servizi , fabbriche o fornitori . sono uguali ma con diverso grado di flessibilità.

qui un esempio usando factory: 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
});

E qui puoi anche usare la funzione store:

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

4

È possibile utilizzare direttamente $ controller ('ParentController', {$ scope: $ scope}) Esempio

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

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


1

Ho scritto una funzione per fare questo:

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

Puoi usarlo in questo modo:

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.
            }]
        )
    );
}();

Professionisti:

  1. Non è necessario registrare il controller di base.
  2. Nessuno dei controller deve conoscere i servizi $ controller o $ injector.
  3. Funziona bene con la sintassi di iniezione dell'array di Angular, che è essenziale se il tuo javascript verrà minimizzato.
  4. Puoi facilmente aggiungere ulteriori servizi iniettabili al controller di base, senza dover ricordare di aggiungerli e passarli da tutti i controller per bambini.

Contro:

  1. Il controller di base deve essere definito come una variabile, che rischia di inquinare l'ambito globale. Ho evitato questo nel mio esempio di utilizzo avvolgendo tutto in una funzione di auto-esecuzione anonima, ma ciò significa che tutti i controller figlio devono essere dichiarati nello stesso file.
  2. Questo modello funziona bene per i controller che sono istanziati direttamente dal tuo html, ma non è così buono per i controller che crei dal tuo codice tramite il servizio $ controller (), perché la sua dipendenza dall'iniettore ti impedisce di iniettare direttamente extra, non -servizio parametri dal tuo codice chiamante.

1

Considero l'estensione dei controller come una cattiva pratica. Piuttosto metti la tua logica condivisa in un servizio. Gli oggetti estesi in JavaScript tendono a diventare piuttosto complessi. Se vuoi usare l'ereditarietà, consiglierei il dattiloscritto. Tuttavia, i controller sottili sono il modo migliore per andare dal mio punto di vista.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.