¿Cómo puedo agregar algunas pequeñas funciones de utilidad a mi aplicación AngularJS?

146

Me gustaría agregar algunas funciones de utilidad a mi aplicación AngularJS. Por ejemplo:

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

¿Es la mejor manera de hacer esto para agregarlos como un servicio? De lo que he leído, puedo hacer esto, pero luego me gustaría usarlos en mis páginas HTML, ¿es posible que estén en un servicio? Por ejemplo, ¿puedo usar lo siguiente?

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

¿Puede alguien darme un ejemplo de cómo podría agregar estos? ¿Debo crear un servicio o hay alguna otra forma de hacerlo? Lo más importante es que me gustaría que estas funciones de utilidad en un archivo y no se combinen con otra parte de la configuración principal.

Entiendo que hay algunas soluciones, pero ninguna de ellas es tan clara.

Solución 1 - Propuesta por Urban

$scope.doSomething = ServiceName.functionName;

El problema aquí es que tengo 20 funciones y diez controladores. Si hiciera esto, significaría agregar mucho código a cada controlador.

Solución 2 : propuesta por mí

    var factory = {

        Setup: function ($scope) {

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

La desventaja de esto es que, al comienzo de cada controlador, tendría una o más de estas llamadas de Configuración a cada servicio que pasó el alcance de $.

Solución 3 - Propuesta por Urban

La solución propuesta por urban de crear un servicio genérico se ve bien. Aquí está mi configuración principal:

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

¿Debo agregar el servicio genérico a esto y cómo podría hacerlo?

Alan2
fuente
mira

Respuestas:

107

EDITAR 7/1/15:

Escribí esta respuesta hace bastante tiempo y no he estado siguiendo mucho con angular durante un tiempo, pero parece que esta respuesta sigue siendo relativamente popular, por lo que quería señalar que un par de puntos @nicolas Las marcas a continuación son buenas. Por un lado, inyectar $ rootScope y adjuntar los ayudantes allí evitará que tenga que agregarlos para cada controlador. Además, estoy de acuerdo en que si lo que está agregando debe considerarse como servicios O filtros angulares, deben adoptarse en el código de esa manera.

Además, a partir de la versión actual 1.4.2, Angular expone una API "Proveedor", que se puede inyectar en bloques de configuración. Vea estos recursos para más:

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

Inyección de valor de dependencia AngularJS dentro de module.config

No creo que vaya a actualizar los bloques de código reales a continuación, porque en estos días no estoy usando activamente Angular y realmente no quiero arriesgarme a una nueva respuesta sin sentirme cómodo de que realmente se ajuste a lo mejor prácticas Si alguien más se siente a gusto, anímate.

EDITAR 2/3/14:

Después de pensar en esto y leer algunas de las otras respuestas, creo que prefiero una variación del método presentado por @Brent Washburne y @Amogh Talpallikar. Especialmente si está buscando utilidades como isNotString () o similar. Una de las ventajas claras aquí es que puede reutilizarlos fuera de su código angular y puede usarlos dentro de su función de configuración (que no puede hacer con los servicios).

Dicho esto, si está buscando una forma genérica de reutilizar lo que deberían ser servicios, la respuesta anterior creo que sigue siendo buena.

Lo que haría ahora es:

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

Luego, en su parcial, puede usar:

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

Antigua respuesta a continuación:

Sería mejor incluirlos como un servicio. Si va a reutilizarlos en varios controladores, incluirlos como servicio evitará que tenga que repetir el código.

Si desea utilizar las funciones de servicio en su html parcial, debe agregarlas al alcance de ese controlador:

$scope.doSomething = ServiceName.functionName;

Luego, en su parcial, puede usar:

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

Aquí hay una manera de mantener todo esto organizado y libre de demasiadas molestias:

Separe su controlador, servicio y código / configuración de enrutamiento en tres archivos: controllers.js, services.js y app.js. El módulo de la capa superior es "app", que tiene las dependencias app.controllers y app.services como dependencias. Entonces app.controllers y app.services pueden declararse como módulos en sus propios archivos. Esta estructura organizativa se acaba de tomar 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;
  });

Luego, en su parcial, puede usar:

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

De esa manera, solo agrega una línea de código a cada controlador y puede acceder a cualquiera de las funciones de los servicios donde sea que ese alcance sea accesible.

urban_raccoons
fuente
Tengo quizás veinte de estas funciones y quiero usarlas en múltiples controladores. Pensé en esto, pero no es tan práctico tener el código como: $ scope.doSomething = ServiceName.functionName; dentro de cada controlador. Actualizaré mi pregunta con un poco más de detalles. gracias
Alan2
Sí, eso tiene sentido si necesita agregar una línea para cada función en los servicios, pero si puede agregar todo el servicio (con todas sus funciones) al alcance en una línea, creo que tiene sentido. No tengo muy claro cómo podría funcionar la solución 2 que mencionaste.
urban_raccoons
1
@urban_racoons: también comencé de esta manera, pero desafortunadamente no puedes inyectar tales servicios en la configuración. Quería acceder a mi auth_service dentro de un interceptor para agregar token al encabezado, pero luego me di cuenta de que el servicio no se puede inyectar en la configuración. solo las constantes pueden. Creo que agregar funciones a las constantes debería ser un mejor enfoque.
Amogh Talpallikar
1
Solo pregunto, porque no soy principalmente un tipo JS, pero ¿usaría su propio espacio de nombres produciría una copia o un singleton? Si tiene una tonelada de módulos, parece una pérdida de memoria tener copias del mismo servicio, especialmente si solo desea usar un ayudante.
Eric Keyte
3
@EricKeyte El espacio de nombres es un objeto literal, que es una especie de singleton que es bastante común en JS. Perdón por la respuesta tardía :)
urban_raccoons
32

Al llegar a este viejo hilo, quería enfatizar que

1 °) las funciones de utilidad pueden (¿deberían?) Agregarse al rootcope a través de module.run. No es necesario instanciar un controlador de nivel raíz específico para este propósito.

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

2 °) Si organiza su código en módulos separados, debe usar servicios angulares o de fábrica y luego inyectarlos en la función pasada al bloque de ejecución, de la siguiente manera:

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 °) Entiendo que en las vistas, para la mayoría de los casos, necesita estas funciones auxiliares para aplicar algún tipo de formato a las cadenas que muestra. Lo que necesita en este último caso es usar filtros angulares

Y si ha estructurado algunos métodos auxiliares de bajo nivel en servicios angulares o de fábrica, simplemente inyéctelos dentro de su constructor de filtros:

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

Y en tu opinión:

{{ aString | myFilter }}   
nicolas
fuente
Ambas soluciones se refieren al runtiempo. ¿Qué pasa con el tiempo de configuración? ¿No necesitamos utilidades por ahí?
Cyril CHAPON
tiempo de configuración que necesita un proveedor (un tipo de servicio) desproteger el documento angular js
nicolas
1
La solución # 3 me parece la mejor. Una vez que se registra un filtro, puede usarlo en cualquier otro lugar. Lo usé para mi formato de moneda y formato de fecha.
Olantobi
6

¿Entiendo correctamente que solo desea definir algunos métodos de utilidad y ponerlos a disposición en plantillas?

No tiene que agregarlos a cada controlador. Simplemente defina un solo controlador para todos los métodos de utilidad y adjunte ese controlador a <html> o <body> (usando la directiva ngController). Cualquier otro controlador que adjunte en cualquier lugar bajo <html> (es decir, en cualquier lugar, punto) o <body> (en cualquier lugar menos <head>) heredará ese $ scope y tendrá acceso a esos métodos.

Willis Blackburn
fuente
1
Esta es definitivamente la mejor manera de hacer esto. Solo tenga un controlador de utilidad y póngalo en el contenedor / contenedor div de todo el proyecto, todos los controladores dentro heredarán: <div class="main-container" ng-controller="UtilController as util">luego, en cualquier vista interior:<button ng-click="util.isNotString(abc)">
Ian J Miller
4

La forma más fácil de agregar funciones de utilidad es dejarlas a nivel global:

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

Entonces, la forma más simple de agregar una función de utilidad (a un controlador) es asignarla de $scopeesta manera:

$scope.doSomething = myUtilityFunction;

Entonces puedes llamarlo así:

{{ doSomething(x) }}

o así:

ng-click="doSomething(x)"

EDITAR:

La pregunta original es si la mejor manera de agregar una función de utilidad es a través de un servicio. Digo no, si la función es lo suficientemente simple (como el isNotString()ejemplo proporcionado por el OP).

El beneficio de escribir un servicio es reemplazarlo con otro (mediante inyección) con el propósito de probarlo. Llevado al extremo, ¿necesita inyectar cada función de utilidad en su controlador?

La documentación dice simplemente definir el comportamiento en el controlador (como $scope.double): http://docs.angularjs.org/guide/controller

Brent Washburne
fuente
Tener funciones de utilidad como servicio le permite acceder selectivamente a ellas en sus controladores ... si ningún controlador las usa, entonces el servicio no se instanciará.
StuR
De hecho, me gusta tu enfoque y lo incorporé a mi respuesta editada. Tengo la sensación de que alguien podría haberlo rechazado debido a la función global (y la contaminación del espacio de nombres), pero tengo la sensación de que probablemente habría incorporado un enfoque similar al que escribí si pensara que era necesario tomarse de la mano. .
urban_raccoons
Personalmente, no puedo ver un problema al hacer que las funciones genéricas, pequeñas y de utilidad sean globales. Por lo general, son cosas que usa en toda su base de código, por lo que cualquiera se familiarizará con ellas con bastante rapidez. Véalos como pequeñas extensiones del idioma.
Cornel Masson
En su edición, menciona "La documentación dice simplemente definir el comportamiento en el controlador (como $ scope.double)". ¿Está diciendo que la documentación sugiere poner funciones de utilidad en los controladores?
losmescaleros
@losmescaleros Sí, lea la sección "Agregar comportamiento a un objeto de alcance" en la documentación docs.angularjs.org/guide/controller
Brent Washburne
4

Aquí hay un método simple, compacto y fácil de entender que uso.
Primero, agregue un servicio en su 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;

    }]);

Luego, en su controlador, inyecte su objeto auxiliar y use cualquier función disponible con algo como lo siguiente:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

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

}]);
Martin Brousseau
fuente
2
Esto muestra claramente un problema por el cual a veces no me gusta Angular: decir "agregar un servicio" ... y luego en el código crear una nueva fábrica (). A partir de los patrones de diseño, no son las mismas cosas: la fábrica generalmente se usa para producir nuevos objetos, y el servicio es, bueno, para "servir" alguna funcionalidad o recurso. En esos momentos quiero decir "WT *, Angular".
JustAMartin
1

También puede usar el servicio constante como tal. La definición de la función fuera de la llamada constante también permite que sea recursiva.

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
fuente
0

¿Por qué no utilizar la herencia del controlador? Todos los métodos / propiedades definidos en el alcance de HeaderCtrl son accesibles en el controlador dentro de ng-view. $ scope.servHelper es accesible en todos sus controladores.

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


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