Jaki jest zalecany sposób rozszerzenia kontrolerów AngularJS?

193

Mam trzy kontrolery, które są dość podobne. Chcę mieć kontroler, który te trzy rozszerzenia rozszerzają i dzielą jego funkcje.

vladexologija
źródło

Odpowiedzi:

302

Może ty nie rozciągają kontroler ale możliwe jest rozszerzenie kontrolera lub dokonać jednego kontrolera wstawką wielu kontrolerów.

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

Po utworzeniu kontrolera nadrzędnego zawarta w nim logika jest również wykonywana. Aby uzyskać więcej informacji, zobacz $ controller (), ale tylko $scopewartość musi zostać przekazana. Wszystkie pozostałe wartości zostaną wstrzyknięte normalnie.

@mwarren , twoje obawy są rozwiązywane automatycznie przez wstrzyknięcie zależności kątowej . Wszystko, czego potrzebujesz, to wstrzyknięcie $ scope, chociaż w razie potrzeby możesz zastąpić inne wstrzyknięte wartości. Weź następujący przykład:

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

Chociaż dokument $ nie jest przekazywany do „simpleController”, gdy jest tworzony przez „complexController”, dokument $ jest dla nas wstrzykiwany.

Enzey
źródło
1
Zdecydowanie najszybsze, najczystsze i najłatwiejsze rozwiązanie! Dzięki!
MK
idealne, niesamowite rozwiązanie!
Kamil Lach
8
Myślę, że nie potrzebujesz tego $.extend(), możesz po prostu zadzwonić$controller('CtrlImpl', {$scope: $scope});
tomraithel
5
@tomraithel nieużywanie angular.extend(lub $.extend) w rzeczywistości oznacza rozszerzenie $scopejedynego, ale jeśli kontroler podstawowy również definiuje niektóre właściwości (np. this.myVar=5), masz dostęp tylko do this.myVarkontrolera rozszerzającego podczas używaniaangular.extend
schellmax
1
To jest świetne! Pamiętaj tylko, aby pamiętać o rozszerzeniu wszystkich potrzebnych funkcji, tj. Miałem coś w stylu: handleSubmitClickktóry zadzwoniłby, handleLoginktóry z kolei miałby loginSuccessa loginFail. Tak więc, moim rozszerzonego kontrolera Ja wtedy miałem do przeciążenia handleSubmitClick, handleLoginoraz loginSucessdla prawidłowego loginSuccessfunkcjonowania ma być używany.
Justin Kruse
52

Do dziedziczenia można użyć standardowych wzorców dziedziczenia JavaScript. Oto demo, które wykorzystuje$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);

Jeśli używasz controllerAsskładni (którą bardzo polecam), jeszcze łatwiej jest użyć klasycznego wzorca dziedziczenia:

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

Innym sposobem może być stworzenie po prostu „abstrakcyjnej” funkcji konstruktora, która będzie twoim podstawowym kontrolerem:

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

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

Wpis na blogu na ten temat

Minko Gechev
źródło
16

Cóż, nie jestem do końca pewien, co chcesz osiągnąć, ale zazwyczaj usługi są właściwą drogą. Możesz także użyć charakterystyki dziedziczenia Scope Angulara, aby współdzielić kod między kontrolerami:

<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
źródło
Dziel te same zmienne i funkcje między kontrolerami, które robią podobne rzeczy (jedna służy do edycji, druga do tworzenia itp.). To zdecydowanie jedno z rozwiązań ...
vladexologija
1
Dziedziczenie $ scope jest zdecydowanie najlepszym sposobem na zrobienie tego, znacznie lepszym niż zaakceptowana odpowiedź.
snez
W końcu wydawało się, że jest to najbardziej kątowa droga. Mam trzy bardzo krótkie ParentController na trzech różnych stronach, które właśnie ustawiają wartość $ scope, którą childController może pobrać. ChildController, który pierwotnie myślałem o rozszerzeniu, zawiera całą logikę kontrolera.
mwarren,
nie $scope.$parent.fx( ) byłby o wiele czystszym sposobem na zrobienie tego, skoro tak naprawdę jest to zdefiniowane?
Akash
15

Nie rozbudowujesz kontrolerów. Jeśli wykonują te same podstawowe funkcje, funkcje te należy przenieść do usługi. Tę usługę można wprowadzić do kontrolerów.

Bart
źródło
3
Dzięki, ale mam 4 funkcje, które już korzystają z usług (zapisywanie, usuwanie itp.) I takie same istnieją we wszystkich trzech kontrolerach. Jeśli przedłużenie nie jest opcją, czy istnieje możliwość „pomieszania”?
vladexologija
4
@ vladexologija Zgadzam się z Bartem tutaj. Myślę, że usługi są różne. Powinieneś spróbować przenieść jak najwięcej logiki ze sterownika (do usług). Więc jeśli masz 3 kontrolery, które muszą wykonywać podobne zadania, usługa wydaje się być właściwym podejściem. Rozszerzanie kontrolerów nie wydaje się naturalne w Angular.
ganaraj
6
@vladexologija Oto przykład tego, co mam na myśli: jsfiddle.net/ERGU3 To bardzo proste, ale wpadniesz na pomysł.
Bart
3
Odpowiedź jest dość bezużyteczna bez żadnych argumentów i dalszych wyjaśnień. Myślę też, że nieco brakuje ci punktu OP. Istnieje już wspólna usługa. Jedyne, co robisz, to bezpośrednio ujawniać tę usługę. Nie wiem czy to dobry pomysł. Twoje podejście kończy się niepowodzeniem, jeśli potrzebny jest dostęp do zakresu. Ale kierując się twoim rozumowaniem, wyraźnie ujawnię zakres jako właściwość zakresu w widoku, aby można go było przekazać jako argument.
lepszy oliver
6
Klasycznym przykładem może być sytuacja, gdy masz w witrynie dwa raporty oparte na formularzu, z których każdy zależy od wielu takich samych danych i każdy korzysta z wielu usług wspólnych. Możesz teoretycznie spróbować umieścić wszystkie swoje oddzielne usługi w jednej dużej usłudze za pomocą dziesiątek wywołań AJAX, a następnie zastosować metody publiczne, takie jak „getEverythingINeedForReport1” i „getEverythingINeedForReport2”, i ustawić wszystko jako jeden gigantyczny obiekt, ale wtedy naprawdę umieszczając w swojej usłudze to, co zasadniczo jest logiką kontrolera. Rozszerzenie kontrolerów absolutnie ma w pewnych okolicznościach zastosowanie.
tobylaroni
10

Kolejne dobre rozwiązanie zaczerpnięte z tego artykułu :

// 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
źródło
1
To zadziałało dla mnie bardzo dobrze. Ma tę zaletę, że ułatwia refaktoryzację z sytuacji, w której zacząłeś od jednego kontrolera, stworzyłeś inny bardzo podobny, a potem chciałeś zrobić kod DRYer. Nie musisz zmieniać kodu, wystarczy go wyciągnąć i gotowe.
Eli Albert
7

Możesz utworzyć usługę i odziedziczyć jej zachowanie w dowolnym kontrolerze, po prostu wstrzykując ją.

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

    var reusableCode = {};

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

    return reusableCode;
});

Następnie w kontrolerze, który chcesz rozszerzyć z powyższej usługi kodu wielokrotnego użytku:

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
źródło
5

Możesz spróbować czegoś takiego (nie testowałem):

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

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

}));
karaxuna
źródło
1
Tak. Nie trzeba przedłużać, wystarczy wywołać w kontekście
TaylorMac,
4

Możesz rozszerzyć o usługi , fabryki lub dostawców . są takie same, ale o różnym stopniu elastyczności.

tutaj przykład przy użyciu fabryki: 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
});

I tutaj możesz również użyć funkcji sklepu:

<div ng-controller="myController">
    <input ng-blur="myFactory.store()" />
</div>
aaafly
źródło
4

Możesz bezpośrednio użyć kontrolera $ („ParentController”, {$ scope: $ scope}) Przykład

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

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    //extend parent controller
    $controller('CtrlImpl', {$scope: $scope});
}]);
Anand Gargate
źródło
1

Napisałem funkcję, aby to zrobić:

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

Możesz użyć tego w następujący sposób:

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

Plusy:

  1. Nie musisz rejestrować kontrolera bazowego.
  2. Żaden ze sterowników nie musi wiedzieć o usługach $ kontroler ani $ injector.
  3. Działa dobrze ze składnią iniekcji tablicy kątowej - co jest niezbędne, jeśli skrypt javascript zostanie zminimalizowany.
  4. Możesz łatwo dodawać dodatkowe usługi do wstrzykiwania do kontrolera podstawowego, bez konieczności pamiętania o dodawaniu ich i przekazywaniu ze wszystkich kontrolerów podrzędnych.

Cons:

  1. Podstawowy kontroler musi być zdefiniowany jako zmienna, która grozi skażeniem zasięgu globalnego. Uniknąłem tego w moim przykładzie użycia, pakując wszystko w anonimową, samoczynnie wykonującą się funkcję, ale to oznacza, że ​​wszystkie kontrolery potomne muszą zostać zadeklarowane w tym samym pliku.
  2. Ten wzorzec działa dobrze w przypadku kontrolerów tworzonych bezpośrednio z html, ale nie jest tak dobry w przypadku kontrolerów tworzonych z kodu za pomocą usługi $ controller (), ponieważ jego zależność od inżektora uniemożliwia bezpośrednie wstrzyknięcie dodatkowych, nie -obsługuje parametry z twojego kodu wywoławczego.
Dan King
źródło
1

Rozważanie rozszerzenia kontrolerów uważam za złą praktykę. Zamiast tego umieść wspólną logikę w usłudze. Rozszerzone obiekty w javascript stają się raczej skomplikowane. Jeśli chcesz użyć dziedziczenia, polecam maszynopis. Mimo to cienkie kontrolery są moim zdaniem lepszym sposobem.

Brecht Billiet
źródło