Come posso aggiungere alcune piccole funzioni di utilità alla mia applicazione AngularJS?


146

Vorrei aggiungere alcune funzioni di utilità alla mia applicazione AngularJS. Per esempio:

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

È il modo migliore per farlo per aggiungerli come servizio? Da quello che ho letto, posso farlo, ma poi vorrei usarli nelle mie pagine HTML, quindi è ancora possibile se sono in un servizio? Ad esempio, posso usare quanto segue:

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

Qualcuno può darmi un esempio di come potrei aggiungere questi. Devo creare un servizio o esiste un altro modo di farlo. Più importante vorrei che queste funzioni di utilità in un file non combinate con un'altra parte della configurazione principale.

Capisco che ci sono alcune soluzioni, ma nessuna di queste è così chiara.

Soluzione 1 - Proposta da Urban

$scope.doSomething = ServiceName.functionName;

Il problema qui è che ho 20 funzioni e dieci controller. Se lo facessi significherebbe aggiungere molto codice a ciascun controller.

Soluzione 2 - Proposta da me

    var factory = {

        Setup: function ($scope) {

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

Lo svantaggio di questo è che all'inizio di ogni controller avrei una o più di queste chiamate di installazione a ciascun servizio che ha superato l'ambito $.

Soluzione 3 - Proposta da Urban

La soluzione proposta da urban di creare un servizio generico sembra buona. Ecco la mia configurazione principale:

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

Devo aggiungere il servizio generico a questo e come posso farlo?


Risposte:


107

MODIFICA 7/1/15:

Ho scritto questa risposta molto tempo fa e non ho tenuto molto al passo con l'angolazione da un po ', ma sembra che questa risposta sia ancora relativamente popolare, quindi ho voluto sottolineare che un paio del punto @nicolas le marche qui sotto sono buone. Per uno, iniettando $ rootScope e collegando gli helper lì ti impedirai di doverli aggiungere per ogni controller. Inoltre, concordo sul fatto che se ciò che si sta aggiungendo dovesse essere considerato come un servizio angolare O un filtro, dovrebbe essere adottato nel codice in quel modo.

Inoltre, a partire dall'attuale versione 1.4.2, Angular espone un'API "Provider", che può essere iniettata in blocchi di configurazione. Vedi queste risorse per ulteriori informazioni:

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

Iniezione di dipendenza AngularJS del valore all'interno di module.config

Non penso che aggiornerò i blocchi di codice attuali di seguito, perché attualmente non sto usando attivamente Angular e non voglio davvero rischiare una nuova risposta senza sentirmi a mio agio che è effettivamente conforme al nuovo migliore pratiche. Se qualcun altro si sente all'altezza, provaci.

MODIFICA 2/3/14:

Dopo aver riflettuto su questo e aver letto alcune delle altre risposte, in realtà penso di preferire una variante del metodo proposto da @Brent Washburne e @Amogh Talpallikar. Soprattutto se stai cercando utility come isNotString () o simili. Uno dei chiari vantaggi qui è che puoi riutilizzarli al di fuori del tuo codice angolare e puoi usarli all'interno della tua funzione di configurazione (cosa che non puoi fare con i servizi).

Detto questo, se stai cercando un modo generico per riutilizzare quelli che dovrebbero essere correttamente servizi, la vecchia risposta penso che sia ancora buona.

Quello che vorrei fare ora è:

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

Quindi nel tuo parziale puoi usare:

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

Vecchia risposta di seguito:

Potrebbe essere meglio includerli come servizio. Se li riutilizzerai su più controller, inclusi quelli come servizio, ti impedirà di ripetere il codice.

Se desideri utilizzare le funzioni di servizio nel tuo html parziale, dovresti aggiungerle all'ambito di quel controller:

$scope.doSomething = ServiceName.functionName;

Quindi nel tuo parziale puoi usare:

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

Ecco un modo per mantenerlo tutto organizzato e libero da troppe seccature:

Separa il tuo controller, servizio e codice di routing / config in tre file: controllerers.js, services.js e app.js. Il modulo di livello superiore è "app", che ha app.controller e app.services come dipendenze. Quindi app.controllers e app.services possono essere dichiarati come moduli nei propri file. Questa struttura organizzativa è appena presa da 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;
  });

Quindi nel tuo parziale puoi usare:

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

In questo modo si aggiunge una sola riga di codice a ciascun controller e si è in grado di accedere a qualsiasi funzione dei servizi ovunque tale ambito sia accessibile.


Ho forse venti di queste funzioni e voglio usarle in più controller. Ci ho pensato, ma non è così pratico avere il codice come: $ scope.doSomething = ServiceName.functionName; all'interno di ciascun controller. Aggiornerò la mia domanda con qualche dettaglio in più. grazie
Alan2

sì, ha senso se è necessario aggiungere una riga per ogni funzione nei servizi, ma se è possibile aggiungere l'intero servizio (con tutte le sue funzioni) all'ambito in una riga, penso che abbia senso. Non sono troppo chiaro su come potrebbe funzionare la soluzione 2 che hai citato?
urban_raccoons,

1
@urban_racoons: anch'io ho iniziato in questo modo, ma senza ragione non puoi iniettare tali servizi in config. Volevo accedere al mio auth_service all'interno di un intercettore per aggiungere token all'intestazione, ma poi mi sono reso conto che il servizio non può essere iniettato in config. solo le costanti possono. Penso che l'aggiunta di funzioni alle costanti dovrebbe essere un approccio migliore.
Amogh Talpallikar,

1
Sto solo chiedendo, perché non sono principalmente un ragazzo di JS, ma usando il tuo spazio dei nomi produresti una copia o un singleton? Se hai una tonnellata di moduli, sembra che uno spreco di memoria abbia copie dello stesso servizio, specialmente se vuoi usare solo un aiutante.
Eric Keyte,

3
@EricKeyte Lo spazio dei nomi è un oggetto letterale, che è una specie di singleton che è abbastanza comune in JS.
Ci

32

Venendo a questo vecchio thread ho voluto sottolineare questo

1 °) Le funzioni di utilità possono (dovrebbero?) Essere aggiunte al rootcope tramite module.run. Non è necessario installare un controller di livello radice specifico per questo scopo.

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

2 °) Se organizzi il tuo codice in moduli separati, dovresti usare i servizi angolari o factory e quindi iniettarli nella funzione passata al blocco di esecuzione, come segue:

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 °) La mia comprensione è che nelle viste, per la maggior parte dei casi sono necessarie queste funzioni di supporto per applicare un tipo di formattazione alle stringhe visualizzate. Quello che ti serve in quest'ultimo caso è usare i filtri angolari

E se hai strutturato alcuni metodi di supporto di basso livello in servizi angolari o in fabbrica, basta iniettarli nel costruttore del filtro:

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

E a tuo avviso:

{{ aString | myFilter }}   

Entrambe le soluzioni riguardano il runtempo. Che dire del tempo di configurazione? Non abbiamo bisogno di utility là fuori?
Cyril CHAPON

tempo di configurazione hai bisogno di un fornitore (un tipo di servizio) controlla il documento angolare js
nicolas

1
La soluzione n. 3 mi sembra la migliore. Una volta registrato un filtro, puoi usarlo in qualsiasi altro luogo. L'ho usato per la mia formattazione di valuta e la formattazione della data.
Olantobi,

6

Capisco correttamente che vuoi solo definire alcuni metodi di utilità e renderli disponibili nei modelli?

Non è necessario aggiungerli a tutti i controller. Basta definire un singolo controller per tutti i metodi di utilità e collegare quel controller a <html> o <body> (usando la direttiva ngController). Qualsiasi altro controller collegato in qualsiasi punto <html> (ovvero ovunque, punto) o <body> (ovunque tranne <head>) erediterà tale ambito $ e avrà accesso a tali metodi.


1
questo è sicuramente il modo migliore per farlo. Basta avere un controller di utilità e inserirlo nel div wrapper / container dell'intero progetto, tutti i controller all'interno erediteranno: <div class="main-container" ng-controller="UtilController as util">quindi in qualsiasi vista interna:<button ng-click="util.isNotString(abc)">
Ian J Miller

4

Il modo più semplice per aggiungere funzioni di utilità è lasciarle a livello globale:

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

Quindi, il modo più semplice per aggiungere una funzione di utilità (a un controller) è assegnarlo a $scope, in questo modo:

$scope.doSomething = myUtilityFunction;

Quindi puoi chiamarlo così:

{{ doSomething(x) }}

o in questo modo:

ng-click="doSomething(x)"

MODIFICARE:

La domanda originale è se il modo migliore per aggiungere una funzione di utilità è attraverso un servizio. Dico di no, se la funzione è abbastanza semplice (come isNotString()nell'esempio fornito dall'OP).

Il vantaggio di scrivere un servizio è sostituirlo con un altro (tramite iniezione) a scopo di test. Portato all'estremo, è necessario iniettare ogni singola funzione di utilità nel controller?

La documentazione dice di definire semplicemente il comportamento nel controller (come $scope.double): http://docs.angularjs.org/guide/controller


Avere funzioni di utilità come servizio consente di accedervi in ​​modo selettivo nei controller. Se nessun controller li utilizza, il servizio non verrà istanziato.
StuR

In realtà mi piace il tuo approccio e l'ho incorporato nella mia risposta modificata. Ho la sensazione che qualcuno potrebbe averti sottovalutato a causa della funzione globale (e dell'inquinamento dello spazio dei nomi), ma ho la sensazione che avresti probabilmente incorporato un approccio simile a quello che ho scritto se pensassi che fosse necessaria una stretta di mano .
urban_raccoons

Personalmente, non riesco a vedere un problema nel rendere globali, generiche, piccole funzioni di utilità. In genere sarebbero cose che usi su tutto il tuo codebase, quindi chiunque si familiarizzerebbe con loro abbastanza rapidamente. Vedili come piccole estensioni della lingua.
Cornel Masson,

Nella tua modifica menzioni "La documentazione dice semplicemente di definire il comportamento nel controller (come $ scope.double)". Stai dicendo che la documentazione suggerisce di inserire le funzioni di utilità nei controller?
losmescaleros,

@losmescaleros Sì, leggi la sezione "Aggiunta di comportamenti a un oggetto Scope" nella documentazione docs.angularjs.org/guide/controller
Brent Washburne,

4

Ecco un metodo semplice, compatto e di facile comprensione che utilizzo.
Innanzitutto, aggiungi un servizio nel tuo 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;

    }]);

Quindi, nel controller, iniettare l'oggetto helper e utilizzare qualsiasi funzione disponibile con qualcosa di simile al seguente:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

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

}]);

2
Questo mostra chiaramente un problema per cui a volte non mi piace Angular: dire "aggiungi un servizio" ... e poi nel codice creando un nuovo factory (). Dai modelli di progettazione, non sono le stesse cose: la fabbrica viene solitamente utilizzata per produrre nuovi oggetti e il servizio è, beh, per "servire" alcune funzionalità o risorse. In tali momenti voglio dire "WT *, Angular".
JustAMartin,

1

È inoltre possibile utilizzare il servizio costante in quanto tale. La definizione della funzione al di fuori della chiamata costante consente anche che sia ricorsiva.

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

Perché non usare l'ereditarietà del controller, tutti i metodi / proprietà definiti nell'ambito di HeaderCtrl sono accessibili nel controller all'interno di ng-view. $ scope.servHelper è accessibile in tutti i controller.

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


<div ng-controller="HeaderCtrl">
  <div ng-view=""></div>
</div>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.