Was ist die empfohlene Möglichkeit, AngularJS-Controller zu erweitern?

193

Ich habe drei Controller, die ziemlich ähnlich sind. Ich möchte einen Controller haben, den diese drei erweitern und seine Funktionen teilen.

vladexologija
quelle

Antworten:

302

Vielleicht erweitern Sie einen Controller nicht, aber es ist möglich, einen Controller zu erweitern oder einen einzelnen Controller zu einer Mischung aus mehreren Controllern zu machen.

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

Wenn der übergeordnete Controller erstellt wird, wird auch die darin enthaltene Logik ausgeführt. Weitere Informationen zu $ ​​controller () finden Sie unter, aber nur der $scopeWert muss übergeben werden. Alle anderen Werte werden normal injiziert.

@mwarren , Ihr Anliegen wird automatisch durch Angular Dependency Injection beseitigt . Alles, was Sie brauchen, ist $ scope zu injizieren, obwohl Sie die anderen injizierten Werte bei Bedarf überschreiben können. Nehmen Sie das folgende Beispiel:

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

Obwohl $ document nicht an 'simpleController' übergeben wird, wenn es von 'complexController' erstellt wird, wird $ document für uns injiziert.

Enzey
quelle
1
Mit Abstand die schnellste, sauberste und einfachste Lösung dafür! Vielen Dank!
MK
perfekte, tolle Lösung!
Kamil Lach
8
Ich denke, das brauchst du nicht $.extend(), du kannst einfach anrufen$controller('CtrlImpl', {$scope: $scope});
Tomraithel
5
@tomraithel nicht verwenden angular.extend(oder $.extend) bedeutet eigentlich das Erweitern des $scopeeinzigen, aber wenn Ihr Basis-Controller auch einige Eigenschaften definiert (z. B. this.myVar=5), haben Sie nur Zugriff auf this.myVarden erweiterten Controller, wenn Sieangular.extend
schellmax
1
Das ist toll! Denken Sie nur daran, alle benötigten Funktionen zu erweitern, dh ich hatte so etwas wie: handleSubmitClickwelche würde anrufen, handleLoginwelche wiederum ein loginSuccessund hatten loginFail. Also, in meinem erweiterten Controller ich dann musste die zu überlasten handleSubmitClick, handleLoginund loginSucessfür die korrekte loginSuccessFunktion zu nutzen.
Justin Kruse
52

Für die Vererbung können Sie Standard-JavaScript-Vererbungsmuster verwenden. Hier ist eine Demo, die verwendet$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);

Wenn Sie die controllerAsSyntax verwenden (die ich sehr empfehle), ist es noch einfacher, das klassische Vererbungsmuster zu verwenden:

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

Eine andere Möglichkeit könnte darin bestehen, nur eine "abstrakte" Konstruktorfunktion zu erstellen, die Ihr Basis-Controller sein wird:

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

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

Blogbeitrag zu diesem Thema

Minko Gechev
quelle
16

Nun, ich bin mir nicht ganz sicher, was Sie erreichen möchten, aber normalerweise sind Services der richtige Weg. Sie können auch die Scope-Vererbungseigenschaften von Angular verwenden, um Code zwischen Controllern freizugeben:

<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
}
Andre Goncalves
quelle
Teilen Sie dieselben Variablen und Funktionen zwischen Controllern, die ähnliche Aufgaben ausführen (eine dient zum Bearbeiten, eine andere zum Erstellen usw.). Dies ist definitiv eine der Lösungen ...
vladexologija
1
Die Vererbung von $ scope ist bei weitem der beste Weg, viel besser als die akzeptierte Antwort.
Snez
Am Ende schien dies der eckigste Weg zu sein. Ich habe drei sehr kurze parentController auf drei verschiedenen Seiten, die nur einen $ scope-Wert festlegen, den der childController abrufen kann. Der childController, an dessen Erweiterung ich ursprünglich gedacht hatte, enthält die gesamte Controller-Logik.
Mwarren
wäre das nicht $scope.$parent.fx( ) viel sauberer, da es dort tatsächlich definiert ist?
Akash
15

Sie erweitern keine Controller. Wenn sie dieselben Grundfunktionen ausführen, müssen diese Funktionen in einen Dienst verschoben werden. Dieser Dienst kann in Ihre Controller eingespeist werden.

Bart
quelle
3
Danke, aber ich habe 4 Funktionen, die bereits Dienste verwenden (Speichern, Löschen usw.) und die in allen drei Controllern gleich sind. Wenn eine Erweiterung keine Option ist, gibt es eine Möglichkeit für ein 'Mixin'?
Vladexologija
4
@vladexologija Ich stimme Bart hier zu. Ich denke, Dienstleistungen sind Mixins. Sie sollten versuchen, so viel Logik wie möglich aus dem Controller (in Dienste) zu verschieben. Wenn Sie also 3 Controller haben, die ähnliche Aufgaben ausführen müssen, scheint ein Service der richtige Ansatz zu sein. Das Erweitern von Controllern fühlt sich in Angular nicht natürlich an.
Ganaraj
6
@vladexologija Hier ist ein Beispiel für das, was ich meine: jsfiddle.net/ERGU3 Es ist sehr einfach, aber Sie werden auf die Idee kommen.
Bart
3
Die Antwort ist ohne Argumente und weitere Erklärungen ziemlich nutzlos. Ich denke auch, dass Sie den Punkt des OP etwas verfehlen. Es gibt bereits einen gemeinsamen Dienst. Das einzige, was Sie tun, ist, diesen Dienst direkt verfügbar zu machen. Ich weiß nicht, ob das eine gute Idee ist. Ihr Ansatz schlägt auch fehl, wenn Zugriff auf den Bereich erforderlich ist. Nach Ihrer Überlegung würde ich den Gültigkeitsbereich jedoch explizit als Eigenschaft des Gültigkeitsbereichs für die Ansicht verfügbar machen, damit er als Argument übergeben werden kann.
Ein besserer Oliver
6
Ein klassisches Beispiel wäre, wenn Sie zwei formulargesteuerte Berichte auf Ihrer Site haben, von denen jeder von vielen gleichen Daten abhängt und von denen jeder viele gemeinsam genutzte Dienste verwendet. Sie könnten theoretisch versuchen, alle Ihre separaten Dienste mit Dutzenden von AJAX-Aufrufen in einem großen Dienst zusammenzufassen und dann öffentliche Methoden wie "getEverythingINeedForReport1" und "getEverythingINeedForReport2" zu verwenden und alles auf ein Mammutbereichsobjekt zu setzen, aber dann sind Sie es Setzen Sie wirklich das, was im Wesentlichen Controller-Logik ist, in Ihren Dienst. Das Erweitern von Controllern hat unter bestimmten Umständen einen Anwendungsfall.
Tobylaroni
10

Noch eine gute Lösung aus diesem Artikel :

// 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
quelle
1
Das hat bei mir sehr gut funktioniert. Es hat den Vorteil eines einfachen Refactorings aus einer Situation heraus, in der Sie mit einem Controller begonnen haben, einen anderen sehr ähnlichen erstellt haben und dann den Code DRYer erstellen wollten. Sie müssen den Code nicht ändern, sondern ziehen ihn einfach heraus und fertig.
Eli Albert
7

Sie können einen Dienst erstellen und sein Verhalten in jedem Controller erben, indem Sie ihn einfach einfügen.

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

    var reusableCode = {};

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

    return reusableCode;
});

Dann in Ihrem Controller, den Sie vom oben genannten reusableCode-Dienst erweitern möchten:

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

Raghavendra
quelle
5

Sie können so etwas ausprobieren (noch nicht getestet):

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

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

}));
Karaxuna
quelle
1
Jep. Keine Notwendigkeit zu erweitern, rufen Sie einfach mit dem Kontext auf
TaylorMac
4

Sie können mit einem Service , Fabriken oder Anbietern erweitern . Sie sind gleich, aber unterschiedlich flexibel.

Hier ein Beispiel mit 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
});

Und hier können Sie auch die Store-Funktion nutzen:

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

Sie können das Beispiel $ controller ('ParentController', {$ scope: $ scope}) direkt verwenden

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

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

Ich habe eine Funktion geschrieben, um dies zu tun:

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

Sie können es so verwenden:

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

Vorteile:

  1. Sie müssen den Basis-Controller nicht registrieren.
  2. Keiner der Controller muss über die $ Controller- oder $ Injector-Dienste Bescheid wissen.
  3. Es funktioniert gut mit der Array-Injection-Syntax von Angular - was wichtig ist, wenn Ihr Javascript minimiert werden soll.
  4. Sie können dem Basis-Controller problemlos zusätzliche injizierbare Dienste hinzufügen, ohne daran denken zu müssen, sie allen untergeordneten Controllern hinzuzufügen und von diesen weiterzuleiten.

Nachteile:

  1. Der Basis-Controller muss als Variable definiert werden, wodurch die Gefahr besteht, dass der globale Bereich verschmutzt wird. Ich habe dies in meinem Verwendungsbeispiel vermieden, indem ich alles in eine anonyme selbstausführende Funktion eingeschlossen habe. Dies bedeutet jedoch, dass alle untergeordneten Controller in derselben Datei deklariert werden müssen.
  2. Dieses Muster eignet sich gut für Controller, die direkt von Ihrem HTML-Code instanziiert werden, ist jedoch nicht so gut für Controller, die Sie aus Ihrem Code über den Dienst $ controller () erstellen, da die Abhängigkeit vom Injektor verhindert, dass Sie zusätzliche, nicht direkt injizieren -Service-Parameter aus Ihrem Aufrufcode.
Dan King
quelle
1

Ich betrachte die Erweiterung von Controllern als schlechte Praxis. Stellen Sie Ihre gemeinsame Logik lieber in einen Dienst. Erweiterte Objekte in Javascript werden in der Regel recht komplex. Wenn Sie die Vererbung verwenden möchten, würde ich Typoskript empfehlen. Trotzdem sind Thin Controller aus meiner Sicht der bessere Weg.

Brecht Billiet
quelle