Jak mogę dodać małe funkcje narzędziowe do mojej aplikacji AngularJS?

146

Chciałbym dodać kilka funkcji narzędziowych do mojej aplikacji AngularJS. Na przykład:

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

Czy najlepiej to zrobić, aby dodać je jako usługę? Z tego, co przeczytałem, mogę to zrobić, ale chciałbym użyć ich na swoich stronach HTML, więc czy nadal jest to możliwe, jeśli są w usłudze? Na przykład mogę użyć następującego:

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

Czy ktoś może mi podać przykład, jak mógłbym je dodać. Powinienem stworzyć usługę, czy jest na to inny sposób. Co najważniejsze, chciałbym, aby te funkcje narzędziowe znajdowały się w pliku, a nie w połączeniu z inną częścią głównej konfiguracji.

Rozumiem, że istnieje kilka rozwiązań, ale żadne z nich nie jest tak jasne.

Rozwiązanie 1 - zaproponowane przez Urbana

$scope.doSomething = ServiceName.functionName;

Problem w tym, że mam 20 funkcji i dziesięć kontrolerów. Gdybym to zrobił, oznaczałoby to dodanie dużej ilości kodu do każdego kontrolera.

Rozwiązanie 2 - zaproponowane przeze mnie

    var factory = {

        Setup: function ($scope) {

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

Wadą tego jest to, że na początku każdego kontrolera miałbym jedno lub więcej wywołań Instalatora do każdej usługi, która przeszła przez zakres $ scope.

Rozwiązanie 3 - zaproponowane przez Urbana

Dobrze wygląda rozwiązanie zaproponowane przez urbanistę polegające na stworzeniu usługi generycznej. Oto moja główna konfiguracja:

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

Czy powinienem dodać do tego usługę ogólną i jak mogę to zrobić?

Alan2
źródło
sprawdź moją odpowiedź tutaj stackoverflow.com/a/51464584/4251431
Basheer AL-MOMANI

Odpowiedzi:

107

EDYCJA 7/1/15:

Napisałem tę odpowiedź dość dawno temu i od jakiegoś czasu niewiele nadążam za angularem, ale wygląda na to, że ta odpowiedź jest nadal stosunkowo popularna, więc chciałem zwrócić uwagę, że kilka punktów @nicolas sprawia, że ​​poniżej są dobre. Po pierwsze, wstrzyknięcie $ rootScope i dołączenie tam pomocników pozwoli uniknąć konieczności dodawania ich dla każdego kontrolera. Ponadto - zgadzam się, że jeśli to, co dodajesz, powinno być traktowane jako usługi Angular LUB filtry, to należy je zaadaptować do kodu w ten sposób.

Ponadto, od aktualnej wersji 1.4.2, Angular udostępnia API „Provider”, które może być wstrzykiwane do bloków konfiguracyjnych. Zobacz te zasoby, aby uzyskać więcej informacji:

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

Wstrzyknięcie zależności od AngularJS wartości wewnątrz module.config

Nie sądzę, żebym zaktualizował rzeczywiste bloki kodu poniżej, ponieważ obecnie nie używam aktywnie Angular i nie chcę ryzykować nowej odpowiedzi bez poczucia komfortu, że faktycznie dostosowuje się do nowego najlepszego praktyki. Jeśli ktoś inny czuje się na siłach, zrób to.

EDYCJA 2/3/14:

Po przemyśleniu tego i przeczytaniu niektórych innych odpowiedzi wydaje mi się, że wolę odmianę metody przedstawioną przez @Brent Washburne i @Amogh Talpallikar. Zwłaszcza jeśli szukasz narzędzi takich jak isNotString () lub podobnych. Jedną z wyraźnych zalet jest to, że możesz ich ponownie używać poza swoim kodem kątowym i możesz ich używać wewnątrz funkcji konfiguracyjnej (czego nie możesz zrobić z usługami).

Biorąc to pod uwagę, jeśli szukasz ogólnego sposobu ponownego wykorzystania tego, co powinno być usługami, myślę, że stara odpowiedź jest nadal dobra.

To, co bym teraz zrobił, to:

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

Następnie w części możesz użyć:

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

Stara odpowiedź poniżej:

Najlepiej byłoby uwzględnić je jako usługę. Jeśli zamierzasz ponownie użyć ich na wielu kontrolerach, w tym jako usługa, unikniesz konieczności powtarzania kodu.

Jeśli chcesz korzystać z funkcji usług w części html, powinieneś dodać je do zakresu tego kontrolera:

$scope.doSomething = ServiceName.functionName;

Następnie w części możesz użyć:

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

Oto sposób, w jaki możesz to wszystko zorganizować i uniknąć zbytniego kłopotu:

Podziel swój kontroler, usługę i kod / konfigurację na trzy pliki: controllers.js, services.js i app.js. Moduł górnej warstwy to „aplikacja”, która zawiera kontrolery aplikacji i usługi aplikacji jako zależności. Następnie app.controllers i app.services można zadeklarować jako moduły we własnych plikach. Ta struktura organizacyjna pochodzi właśnie z 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;
  });

Następnie w części możesz użyć:

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

W ten sposób dodajesz tylko jedną linię kodu do każdego kontrolera i masz dostęp do dowolnej funkcji usług, gdziekolwiek ten zakres jest dostępny.

urban_raccoons
źródło
Mam może dwadzieścia takich funkcji i chcę ich używać w wielu kontrolerach. Myślałem o tym, ale nie jest to zbyt praktyczne, aby mieć taki kod: $ scope.doSomething = ServiceName.functionName; wewnątrz każdego kontrolera. Zaktualizuję moje pytanie o trochę więcej szczegółów. dzięki
Alan2
tak, to ma sens, jeśli musisz dodać linię dla każdej funkcji w usługach, ale jeśli możesz dodać całą usługę (ze wszystkimi jej funkcjami) do zakresu w jednej linii, myślę, że ma to sens. Nie jestem pewien, jak może działać rozwiązanie 2, o którym wspomniałeś?
urban_raccoons
1
@urban_racoons: Też zacząłem w ten sposób, ale niestety nie możesz wstawić takich usług w config. Chciałem uzyskać dostęp do mojej usługi auth_service wewnątrz przechwytywacza, aby dodać token do nagłówka, ale potem zdałem sobie sprawę, że usługi nie można wstrzyknąć w config. tylko stałe mogą. Myślę, że dodanie funkcji do stałych powinno być lepszym podejściem.
Amogh Talpallikar
1
Pytam tylko, ponieważ nie jestem przede wszystkim facetem od JS, ale czy użycie własnej przestrzeni nazw utworzy kopię lub singleton? Jeśli masz mnóstwo modułów, posiadanie kopii tej samej usługi wydaje się stratą pamięci, zwłaszcza jeśli chcesz użyć tylko jednego pomocnika.
Eric Keyte
3
@EricKeyte Przestrzeń nazw jest literałem obiektu, który jest rodzajem singletonu, który jest dość powszechny w JS. Przepraszamy za opóźnioną odpowiedź :)
urban_raccoons
32

Wracając do tego starego wątku, chciałem to podkreślić

1 °) funkcje narzędziowe można (należy?) Dodać do zakresu głównego za pośrednictwem module.run. W tym celu nie ma potrzeby instalowania określonego kontrolera poziomu głównego.

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

2 °) Jeśli organizujesz swój kod w oddzielne moduły, powinieneś użyć angular usług lub fabryki, a następnie wstrzyknąć je do funkcji przekazanej do bloku uruchamiania, w następujący sposób:

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 °) Rozumiem, że w widokach w większości przypadków te funkcje pomocnicze są potrzebne do zastosowania pewnego rodzaju formatowania do wyświetlanych ciągów. W tym ostatnim przypadku potrzebujesz filtrów kątowych

A jeśli ustrukturyzowałeś niektóre metody pomocnicze niskiego poziomu w usługach kątowych lub fabryce, po prostu wstrzyknij je w konstruktorze filtrów:

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

Twoim zdaniem:

{{ aString | myFilter }}   
nicolas
źródło
Oba rozwiązania dotyczą run-czasu. A co z czasem konfiguracji? Czy nie potrzebujemy tam narzędzi?
Cyril CHAPON
config czas potrzebujesz dostawcy (rodzaj usługi) kasy angular js doc
nicolas
1
Rozwiązanie nr 3 wydaje mi się najlepsze. Po zarejestrowaniu filtra możesz go używać w dowolnym innym miejscu. Użyłem go do formatowania mojej waluty i formatowania daty.
Olantobi
6

Czy dobrze rozumiem, że chcesz po prostu zdefiniować niektóre metody narzędziowe i udostępnić je w szablonach?

Nie musisz ich dodawać do każdego kontrolera. Po prostu zdefiniuj jeden kontroler dla wszystkich metod narzędziowych i dołącz ten kontroler do <html> lub <body> (używając dyrektywy ngController). Wszystkie inne kontrolery, które dołączysz w dowolnym miejscu pod <html> (czyli gdziekolwiek, kropka) lub <body> (gdziekolwiek poza <head>) odziedziczą ten $ scope i będą miały dostęp do tych metod.

Willis Blackburn
źródło
1
jest to zdecydowanie najlepszy sposób, aby to zrobić. Wystarczy mieć kontroler narzędziowy i umieścić go w module div wrapper / container div całego projektu, wszystkie kontrolery w nim odziedziczą: <div class="main-container" ng-controller="UtilController as util">wtedy w dowolnych widokach wnętrza:<button ng-click="util.isNotString(abc)">
Ian J Miller
4

Najłatwiejszym sposobem dodania funkcji narzędziowych jest pozostawienie ich na poziomie globalnym:

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

Następnie najprostszym sposobem dodania funkcji użytkowej (do kontrolera) jest przypisanie jej do $scope, na przykład:

$scope.doSomething = myUtilityFunction;

Następnie możesz to nazwać tak:

{{ doSomething(x) }}

lub tak:

ng-click="doSomething(x)"

EDYTOWAĆ:

Pierwotne pytanie brzmi, czy najlepszym sposobem dodania funkcji narzędzia jest usługa. Mówię nie, jeśli funkcja jest wystarczająco prosta (jakisNotString() przykład dostarczony przez OP).

Zaletą pisania usługi jest zastąpienie jej inną (poprzez wstrzyknięcie) w celu testowania. Doprowadzając do skrajności, czy musisz wstrzyknąć każdą funkcję narzędzia do kontrolera?

Dokumentacja mówi, aby po prostu zdefiniować zachowanie w kontrolerze (np. $scope.double): Http://docs.angularjs.org/guide/controller

Brent Washburne
źródło
Posiadanie funkcji narzędziowych jako usługi pozwala na selektywny dostęp do nich w kontrolerach. Jeśli żaden kontroler ich nie używa, to usługa nie zostanie utworzona.
StuR
Właściwie podoba mi się twoje podejście i włączyłem je do mojej zredagowanej odpowiedzi. Wydaje mi się, że ktoś mógł cię przegłosować z powodu funkcji globalnej (i zanieczyszczenia przestrzeni nazw), ale mam wrażenie, że prawdopodobnie zastosowałbyś podejście podobne do tego, które napisałem, gdybyś myślał, że potrzeba dużo trzymania ręki .
urban_raccoons
Osobiście nie widzę problemu z globalizacją ogólnych, małych funkcji narzędziowych. Zwykle są to rzeczy, których używasz w całym swoim kodzie, więc każdy może się z nimi szybko zapoznać. Postrzegaj je jako małe rozszerzenia języka.
Cornel Masson,
W swojej edycji wspominasz "Dokumentacja mówi, aby po prostu zdefiniować zachowanie w kontrolerze (np. $ Scope.double)". Chcesz powiedzieć, że dokumentacja sugeruje umieszczenie funkcji narzędziowych w kontrolerach?
losmescaleros
@losmescaleros Tak, przeczytaj sekcję „Dodawanie zachowania do obiektu zakresu” w dokumentacji docs.angularjs.org/guide/controller
Brent Washburne
4

Oto prosta, zwarta i łatwa do zrozumienia metoda, której używam.
Najpierw dodaj usługę w swoim 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;

    }]);

Następnie w kontrolerze wstrzyknij obiekt pomocniczy i użyj dowolnej dostępnej funkcji z czymś podobnym do następującego:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

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

}]);
Martin Brousseau
źródło
2
To jasno pokazuje jeden problem, dlaczego czasami nie lubię Angulara: mówienie „dodaj usługę” ... a potem w kodzie tworzenie nowej fabryki (). Z wzorców projektowych to nie to samo - fabryka jest zwykle wykorzystywana do produkcji nowych obiektów, a usługa służy, no cóż, do „obsługi” jakiejś funkcjonalności lub zasobu. W takich chwilach chcę powiedzieć „WT *, Angular”.
JustAMartin
1

Możesz również skorzystać z usługi stałej jako takiej. Zdefiniowanie funkcji poza stałym wywołaniem umożliwia również jej rekurencję.

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
źródło
0

Dlaczego nie skorzystać z dziedziczenia kontrolera, wszystkie metody / właściwości zdefiniowane w zakresie HeaderCtrl są dostępne w kontrolerze wewnątrz ng-view. $ scope.servHelper jest dostępny we wszystkich twoich kontrolerach.

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


<div ng-controller="HeaderCtrl">
  <div ng-view=""></div>
</div>
Kie
źródło