Wie kann ich meiner AngularJS-Anwendung einige kleine Dienstprogrammfunktionen hinzufügen?

146

Ich möchte meiner AngularJS-Anwendung einige Dienstprogrammfunktionen hinzufügen. Beispielsweise:

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

Ist dies der beste Weg, um sie als Service hinzuzufügen? Nach dem, was ich gelesen habe, kann ich dies tun, aber dann möchte ich diese in meinen HTML-Seiten verwenden. Ist es also immer noch möglich, wenn sie in einem Dienst sind? Zum Beispiel kann ich Folgendes verwenden:

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

Kann mir jemand ein Beispiel geben, wie ich diese hinzufügen könnte. Sollte ich einen Service erstellen oder gibt es eine andere Möglichkeit, dies zu tun? Am wichtigsten ist, dass ich diese Dienstprogrammfunktionen in einer Datei und nicht mit einem anderen Teil der Hauptkonfiguration kombinieren möchte.

Ich verstehe, dass es einige Lösungen gibt, aber keine davon ist so klar.

Lösung 1 - Vorgeschlagen von Urban

$scope.doSomething = ServiceName.functionName;

Das Problem hier ist, dass ich 20 Funktionen und zehn Controller habe. Wenn ich das tun würde, würde es bedeuten, jedem Controller viel Code hinzuzufügen.

Lösung 2 - Vorgeschlagen von mir

    var factory = {

        Setup: function ($scope) {

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

Dies hat den Nachteil, dass ich zu Beginn jedes Controllers einen oder mehrere dieser Setup-Aufrufe für jeden Dienst habe, der den $ -Bereich überschritten hat.

Lösung 3 - Vorgeschlagen von Urban

Die von urban vorgeschlagene Lösung zur Schaffung eines generischen Dienstes sieht gut aus. Hier ist meine Hauptkonfiguration:

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

Sollte ich den generischen Service hinzufügen und wie könnte ich das tun?

Alan2
quelle
Überprüfen Sie meine Antwort hier stackoverflow.com/a/51464584/4251431
Basheer AL-MOMANI

Antworten:

107

EDIT 7/1/15:

Ich habe diese Antwort vor ziemlich langer Zeit geschrieben und habe eine Weile nicht viel mit Angular Schritt gehalten, aber es scheint, als ob diese Antwort immer noch relativ beliebt ist, deshalb wollte ich darauf hinweisen, dass ein paar Punkte @nicolas Marken unten sind gut. Zum einen müssen Sie durch das Injizieren von $ rootScope und das Anhängen der Helfer diese nicht für jeden Controller hinzufügen. Außerdem stimme ich zu, dass wenn Sie das, was Sie hinzufügen, als Angular-Dienste ODER Filter betrachtet werden sollen, diese auf diese Weise in den Code übernommen werden sollten.

Außerdem stellt Angular ab der aktuellen Version 1.4.2 eine "Provider" -API zur Verfügung, die in Konfigurationsblöcke eingefügt werden darf. Weitere Informationen finden Sie in diesen Ressourcen:

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

AngularJS-Abhängigkeitsinjektion des Werts innerhalb von module.config

Ich glaube nicht, dass ich die eigentlichen Codeblöcke unten aktualisieren werde, da ich Angular heutzutage nicht wirklich aktiv benutze und keine neue Antwort riskieren möchte, ohne mich sicher zu fühlen, dass sie tatsächlich dem neuen Besten entspricht Praktiken Methoden Ausübungen. Wenn jemand anderes Lust dazu hat, machen Sie es auf jeden Fall.

EDIT 2/3/14:

Nachdem ich darüber nachgedacht und einige der anderen Antworten gelesen habe, denke ich tatsächlich, dass ich eine Variation der von @Brent Washburne und @Amogh Talpallikar vorgebrachten Methode bevorzuge. Besonders wenn Sie nach Dienstprogrammen wie isNotString () oder ähnlichem suchen. Einer der klaren Vorteile hierbei ist, dass Sie sie außerhalb Ihres Winkelcodes wiederverwenden und innerhalb Ihrer Konfigurationsfunktion verwenden können (was Sie mit Diensten nicht tun können).

Abgesehen davon, wenn Sie nach einer generischen Möglichkeit suchen, die eigentlich wiederverwendeten Dienste wiederzuverwenden, ist die alte Antwort meiner Meinung nach immer noch eine gute.

Was ich jetzt tun würde, ist:

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

Dann können Sie in Ihrem Teil verwenden:

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

Alte Antwort unten:

Es ist möglicherweise am besten, sie als Service einzuschließen. Wenn Sie sie auf mehreren Controllern wiederverwenden möchten, müssen Sie den Code nicht wiederholen, wenn Sie sie als Dienst einbeziehen.

Wenn Sie die Servicefunktionen in Ihrem HTML-Teil verwenden möchten, sollten Sie sie dem Bereich dieses Controllers hinzufügen:

$scope.doSomething = ServiceName.functionName;

Dann können Sie in Ihrem Teil verwenden:

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

Hier ist eine Möglichkeit, wie Sie alles organisiert und frei von zu viel Ärger halten können:

Trennen Sie Ihren Controller-, Service- und Routing-Code / Ihre Konfiguration in drei Dateien: controller.js, services.js und app.js. Das Modul der obersten Ebene ist "app", das app.controllers und app.services als Abhängigkeiten hat. Dann können app.controllers und app.services als Module in ihren eigenen Dateien deklariert werden. Diese Organisationsstruktur stammt nur aus 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;
  });

Dann können Sie in Ihrem Teil verwenden:

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

Auf diese Weise fügen Sie jedem Controller nur eine Codezeile hinzu und können auf alle Dienstfunktionen zugreifen, wo immer auf diesen Bereich zugegriffen werden kann.

urban_raccoons
quelle
Ich habe vielleicht zwanzig dieser Funktionen und möchte sie in mehreren Controllern verwenden. Ich habe darüber nachgedacht, aber es ist nicht so praktisch, den Code wie folgt zu haben: $ scope.doSomething = ServiceName.functionName; in jedem Controller. Ich werde meine Frage mit etwas mehr Details aktualisieren. danke
Alan2
Ja, das ist sinnvoll, wenn Sie für jede Funktion in den Diensten eine Zeile hinzufügen müssen. Wenn Sie jedoch den gesamten Dienst (mit allen Funktionen) in einer Zeile zum Gültigkeitsbereich hinzufügen können, ist dies meiner Meinung nach sinnvoll. Mir ist nicht klar, wie die von Ihnen erwähnte Lösung 2 funktionieren könnte.
urban_raccoons
1
@urban_racoons: Ich habe auch so angefangen, aber leider können Sie solche Dienste nicht in die Konfiguration einfügen. Ich wollte in einem Interceptor auf meinen auth_service zugreifen, um dem Header ein Token hinzuzufügen, aber dann wurde mir klar, dass der Service nicht in die Konfiguration eingefügt werden kann. nur Konstanten können. Ich denke, das Hinzufügen von Funktionen zu Konstanten sollte ein besserer Ansatz sein.
Amogh Talpallikar
1
Ich frage nur, weil ich nicht in erster Linie ein JS-Typ bin, aber würde die Verwendung Ihres eigenen Namespace eine Kopie oder einen Singleton erzeugen? Wenn Sie eine Menge Module haben, scheint es eine Verschwendung von Speicher zu sein, Kopien desselben Dienstes zu haben, insbesondere wenn Sie nur einen Helfer verwenden möchten.
Eric Keyte
3
@EricKeyte Der Namespace ist ein Objektliteral, eine Art Singleton, die in JS ziemlich häufig vorkommt. Entschuldigung für die verspätete Antwort :)
urban_raccoons
32

Als ich auf diesen alten Thread kam, wollte ich das betonen

1 °) Utility-Funktionen können (sollten?) Über module.run zum Rootscope hinzugefügt werden. Zu diesem Zweck muss kein bestimmter Root-Level-Controller instanziiert werden.

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

2 °) Wenn Sie Ihren Code in separate Module organisieren Sie Winkel verwenden sollen , Dienstleistungen oder Fabrik und sie dann in die Funktion des Laufblock, wie folgt übergeben injizieren:

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 °) Ich verstehe, dass Sie in Ansichten in den meisten Fällen diese Hilfsfunktionen benötigen, um eine Art Formatierung auf die von Ihnen angezeigten Zeichenfolgen anzuwenden. In diesem letzten Fall müssen Sie Winkelfilter verwenden

Und wenn Sie einige Hilfsmethoden auf niedriger Ebene in Winkeldienste oder Factory strukturiert haben, fügen Sie sie einfach in Ihren Filterkonstruktor ein:

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

Und aus Ihrer Sicht:

{{ aString | myFilter }}   
nicolas
quelle
Beide Lösungen betreffen die runZeit. Was ist mit der Konfigurationszeit? Brauchen wir da draußen keine Versorgungsunternehmen?
Cyril CHAPON
Konfigurationszeit Sie benötigen einen Anbieter (eine Art Dienst) Check-out der eckigen js doc
nicolas
1
Die Lösung Nr. 3 scheint mir die beste zu sein. Sobald ein Filter registriert ist, können Sie ihn an einer anderen Stelle verwenden. Ich habe es für meine Währungsformatierung und Datumsformatierung verwendet.
Olantobi
6

Verstehe ich richtig, dass Sie nur einige Dienstprogrammmethoden definieren und in Vorlagen verfügbar machen möchten?

Sie müssen sie nicht jedem Controller hinzufügen. Definieren Sie einfach einen einzelnen Controller für alle Dienstprogrammmethoden und hängen Sie diesen Controller an <html> oder <body> an (mithilfe der ngController-Direktive). Alle anderen Controller, die Sie irgendwo unter <html> (dh irgendwo, Punkt) oder <body> (irgendwo außer <head>) anhängen, erben diesen $ scope und haben Zugriff auf diese Methoden.

Willis Blackburn
quelle
1
Dies ist definitiv der beste Weg, dies zu tun. Haben Sie einfach einen Utility-Controller und fügen Sie ihn in den Wrapper / Container-Bereich des gesamten Projekts ein. Alle darin enthaltenen Controller erben: <div class="main-container" ng-controller="UtilController as util">dann in allen Innenansichten:<button ng-click="util.isNotString(abc)">
Ian J Miller
4

Der einfachste Weg, Dienstprogrammfunktionen hinzuzufügen, besteht darin, sie auf globaler Ebene zu belassen:

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

Der einfachste Weg, eine Dienstprogrammfunktion (zu einem Controller) hinzuzufügen, besteht darin, sie $scopewie folgt zuzuweisen :

$scope.doSomething = myUtilityFunction;

Dann können Sie es so nennen:

{{ doSomething(x) }}

oder so:

ng-click="doSomething(x)"

BEARBEITEN:

Die ursprüngliche Frage ist, ob der beste Weg, eine Dienstprogrammfunktion hinzuzufügen, ein Dienst ist. Ich sage nein, wenn die Funktion einfach genug ist (wie das isNotString()Beispiel des OP).

Der Vorteil des Schreibens eines Dienstes besteht darin, ihn zum Testen durch einen anderen (durch Injektion) zu ersetzen. Müssen Sie im Extremfall jede einzelne Utility-Funktion in Ihren Controller einspeisen?

In der Dokumentation heißt es, einfach das Verhalten im Controller zu definieren (wie $scope.double): http://docs.angularjs.org/guide/controller

Brent Washburne
quelle
Wenn Sie Dienstprogrammfunktionen als Dienst haben, können Sie in Ihren Controllern selektiv darauf zugreifen. Wenn keine Controller sie verwenden, wird der Dienst nicht instanziiert.
StuR
Ich mag Ihren Ansatz tatsächlich und habe ihn in meine bearbeitete Antwort aufgenommen. Ich habe das Gefühl, dass Sie jemand aufgrund der globalen Funktion (und der Verschmutzung des Namespaces) herabgestimmt hat, aber ich habe das Gefühl, dass Sie wahrscheinlich einen ähnlichen Ansatz gewählt hätten wie den, den ich geschrieben habe, wenn Sie der Meinung wären, dass viel Händchenhalten notwendig ist .
urban_raccoons
Persönlich sehe ich kein Problem darin, generische, kleine Dienstprogrammfunktionen global zu gestalten. Es sind normalerweise Dinge, die Sie in Ihrer gesamten Codebasis verwenden, sodass sich jeder schnell mit ihnen vertraut macht. Sehen Sie sie als kleine Erweiterungen der Sprache.
Cornel Masson
In Ihrer Bearbeitung erwähnen Sie "In der Dokumentation heißt es, einfach das Verhalten im Controller zu definieren (wie $ scope.double)". Wollen Sie damit sagen, dass in der Dokumentation vorgeschlagen wird, Dienstprogrammfunktionen in Steuerungen zu integrieren?
Losmescaleros
@losmescaleros Ja, lesen Sie den Abschnitt "Hinzufügen von Verhalten zu einem Bereichsobjekt" in der Dokumentation Bereichsobjekt docs.angularjs.org/guide/controller
Brent Washburne
4

Hier ist eine einfache, kompakte und leicht verständliche Methode, die ich verwende.
Fügen Sie zunächst einen Dienst in Ihr js ein.

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;

    }]);

Injizieren Sie dann in Ihrem Controller Ihr Hilfsobjekt und verwenden Sie eine der verfügbaren Funktionen wie folgt:

app.controller('MainCtrl', [

'$scope',
'Helpers',

function($scope, Helpers){

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

}]);
Martin Brousseau
quelle
2
Dies zeigt deutlich ein Problem, warum ich Angular manchmal nicht mag: "Add a Service" sagen ... und dann im Code eine neue Factory erstellen (). Von den Entwurfsmustern her sind sie nicht die gleichen Dinge - die Fabrik wird normalerweise zur Herstellung neuer Objekte verwendet, und der Service dient dazu, bestimmte Funktionen oder Ressourcen zu "bedienen". In solchen Momenten möchte ich "WT *, Angular" sagen.
JustAMartin
1

Sie können auch den ständigen Service als solchen nutzen. Wenn Sie die Funktion außerhalb des konstanten Aufrufs definieren, kann sie auch rekursiv sein.

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

Warum nicht die Controller-Vererbung verwenden? Alle im Bereich von HeaderCtrl definierten Methoden / Eigenschaften sind im Controller in ng-view verfügbar. Auf $ scope.servHelper kann in allen Ihren Controllern zugegriffen werden.

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


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