Як я можу додати деякі невеликі утилітні функції до свого додатка AngularJS?


146

Я хотів би додати деякі функції утиліти до свого додатка AngularJS. Наприклад:

$scope.isNotString = function (str) {
    return (typeof str !== "string");
}

Це найкращий спосіб зробити це, щоб додати їх як послугу? З того, що я прочитав, я можу це зробити, але тоді я хотів би використовувати їх на своїх HTML-сторінках, так чи це все-таки можливо, якщо вони є службою? Наприклад, чи можу я використовувати наступне:

 <button data-ng-click="doSomething()"
         data-ng-disabled="isNotString(abc)">Do Something
 </button>

Чи може хтось надати мені приклад того, як я міг би їх додати. Чи потрібно створити послугу чи є якийсь інший спосіб зробити це. Найголовніше, що я хотів би, щоб ці утилітні функції були у файлі, а не поєднувалися з іншою частиною основної настройки.

Я розумію, що існує кілька рішень, але жодне з них не є настільки зрозумілим.

Рішення 1 - Запропоноване Урбаном

$scope.doSomething = ServiceName.functionName;

Проблема тут у тому, що у мене є 20 функцій і десять контролерів. Якщо я це зробив, це означало б додати багато коду до кожного контролера.

Рішення 2 - Запропонований мною

    var factory = {

        Setup: function ($scope) {

            $scope.isNotString = function (str) {
                return (typeof str !== "string");
            }

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

Рішення 3 - Запропоноване Урбаном

Пропоноване міським рішенням створення загальної послуги виглядає добре. Ось моя основна настройка:

var app = angular
    .module('app', ['ngAnimate', 'ui.router', 'admin', 'home', 'questions', 'ngResource', 'LocalStorageModule'])
    .config(['$locationProvider', '$sceProvider', '$stateProvider',
        function ($locationProvider, $sceProvider, $stateProvider) {

            $sceProvider.enabled(false);
            $locationProvider.html5Mode(true);

Чи варто додати до цього загальну послугу і як я можу це зробити?


перевірити мою відповідь тут stackoverflow.com/a/51464584/4251431
Башир Аль-MOMANI

Відповіді:


107

РЕДАКЦІЯ 7/1/15:

Цю відповідь я написав досить давно, і якийсь час не тримався багато з кутовим, але здається, що ця відповідь все ще відносно популярний, тому я хотів би зазначити, що пара моменту @nicolas робить нижче хороші. Для одного, введення $ rootScope та приєднання помічників утримає вас від необхідності додавати їх до кожного контролера. Крім того - я погоджуюся, що якщо те, що ви додаєте, слід розглядати як фільтри Angular Services АБО фільтри, вони повинні бути прийняті в код таким чином.

Також, з поточної версії 1.4.2, Angular розкриває API "Провайдера", який дозволено вводити в конфігураційні блоки. Докладніше про ці ресурси:

https://docs.angularjs.org/guide/module#module-loading-dependitions

Інжекційна залежність кутової JS значення всередині модуля.config

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

EDIT 2/3/14:

Поміркувавши над цим і прочитавши деякі інші відповіді, я насправді вважаю, що віддаю перевагу варіанту способу, яку вигадали @Brent Washburne та @Amogh Talpallikar. Особливо, якщо ви шукаєте такі утиліти, як isNotString () або подібні. Однією з чітких переваг тут є те, що ви можете повторно використовувати їх поза своїм кутовим кодом, і ви можете використовувати їх всередині своєї функції конфігурації (що ви не можете зробити з сервісами).

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

Що я зараз би робив:

app.js:

var MyNamespace = MyNamespace || {};

 MyNamespace.helpers = {
   isNotString: function(str) {
     return (typeof str !== "string");
   }
 };

 angular.module('app', ['app.controllers', 'app.services']).                             
   config(['$routeProvider', function($routeProvider) {
     // Routing stuff here...
   }]);

controller.js:

angular.module('app.controllers', []).                                                                                                                                                                                  
  controller('firstCtrl', ['$scope', function($scope) {
    $scope.helpers = MyNamespace.helpers;
  });

Тоді у своїй частковій частині ви можете використовувати:

<button data-ng-click="console.log(helpers.isNotString('this is a string'))">Log String Test</button>

Стара відповідь нижче:

Можливо, найкраще включити їх як послугу. Якщо ви збираєтесь повторно використовувати їх через декілька контролерів, включення їх як послуги не дозволить повторити код.

Якщо ви хочете використовувати сервісні функції у вашому html частково, то вам слід додати їх до сфери використання цього контролера:

$scope.doSomething = ServiceName.functionName;

Тоді у своїй частковій частині ви можете використовувати:

<button data-ng-click="doSomething()">Do Something</button>

Ось як ви можете зберегти це все організовано і без зайвих клопотів:

Розділіть свій контролер, сервіс та код / ​​конфігурацію маршрутизації на три файли: controllers.js, services.js та app.js. Модуль верхнього шару - це "додаток", у якому залежно від залежностей залежать програми app.controllers та app.services. Тоді app.controllers та app.services можуть бути оголошені модулями у власних файлах. Ця організаційна структура щойно взята з кутового насіння :

app.js:

 angular.module('app', ['app.controllers', 'app.services']).                             
   config(['$routeProvider', function($routeProvider) {
     // Routing stuff here...
   }]);  

services.js:

 /* Generic Services */                                                                                                                                                                                                    
 angular.module('app.services', [])                                                                                                                                                                        
   .factory("genericServices", function() {                                                                                                                                                   
     return {                                                                                                                                                                                                              
       doSomething: function() {   
         //Do something here
       },
       doSomethingElse: function() {
         //Do something else here
       }
    });

controller.js:

angular.module('app.controllers', []).                                                                                                                                                                                  
  controller('firstCtrl', ['$scope', 'genericServices', function($scope, genericServices) {
    $scope.genericServices = genericServices;
  });

Тоді у своїй частковій частині ви можете використовувати:

<button data-ng-click="genericServices.doSomething()">Do Something</button>
<button data-ng-click="genericServices.doSomethingElse()">Do Something Else</button>

Таким чином, ви додаєте лише один рядок коду до кожного контролера і отримуєте доступ до будь-якої з функцій сервісу, де б доступний цей діапазон.


У мене можливо двадцять цих функцій, і я хочу використовувати їх у кількох контролерах. Я подумав над цим, але не так практично, щоб мати код на зразок: $ range.doSomething = ServiceName.functionName; всередині кожного контролера. Я оновлю своє запитання трохи більше деталей. спасибі
Alan2

так, це має сенс, якщо вам потрібно додати рядок для кожної функції в сервісах, але якщо ви можете додати всю службу (з усіма її функціями) до області в одному рядку, я думаю, це має сенс. Мені не дуже зрозуміло, як може працювати рішення 2, яке ви згадали?
urban_raccoons

1
@urban_racoons: Я також почав так, але, на жаль, ви не можете ввести такі конфігурації в конфігурацію. Я хотів отримати доступ до своєї auth_service всередині перехоплювача, щоб додати маркер до заголовка, але тоді я зрозумів, що сервіс не може бути введений у config. тільки константи можуть. Я думаю, що краще додавати функції до констант.
Amogh Talpallikar

1
Я просто запитую, тому що я в першу чергу не хлопець JS, але чи використовуючи ваш власний простір імен, витворите копію чи синглтон? Якщо у вас є тонна модулів, здається, що марно пам'ять мати копії однієї служби, особливо якщо ви хочете скористатися лише одним помічником.
Ерік Кейт

3
@EricKeyte Простір імен - це об'єкт буквально, який є своєрідним синглетом, що є досить поширеним у JS. Вибачте за затримку відповіді :)
urban_raccoons

32

Наближаючись до цієї старої нитки, я хотів це наголосити

1 °) корисні функції можуть (повинні?) Бути додані до кореневого екрану через module.run. Для цього не потрібно створювати конкретний контролер рівня кореня.

angular.module('myApp').run(function($rootScope){
  $rootScope.isNotString = function(str) {
   return (typeof str !== "string");
  }
});

2 °) Якщо ви організовуєте свій код в окремі модулі, вам слід скористатись кутовими службами або фабрикою, а потім ввести їх у функцію, передану до блоку запуску, таким чином:

angular.module('myApp').factory('myHelperMethods', function(){
  return {
    isNotString: function(str) {
      return (typeof str !== 'string');
    }
  }
});

angular.module('myApp').run(function($rootScope, myHelperMethods){ 
  $rootScope.helpers = myHelperMethods;
});

3 °) Я розумію, що в представленнях для більшості випадків вам потрібні ці допоміжні функції, щоб застосувати якесь форматування до відображуваних рядків. У цьому останньому випадку вам потрібно використовувати кутові фільтри

І якщо ви структурували кілька помічників низького рівня в кутових службах або на заводі, просто введіть їх у свій конструктор фільтрів:

angular.module('myApp').filter('myFilter', function(myHelperMethods){ 
  return function(aString){
    if (myHelperMethods.isNotString(aString)){
      return 
    }
    else{
      // something else 
    }
  }
);

І на ваш погляд:

{{ aString | myFilter }}   

Обидва рішення стосуються runчасу. А як щодо часу налаштування? Не потрібні нам комунальні послуги там?
Кирило ШАПОН

час конфігурації, який вам потрібен, провайдер (вид послуги) замовити кутовий js doc
nicolas

1
Рішення №3 мені здається найкращим. Після того, як фільтр зареєстрований, ви можете використовувати його де завгодно. Я використовував це для мого форматування валюти та форматування дати.
Олантабі

6

Чи правильно я розумію, що ви просто хочете визначити деякі корисні методи та зробити їх доступними у шаблонах?

Не потрібно їх додавати до кожного контролера. Просто визначте єдиний контролер для всіх методів утиліти і додайте цей контролер до <html> або <body> (використовуючи директиву ngController). Будь-які інші контролери, які ви приєднаєте де-небудь під <html> (мається на увазі де-небудь, період) або <body> (де завгодно, але <head>), успадкують цю область $ і матимуть доступ до цих методів.


1
це, безумовно, найкращий спосіб зробити це. Просто встановіть контролер утиліти і помістіть його в дірку обгортки / контейнера всього проекту, всі контролери в межах успадковуватимуться: <div class="main-container" ng-controller="UtilController as util">тоді в будь-яких видах інтер'єру:<button ng-click="util.isNotString(abc)">
Ian J Miller

4

Найпростіший спосіб додавання корисних функцій - це залишити їх на глобальному рівні:

function myUtilityFunction(x) { return "do something with "+x; }

Тоді найпростіший спосіб додати функцію утиліти (до контролера) - це призначити її $scopeтак:

$scope.doSomething = myUtilityFunction;

Тоді ви можете назвати це так:

{{ doSomething(x) }}

або так:

ng-click="doSomething(x)"

Редагувати:

Оригінальне питання полягає в тому, чи найкращий спосіб додати функцію утиліти через сервіс. Я кажу "ні", якщо функція досить проста (як isNotString()приклад, наданий ОП).

Перевага написання послуги полягає в тому, щоб замінити її на іншу (через ін'єкцію) з метою тестування. Чи доведеться до крайності, чи потрібно вводити кожну функцію утиліти у свій контролер?

Документація говорить про те, щоб просто визначити поведінку в контролері (як $scope.double): http://docs.angularjs.org/guide/controller


Наявність утилітних функцій як сервісу дозволяє вибірково отримувати доступ до них у своїх контролерах .. якщо жодні контролери не користуються ними, то служба не буде створена миттєво.
СвітР

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

Особисто я не бачу проблем із тим, щоб зробити загальні, маленькі, корисні функції глобальними. Зазвичай це речі, якими ви користуєтесь у всій своїй кодовій базі, тому хтось досить швидко ознайомиться з ними. Розглядайте їх як невеликі розширення до мови.
Корнел Массон

У своєму редагуванні ви згадуєте "Документація говорить, щоб просто визначити поведінку в контролері (як, наприклад, $ range.double)". Ви хочете сказати, що документація пропонує включити функції корисності в контролери?
losmescaleros

@losmescaleros Так, прочитайте розділ "Додавання поведінки до об'єкта" в документації docs.angularjs.org/guide/controller
Brent Washburne

4

Ось простий, компактний і легкий для розуміння метод, який я використовую.
Спочатку додайте послугу у свій js.

app.factory('Helpers', [ function() {
      // Helper service body

        var o = {
        Helpers: []

        };

        // Dummy function with parameter being passed
        o.getFooBar = function(para) {

            var valueIneed = para + " " + "World!";

            return valueIneed;

          };

        // Other helper functions can be added here ...

        // And we return the helper object ...
        return o;

    }]);

Потім введіть у свій контролер об’єкт помічника та використовуйте будь-яку доступну функцію з чимось на зразок наступного:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

    $scope.sayIt = Helpers.getFooBar("Hello");
    console.log($scope.sayIt);

}]);

2
Це чітко показує одне питання, чому мені іноді не подобається Angular: кажучи "додати послугу" ... а потім у коді, створюючи новий завод (). З дизайнерських моделей вони не є одними і тими ж речами - фабрика зазвичай використовується для виробництва нових об’єктів, а сервіс - це, ну, для "обслуговування" певної функціональності чи ресурсу. У такі моменти хочеться сказати "WT *, Angular".
JustAMartin

1

Ви також можете використовувати постійну послугу як таку. Визначення функції поза постійним викликом дозволяє також бути рекурсивним.

function doSomething( a, b ) {
    return a + b;
};

angular.module('moduleName',[])
    // Define
    .constant('$doSomething', doSomething)
    // Usage
    .controller( 'SomeController', function( $doSomething ) {
        $scope.added = $doSomething( 100, 200 );
    })
;

0

Чому б не використовувати спадщину контролера, усі методи / властивості, визначені в області HeaderCtrl, доступні в контролері всередині ng-view. $ range.servHelper доступний у всіх ваших контролерах.

    angular.module('fnetApp').controller('HeaderCtrl', function ($scope, MyHelperService) {
      $scope.servHelper = MyHelperService;
    });


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