Comment puis-je ajouter de petites fonctions utilitaires à mon application AngularJS?

146

Je voudrais ajouter quelques fonctions utilitaires à mon application AngularJS. Par exemple:

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

La meilleure façon de procéder est-elle de les ajouter en tant que service? D'après ce que j'ai lu, je peux le faire, mais j'aimerais les utiliser dans mes pages HTML, est-ce donc toujours possible s'ils sont dans un service? Par exemple, puis-je utiliser ce qui suit:

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

Quelqu'un peut-il me donner un exemple de la façon dont je pourrais les ajouter. Dois-je créer un service ou y a-t-il une autre façon de le faire? Le plus important, je voudrais ces fonctions utilitaires dans un fichier et non combinées avec une autre partie de la configuration principale.

Je comprends qu'il existe quelques solutions, mais aucune d'elles n'est aussi claire.

Solution 1 - Proposée par Urban

$scope.doSomething = ServiceName.functionName;

Le problème ici est que j'ai 20 fonctions et dix contrôleurs. Si je faisais cela, cela signifierait ajouter beaucoup de code à chaque contrôleur.

Solution 2 - Proposé par moi

    var factory = {

        Setup: function ($scope) {

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

L'inconvénient est qu'au début de chaque contrôleur, j'aurais un ou plusieurs de ces appels de configuration à chaque service qui a passé la portée $.

Solution 3 - Proposée par Urban

La solution proposée par urban de créer un service générique semble bonne. Voici ma configuration 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);

Dois-je ajouter le service générique à cela et comment pourrais-je le faire?

Alan2
la source
vérifiez ma réponse ici stackoverflow.com/a/51464584/4251431
Basheer AL-MOMANI

Réponses:

107

EDIT 7/1/15:

J'ai écrit cette réponse il y a assez longtemps et je n'ai pas beaucoup suivi angular depuis un moment, mais il semble que cette réponse soit encore relativement populaire, alors je voulais souligner que quelques-uns des points @nicolas les marques ci-dessous sont bonnes. D'une part, injecter $ rootScope et y attacher les helpers vous évitera d'avoir à les ajouter pour chaque contrôleur. Aussi - je conviens que si ce que vous ajoutez doit être considéré comme des services OU des filtres angulaires, ils doivent être adoptés dans le code de cette manière.

De plus, à partir de la version actuelle 1.4.2, Angular expose une API "Provider", qui est autorisée à être injectée dans les blocs de configuration. Consultez ces ressources pour en savoir plus:

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

Injection de dépendance AngularJS de valeur à l'intérieur de module.config

Je ne pense pas que je vais mettre à jour les blocs de code réels ci-dessous, car je n'utilise pas vraiment activement Angular ces jours-ci et je ne veux pas vraiment risquer une nouvelle réponse sans me sentir à l'aise avec le fait qu'elle se conforme réellement au nouveau meilleur les pratiques. Si quelqu'un d'autre se sent à la hauteur, allez-y.

MODIFIER 2/3/14:

Après avoir réfléchi à cela et lu certaines des autres réponses, je pense en fait que je préfère une variante de la méthode évoquée par @Brent Washburne et @Amogh Talpallikar. Surtout si vous recherchez des utilitaires comme isNotString () ou similaire. L'un des avantages évidents ici est que vous pouvez les réutiliser en dehors de votre code angulaire et vous pouvez les utiliser à l'intérieur de votre fonction de configuration (ce que vous ne pouvez pas faire avec les services).

Cela étant dit, si vous cherchez un moyen générique de réutiliser ce qui devrait être correctement des services, je pense que l'ancienne réponse est toujours bonne.

Ce que je ferais maintenant, c'est:

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

Ensuite, dans votre partiel, vous pouvez utiliser:

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

Ancienne réponse ci-dessous:

Il serait peut-être préférable de les inclure en tant que service. Si vous allez les réutiliser sur plusieurs contrôleurs, les inclure en tant que service vous évitera d'avoir à répéter le code.

Si vous souhaitez utiliser les fonctions de service dans votre partiel html, vous devez les ajouter à la portée de ce contrôleur:

$scope.doSomething = ServiceName.functionName;

Ensuite, dans votre partiel, vous pouvez utiliser:

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

Voici un moyen de garder tout cela organisé et sans trop de tracas:

Séparez votre contrôleur, service et code / configuration de routage en trois fichiers: controllers.js, services.js et app.js. Le module de couche supérieure est "app", qui a app.controllers et app.services comme dépendances. Ensuite, app.controllers et app.services peuvent être déclarés en tant que modules dans leurs propres fichiers. Cette structure organisationnelle est juste tirée de 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;
  });

Ensuite, dans votre partiel, vous pouvez utiliser:

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

De cette façon, vous n'ajoutez qu'une seule ligne de code à chaque contrôleur et pouvez accéder à toutes les fonctions des services partout où cette portée est accessible.

tabacs_urbains
la source
J'ai peut-être vingt de ces fonctions et je souhaite les utiliser dans plusieurs contrôleurs. J'ai pensé à cela mais ce n'est pas si pratique d'avoir le code comme: $ scope.doSomething = ServiceName.functionName; à l'intérieur de chaque contrôleur. Je mettrai à jour ma question avec un peu plus de détails. merci
Alan2
oui, cela a du sens si vous devez ajouter une ligne pour chaque fonction dans les services, mais si vous pouvez ajouter l'ensemble du service (avec toutes ses fonctions) à la portée en une seule ligne, je pense que cela a du sens. Je ne sais pas trop comment la solution 2 que vous avez mentionnée pourrait fonctionner?
urban_raccoons
1
@urban_racoons: J'ai aussi commencé de cette façon, mais malheureusement, vous ne pouvez pas injecter de tels services dans config. Je voulais accéder à mon auth_service à l'intérieur d'un intercepteur pour ajouter un jeton à l'en-tête, mais j'ai ensuite réalisé que le service ne pouvait pas être injecté dans la configuration. seules les constantes le peuvent. Je pense que l'ajout de fonctions aux constantes devrait être une meilleure approche.
Amogh Talpallikar
1
Je demande juste, parce que je ne suis pas principalement un gars de JS, mais est-ce que l'utilisation de votre propre espace de noms produirait une copie ou un singleton? Si vous avez une tonne de modules, cela semble être une perte de mémoire d'avoir des copies du même service, surtout si vous ne voulez utiliser qu'un seul assistant.
Eric Keyte
3
@EricKeyte L'espace de noms est un littéral d'objet, qui est une sorte de singleton qui est assez courant dans JS. Désolé pour la réponse retardée :)
urban_raccoons
32

En venant sur ce vieux fil, je voulais souligner que

1 °) des fonctions utilitaires peuvent (devraient?) Être ajoutées au rootscope via module.run. Il n'est pas nécessaire d'instancier un contrôleur de niveau racine spécifique à cette fin.

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

2 °) Si vous organisez votre code en modules séparés vous devez utiliser des services angulaires ou une usine puis les injecter dans la fonction passée au bloc d'exécution, comme suit:

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 °) Je crois comprendre que dans les vues, dans la plupart des cas, vous avez besoin de ces fonctions d'assistance pour appliquer une sorte de formatage aux chaînes que vous affichez. Ce dont vous avez besoin dans ce dernier cas est d'utiliser des filtres angulaires

Et si vous avez structuré certaines méthodes d'assistance de bas niveau en services angulaires ou en usine, injectez-les simplement dans votre constructeur de filtre:

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

Et à votre avis:

{{ aString | myFilter }}   
Nicolas
la source
Les deux solutions concernent le runtemps. Qu'en est-il du temps de configuration? N'avons-nous pas besoin de services publics?
Cyril CHAPON
config time vous avez besoin d'un fournisseur (une sorte de service) checkout the angular js doc
nicolas
1
La solution n ° 3 me semble la meilleure. Une fois qu'un filtre est enregistré, vous pouvez l'utiliser n'importe où ailleurs. Je l'ai utilisé pour le formatage de ma devise et de la date.
Olantobi
6

Ai-je bien compris que vous souhaitez simplement définir certaines méthodes utilitaires et les rendre disponibles dans des modèles?

Vous n'êtes pas obligé de les ajouter à chaque contrôleur. Définissez simplement un contrôleur unique pour toutes les méthodes utilitaires et attachez ce contrôleur à <html> ou <body> (en utilisant la directive ngController). Tous les autres contrôleurs que vous attachez n'importe où sous <html> (c'est-à-dire n'importe où, point) ou <body> (n'importe où sauf <head>) hériteront de cette $ scope et auront accès à ces méthodes.

Willis Blackburn
la source
1
c'est certainement la meilleure façon de le faire. Il suffit d'avoir un contrôleur utilitaire et de le mettre dans le div wrapper / container de l'ensemble du projet, tous les contrôleurs à l'intérieur hériteront: <div class="main-container" ng-controller="UtilController as util">puis dans toutes les vues intérieures:<button ng-click="util.isNotString(abc)">
Ian J Miller
4

Le moyen le plus simple d'ajouter des fonctions utilitaires est de les laisser au niveau global:

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

Ensuite, le moyen le plus simple d'ajouter une fonction utilitaire (à un contrôleur) est de l'attribuer $scope, comme ceci:

$scope.doSomething = myUtilityFunction;

Ensuite, vous pouvez l'appeler comme ceci:

{{ doSomething(x) }}

ou comme ça:

ng-click="doSomething(x)"

ÉDITER:

La question initiale est de savoir si la meilleure façon d'ajouter une fonction utilitaire consiste à utiliser un service. Je dis non, si la fonction est assez simple (comme l' isNotString()exemple fourni par l'OP).

L'intérêt d'écrire un service est de le remplacer par un autre (via injection) à des fins de test. Poussé à l'extrême, avez-vous besoin d'injecter chaque fonction utilitaire dans votre contrôleur?

La documentation dit de définir simplement le comportement dans le contrôleur (comme $scope.double): http://docs.angularjs.org/guide/controller

Brent Washburne
la source
Avoir des fonctions utilitaires en tant que service vous permet d'y accéder de manière sélective dans vos contrôleurs .. si aucun contrôleur ne les utilise, alors le service ne sera pas instancié.
StuR
En fait, j'aime un peu votre approche et je l'ai incorporée dans ma réponse modifiée. J'ai le sentiment que quelqu'un vous aurait peut-être défavorisé en raison de la fonction globale (et de la pollution de l'espace de noms), mais j'ai le sentiment que vous auriez probablement incorporé une approche similaire à celle que j'ai écrite si vous pensiez qu'une grande prise en main était nécessaire. .
urban_raccoons
Personnellement, je ne vois pas de problème pour rendre les fonctions génériques, petites et utilitaires globales. Ce sont généralement des éléments que vous utilisez partout dans votre base de code, de sorte que tout le monde se familiarise avec eux assez rapidement. Voyez-les comme de petites extensions du langage.
Cornel Masson
Dans votre édition, vous mentionnez "La documentation dit de définir simplement le comportement dans le contrôleur (comme $ scope.double)". Êtes-vous en train de dire que la documentation suggère de mettre des fonctions utilitaires dans les contrôleurs?
losmescaleros
@losmescaleros Oui, lisez la section "Ajouter un comportement à un objet Scope" dans la documentation docs.angularjs.org/guide/controller
Brent Washburne
4

Voici une méthode simple, compacte et facile à comprendre que j'utilise.
Tout d'abord, ajoutez un service dans votre 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;

    }]);

Ensuite, dans votre contrôleur, injectez votre objet d'assistance et utilisez n'importe quelle fonction disponible avec quelque chose comme ce qui suit:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

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

}]);
Martin Brousseau
la source
2
Cela montre clairement un problème pour lequel je n'aime parfois pas Angular: dire "ajouter un service" ... puis dans le code, créer une nouvelle fabrique (). D'après les modèles de conception, ce ne sont pas les mêmes choses - l'usine est généralement utilisée pour produire de nouveaux objets, et le service est, eh bien, pour «servir» certaines fonctionnalités ou ressources. Dans de tels moments, je veux dire "WT *, Angular".
JustAMartin
1

Vous pouvez également utiliser le service constant en tant que tel. Définir la fonction en dehors de l'appel constant lui permet également d'être récursive.

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
la source
0

Pourquoi ne pas utiliser l'héritage du contrôleur, toutes les méthodes / propriétés définies dans la portée de HeaderCtrl sont accessibles dans le contrôleur à l'intérieur de ng-view. $ scope.servHelper est accessible dans tous vos contrôleurs.

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


<div ng-controller="HeaderCtrl">
  <div ng-view=""></div>
</div>
Kie
la source