AngularJS ControllerAs Schreibweise
Die ControllerAs-Schreibweise gibt es zwar schon länger (seit AngularJS 1.2.0), hat sich aber in den letzten Monaten zu einer Best practice entwickelt. Es gibt verschiedene Gründe dafür:
- $scope-Eigenschaften werden nicht überschrieben bei verschachtelten Controllern/Scopes
- Wir können die Controller als Klassen nutzen mit this
- die Schreibweise ist ähnlicher der AngularJS 2-Schreibweise für Controller als die klassische Controller-Schreibweise mit $scope
- Mit der controllerAs-Eigenschaft in Direktiven können wir Komponenten (Direktive + View) implementieren die AngularJS 2- Komponenten ähnlich sind
In diesem Artikel werden wir uns mit der ControllerAs-Schreibweise in Kombination mit der ng-controller-Direktive beschäftigen. In weiteren Blogartikeln werden wir uns dann ControllerAs mit ngRoute und Direktiven anschauen.
Klassische Controller-Schreibweise
Ausschnitt aus index.html
<div ng-controller="MainCtrl">
<span>{{name}}</span>
</div>
app.js
angular.module('testApp', [])
.controller('MainCtrl', function($scope) {
$scope.name = 'MyName';
});
In diesem kleinen Beispiel ist es klar, dass {{name}} sich auf $scope.name im MainCtrl bezieht. Was ist aber, wenn wir verschachtelte Scopes/Controller haben? Hier noch ein Beispiel mit verschachtelte Controllern, um die Problematik zu verdeutlichen.
Ausschnitt aus index.html
<div ng-controller="MainCtrl">
<span>{{name}}</span>
<div ng-controller="SubCtrl">
<span>{{name}}</span>
</div>
</div>
app.js
angular.module('testApp', [])
.controller('MainCtrl', function($scope) {
$scope.name = 'MainCtrl';
})
.controller('SubCtrl', function($scope) {
$scope.name = 'SubCtrl';
});
Jetzt haben wir zweimal {{name}} im HTML. Welcher Controller definiert den Wert für die zwei Ausdrücke? Diese Frage kann nicht beantwortet werden, ohne im JavaScript-Code nachzuschauen. In unserem Beispiel haben beide Controller die name-Eigenschaft im $scope, und somit definiert MainCtrl den Wert für den ersten Ausdruck und SubCtrl denjenigen für den zweiten Ausdruck. Wenn wir die ControllerAs-Schreibweise nutzen, können wir schon im HTML sehen, welcher Controller den Wert für den jeweiligen Ausdruck definiert, ohne im Code nachschauen zu müssen.
ControllerAs-Schreibweise
Ausschnitt aus index.html
<div ng-controller="MainCtrl as mainCtrl">
<span>{{mainCtrl.name}}</span>
</div>
app.js
angular.module('testApp', [])
.controller('MainCtrl', function() {
this.name = 'MyName';
});
Wenn wir die ControllerAs-Schreibweise nutzen, ändern sich 3 Dinge gegenüber der klassische Schreibweise. Im JavaScript-Code nutzen wir jetzt kein $scope mehr. Stattdesen nutzen wir this und definieren dort alle Eigenschaften, die wir sonst im $scope definieren würden. Im HTML haben wir zwei Unterschiede. Als erstes nutzen wir as nach dem Namen des Controllers in der ng-controller-Direktive. Mit Hilfe von as definieren wir einen Namespace. In unserem Beispiel ist unser Namespace mainCtrl. Als zweites nutzen wir unseren Namespace als Präfix für alle this-Eigenschaften, also statt {{name}} schreiben wir {{mainCtrl.name}}.
Jetzt schauen wir uns nochmal das Beispiel mit den verschachtelten Controllern an, aber diesmal nutzen wir die ControllerAs-Schreibweise statt der klassischen Controller-Schreibweise.
<div ng-controller="MainCtrl as mainCtrl">
<span>{{mainCtrl.name}}</span>
<div ng-controller="SubCtrl as subCtrl">
<span>{{mainCtrl.name}}</span>
</div>
</div>
app.js
angular.module('testApp', [])
.controller('MainCtrl', function() {
this.name = 'MainCtrl';
})
.controller('SubCtrl', function() {
this.name = 'SubCtrl';
});
Jetzt haben wir für jeden unserer Controller einen eigenen Namespace, mainCtrl für MainCtrl und subCtrl für SubCtrl, definiert. Statt {{name}} nutzen wir jetzt {{namespace.name}}, und es ist schon im HTML klar, dass MainCtlr den Wert für die Ausdrücke definiert, da wir in beiden Fällen das Präfix mainCtrl für den Ausdruck {{name}} benutzt haben. Obwohl der SubCtrl auch eine name-Eigenschaft definiert, können wir im HTML genau sagen, ob wir die name-Eigenschaft des MainCtrl oder des SubCtrl nutzen möchten. Bei der klassischen Schreibweise ist das nicht so einfach möglich.
Allerdings bringt das ControllerAs-Konstrukt nicht nur Vorteile mit sich, sondern auch Nachteile. Erstens müssen wir mehr Code im HTML schreiben, und zweitens müssen wir auf die this-Bindung achten, wenn wir mit Callbacks arbeiten. Aus diesem Grund nutzen viele Entwickler this nicht direkt. Oft sieht man am Anfang des Controllers die Anweisung var vm = this;. Dann wird vm benutzt statt this, um Eigenschaften und Funktionen zu definieren. Mehr Informationen über die Bindung des this-Wertes finden Sie in unserem Blogartikel hier.
ControllerAs mit $scope
Auch wenn wir die ControllerAs-Schreibweise nutzen, gibt es Fälle, in denen wir die $scope-Variable brauchen, z. B. wenn wir mit Angular-Events arbeiten. Genauso wie das $scope-Objekt wird das this-Objekt im Controller benutzt, um die View mit dem Modell zu verbinden. Aber das this-Objekt hat keine vordefinierte Methoden wie das $scope-Objekt. Wenn wir Zugriff auf $on, $broadcast und weitere $scope-Methoden brauchen, müssen wir den Scope per Dependency injection injizieren. Hier ein Beispiel, wie das funktioniert.
Ausschnitt aus index.html
<div ng-controller="MainCtrl as mainCtrl">
<span>{{mainCtrl.name}}</span>
</div>
app.js
angular.module('testApp', [])
.controller('MainCtrl', function($scope) {
var vm = this;
vm.name = 'MyName';
$scope.$on('EventName', function() {
vm.data = [];
});
});
Hier injizieren wir das $scope-Objekt genauso, wie wir es bei der klassischen Schreibweise machen, und weisen den this-Wert der Variable vm zu, um später Probleme bei der this-Bindung zu vermeiden.
Zum Schluss möchte ich noch sagen, dass es sich kaum lohnt, alte Controller umzuschreiben, es ist aber durchaus eine gute Idee, die ControllerAs-Schreibweise für neue Controller zu nutzen. Dadurch können wir Fehler vermeiden, und es wird wahrscheinlich einfacher, die ControllerAs-Schreibweise nach AngularJS 2 zu portieren.