ENTERPRISEY ANGULAR.JS THE GOOD, THE BAD, AND THE UGLY EnterJS 2015 - Ralph Guderlei
Technology Advisor @ @rguderlei
ENTERPRISE APPLICATIONS? komplexe Datenmodelle (100+ Entities) komplexe Geschäftsprozesse Integration mit Drittsystemen relativ wenig Concurrent User
AGENDA 1. Angular.js 2. Organisation großer Projekte 3. Performance 4. komplexe Prozesse
ANGULAR.JS
ANGULAR.JS Javascript MV*-Framework entwickelt von Google momentan das populärste JS-MVC-Framework
TEMPLATES HTML-Fragmente deklaratives two-way databinding <label>name:</label> <input type="text" ng model="yourname" placeholder="enter a name here"> <hr> <h1>hello {{yourname}}!</h1>
CONTROLLER & SCOPE Controller ist Konstruktor für Scope Scope ist ViewModel simpleapp.controller('simplecontroller', function($scope) { $scope.yourname = 'enterjs'; })
DEMO Name: world Hello world!
SERVICES enthalten Daten und Funktionalität Singletons erlauben die gemeinsame Nutzung von Daten
DIREKTIVEN DOM-Manipulationen geschlossene Komponente: isolated scope bestehen meistens aus Template+Controller
ORGANISATION GROSSER PROJEKTE
STRUKTUR & NAMENSKONVENTIONEN Feature-basierte Ordnerstruktur Ordnerstruktur entspricht Modul-Namen 1 Modul pro Datei oft 1 Element (Controller, Service, Direktive) pro Datei Name enthält Elementtyp: foo.service.js
ROUTER Router-States definieren die Zustände der Anwendung $stateprovider.state('home', {}).state('movies', {abstract: true,...}).state('movies.list', {...}).state('movies.detail', {...}); Anwendungs-Zustände entsprechen Sichten, z.b. Filmliste/-details Ordnerstruktur der beteiligten Elemente entspricht dem Router-State/URL
TEMPLATES - DON'TS enthalten Logik: Rechte/Rollen/Varianten besser über Router-States abbilden ng-include <section id="headline"> <h1>exxcellent Movie Database</h1> </section> <a href="..." ng show="isadmin()">...</a> <section id="topmovies"> <div ng include="'movielist.tpl.html'" ng controller="movielistc </section>
TEMPLATES - DOS Darstellung der Daten Strukturierung über Direktiven <section id="headline"> <h1>exxcellent Movie Database</h1> </section> <section id="topmovies"> <h3>top movies</h3> <movie list movies="movies.topmovies"></movie list> </section>
DIREKTIVEN isolated scope verbessert Nachvollziehbarkeit Markup besser verständlich <ul class="list group"> <li ng repeat="movie in movies" class="list group item"> <movie element movie="movie"></movie element> </li> </ul> angular.module('angularlt.home.movielist', []).directive('movielist', function(){ return { restrict: 'E', templateurl: 'app/home/movielist.tpl.html', scope: { movies: '=' } }; });
CONTROLLER - DON'TS Daten aktiv laden Geschäftslogik Scope inheritance (ng-include) $scope.$watch angular.module('angularlt.home',[]).controller('homecontroller', function ($scope, $http) { $http.get('/api/movies', function(response){ $scope.topmovies = _.take(response.data, 10); }); });
CONTROLLER - DOS $scope: Verbindet Funktionalität/Daten mit Templates nur Darstellungs-Logik Daten im resolve-step des Routers laden skinny controller angular.module('angularlt.home',[]).controller('homecontroller', function ($scope, movieservice) { $scope.movies = movieservice; });
SERVICES Service ist Modell enthalten "Geschäftslogik" interagieren mit dem Backend halten Daten (z.b. angemeldeter Benutzer) erzeugen Events
ZUSAMMENFASSUNG Angular Template Controller $scope Service Rest der Welt View Konstruktor View-Model ViewModel Model
PERFORMANCE
BEISPIEL - DATENMODELL
URSACHEN Two-Way Databinding: viele Watcher Limit ca. 2000 Watcher ineffizientes Backend
OPTIONEN - WATCHER Transparenz schaffen: Chrome Extension weniger Daten One-Time Binding (Angular 1.3, BindOnce)
OPTIONEN - BACKEND weniger HTTP-Calls, Caching Datenvolumen auf Serverseite reduzieren Antwortzeiten reduzieren bei vielen Direktiven: Template Cache nutzen
KOMPLEXE PROZESSE
BEISPIEL
TYPISCHE PROZESSE viele Einzelschritte nicht sequenziell unterschiedliche Aktoren beteiligt
ROUTER ALS STATEMACHINE jeder Zustand des Vorgangs ein Router-State Zustände individuell darstellbar Zustände bookmarkfähig Zulässigkeit der Transition bei onenter
ROUTER ALS STATEMACHINE - BEISPIEL $stateprovider.state('process', {abstract: true,...}).state('process.created', {...}).state('process.submitted', {...});
SYNCHRONISATION MIT BACKEND Idee: Vorgang als REST-Resource Repräsentation enthält Links auf zulässige Transitionen REST-Service für Transitionen keine Logik im Client Standards: Siren, HAL, collection+json, UBER
SYNCHRONISATION MIT BACKEND - BEISPIEL POST /api/processes > 201 Created { "links": { "_self": "/api/processes/4711", "revoke": "/api/processes/4711/actions/revoke", "submit": "/api/processes/4711/actions/submit" }, "state" : "created", "data" : {} }
SYNCHRONISATION MIT BACKEND - BEISPIEL PUT /api/processes/4711/actions/submit > 202 Accepted { "links": { "_self": "/api/processes/4711", "revoke": "/api/processes/4711/actions/revoke" }, "state" : "submitted", "data" : {} }
FAZIT
THE GOOD großes Ökosystem, viele Erweiterungen (angulartranslate) gibt Strukturen vor weite Verbreitung viele Informationsquellen
THE BAD hohe Komplexität des Frameworks aufwändige Einarbeitung anfällig für Performance-Probleme viele Möglichkeiten für unwartbare Implementierung
THE UGLY eigenes Modulsystem schwer zu debuggen Verbose: "Javascript EE" Entwickler programmieren "Angular", verstehen aber wenig von Javascript
AUSBLICK Angular 1.x weiter verwenden spannend: Migration zu Angular 2 Alternativen (z.b. React, Polymer) beobachten
VIELEN DANK FÜR IHRE AUFMERKSAMKEIT!