Как я могу добавить несколько небольших служебных функций в мое приложение 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 - Предложено Urban

$scope.doSomething = ServiceName.functionName;

Проблема в том, что у меня 20 функций и десять контроллеров. Если бы я сделал это, это означало бы добавление большого количества кода для каждого контроллера.

Решение 2 - предложено мной

    var factory = {

        Setup: function ($scope) {

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

Недостатком этого является то, что при запуске каждого контроллера у меня будет один или несколько из этих вызовов установки для каждой службы, которая прошла область $.

Решение 3 - Предложено Urban

Городское решение по созданию универсального сервиса выглядит хорошо. Вот моя основная установка:

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

Должен ли я добавить общий сервис к этому и как я могу это сделать?

alan2
источник
проверьте мой ответ здесь stackoverflow.com/a/51464584/4251431
Башир АЛЬ-МОМАНИ

Ответы:

107

РЕДАКТИРОВАТЬ 7/1/15:

Я написал этот ответ довольно давно и не слишком долго следил за углом, но кажется, что этот ответ все еще относительно популярен, поэтому я хотел бы отметить, что пара моментов @nicolas делает ниже хороши. Например, добавление $ rootScope и добавление туда помощников избавит вас от необходимости добавлять их для каждого контроллера. Кроме того - я согласен с тем, что если то, что вы добавляете, следует рассматривать как фильтры ИЛИ Angular, они должны быть включены в код таким образом.

Кроме того, начиная с текущей версии 1.4.2, Angular предоставляет API «Провайдер», который разрешено вставлять в блоки конфигурации. Смотрите эти ресурсы для получения дополнительной информации:

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

Внедрение зависимости AngularJS в значение внутри module.config

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

РЕДАКТИРОВАТЬ 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>

Вот способ, которым вы могли бы сохранить все это организованным и без лишних хлопот:

Разделите ваш контроллер, сервис и код маршрутизации / config на три файла: controllers.js, services.js и app.js. Модуль верхнего уровня - это «приложение», в котором зависимости app.controllers и app.services являются зависимостями. Затем app.controllers и app.services могут быть объявлены как модули в своих собственных файлах. Эта организационная структура только что взята из Angular Seed :

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>

Таким образом, вы добавляете только одну строку кода к каждому контроллеру и можете получить доступ к любой из функций служб, где бы ни была доступна эта область.

urban_raccoons
источник
У меня может быть двадцать из этих функций, и я хочу использовать их в нескольких контроллерах. Я думал об этом, но не очень удобно иметь такой код: $ scope.doSomething = ServiceName.functionName; внутри каждого контроллера. Я обновлю свой вопрос с более подробной информацией. спасибо
Alan2
да, это имеет смысл, если вам нужно добавить строку для каждой функции в сервисах, но если вы можете добавить весь сервис (со всеми его функциями) в область в одну строку, я думаю, что это имеет смысл. Мне не очень понятно, как может работать упомянутое вами решение 2?
urban_raccoons
1
@urban_racoons: я тоже так начал, но, к сожалению, вы не можете внедрить такие сервисы в конфиге. Я хотел получить доступ к своему auth_service внутри перехватчика, чтобы добавить токен в заголовок, но потом я понял, что сервис не может быть введен в config. могут только константы. Я думаю, что добавление функций к константам должно быть лучшим подходом.
Амог Талпалликар
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 документ
Николас
1
Решение № 3 кажется мне лучшим. Как только фильтр зарегистрирован, вы можете использовать его где угодно. Я использовал его для форматирования своей валюты и форматирования даты.
Олантоби
6

Правильно ли я понимаю, что вы просто хотите определить некоторые служебные методы и сделать их доступными в шаблонах?

Вам не нужно добавлять их в каждый контроллер. Просто определите один контроллер для всех служебных методов и подключите этот контроллер к <html> или <body> (используя директиву ngController). Любые другие контроллеры, которые вы присоединяете в любом месте под <html> (имеется в виду где угодно, точка) или <body> (где угодно, кроме <head>), будут наследовать этот $ scope и иметь доступ к этим методам.

Уиллис Блэкберн
источник
1
это определенно лучший способ сделать это. Просто имейте служебный контроллер и поместите его в div-оболочку / контейнер всего проекта, все контроллеры внутри будут наследовать: <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

Брент Уошберн
источник
Наличие служебных функций в качестве службы позволяет вам выборочно обращаться к ним в ваших контроллерах ... если их не используют никакие контроллеры, то служба не будет создана.
StuR
Мне действительно нравится ваш подход и я включил его в отредактированный ответ. У меня есть ощущение, что кто-то может отказать вам в голосовании из-за глобальной функции (и загрязнения пространства имен), но у меня есть ощущение, что вы, вероятно, включили бы подход, аналогичный тому, который я выписал, если бы вы думали, что необходимо много ручных операций. ,
urban_raccoons
Лично я не вижу проблемы в том, чтобы сделать общие, маленькие, вспомогательные функции глобальными. Обычно это вещи, которые вы используете по всей своей кодовой базе, поэтому любой может ознакомиться с ними довольно быстро. Рассматривайте их как небольшие расширения языка.
Корнел Массон
В вашем редакторе вы упоминаете «Документация говорит просто определить поведение в контроллере (например, $ scope.double)». Вы говорите, что документация предлагает поместить служебные функции в контроллеры?
losmescaleros
@losmescaleros Да, прочитайте раздел «Добавление поведения к объекту области» в документации docs.angularjs.org/guide/controller
Брент Уошборн,
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 );
    })
;
b.kelley
источник
0

Почему бы не использовать наследование контроллера, все методы / свойства, определенные в области действия HeaderCtrl, доступны в контроллере внутри ng-view. $ scope.servHelper доступен во всех ваших контроллерах.

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


<div ng-controller="HeaderCtrl">
  <div ng-view=""></div>
</div>
кие
источник