Inhaltsverzeichnis


Nichts verpassen?

angularjs ÔÇó services | Tilman Potthof ÔÇó | 9 Minuten

In diesem Artikel werden alle relevanten Wege zum Erzeugen von Services gezeigt und praktische Tipps gegeben, wie ihr die ├╝bliche Verwirrung mit den unterschiedlichen Methoden einfach umgehen k├Ânnt. Wenn man sich mit Services in Angular besch├Ąftigt, dann st├Â├čt man fr├╝her oder sp├Ąter darauf, dass es zum Erzeugen die Methoden service(), factory() und provider() gibt. Alle haben gemeinsam, dass sie Services erzeugen, aber deren Unterschiede, sowie Vor- und Nachteile sind nicht sofort ersichtlich.

Was ist ein Service?

Ein Service ist ein JavaScript-Objekt, dass sich ├╝ber Dependency Injection in andere Anwendungskomponenten (Controller, Direktiven, Services, Filter) einbinden l├Ąsst. An einem einfachen Beispiel l├Ąsst sich zeigen, wie man einen Wert aus einem Controller in einen Service auslagern kann.

var app = angular.module("myApp", []);
app.controller("myController", function ($scope, myService) {
  $scope.myService = myService;
});
app.factory("myService", function () {
  return {
      myImportantValue: 42
  };
});

Anschlie├čend kann man in einem Template den Wert ├╝ber den Service abrufen.

<div ng-controller="myController">{{ myService.myImportantValue }}</div>

Nat├╝rlich lohnt sich ein Service weniger f├╝r einzelne Werte, sondern vor allem dann, wenn man komplexe Anwendungslogik aufteilen m├Âchte.

Was sind die Vorteile?

Das Konzept von Services in AngularJS ist unerl├Ąsslich, wenn man eine gro├če Anwendung in viele kleine sinnvolle Einheiten aufteilen m├Âchte. Im besten Fall erreicht man damit, dass eine komplexe und weiter wachsende Anwendung trotzdem erweiterbar und wartbar bleibt, ohne dass sich jede ├änderung anf├╝hlt, wie das Herausziehen eines Bausteins aus einem Jenga-Turm. Vor allem sehr gro├če Controller-Methoden sind ein Warnzeichen f├╝r zu unzureichende Strukturierung der Anwendungslogik.

Wie unterscheiden sich die drei Methoden?

F├╝r den einfachsten und h├Ąufigsten Anwendungsfall - das Bereitstellen eines Objekts f├╝r andere Komponenten - sind die F├Ąhigkeiten aller drei Methoden gleich. Um das an einem Beispiel zu zeigen, bauen wir dreimal einen identischen userService mit allen Methoden. Der Service soll dabei eine private, nicht ver├Ąnderbare Liste von Benutzern haben. Au├čerdem gibt es die Methode users(), die eine flache Kopie der Liste zur├╝ckgibt, und die Methode addUser() zum Hinzuf├╝gen von neuen Benutzern.

Beispiel factory()

Die Methode factory() ist sehr leicht zu verwenden. Das registrierte Service-Objekt ist der return-Wert der Funktion. Durch die Funktion haben wir die M├Âglichkeit private Variablen als Closure (Wikipedia) zu kapseln. Das hei├čt, dass wir keinen direkten Zugriff mehr auf unsere privateUserList Variable haben, nachdem unsere Funktion das Service-Objekt erzeugt hat. Stattdessen k├Ânnen nur noch die Service-Methoden auf der Liste operieren. Dadurch, dass wir die Liste in der users() Methode als flache Kopie zur├╝ckgeben, haben wir einen Service geschaffen, der kein L├Âschen von Benutzern aus der Liste zul├Ąsst.

angular.module("myApp").factory("userService", function () {
  var privateUserList = [];
  return {
    users: function () {
      return [].concat(privateUserList);
    },
    addUser: function (username, email) {
      privateUserList.push({username: username, email: email});
    }
  };
});

Wenn wir den Service jetzt in andere Komponenten einbinden, wird das Service-Objekt wie ein Singleton (Wikipedia) genau einmal erzeugt. Sobald man das wei├č, kann man dieses Wissen nutzen, um mit Service-Objekten Daten zwischen mehreren Controllern zu teilen.

angular.module('myApp').controller('formController', function($scope, userService) {
  $scope.addUser = function() {
    userService.addUser($scope.username, $scope.email);
    $scope.username = "";
    $scope.email = "";
  }
});

angular.module('myApp').controller('userListController', function($scope, userService) {
  $scope.users = userService.users;
});

In einem Template k├Ânnen wir beide Controller unanh├Ąngig voneinander einsetzen und trotzdem finden alle Operationen ├╝ber den userService auf der gleichen privaten Benutzer-Liste statt.

<body>
  <form ng-controller="formController">
    <input type="text"  ng-model="username" placeholder="Benutzername"/>
    <input type="text"  ng-model="email" placeholder="E-Mail"/>
    <button ng-click="addUser()">Benutzer hinzuf├╝gen</button>
  </form>
  <hr/>
  <ul ng-controller="userListController">
    <li ng-repeat="user in users() track by user.username">
      {{ user.username }}
      ({{ user.email }})
    </li>
  </ul>
</body>

Beispiel service()

Die Methode service() unterscheidet sich nur in der Art, wie das Service-Objekt erstellt wird. Die Funktion wird von Angular mit new aufgerufen und erzeugt so eine Instanz des Service Objekts. Daher werden Attribute und Funktionen, wie bei einem Konstruktur, an die this Variable angef├╝gt. Achtung, aber auch hier wird das Service-Objekt nur einmal als Singleton erzeugt.

angular.module("myApp").service("userSerivce", function () {
  var privateUserList = [];
  this.users = function () {
    return angular.copy(privateUserList);
  };
  this.addUser = function (username, email) {
    privateUserList.push({username: username, email: email});
  };
});

Funktional bietet diese M├Âglichkeit keine Vorteile, aber man kann schon bestehende JavaScript-Konstruktoren verwenden, falls man dies m├Âchte.

angular.module("myApp").service("myService", MyExistingServiceConstructor);

Beispiel provider()

Die letzte und m├Ąchtigste M├Âglichkeit ist die provider() Methode. Der Service selbst wird, wie in der factory() Methode, aus einem return Wert erzeugt. Allerdings wird diese Funktion als $get Methode an den Provider angef├╝gt.

angular.module("myApp").provider("userService", function() {
  this.$get = function() {
    var privateUserList = [];
    return {
      users: function() {
        return [].concat(privateUserList);
      },
      addUser: function(username, email) {
        privateUserList.push({
          username: username,
          email: email
        });
      }
    };
  };
});

Damit haben wir erst mal nichts gewonnen, sondern nur eine kompliziertere Schreibweise gefunden, um das Gleiche zu tun wie die factory() Methode. Einen Vorteil haben wir erst, wenn wir unseren Service konfigurierbar m├Âchten.

Konfigurierbare Services

F├╝r einige eingebaute Services gibt es die M├Âglichkeit, ihre Provider in der Konfigurations-Phase anzupassen. Zwei relative bekannte Beispiele sind der $routeProvider, mit dem man seine Routen festlegen kann, oder der $locationProvider, f├╝r den man den HTML5 Modus aktivieren kann, sodass kein Hash f├╝r die Pfade ben├Âtigt wird.

angular.module('myApp', [
  'ngRoute'
]).config(function ($routeProvider, $locationProvider) {
  $locationProvider.html5Mode(true);

  $routeProvider.when("/", {
    templateUrl: "dashboard.html",
  }).when("/settings", {
    templateUrl: "settings.html",
  }).otherwise({
    redirectTo: "/"
  });
});

Konfiguration eigener Services

Allerdings ist diese M├Âglichkeit nicht nur den eingebauten Services vorbehalten, sondern wir k├Ânnen auch unsere eigenen Services konfigurierbar machen, wenn wir die provider() Methode verwenden. Als Beispiel wollen wir unseren userService so erweitern, dass wir in der Konfigurationsphase initiale Benutzer hinzuf├╝gen k├Ânnen. Daf├╝r verschieben wir die Variable privateUserList in die ├Ąu├čere Funktion und f├╝gen dem userServiceProvider ├╝ber die this Variable eine Methode addInitialUser() hinzu.

angular.module("myApp").provider("userService", function() {
  var privateUserList = [];

  this.addInitialUser = function(username, email) {
    privateUserList.push({
      username: username,
      email: email
    });
  };

  this.$get = function() {
    return {
      users: function() {
        return [].concat(privateUserList);
      },
      addUser: function(username, email) {
        privateUserList.push({
          username: username,
          email: email
        });
      }
    };
  };
});

Anschlie├čend k├Ânnen wir, wie gew├╝nscht, in der Konfigurationsphase Benutzer hinzuf├╝gen.

angular.module('myApp').config(function(userServiceProvider) {
  userServiceProvider.addInitialUser("admin", "admin@example.com");
});

TL; DR

Wenn ihr mit AngularJS Anwendungsbausteine als Services auslagern wollt, dann nehmt ihr am einfachsten die factory() Methode, deren return-Wert ein Objekt ist, das ihr in anderen Komponenten (Controllern, Direktiven etc.) per Dependency Injection einf├╝gen k├Ânnt. Der so erstellte Service existiert nur einmal und diese Instanz wird ├╝ber alle Komponenten geteilt.