Angular.js

Een veel gebruikte manier om web applicaties te maken in de Java wereld is met behulp van frameworks en technologieën zoals JSF, GWT, Vaadin, Wicket of Seam. Met deze frameworks programmeer je de business logica in Java en gebruik je vaak een templating taal voor het maken van je web pagina’s. De web frontend en de business logica zitten dan vaak dicht bij elkaar geïntegreerd.

De laatste tijd zie je echter de opkomst van volwaardige web applicaties waar de hele web frontend gebouwd wordt in javascript en die met een set van backend services communiceert via REST. Denk hierbij aan javascript libraries zoals backbone.js, ember.js en knockout.js. Een relatief nieuwe toevoeging aan dit rijtje is AngularJS.  

AngularJS is een javascript library, ontwikkeld door Google, waarmee je op een eenvoudige, intuïtieve en declaratieve manier web applicaties kan bouwen. In dit artikel geef ik een overzicht van wat AngularJS is, hoe het werkt en wat de belangrijkste concepten zijn die je in AngularJS tegen komt. De kortste uitleg waarom AngularJS ontwikkeld is komt van de site van AngularJS zelf (angularjs.org):


“HTML is great for declaring static documents, but it falters when we try to use it for declaring dynamic views in web-applications. AngularJS lets you extend HTML vocabulary for your application. The resulting environment is extraordinarily expressive, readable, and quick to develop. Other frameworks deal with HTML’s shortcomings by either abstracting away HTML, CSS, and/or JavaScript or by providing an imperative way for manipulating the DOM. Neither of these address the root problem that HTML was not designed for dynamic views.”

Om bovenstaande te bereiken biedt AngularJS je de mogelijkheid om HTML uit te breiden met eigen attributen, waarmee je eenvoudig, op een declaratieve manier, je pagina kunt koppelen aan je, ook in javascript geschreven, applicatie logica. Dit klinkt allemaal erg abstract, maar als je kijkt naar het volgende voorbeeld zal het snel duidelijk worden hoe AngularJS dit voor elkaar krijgt.

Minimale AngularJS applicatie
Dit voorbeeld bestaat uit drie bestanden:

  • index.html: de web pagina die de verschillende scripts laadt
  • app.js: bevat de javascript code die nodig is om de applicatie te ‘bootstrappen’
  • controllers.js: bevat de controllers die je vanuit de web pagina benadert

Laten we beginnen met de index.html:


index.html:
<!doctype html>
  <html xmlns:ng="http://angularjs.org" ng-app="jmApp">
  <body ng-controller="JavaMagazineController">
 
    <p>{{greeting}}</p>
    <script src="lib/angular/angular.js" type="text/javascript"></script>
    <script src="js/app.js" type="text/javascript"></script>
    <script src="js/controllers.js" type="text/javascript"></script>
 
  </body>
</html>

Dit is een standaard HTML pagina, waaraan een aantal AngularJS attributen toegevoegd zijn. Deze worden directives genoemd in AngularJS. Voordat ik dit uit ga leggen, kijken we eerst even naar de app.js en de controllers.js bestanden.


app.js:
var myApp = angular.module('jmApp', []);


controllers.js:
var myApp = angular.module('jmApp', []);myApp.controller('JavaMagazineController', ['$scope', function (scope) {
    scope.greeting = 'Hello JavaMagazine!';
}]);

Wat je hier ziet is een minimale AngularJS applicatie. Als je dit in je browser opent, krijg je, zoals je waarschijnlijk al wel verwacht had, het volgende te zien:


Screenshot 1

 

Maar hoe werkt dit? Laten we er stapje voor stapje doorheen lopen:

  1. In de HTML pagina hebben we de ng-app=”JMApp” directive staan. Dit directive wordt gebruikt voor het bootstrappen van je module. Het bootstrappen zelf gebeurt in het app.js bestand via de var myApp = angular.module('jmApp', []); code. Hiermee vertel je AngularJS dat hij de module met de naam JMApp moet initialiseren.
  2. Naast de module wordt tijdens het laden van de javascript bestanden ook al een controller aangemaakt. De controller is gedefinieerd in het controllers.js bestand. Zoals je kunt zien in de code, registreer je de controller op de module die in app.js aangemaakt is.
  3. Tijdens het initialiseren van je applicatie zal AngularJS je pagina controleren voor andere directives en deze verwerken. In dit geval komen we het directive ng-controller="JavaMagazineController" tegen.  Dit directive geeft aan dat all directives, en andere AngualrJS constructies, die op Geneste elementen van dit HTML element staan, afgehandeld worden door de controller met de naam JavaMagazineController.
  4. De laatste AngularJS specifieke constructie die je hier kunt zien is {{greeting}}. Dit is de manier waarop je data uit je controller kunt koppelen aan je pagina. AngularJS zal eerst bepalen welke controller hij moet raadplegen. In dit geval is dit de JavaMagazineController.  Vervolgens wordt er gekeken of er een variabele met de naam greeting gekoppeld is aan het scope object (alleen javascript variabelen die je definieert op het scope object kun je vanuit je HTML pagina benaderen). Deze waarde wordt getoond, wat resulteert in screenshot 1.

In dit voorbeeld zie je al een aantal concepten van AngularJS terugkomen. Databinding gaat via {{var}}, je gebruikt directives om AngularJS functionaliteit aan te roepen en via controllers wordt data en functionaliteit ontsloten. Dit artikel is te kort om in detail in te gaan in wat AngularJS allemaal kan, daarom pakken we een aantal features van AngularJS eruit, zodat je een beeld krijgt wat mogelijk is, en hoe eenvoudig het werken met AngularJS is.

Angular-Seed
Angular-Seed is niet zozeer een feature van AngularJS, maar een manier om snel te kunnen starten met AngularJS. Via git kun je dit project clonen en dan heb je direct een werkende applicatie met een net opgezette structuur, vanuit waar je je eigen applicatie kan ontwikkelen:


jos@Joss-MacBook-Pro.local:~/git$ git clone https://github.com/angular/angular-seed
Cloning into angular-seed...
remote: Counting objects: 978, done.
remote: Compressing objects: 100% (464/464), done.
remote: Total 978 (delta 501), reused 849 (delta 419)
Receiving objects: 100% (978/978), 6.37 MiB | 1.11 MiB/s, done.
Resolving deltas: 100% (501/501), done.
jos@Joss-MacBook-Pro.local:~/git$

De applicatie kun je nu direct openen in je browser (liefst via een lokaal geïnstalleerde web browser om security issues te voorkomen): localhost/Dev/git/angular-seed/app/

Directives
AngularJS komt met een groot aantal directives die je kunt gebruiken in je HTML pagina. Een aantal hiervan behandelen we hier, zodat je een beeld krijgt van wat er mogelijk is en hoe directives werken. Voor een compleet overzicht van de directives die beschikbaar zijn kun je kijken op de docs.angularjs.org/api pagina. Het maken van eigen directives is ook eenvoudig. Voor meer informatie hierover; kijk op docs.angularjs.org/guide/directive.

ng-repeat
Het eerste wat je vaak nodig hebt in web applicaties is de mogelijkheid om te itereren over data in je model. In AngularJS doe je dit met behulp van het ng-repeat directive:


Index.html:
...
<ul>
    <li ng-repeat="stock in stocks">
        {{$index + 1}}. {{stock.symbol}}  is {{stock.name}}.
    </li>
</ul>
...


Controllers.js:
...
 scope.stocks = [{
     "symbol" : "GOOG",
     "name" : "Google"
 },{
     "symbol" : "AAPL",
     "name" : "Apple"
 }];
...

In deze twee fragmenten zie je hoe je met AngularJS kunt itereren over een array die door de controller ontsloten wordt.

ng-show en ng-hide
Met deze twee directives kun je conditioneel een gedeelte van je pagina verbergen of tonen:


Index.html:
...
<p ng-show="showme">Alleen zichtbaar als showme = true</p>
<p ng-hide="showme">Alleen zichtbaar als showme = false</p>
...


Controllers.js:
...
scope.showme = true;
...

Naast deze twee directives heb je ook nog ng-class. Met ng-class kun je de classes van een element koppelen aan een waarde uit je model. Hiermee kun je hetzelfde effect bereiken.

ng-click, ng-mousedown, ng-mouseenter, ng-mouse…
De laatste directives die besproken worden zijn de ‘mouse’ gerelateerde. Je kunt met AngularJS direct mouse events koppelen aan functies in je model. In het volgende voorbeeld is de ng-onclick gekoppeld aan een knop. Zodra je op de knop klikt wordt er een functie in de controller aangeroepen die een teller ophoogt. Deze teller is ook weer gebonden vanuit de HTML pagina, dus je zult de waarde hiervan direct zien ophogen.


Index.html:
...
<p>Count: {{count}}</p>
<input type="button" ng-click="increaseCount($event)" value="click"/>
...


Controller.js:
scope.count = 0;
scope.increaseCount = function(event) {
  scope.count++;
};

In dit voorbeeld binden we de ng-click aan de increaseCount functie van de controller. In de increaseCount functie in de controller verhogen we de teller en AngularJS zorgt voor de rest. Het resultaat hiervan ziet er dan zo uit:
 


Screenshot 2

Hetzelfde principe kan ook gevolgd worden voor de overige ng-mouse* directives die AngularJS aanbiedt.

Services
Met directives definieer je op een declaratieve manier hoe je ‘view’ eruit komt te zien. Een ander belangrijk concept in AngularJS zijn services. Met een service kun je een brok herbruikbare functionaliteit via dependcy injection beschikbaar stellen binnen je controller. De business logica die je binnen je web applicatie nodig hebt, kun je dus op een goede manier abstraheren en beschikbaar stellen voor de rest van je applicatie. Hoe gaat dit in zijn werk?

Maak een eigen service
Als eerste heb je een service nodig:


services.js:
var DummyService = function() {
 
    this.doSomething = function() {
        console.log('doing something')
    }
}

Een service is niets anders dan een javascript object dat een aantal functies ontsluit. In dit geval wordt de functie ‘doSomething’ aangeboden. Als we een service gemaakt hebben, zal deze ook bij de module geregistreerd moeten worden:


app.js:
var myApp = angular.module('jmApp', []);
myApp.factory('dummy', function() {
    var dummy = new DummyService();
    return dummy;
});

Via de factory function op de module die je in het begin van dit artikel gemaakt hebt, kun je services registreren. In dit geval registreren we een service met de naam ‘dummy’. Het tweede argument is een factory function die een instantie van de service teruggeeft. In dit geval wordt er een nieuwe DummyService geïnstantieerd, en teruggegeven.

Je kunt services op meerdere plekken gebruiken. Meestal zal je dit gebruiken vanuit een controller, een andere service of mogelijk een filter (die je verder in het artikel nog tegenkomt). In ons voorbeeld hebben we al een controller. Om de service vanuit die controller te gebruiken doe je het volgende:


controllers.js
myApp.controller('JavaMagazineController',
    ['$scope','dummy', function (scope, dummy) {
 
   ...
    scope.callService = function() {
        dummy.doSomething();
    }
}]);

In de controller functie van onze module, die je gebruikt om een controller aan te maken, geef je de naam van een controller aan (JavaMagazineController) en de function die de controller beschrijft. Daarnaast geef je ook aan welke services geïnject moeten worden. In dit geval is dat $scope en dummy. $scope is een standaard service die vanuit AngularJS aangeboden wordt, en dummy is de naam van de service die je net zelf gemaakt hebt. Het aantal services wat je hier definieert, neem je ook op in de constructor van je controller function ‘function (scope, dummy)’ en AngularJS zorgt ervoor dat de juiste service wordt geïnjecteerd.

Inject een service in een andere service
Het kan ook zijn dat je in je eigen service gebruik wil maken van andere services. AngularJS biedt een groot aantal services die je hiervoor kunt gebruiken. Het injecteren van een service in een andere service volgt hetzelfde principe wat je net zag voor de controller:


app.js
myApp.factory('dummy', ['$log',function(log) {
    var dummy = new DummyService(log);
    return dummy;
}]);

Hier geef je, op dezelfde manier als je eerder voor de controller hebt gedaan, de dependencies aan die je service verwacht. In dit geval willen we graag dat $log, een standaard AngularJS service, in de factory functie voor onze service geïnjecteerd wordt. De service implementatie verandert nu natuurlijk ook:


services.js
var DummyService = function(log) {
    this.doSomething = function() {
        log.warn('doing something');
    }
}

Voordat we doorgaan met filters nog een laatste punt over services. AngularJS heeft veel standaard services die je kunt gebruiken. Zo zijn er services specifiek voor het maken van REST calls, voor logging, voor routering en vele andere. Op de AngularJS API site staat daar uitleg over: docs.angularjs.org/api/

Filters
Het laatste onderdeel wat besproken wordt zijn filters. Met filters kun je data formateren voordat deze aan de gebruiker getoond wordt. AngularJS heeft op de API pagina een aantal standaard filters staan die je direct kunt gebruiken, maar het is ook erg eenvoudig om zelf een filter te maken:


app.js
//src: http://docs.angularjs.org/guide/dev_guide.templates.filters.creating_filters
myApp.filter('reverse', function() {
  return function(input, uppercase) {
      var out = "";
      for (var i = 0; i < input.length; i++) {
        out = input.charAt(i) + out;
      }
      // conditional based on optional argument
      if (uppercase) {
        out = out.toUpperCase();
      }
      return out;
    }
  });

Hier zie je de filter function van de module gebruikt worden om een filter te maken. Het concept hierachter is precies hetzelfde als voor het maken van een service.  Je definieert een naam ‘reverse’, en definieert een factory function. Ook hier kun je weer op dezelfde manier services injecteren zoals je eerder hebt kunnen zien. Om dit te gebruiken moet je de pagina (van het eerste voorbeeld) aanpassen:


index.html
...
<p>{{greeting}}</p>
...

En het resultaat hiervan ziet er als volgt uit:


Screenshot 3

Conclusie
AngularJS biedt een goede uitbreiding op HTML. Het biedt een erg flexibel MVC model, dat goed testbaar en eenvoudig te gebruiken is. Het dwingt verder geen specifiek ontwikkelmodel af, en je kunt de onderdelen gebruiken die je wil. Verder biedt AngularJS erg goede two-way data binding, zonder dat je daar je model voor aan hoeft te passen, en biedt een simpel dependency injection model.

In dit artikel heb ik slechts een heel klein gedeelte laten zien van wat er allemaal mogelijk is met AngularJS. AngularJS is een framework wat er voor zorgt dat je eenvoudig het MVC model kunt implementeren, en zorgt ervoor dat je op een nette, goed onderhoudbare manier web applicaties kunt ontwikkelen. Het zorgt er niet voor dat je je als ontwikkelaar aan bepaalde conventies moet houden, maar biedt je juist handvatten en best practices die je kunt gebruiken. Meer informatie kun je vinden op de site van AngularJS