Como posso adicionar algumas pequenas funções utilitárias ao meu aplicativo AngularJS?

146

Gostaria de adicionar algumas funções utilitárias ao meu aplicativo AngularJS. Por exemplo:

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

É a melhor maneira de fazer isso para adicioná-los como um serviço? Pelo que li, posso fazer isso, mas gostaria de usá-los nas minhas páginas HTML, por isso ainda é possível se eles estiverem em um serviço? Por exemplo, posso usar o seguinte:

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

Alguém pode me dar um exemplo de como eu poderia adicioná-los. Devo criar um serviço ou existe alguma outra maneira de fazê-lo. O mais importante é que eu gostaria dessas funções utilitárias em um arquivo e não combinadas com outra parte da configuração principal.

Entendo que existem algumas soluções, mas nenhuma é tão clara.

Solução 1 - Proposta pela Urban

$scope.doSomething = ServiceName.functionName;

O problema aqui é que tenho 20 funções e dez controladores. Se eu fizesse isso, significaria adicionar muito código a cada controlador.

Solução 2 - Proposta por mim

    var factory = {

        Setup: function ($scope) {

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

A desvantagem disso é que, no início de cada controlador, eu teria uma ou mais dessas chamadas de instalação para cada serviço que passasse o escopo $.

Solução 3 - Proposta pela Urban

A solução proposta pela urban de criar um serviço genérico parece boa. Aqui está a minha configuração 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);

Devo adicionar o serviço genérico a isso e como posso fazê-lo?

Alan2
fonte
verifique minha resposta aqui stackoverflow.com/a/51464584/4251431
Basheer AL-MOMANI

Respostas:

107

EDIT 1/7/15:

Eu escrevi essa resposta há muito tempo e não acompanho muito o angular há um tempo, mas parece que essa resposta ainda é relativamente popular, então eu gostaria de salientar que alguns dos pontos @nicolas faz abaixo são bons. Por um lado, injetar $ rootScope e anexar os auxiliares impedirá que você os adicione em todos os controladores. Além disso - concordo que, se o que você está adicionando deve ser considerado como serviços angulares OU filtros, eles devem ser adotados no código dessa maneira.

Além disso, a partir da versão atual 1.4.2, o Angular expõe uma API "Provider", que pode ser injetada em blocos de configuração. Veja estes recursos para mais:

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

Injeção de valor AngularJS dentro do module.config

Eu não acho que vou atualizar os blocos de código reais abaixo, porque atualmente não estou usando ativamente o Angular e não quero arriscar uma nova resposta sem me sentir confortável por estar realmente em conformidade com o novo melhor práticas. Se alguém quiser, por todos os meios, vá em frente.

EDIT 2/3/14:

Depois de pensar sobre isso e ler algumas das outras respostas, acho que prefiro uma variação do método apresentado por @Brent Washburne e @Amogh Talpallikar. Especialmente se você estiver procurando por utilitários como isNotString () ou similar. Uma das vantagens claras aqui é que você pode reutilizá-las fora do seu código angular e usá-las dentro da sua função de configuração (o que você não pode fazer com os serviços).

Dito isto, se você está procurando uma maneira genérica de reutilizar o que deveria ser adequadamente os serviços, acho que a resposta antiga ainda é boa.

O que eu faria agora é:

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

Então, na sua parcial, você pode usar:

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

Antiga resposta abaixo:

Talvez seja melhor incluí-los como um serviço. Se você for reutilizá-los em vários controladores, incluí-los como um serviço impedirá que você precise repetir o código.

Se você deseja usar as funções de serviço em seu html parcial, adicione-as ao escopo desse controlador:

$scope.doSomething = ServiceName.functionName;

Então, na sua parcial, você pode usar:

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

Aqui está uma maneira de manter tudo organizado e livre de muitos aborrecimentos:

Separe seu controlador, serviço e código / configuração de roteamento em três arquivos: controllers.js, services.js e app.js. O módulo da camada superior é "app", que possui app.controllers e app.services como dependências. Em seguida, app.controllers e app.services podem ser declarados como módulos em seus próprios arquivos. Essa estrutura organizacional foi retirada 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;
  });

Então, na sua parcial, você pode usar:

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

Dessa forma, você adiciona apenas uma linha de código a cada controlador e pode acessar qualquer uma das funções de serviço sempre que esse escopo estiver acessível.

urban_raccoons
fonte
Eu tenho talvez vinte dessas funções e quero usá-las em vários controladores. Pensei sobre isso, mas não é tão prático ter o código como: $ scope.doSomething = ServiceName.functionName; dentro de cada controlador. Vou atualizar minha pergunta com um pouco mais de detalhes. graças
Alan2
Sim, isso faz sentido se você precisar adicionar uma linha para cada função nos serviços, mas se você puder adicionar todo o serviço (com todas as suas funções) ao escopo em uma linha, acho que faz sentido. Não sei muito bem como a solução 2 que você mencionou pode funcionar.
urban_raccoons
1
@urban_racoons: Eu também comecei dessa maneira, mas, infelizmente, você não pode injetar esses serviços na configuração. Eu queria acessar meu auth_service dentro de um interceptador para adicionar token ao cabeçalho, mas percebi que o serviço não pode ser injetado na configuração. somente constantes podem. Eu acho que adicionar funções a constantes deve ser uma abordagem melhor.
Amogh Talpallikar
1
Só estou perguntando, porque não sou principalmente um cara de JS, mas usar seu próprio espaço para nome produziria uma cópia ou um singleton? Se você possui vários módulos, parece um desperdício de memória ter cópias do mesmo serviço, especialmente se você deseja usar apenas um auxiliar.
Eric Keyte
3
@EricKeyte O espaço para nome é um literal de objeto, que é um tipo de singleton bastante comum em JS. Desculpe pela resposta atrasada :)
urban_raccoons
32

Chegando neste tópico antigo, eu queria enfatizar que

1 °) as funções de utilidade podem (devem?) Ser adicionadas ao rootcope via module.run. Não há necessidade de instanciar um controlador de nível raiz específico para esse fim.

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

2 °) Se você organizar seu código em módulos separados, deverá utilizar serviços angulares ou de fábrica e injetá-los na função passada para o bloco de execução, como 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 °) Meu entendimento é que, nas visualizações, na maioria dos casos, você precisa dessas funções auxiliares para aplicar algum tipo de formatação às seqüências de caracteres exibidas. O que você precisa neste último caso é usar filtros angulares

E se você estruturou alguns métodos auxiliares de baixo nível em serviços angulares ou de fábrica, basta injetá-los no construtor de filtros:

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

E na sua opinião:

{{ aString | myFilter }}   
Nicolas
fonte
Ambas as soluções dizem respeito ao runtempo. E o tempo de configuração? Não precisamos de utilitários por aí?
Cyril CHAPON
tempo de configuração que você precisa de um provedor (uma espécie de serviço) checkout da js angulares doc
nicolas
1
A solução nº 3 parece ser a melhor para mim. Depois que um filtro é registrado, você pode usá-lo em qualquer outro lugar. Usei-o para minha formatação de moeda e formatação de data.
precisa
6

Entendi corretamente que você apenas deseja definir alguns métodos utilitários e disponibilizá-los em modelos?

Você não precisa adicioná-los a todos os controladores. Apenas defina um único controlador para todos os métodos utilitários e conecte-o a <html> ou <body> (usando a diretiva ngController). Quaisquer outros controladores que você anexar em qualquer lugar em <html> (que significa qualquer lugar, período) ou <body> (em qualquer lugar, exceto <head>) herdarão esse escopo $ e terão acesso a esses métodos.

Willis Blackburn
fonte
1
esta é definitivamente a melhor maneira de fazer isso. Basta ter um controlador de utilidade e colocá-lo na divisória wrapper / container de todo o projeto, todos os controladores dentro dele herdarão: <div class="main-container" ng-controller="UtilController as util">então, em qualquer vista interna:<button ng-click="util.isNotString(abc)">
Ian J Miller #:
4

A maneira mais fácil de adicionar funções utilitárias é deixá-las no nível global:

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

Então, a maneira mais simples de adicionar uma função de utilidade (a um controlador) é atribuí-la $scope, assim:

$scope.doSomething = myUtilityFunction;

Então você pode chamar assim:

{{ doSomething(x) }}

ou assim:

ng-click="doSomething(x)"

EDITAR:

A pergunta original é se a melhor maneira de adicionar uma função de utilitário é através de um serviço. Eu digo não, se a função for simples o suficiente (como o isNotString()exemplo fornecido pelo OP).

O benefício de escrever um serviço é substituí-lo por outro (via injeção) para fins de teste. Levado ao extremo, você precisa injetar todas as funções de utilidade em seu controlador?

A documentação diz para simplesmente definir o comportamento no controlador (como $scope.double): http://docs.angularjs.org/guide/controller

Brent Washburne
fonte
Ter funções utilitárias como um serviço permite acessá-las seletivamente em seus controladores. Se nenhum controlador as usar, o serviço não será instanciado.
StuR 28/02
Na verdade, eu meio que gostei da sua abordagem e a incorporei na minha resposta editada. Tenho a impressão de que alguém pode ter lhe rebaixado a votação por causa da função global (e da poluição do namespace), mas tenho a sensação de que você provavelmente incorporaria uma abordagem semelhante à que escrevi se pensasse que era necessária muita mão-de-obra. .
urban_raccoons
Pessoalmente, não vejo problema em tornar globais as funções genéricas, pequenas e utilitárias. Normalmente, são coisas que você usa em toda a sua base de código, para que qualquer pessoa se familiarize com elas rapidamente. Veja-os como pequenas extensões para o idioma.
quer tocar hoje
Na sua edição, você menciona "A documentação diz para simplesmente definir o comportamento no controlador (como $ scope.double)". Você está dizendo que a documentação sugere colocar funções utilitárias nos controladores?
Losmescaleros 20/07
@losmescaleros Sim, leia a seção "Adicionando comportamento a um objeto Scope" na documentação docs.angularjs.org/guide/controller
Brent Washburne
4

Aqui está um método simples, compacto e fácil de entender que eu uso.
Primeiro, adicione um serviço em seus 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;

    }]);

Em seguida, no seu controlador, injete seu objeto auxiliar e use qualquer função disponível com algo como o seguinte:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

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

}]);
Martin Brousseau
fonte
2
Isso mostra claramente um problema por que às vezes eu não gosto do Angular: dizendo "adicionar um serviço" ... e depois no código criando uma nova fábrica (). Dos padrões de design, eles não são as mesmas coisas - a fábrica geralmente é usada para produzir novos objetos e o serviço é, também, para "servir" algumas funcionalidades ou recursos. Nesses momentos, quero dizer "WT *, Angular".
JustAMartin
1

Você também pode usar o serviço constante como tal. Definir a função fora da chamada constante também permite que ela seja 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
fonte
0

Por que não usar a herança do controlador, todos os métodos / propriedades definidos no escopo do HeaderCtrl estão acessíveis no controlador dentro da ng-view. $ scope.servHelper está acessível em todos os seus controladores.

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


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