Який рекомендований спосіб розширення контролерів 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.
}]);

Коли створюється батьківський контролер, також виконується логіка, що міститься в ньому. Для отримання додаткової інформації про, але тільки $scopeзначення, потрібно передати лише значення $ controller () . Усі інші значення будуть вводитися нормально.

@mwarren , ваша турбота піклується про автоматичне магічне введення кутової залежності. Все, що вам потрібно, це ввести $ range, хоча при бажанні ви можете змінити інші введені значення. Візьмемо такий приклад:

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

Хоча $ документ не передається у "simpleController", коли він створюється "complexController", $ документ вводиться для нас.


1
На сьогоднішній день найшвидше, найчистіше та найпростіше рішення для цього! Дякую!
МК

ідеальне, дивовижне рішення!
Kamil Lach

8
Я думаю, що вам це не потрібно $.extend(), ви можете просто зателефонувати$controller('CtrlImpl', {$scope: $scope});
tomraithel

5
@tomraithel не використовує angular.extend(або $.extend) насправді означає розширення $scopeєдиного, але якщо ваш базовий контролер також визначає деякі властивості (наприклад 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
Успадкування $ range - це найкращий спосіб зробити це набагато краще, ніж прийнята відповідь.
snez

Зрештою, це здавалося найбільш кутним шляхом. У мене є три дуже короткі батьківські контролери на трьох різних сторінках, які просто встановлюють значення діапазону $, яке може підбирати дочірній контролер. ChildController, про який я спочатку думав про розширення, містить всю логіку контролера.
mwarren

це не $scope.$parent.fx( ) буде набагато чистішим способом зробити це, оскільки саме там він фактично визначений?
Акаш

15

Ви не поширюєте контролери. Якщо вони виконують однакові основні функції, то ці функції потрібно перемістити в службу. Ця послуга може бути введена у ваші контролери.


3
Дякую, але у мене є 4 функції, які вже використовують сервіси (збереження, видалення тощо) і однакові існують у всіх трьох контролерах. Якщо розширення не є варіантом, чи існує можливість "змішати"?
урядxologija

4
@vladexologija Я згоден з Баром тут. Я думаю, що послуги - це міксин. Вам слід спробувати перемістити якомога більше логіки з контролера (на послуги). Отже, якщо у вас є три контролери, яким потрібно виконувати подібні завдання, то сервіс, здається, є правильним підходом. Розширення контролерів не відчуває себе природним у кутових.
ganaraj

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

Потім у своєму контролері, який ви хочете поширити з вищевказаної послуги багаторазового коду:

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

Ви можете спробувати щось подібне (не тестували):

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", {$ range: $ range}) Приклад

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. Жоден із контролерів не повинен знати про контролер $ або послуги інжектора $.
  3. Він добре працює з синтаксисом введення масиву кутових - це важливо, якщо ваш javascript буде змінено.
  4. Ви можете легко додати додаткові послуги для ін'єкцій до базового контролера, не пам'ятаючи також про те, щоб додати їх та пропустити через них всі ваші дочірні контролери.

Мінуси:

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

1

Я вважаю розширення контролерів поганою практикою. Швидше введіть вашу спільну логіку в сервіс. Розширені об’єкти в javascript, як правило, стають досить складними. Якщо ви хочете скористатися успадкуванням, я рекомендую вводити машинопис. І все-таки тонкі контролери - це кращий шлях для моєї точки зору.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.