AngularJS explained using Marketplace as end-to-end example.
Servlets
Every web application is at least two web applications : a server-side application concerned with business logic, data integrity, transactions, access control; and a client-side application concerned with presentation and user experiences.
Servers shouldn’t generate markup, they should deliver true data and services.
Clients shouldn’t implement business logic, they should implement great user experiences for interacting with the data and services the server is providing.
Everyone gets to do what they’re good at using tools that are good at it.
Hurray!
Okay, enough sermonizing, let’s look at some code.
First, we need to tell AngularJS how to map paths in the URL to functionality. Server-side, we did things like servlet-mappings in web.xml and naming JSPs and handler mappings in Spring Web MVC annotations and so forth.
But hey! We don’t have a server side, remember? We’re client side. So we need some client-side technology within the “single page application”.
HTML5 mode means pretty URLs the way they ought to be.
However, they create a goofy edge case where if a user follows such a URL cold without already having the single page app loaded, well, then they are going to issue a real HTTP request for that path. So you do need your server to be prepared to rewrite that request to point to the front page, thereby bootstrapping the single page application.
So, Marketplace isn’t our only module, so there’s a layer of indirection to contend with here. We tell the overall application what routes exist; we configure what those routes mean within the modules.
So, in main.js
app.config([‘$routeProvider’, ‘$locationProvider’, function($routeProvider, $locationProvider) {
$locationProvider.html5Mode(true);
$routeProvider.
when(‘/apps’, marketplaceRoutes.main).
when(‘/apps/details/:fname’, marketplaceRoutes.details).
when(‘/apps/search/:initFilter’, marketplaceRoutes.main).
when(‘/features’, featuresRoute).
…
And then in marketplace/routes.js
define([‘require’], function(require){
return {
main: {
templateUrl: require.toUrl(‘./partials/marketplace.html’)
},
details: {
templateUrl: require.toUrl(‘./partials/marketplace-details.html’), controller:’MarketplaceDetailsController’
}
}
});
Great, let’s follow that main route, that looks simple.
so my.wisc.edu/web/apps maps to marketplaceRoutes.main which maps to invoking the marketplace.html partial.
What’s a partial? It’s a chunk of html which makes up a part of the overall page.
This allows you to write markup templates in plain old HTML with “natural language template” features which is a fancy way of saying that when you look at the partial itself in a web browser, with no server or AngularJS fanciness or anything, it looks like something. (Contrast JSPs and for that matter XSLT.)
It looks like something. It’s valid HTML, at least inasmuch as a valid part of an HTML file. That doesn’t mean it looks good.
Still, you’re not far from slapping some CSS imports on this and hacking away on the HTML even if you’re a front end developer who knows absolutely nothing about AngularJS.
Human readable. Human editable. Directly renderable.
You don’t have to know AngularJS to read this, but there are some awesome templating features in here to take advantage of.
The first is token replacement. Got something dynamic to say? Of course you do. Here you go.
{{NAMES.title}}
Showing {{ (portlets | filter:searchTermFilter | showApplicable:showAll | showCategory:categoryToShow).length }} of {{ portlets.length }} apps.
<div class=“portlet-container” ng-repeat=“portlet in portlets | filter:searchTermFilter | showApplicable:showAll | showCategory:categoryToShow | orderBy:sortParameter | limitTo:searchResultLimit” ng-class=“{portlet_hover: hover}” ng-mouseenter=“hover = true” ng-mouseleave=“hover = false” ng-click=“showDetails = !showDetails”>
Okay, but where is this dynamic data coming from?
Well, in the good old days of server-side development, in say Spring Web MVC, you’re have written a Controller, in the Model View Controller architecture.
In AngularJS, you’ve still got a controller, it’s client-side.
<div ng-controller=“MarketplaceController as marketplaceCtrl” …```
So, here’s the portlets:
`marketplace/controllers.js`
app.controller(‘MarketplaceController’, [ ‘$sessionStorage’, …, ‘mainService’, function($sessionStorage,…, mainService) {
…
$scope.portlets = [];
marketplaceService.getPortlets().then(function(data) { $scope.portlets = data.portlets; $scope.categories = data.categories; });
Here’s a key insight: the set of portlet directory entries available to the user doesn’t change when the user clicks a different category. What category the user wants to look at right now is an entirely user experience, client-side, display, view layer concern. The data didn’t change. So we don’t need a server round-trip to go ask the server for another filtering of this modest amount of data — we can do this filtering client-side. That’s why you need client-side technology doing this.
### Services
Okay, okay, but at some point you *did* have to get the data from the server.
We could go get that data in the controller (pro tip: don’t do that) but it would be nice to do that once and only once, with appropriate caching, across controllers.
So let’s abstract that out into a “service”.
What we said here:
app.controller(‘MarketplaceController’, [ …, ‘marketplaceService’, …, function(…, marketplaceService, …) {
is that `MarketplaceController` depends on marketplaceService.
`marketplace/services.js`
…
### Directives
Okay, so Services are one way to abstract out