Каков рекомендуемый способ расширения контроллеров AngularJS?

193

У меня есть три контроллера, которые очень похожи. Я хочу иметь контроллер, который расширяет эти три функции и разделяет их функции.

vladexologija
источник

Ответы:

302

Возможно, вы не расширяете контроллер, но возможно расширить контроллер или сделать один контроллер смесью нескольких контроллеров.

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

Когда родительский контроллер создан, логика, содержащаяся в нем, также выполняется. См. $ Controller () для получения дополнительной информации, но $scopeнужно передать только значение. Все остальные значения будут введены нормально.

@mwarren , ваше беспокойство автоматически решается с помощью инъекции угловой зависимости. Все, что вам нужно, это ввести $ scope, хотя при желании вы можете переопределить другие введенные значения. Возьмите следующий пример:

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

Хотя $ document не передается в 'simpleController', когда он создается 'complexController', $ document вводится для нас.

Enzey
источник
1
Безусловно, самое быстрое, чистое и простое решение для этого! Спасибо!
МК
идеальное, отличное решение!
Камил Лах
8
Я думаю, что вам это не нужно $.extend(), вы можете просто позвонить$controller('CtrlImpl', {$scope: $scope});
tomraithel
5
@tomraithel не использовать angular.extend(или $.extend) на самом деле означает расширение $scopeonly, но если ваш базовый контроллер также определяет некоторые свойства (например this.myVar=5), у вас есть доступ только к this.myVarрасширяющему контроллеру при использованииangular.extend
schellmax
1
Это круто! Просто не забудьте расширить все необходимые функции, то есть у меня было что-то вроде: handleSubmitClickкоторый будет вызывать, handleLoginкоторый в свою очередь имеет loginSuccessи loginFail. Таким образом, в моем расширенном контроллере я тогда должен был перегрузить handleSubmitClick, handleLoginи loginSucessдля правильной loginSuccessфункции , которая будет использоваться.
Джастин Крузе
52

Для наследования вы можете использовать стандартные шаблоны наследования JavaScript. Вот демо, которое использует$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);

В случае, если вы используете controllerAsсинтаксис (который я настоятельно рекомендую), еще проще использовать классический шаблон наследования:

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

Другим способом может быть создание просто «абстрактной» функции конструктора, которая будет вашим базовым контроллером:

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

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

Сообщение в блоге на эту тему

Минько Гечев
источник
16

Ну, я не совсем уверен, чего вы хотите достичь, но обычно услуги - это путь. Вы также можете использовать характеристики наследования Scope Angular для совместного использования кода между контроллерами:

<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
}
Андре Гонсалвес
источник
Совместно используйте одни и те же переменные и функции в контроллерах, которые делают похожие вещи (одна для редактирования, другая для создания и т. Д.) Это определенно одно из решений ...
vladexologija
1
Наследование $ scope - безусловно, лучший способ сделать это, намного лучше, чем принятый ответ.
Снеж
В конце концов, это казалось самым сложным путем. У меня есть три очень коротких parentController на трех разных страницах, которые просто устанавливают значение $ scope, которое childController может подобрать. ChildController, который я изначально думал о расширении, содержит всю логику контроллера.
Мваррен
не $scope.$parent.fx( ) будет ли намного более чистый способ сделать это, так как именно здесь это определено?
Акаш
15

Вы не расширяете контроллеры. Если они выполняют те же основные функции, то эти функции необходимо перенести в службу. Эта услуга может быть введена в ваши контроллеры.

Барт
источник
3
Спасибо, но у меня есть 4 функции, которые уже используют службы (сохранение, удаление и т. Д.) И одинаковые существуют во всех трех контроллерах. Если расширение не вариант, есть ли возможность «смешаться»?
Владексология
4
@vladexologija Я согласен с Бартом здесь. Я думаю, что услуги смешаны. Вы должны попытаться переместить как можно больше логики из контроллера (в службы). Таким образом, если у вас есть 3 контроллера, которые должны выполнять аналогичные задачи, то сервис, кажется, является правильным подходом. Расширение контроллеров не кажется естественным в Angular.
Ганарадж
6
@vladexologija Вот пример того, что я имею в виду: jsfiddle.net/ERGU3 Это очень просто, но вы поймете идею.
Барт
3
Ответ довольно бесполезен без каких-либо аргументов и дальнейших объяснений. Я также думаю, что вы несколько упускаете точку зрения ОП. Там уже есть общий сервис. Единственное, что вы делаете, - это непосредственно предоставляете эту услугу. Я не знаю, хорошая ли это идея. Ваш подход также терпит неудачу, если необходим доступ к области. Но, следуя вашим рассуждениям, я бы явно представил область видимости как свойство области видимости, чтобы ее можно было передать в качестве аргумента.
лучший Оливер
6
Классическим примером может служить ситуация, когда на вашем сайте есть два отчета на основе форм, каждый из которых зависит от множества одних и тех же данных, и каждый из которых использует множество общих служб. Теоретически вы можете попытаться поместить все ваши отдельные сервисы в один большой сервис с помощью десятков вызовов AJAX, а затем иметь открытые методы, такие как 'getEverythingINeedForReport1' и 'getEverythingINeedForReport2', и установить все это на один гигантский объект области видимости, но тогда вы действительно вкладывая то, что по сути является логикой контроллера, в ваш сервис. Расширение контроллеров в некоторых случаях имеет смысл.
tobylaroni
10

Еще одно хорошее решение, взятое из этой статьи :

// 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
Это сработало очень хорошо для меня. Он имеет преимущество в простом рефакторинге из ситуации, когда вы начали с одного контроллера, создали другой очень похожий, а затем захотели сделать код DRYer. Вам не нужно менять код, просто вытащите его и все готово.
Эли Альберт
7

Вы можете создать сервис и наследовать его поведение в любом контроллере, просто внедрив его.

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

    var reusableCode = {};

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

    return reusableCode;
});

Затем в вашем контроллере, который вы хотите расширить из вышеупомянутого сервиса reusableCode:

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

    angular.extend($scope, reusableCode);

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

});

ДЕМО ПЛАНКЕР: http://plnkr.co/edit/EQtj6I0X08xprE8D0n5b?p=preview

Raghavendra
источник
5

Вы можете попробовать что-то вроде этого (не проверял):

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

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

}));
karaxuna
источник
1
Ага. Не нужно расширять, просто вызовите с контекстом
TaylorMac
4

Вы можете расширить с помощью услуг , заводов или поставщиков . они одинаковы, но с разной степенью гибкости.

Вот пример использования фабрики: 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
});

И здесь вы можете использовать функцию магазина также:

<div ng-controller="myController">
    <input ng-blur="myFactory.store()" />
</div>
aaafly
источник
4

Вы можете напрямую использовать $ controller ('ParentController', {$ scope: $ scope}). Пример

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

module.controller('CtrlImplAdvanced', ['$scope', '$controller', function ($scope, $controller) {
    //extend parent controller
    $controller('CtrlImpl', {$scope: $scope});
}]);
Ананд Гаргейт
источник
1

Я написал функцию для этого:

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

Вы можете использовать это так:

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

Плюсы:

  1. Вам не нужно регистрировать базовый контроллер.
  2. Ни одному из контроллеров не нужно знать о службах $ controller или $ инжектор.
  3. Он хорошо работает с синтаксисом внедрения массивов angular - что важно, если ваш javascript будет сокращен.
  4. Вы можете легко добавить дополнительные инъекционные сервисы к базовому контроллеру, не забывая также добавлять их и передавать через все дочерние контроллеры.

Минусы:

  1. Базовый контроллер должен быть определен как переменная, которая рискует загрязнить глобальную область. Я избегал этого в своем примере использования, оборачивая все в анонимную самоисполняющуюся функцию, но это означает, что все дочерние контроллеры должны быть объявлены в одном файле.
  2. Этот шаблон хорошо работает для контроллеров, которые создаются непосредственно из вашего html, но не так хорош для контроллеров, которые вы создаете из своего кода через службу $ controller (), потому что его зависимость от инжектора не позволяет вам напрямую вводить дополнительные, не -сервисные параметры из вашего вызывающего кода.
Дэн Кинг
источник
1

Я считаю расширение контроллеров плохой практикой. Скорее поместите свою общую логику в сервис. Расширенные объекты в JavaScript, как правило, становятся довольно сложными. Если вы хотите использовать наследование, я бы порекомендовал машинопись. Тем не менее, тонкие контроллеры - лучший способ пойти по моему мнению.

Брехт Биллиет
источник