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


193

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

Ответы:


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 вводится для нас.


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


5

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

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

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

}));

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>

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, как правило, становятся довольно сложными. Если вы хотите использовать наследование, я бы порекомендовал машинопись. Тем не менее, тонкие контроллеры - лучший способ пойти по моему мнению.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.