AngularJS: Der Unterschied zwischen provider, factory und service
Wenn man sich mit AngularJS anfängt zu beschäftigen, stolpert man zwangsläufig über .provider(), .factroy() und .service(). Drei unterschiedliche Methoden, die doch irgendwie dasselbe machen. In diesem Blogeintrag kann man nachlesen, welche Methode für welchen Einsatz die Richtige ist und auch in was sie sich unterscheiden.
Wenn du denkst, dass sie sich ähneln, dann hast du recht. Sie sind sich sehr ähnlich. In der Tat sind sie alle das Gleiche. Sie sind alle Provider. factory und service sind nur spezielle Fälle von provider.
.provider()
In meinem Beispiel erstelle ich einen Provider, der einen Wert zurückgibt und diesen Wert einfach in der Konsole anzeigt:
html:
<body ng-app = myapp> <div ng-controller = "Ctrl"></div> </body>
js:
var app = angular.module("myapp“,[]); app.provider("aProvider“, function(){ this.$get = function(){ return„"Mein Wert“; }; }); app.controller("Ctrl“, function (aProvider){ console.log("aProvider: " + aProvider); };
Konsolen Ausgang:
aProvider: Mein Wert
Ein Provider übergibt im Grunde einen Wert. Dieser Wert könnte alles sein. In diesem Beispiel ist es ein String mit dem Wert „Mein Wert“, aber es hätte auch eine Funktion oder ein Objekt gewesen sein können.
Wer bekommt was, wie oft?
Angular bekommt nur einmal den Wert, egal wie oft der Provider in Controllern übergeben wird. Das heißt, es ruft $get() nur einmal auf, speichert den Wert von $get() und gibt exakt denselben Wert weiter, egal wie oft.
Um es zu verdeutlichen, habe ich einen weiteren Controller erstellt, dem mein Provider-Wert übergeben wird.
html:
<div ng-controller = "Ctrl“></div> <div ng-controller = "Ctrl2“></div>
js:
app.provider("aProvider“, function(){ this.$get = function(){ console.log("eine Providerfunktion $get() wurde aufgerufen.“); // Wurde neu hinzugefügt retrun "Mein Wert“; }; }); app.controller("Ctrl“, function (aProvider){ console.log("aProvider aus Ctrl: " + aProvider); }; app.controller("Ctrl2“, function (aProvider){ // Neuer Controller hinzugefügt console.log("aProvider aus Ctrl2: " + aProvider); };
Konsolen Ausgang:
eine Providerfunktion $get() wurde aufgerufen.
aProvider aus Ctrl: Mein Wert
aProvider aus Ctrl2: Mein Wert
Wie man sehen kann, wird die $get() Funktion nur einmal aufgerufen.
Bemerke, dass wir in der provider Methode sehr viel Code geschrieben haben, nur um $get() zu erstellen. Wieso übergibt man in diesem Fall Angular eine Funktion, die eine andere Funktion definiert? Wieso übergeben wir nicht einfach direkt die Funktion, die wir benutzen wollen?
Nun, das geht. Angular nennt diese Methode factory.
.factory()
Mit factory lieferst du die Funktion für die $get Methode und Angular übernimmt den Rest. Im Beispielcode würde dies so aussehen:
app.factory("aProvider“, function(){ // von„"provider" auf "factory" gewechselt console.log("Factoryfunktion aufgerufen.“); retrun "Mein Wert“; }); app.controller("Ctrl“, function (aProvider){ console.log("aProvider aus Ctrl: " + aProvider); }; app.controller("Ctrl2“, function (aProvider){ console.log("aProvider aus Ctrl2:„" + aProvider); };
Konsolen Ausgang
Factoryfunktion aufgerufen.
aProvider aus Ctrl: Mein Wert
aProvider aus Ctrl2: Mein Wert
Hier stellt sich meist die Frage, warum man überhaupt provider verwendet, wenn factory gleichbedeutend mit weniger Code ist. Es gibt einige Gründe dafür, die ich aber erst später näher erläutern möchte. Zuerst einmal die Unterschiede.
Bisher haben wir einen einfachen String-Wert zurückgegeben, aber in der Praxis will man meist ein Objekt zurückgeben. Im Beispielcode muss dafür nicht viel geändert werden.
Zum Verdeutlichen, lass uns ein Objekt zurückgeben, welches eine Funktion getValue() aufruft. Nun, es gibt dafür einige ansetzte um in Javascript ein Objekt zu erstellen, ich verwende in diesem Bleispiel einen „Object Constructor“- Ansatz. In dem wir eine Funktion erstellen, die ein Objekt mit Eigenschaften und Funktionen füllt, und verwenden dann das Keyword new, um es zu instanziieren.
// Unser Objekt in Konstruktor hinzugefügt. function aObject(){ this.getValue = function(){ return "mein Wert“; }; } app.factory("aProvider“, function(){ console.log("Factoryfunktion wurd aufgerufen.“); return new aObject(); // erzeuge eine Instanz unseres Objekts }); app.controller("Ctrl“, function(aProvider){ console.log("aProvider aus Ctrl: " + aProvider.getValue()); // aufruf nun von getValue() }); app.controller("Ctrl2“, function(aProvider){ console.log("aProvider aus Ctrl2: " + aProvider.getValue()); // aufruf nun von getValue() });
Konsolen Ausgang
Factoryfunktion aufgerufen.
aProvider aus Ctrl: Mein Wert
aProvider aus Ctrl2: Mein Wert
.service()
Noch immer zu viel Code? Es gibt eine Methode Angular nur die Objekt-Konstruktor-Funktion zu geben und da kommt die service Methode ins Spiel.
Hier ist der gleiche Beispielcode mit Ausnahme von service statt factory:
app.service("aProvider“, function(){ // von "factory" auf „"service" gewechselt this.getValue = function(){ return "mein Wert“; }; }); app.controller("Ctrl“, function(aProvider){ console.log("aProvider aus Ctrl: " + aProvider.getValue()); }); app.controller("Ctrl2“, function(aProvider){ console.log("aProvider aus Ctrl2: " + aProvider.getValue()); });
Konsolen Ausgang
aProvider aus Ctrl: Mein Wert
aProvider aus Ctrl2: Mein Wert
provider vs factory vs service
Zusammenfassend kann gesagt werden, dass provider, factory und service alle Provider sind. Eine factory ist ein spezieller Fall des providers wenn alles, was man von Proverder benötigt, eine $get() funktion ist. Es erlaubt einem, dies mit weniger Code zu schreiben. Ein service ist ein Sonderfall einer factory, wenn man eine Instanz eines neuen Objektes zurückgeben möchte, mit dem gleichen Namen, um weniger Code zu schreiben.
Wann soll man nun was benutzen?
Die Antwort darauf ist, dass man die passenste Version verwendet, um ans Ziel zu kommen. Sagen wir man möchte ein vorhandenes Objekt, welches irgendwo anders definiert wird und Konstruktoragumente annimmt, verwenden. Mit service können keine Argumente an Dienste übergeben werden, also ist hier der Aufruf durch factory die richtige Wahl.
app.factory("aProvider“, function(){ console.log("Factoryfunktion wird aufgerufen.“); retrun new MessageBoxClass("custom argunemt“); });
Einer der Hauptfaktoren für die Entscheidung zwischen provider und factory ist, ob man das Objekt, welches generiert wird, bevor es erzeugt wird, konfigurieren möchte. Dies wird durch den Aufruf der .config() Funktion erzeugt. Bei der man eine Instanz des providers selbst, anstelle des Objekts, welches vom Provider zurückgegeben wird, bekommt. Dies geschieht durch die Zugabe von „Provider“ am Ende des gewählten Providernamens, welchen man an die Controller weitergeben möchte. In diesem Beispiel: aProviderProvider
app.provider("aProvider“, function(){ this.value = "mein Wert“; this.setValue = function(neuerWert){ this.value = neuerWert; }; this.$get = function(){ return this.value; }; }); app.controller("Ctrl“, function(aProvider){ console.log("aProvider aus Ctrl: " + aProvider); }); // config selection hinzugefügt app.config(function(aProviderProvider) { aProviderProvider.setValue("neuer Wert“); });
Es gibt noch einen besonderen Provider, der von mir noch nicht erwähnt wurde. In besonderen Fällen kann auch der Provider value verwendet werden.
In meinem ersten factory Beispiel wurde ein einfacher String-Wert zurückgegeben.
app.factory("aProvider“, function(){ return "Mein Wert“; });
Man hätte dies auch mit der value Methode erzeugen können. Der Vorteil ist auch hier: weniger Code:
app.value("aProvider“, "Mein Wert“);
Nun zur Frage, wann man welche Methode bevorzugen sollte. Wenn man beispielsweise einen Wert berechnen will, dessen Grundlage andere Daten sind, z. B. aus einer externen Quelle und/oder ein Wert berechnet werden soll, wenn er das erste Mal angefordert wird, wäre vermutlich der Provider factory die Wahl.
// Beispiel wenn factory vom provider "value" abhängig ist app.value("multiple“, 3); app.factory("value“, function(multiple){ return 10 * multiple; }); // Beispiel wenn factory von einer externen Quelle abhängig ist app.factory("value“, function(multiple){ var multiple = getDataFromExternalPage(); return 10 * multiple; });
Der Provider value ist nicht der einzige Sonderfall. Ein weiterer Provider mit zwei kleinen Unterschieden zu value wird constant genannt.
Der Unterschied zwischen value und constant ist, dass ein Wert, welcher mit constant spezifiziert wird, wie die provider Methode, während der gesamten Konfigurationsphase verfügbar ist. Wohingegen value wie auch service und factory dies nicht sind. Der zweite Unterschied ist, wie der Namen es vielleicht schon verrät. Der Wert von constant kann nicht geändert werden. Der erste zugewiesene Wert wird behalten. Wenn man im späteren Verlauf versucht, diesen Wert zu ändern, wird dies ignoriert.
Hier ein Beipiel:
app.value("aValue“, "Erster Versuch“); app.value("aValue“, "Zweiter Versuch“); app.constant("aConstant“, "Erster Versuch“); app.constant("aconstant“, "Zweiter Versuch“); app.controller("Ctrl“, function(aValue, aConstant){ console.log("aValue: "„+ aValue); console.log("aConstant: " + aConstant); });
Konsolen Ausgabe
aValue: Zweiter Versuch
aConstant: Erster Versuch
value | bietet einen einfachen wörtlichen Wert |
app.value(„aValue“, 10); |
constant | muss mit .config() in einer Konfigurationsphase darauf zugreifen |
app.constant("aValue“, 10); app.config(function(aValue){ console.log.(aValue); }); |
factory | Der Wert, den man zur Verfügung stellt, muss auf der Grundlage von anderen Daten berechnet werden. |
app.facory("aFactory“, function(){ return 10; }); |
service | Es wird ein Objekt mit Methoden zurückgegeben |
app.service("aService“, function(){ var name = "John“; this.setName = function(neuerName){ this.name = neuerName; }; this.getName = function(){ return this.name; } }); |
provider | Man möchte in der Konfigurationsphase ein Objekt erstellen, welches erstellt wird, bevor es erstellt wird. |
app.provider("begruessung“, function(){ var name; this.setName = function(neuerName){ name = neuerName; }; this.$get = function(){ return new function(){ this.begruesse = function(){ console.log("Hallo " + name); }; }; }; }); app.config(function(begruessungProvider){ begruessungProvider.setName("Sam“); }); |
Methoden
provider(name, provider);
Parameter
name:
->Typ: string
--> Der Name der Instanz
[Note] Der Provider ist erreichbar unter name +'Provider' key.
provider:
->Typ: Objekt oder function()
--> wenn Objekt: $get() methode notwendig. Aufgerufen durch $injector.invoke() wenn Instanz erzeugt werden muss.
--> wenn Konstruktor: neue Instanz wird durch injector.instantiate() erzeugt; nanach wie Objekt behandelt.
Ausgabe:
->Typ: Objekt
--> registrierte provider Instanz
factory(name, $getFn);
Parameter
name:
->Typ: string
--> Der Name der Instanz
$getFn:
->Typ: function() oder Array.<(string|function())>
--> Injizierbare $getFn für die Instanzerstellung. Intern ist es eine Abkürzung für $provide.provider(name,{$get: $getFn}).
Ausgabe:
->Typ: Objekt
--> registrierte provider Instanz
service(name, constructor);
Parameter
name:
->Typ: string
--> Der Name der Instanz
constructor:
->Typ: function() oder Array.<(string|function())>
--> Injizierbare Klasse (Konstruktor Funktion) welche instanziiert wird.
Ausgabe:
->Typ: Objekt
--> registrierte provider Instanz
value(name, value);
Parameter
name:
->Typ: string
--> Der Name der Instanz
value:
->Typ: *
--> Der Wert.
Ausgabe:
->Typ: Objekt
--> registrierte provider Instanz
constant(name, value);
Parameter
name:
->Typ: string
--> Der Name der Konstante
value:
->Typ: *
--> Der konstante Wert.
Ausgabe:
->Typ: Objekt
--> registrierte Instanz