angular 1 tips from the...
TRANSCRIPT
Novatrox Group
ANGULAR 1
TIPS FROM THE TRENCHES
Chris Klug
Augusti 2016
2
Innehållsförteckning Introduktion ................................................................................................................................. 3
Undvik Angular ............................................................................................................................. 4
In-line är av ondo.......................................................................................................................... 4
MyController.js ..................................................................................................................... 5
Module.js ............................................................................................................................. 5
Undvik $scope så mycket som bara möjligt .................................................................................... 5
Använd pub/sub istället för $broadcast och $emit .......................................................................... 6
Undvik $scope i controllers genom att flytta beroendet en nivå ...................................................... 7
MyController.js ..................................................................................................................... 8
MyControllerDirective.js ........................................................................................................ 8
Module.js ............................................................................................................................. 9
Index.html ............................................................................................................................ 9
Dölj $http, och skapa en fasad som är lättare att arbeta med .......................................................... 9
Använd Providers and Configuration istället för inbyggda antaganden ........................................... 12
Använd TypeScript ...................................................................................................................... 13
JavaScript moduler ..................................................................................................................... 14
App.js ................................................................................................................................. 15
Index.htm ........................................................................................................................... 15
Om författaren ........................................................................................................................... 16
Augusti 2016
3
Angular 1 – Tips from the trenches
Introduktion Angular är ett av världens mest användas SPA ramverk, och trots dess fel och brister så har det plockats
upp av en stor del av web-utvecklings-communityn, och använts för att bygga oändligt många stora
och små lösningar. Det är ett ramverk som kommer med det mesta man behöver i lådan, och som gör
utvecklarna produktiva snabbt.
Angular är även ett lätt ramverk att lära sig att arbeta med. Det finns massor av bra, och dåliga,
tutorials på nätet som visar hur man kan komma igång med Angular. Problemet är dock just det, de
visar hur man kommer igång, och ibland till och med hur man kommer igång på fel sätt. Vill man gå
vidare efter det är resurserna färre, och de tenderar att fokusera på att visa upp en, eller möjligen två,
saker som man bör tänka på. Men i mångt och mycket måste man bli ordentligt insatt innan man kan
hitta bra information om hur man utvecklar Angular applikationer som är robusta, möjliga att bygga
vidare på och underhållsbara.
I det här dokumentet har vi på Novatrox samlat tips, idéer och tankar kring Angular-utveckling som vi
samlat på oss under de senaste åren. Alla med fokuset att bygga bättre applikationer. Vi undviker dock
gärna att kalla dem för ”Best Practices” eftersom de flesta saker tenderar att vara bra under vissa
omständigheter, men inte andra. Och ”Best Practices Under Some Circumstances” låter lite
omständligt
Augusti 2016
4
Undvik Angular En grundpelare i alla de tipsen och tankarna du kommer hitta i detta dokument är att försöka undvika
Angular i så hög grad som möjligt. Då menar jag inte att man ska välja ett annat ramverk, utan att man
ska försöka bygga sina egna delar med så lite kopplingar som möjligt till Angular.
Genom att bygga komponenter som inte är beroende av Angular i sig gör det lättare att göra en hel
del saker. Det blir bland annat lättare att testa koden. Det blir lättare att byta ut Angular i framtiden
om det skulle behövas. Och det blir lättare för någon som inte kan Angulars lite mer komplexa delar
att sätta sig in i koden, och kanske till och med hjälpa till med utvecklingen. Så det finns många
anledningar att bygga sina komponenter Angular-agnostiska så långt det går.
Att bygga komponenter som är Angular-agnostiska innebär dock inte att vi ska ignorera Angular. Det
innebär bara att de delar som är beroende av Angular blir mindre, och agerar mer som ett ”lim” mellan
den agnostiska koden, och den som ”känner till” Angular.
Det intressanta är dock att det behövs djupare kunskap om Angular för att bygga agnostiska
komponenter, än det behövs för att bygga saker som är hårt kopplade till Angular.
Så med ”Undvik Angular” så menar jag att man ska undvika Angular där det går, och omfamna Angular
där det behövs.
In-line är av ondo Detta är något de flesta förstår relativt snabbt när man börjar med Angular, men det är ändå tyvärr så
att man in-line är vanligt förekommande när man tittar på tutorials på nätet.
Så vad menar jag då med in-line? In-line kod i Angular är när man bygger sina komponenter
(controllers, directives etc) som in-line funktioner. Ex:
angular.module(”MyModule”, [])
.controller(”MyController”, function($scope) {
$scope.greeting = ”Hello World”;
});
Den kursiva koden i detta exempel är en in-line funktion. Man skapar en funktion ”in-line” med resten
av koden.
Controllern i det här fallet registreras i Angular snabbt och enkelt, och man är igång och skapar nytta
omedelbart, vilket i sig är bra. Problemet är att man har byggt in sig i Angular helt och hållet. Det finns
inget sätt att komma åt den funktionen utan att ”bootstrappa” Angular och be Angular om en instans
av den funktionen. Detta gör bland annat koden mycket svårare att testa, svårare att läsa och ställer
dessutom en del extra krav på ordningen de olika JavaScript filerna läses in. Vill man till exempel ha
flera komponenter i samma modul, så måste man antingen registrera modulen först i en fil, och sen
Augusti 2016
5
registrera komponenterna i andra filer som laddas in efteråt. Detta innebär att koden inte bara är hårt
knuten till Angular, den ställer även krav på kontextet som den laddas i genom att den är hårt knuten
till kod i andra filer.
En bättre lösning är att separera controller-koden från registreringen i Angular enligt:
MyController.js
function MyController($scope) {
$scope.greeting = ”Hello World”;
}
Module.js
angular.module(”MyModule”, [])
.controller(”MyController”, MyController);
Detta skapar fortfarande en koppling mellan filerna i form av att Module.js förväntar sig att
MyController.js redan laddats, men i gengäld så är nu MyController inte beroende av Angular i nån
högre utsträckning.
Även om MyController använder en variabel som heter $scope, så kan jag nu skapa upp en ny instans
och skicka in ett mockat Scope objekt om jag vill. I det här fallet skulle $scope kunna vara vilket
JavaScript objekt som helst.
Har vi sedan flera komponenter som skall registreras så kan de laddas in i vilken ordning som helst, så
länge Module.js laddas in sist, och ser till att registrera dem i Angular.
Undvik $scope så mycket som bara möjligt Sedan Angular 1.2 har man försökt pusha utvecklare att använda den så kallade ControllerAs syntaxen.
Detta innebär att man deklarerar sin controller med följande syntax
<... data-ng-controller=”MyController as ctrl”>{{ctrl.greeting}}</...>
Detta gör att Angular sätter upp Scope/Controller förhållandet lite annorlunda. Istället för att
instansiera controllern och ge den en referens till $scope, och sedan låta controllern sätta egenskaper
på Scope objektet, så skapar Angular istället en instans av controllern, och sätter sedan den instansen
som en egenskap på scopet. På detta vis skapar man databindningar mot den faktiska controllern, och
inte direkt mot scopet.
Genom att ange ”as ctrl” så säger man till Angular att man vill skapa upp en controller och sätta
egenskapen ctrl på scopet till den instansen. Sedan sätter man upp alla sina bindningar till att binda
mot den instansen istället för scopet.
Augusti 2016
6
Scopet är fortfarande ansvarigt för den faktiska bindingen, men det märks mycket mindre. Controllern
kan nu ofta undvika att ta in $scope i de flesta fall, och på så vis blir mer eller mindre helt Angular
agnostisk.
Med exemplet ovan skulle nu controllern se ut så här istället
function MyController() {
this.greeting = ”Hello World”;
}
och databindingen skulle se ut så här
<div data-ng-controller=”MyController as
ctrl”><h1>{{ctrl.greeting}}</h1></div>
Detta gör det ännu enklare för oss att testa vår controller. Nu tar den ju inte ens ett $scope objekt
som vi behöver mocka. Och även om det är lätt att mocka det i det här fallet, så är det ännu lättare
att inte behöva göra nått alls. Dessutom kan man nu teoretiskt sätt använda controllern helt utan
Angular om man vill.
På alla platser där man definierar en controller i Angular så kan man nu ange ”controller as”, och man
bör definitivt göra det.
Använd pub/sub istället för $broadcast och $emit Om man har flera komponenter som behöver kommunicera med varandra i Angular, så är den
uppenbara lösningen att skicka meddelanden upp och ned i scope hierarkin med hjälp av $broadcast
och $emit. En uppenbar lösning som ofta leder till problem, eller fula lösningar.
Idén med att kommunicera mellan controllers är bra, och nödvändig i många situationer. Problemen
med att göra det med hjälp av $broadcast och $emit är tyvärr ett par.
Först och främst bygger det på att vi vet vilka scope som finns ovanför eller under det nuvarande
scopet. Detta gör att vi kopplar oss en del till hur vi kan sätta upp våra scopes, dvs vilka controllers och
directives som kan användas var.
Sen måste vi veta om vi vill skicka meddelandet uppåt eller neråt. Vi kan inte skicka till syskon, utan
bara till föräldrar eller till barn. Vill man publicera till syskon så måste man antingen skicka
meddelanden uppåt till en förälder med hjälp av $emit, för att sedan i föräldern skicka samma
meddelande nedåt till barnen med $broadscast. Något som kopplar oss ännu hårdare till ordningen
på scopen som används. Alternativt kan vi ta en referens till rot-scopet och använda det för att $emit:a
meddelanden till alla scope i hela vyn. Något som har en del uppenbara prestandaproblem.
Augusti 2016
7
Till sist måste vi även ha en referens till $scope för att kunna använda $broadcast och $emit. Något
som jag precis skrev att vi skulle undvika så mycket som möjligt.
Lösningen är dock inte direkt komplicerad som tur är. Det lättaste sättet är att införskaffa ett pub/sub
ramverk och sedan kapsla in den funktionaliteten i en Angular service. Detta gör att våra komponenter
har ett beroende mot en komponent som vi själva skapat, och som bör vara lätt att mocka om det
behövs. Det innebär också att man kan skicka meddelanden åt alla håll och kanter på ett enkelt sätt.
Skicka till föräldrar? Inga problem. Barn? Inga problem! Syskon? Självklart!
Till sist innebär det också att vi har ännu ett verktyg vi kan använda för att slippa ta ett beroende mot
en Angular-feature.
I de flesta projekt hittills har jag kapslat in Postal.js i en enkel ”MessagingService” som erbjuder två
metoder, en ”subscribe” och en ”publish”. Subscribe returnerar ett objekt som kan användas att
”unsubscribea”. En modell som är identisk till den som Postal.js använder sig av, fast inkapslad i ett
par egna funktioner för att inte läcka Postal-specifik funktionalitet.
Undvik $scope i controllers genom att flytta beroendet en
nivå Det finns viss funktionalitet i $scope som vi ibland inte kan komma ifrån. Framförallt är det en fråga
om $watch. Vi är rätt ofta beroende av denna funktionalitet för att utföra det vi behöver, och det finns
tyvärr få bra sätt att bevaka värden utan $watch. Man kan använda getters och setters i vissa fall, men
i vissa fall räcker inte det.
$watch innebär ju dessutom att vi i de flesta fall behöver använda $on också för att kunna koppla bort
vår watch när scopet förstörs. Så oftast får vi kod som ser ut ungefär så här
function MyController($scope) {
var watch = $scope.$watch(“myProp”, function(newVal, oldVal) {
…
});
$scope.$on(“$destroy”, function() {
watch();
});
}
Den här koden gör uppenbart saker som behövs, men som kopplar controller till $scope och Angular.
Det är dessutom inte helt trivial kod som behöver skrivas för att mocka bort $scope i det här fallet om
vi vill kunna skriva tester. För att inte tala om att vi måste veta om hur $scope används för att mocka
rätt delar eftersom vi inte vill mocka hela objektet i onödan.
Augusti 2016
8
En lösning på detta är att bygga controllern helt Angular agnostisk, vilket vi gärna vill, och sen skapa
ett directive som hanterar skapandet av controllern och hanteringen av de $scope specifika sakerna.
Ett directive har redan en hård koppling mot Angular, så det gör inget om vi tar beroenden på
$scope i ett directive. Så att använda detta för att slippa Angular beorendet i controllern är en bra
lösning.
Så hur fungerar det då?
Enkelt sett så skapar vi ett nytt directive för varje controller som behöver denna funktionalitet. Det
ser ut ungefär så här
MyController.js
function MyController() {
this.onValueChanged = function(newValue) {
// Handle change!
}
this.value = “”;
}
MyControllerDirective.js
function MyControllerDirective($injector) {
return {
scope: true,
link: function(scope, element, attrs, controller) {
var ctrl = $injector.instantiate(MyController, { $scope: scope
});
scope[attrs.myController] = ctrl;
var watch = scope.$watch(function() { return ctrl.value; },
function(newVal, oldVal) {
ctrl.onValueChanged(newVal);
});
scope.$on("$destroy", function() {
watch();
});
}
}
}
Augusti 2016
9
Module.js
angular.module(“MyModule”, [])
.directive(“myController”, MyControllerDirective);
Index.html
<div data-my-controller=”ctrl”>
<input type="text" data-ng-model="ctrl.value" />
</div>
Ok, det är ju rätt mycket kod, men det mesta är rätt självförklarande hoppas jag. Man skapar en
controller som inte tar ett beroende på $scope för att bevaka en egenskap. Istället så exponerar man
en funktion för att hantera att något ändrats. I detta fallet onValueChanged(). Sedan skapar man ett
directive som har till uppgift att skapa upp controllern, sätta den som en egenskap på scope objektet
precis som med ”controller as”, och till sist hantera $watch och $scope, och anropa funktionen på
controllern när värdet ändras.
Till sist registrerar man bara directivet i Angular, eftersom det inte finns något behov för Angular att
ens känna till controllern eftersom den skapas upp av directivet.
Användning blir lite förändrad i form av att man inte använder ngController, istället använder man sitt
eget directive för att sätta upp controllern. Så istället för data-ng-controller=”...” så skriver man nått i
stil med data-my-controller=”ctrl”, beroende på vad för namn man registrerat sitt directive under.
Fördelen med detta är än en gång att koppla loss controllern från Angular. Det blir lite mer kod, men
och andra sidan så blir den intressanta koden, dvs, controllern, enklare att förstå och smidigare att
jobba med. Det är dessutom mer likt den modell som Angular 2 arbetar enligt med sina components.
Angular 1.5 har även introducerat components i Angular 1, vilket kan vara intressant om man ser en
potentiell flytt till 2:an i framtiden. Men det är av förklarliga skäl inte en exakt kopia av det som
kommer i 2:an, men är trots det intressant att överväga.
Dölj $http, och skapa en fasad som är lättare att arbeta med De flesta Angular-baserade applikationerna i världen anropar tjänster på nätet för att hämta, eller
lämna/uppdatera information. Det lättaste sättet är att helt enkelt bara ta in en instans av $http och
använda den för att göra anropen. Detta introducerar dock ett gäng intressanta problem.
Först och främst så kopplar vi oss en än gång hårt mot Angular genom att ta ett beroende på $http
hela vägen uppe i controllern.
Sen leder det tilll att vi sprider ut URL:er/paths i våra controllers. Något som kan verka som ett litet
problem, tills man behöver uppdatera några av dem och inte riktigt vet var de används nånstans.
Augusti 2016
10
Det innebär också att det inte finns någon direkt central plats att lägga in felhantering. Det går att göra
detta genom inbyggd funtionalitet i Angular, men det innebär ju att vi bygger in en relativt central del
av vår applikation (felhantering och loggning) i Angular. Något som kanske inte är jättebra i alla fall.
Till sist är det även svårare att hantera autentisering när man sprider ut $http anropen på olika platser
i koden. I SPA-applikationer tenderar vi att använda bearer tokens för autentisering, vilket innebär att
alla anrop som görs måste få en autentiserings header satt innan man skickar iväg det. Att ha denna
logik, inkl logik för att hantera potentiell utloggning, utspridd i hela applikationen är inte speciellt
önskvärt.
Lösningen kring detta är att skapa ett, eller kanske två lager emellan controllern och $http.
Den enklaste lösningen, dvs ett lager, innebär att man kapslar in $http i en mer specifik service. På så
vis ligger alla anropen till en specifik endpoint på ett ställe, och likaså loggning, felhantering och
hantering av eventuell utloggning.
Till exempel skapar man en ProductService om man behöver produkt information från en tjänst.
ProductService:n kapslar sedan in själva anropet till servern med allt vad det innebär, och exponerar
istället en enkel funktion som förklarar för konsumenten vilka parametrar som behövs.
function ProductService($http) {
this.searchProduct = function(query) {
return $http.get("https://www.endpoint.com/products?q=" +
encodeURI(query))
.then(function(response) {
return response.data;
}, function(error) {
// Handle error
})
}
}
För controller innebär det att man istället för att behöva ha URLer och http konfiguration själv, så tar
man ett beroende på ProductService och får ett enkelt API att arbeta med, dvs färdiga funktioner för
det man kan göra.
function ProductController(productService) {
this.onSearchClick = function() {
productService.searchProduct(this.query)
.then(function(products) {
// Handle result
})
}
Augusti 2016
11
this.query = “”;
}
Denna lösning har många fördelar jämfört med att ta in $http i controller. Men den har också en del
nackdelar jämfört med att introducera ett lager till.
Genom att bara ha ett lager som i den här lösningen, så sprider vi fortfarande ut felhantering, loggning,
autentisering etc i ett antal olika services. Något som är bättre att hantera på ett enda ställe.
Så, istället för att ProductService nivån tar ett beroende på $http, så introducerar man en egen
HttpService, vilken ProductService i sin tur tar ett beroende på.
HttpServicen erbjuder grundläggande HTTP funktionalitet, dvs get, put, post etc, men kapslar in $http
och står för felhantering, loggning och autentisering. Det skulle kunna se ut i stil med det här
function HttpService($http, authService) {
this.get = function(path) {
var config = {
method: ‘GET’,
url: path
};
if (authService.userIsAuthenticated) {
config.headers = { Authorization: 'Bearer ' +
authService.accessToken }
};
return $http (config)
.then(function(response) {
return response.data;
}, function(error) {
// Log
// Handle Unauthorized
return getStandardizedErrorResult(error);
})
}
}
Som synes så tar HttpService ett beroende på $http, och kapslar in anropet till denna service. I den
inkapsling så hanteras även loggning och felhantering, samt autentisering.
I det här fallet används en fiktiv service som heter authService, som hanterar huruvida användaren är
inloggad, samt dess access token.
Augusti 2016
12
Detta är självklart en förenkling av vad man bör bygga, men den visar förhoppningsvis vad målet med
denna abstraktion är.
Väljer man dessutom att hantera promises manuellt med $q, så kan man erbjuda funktionalitet som
återinloggning vid utloggning utan att man förlorar data som skulle ha postats till servern etc. Något
som är väldigt svårt att göra på ett strukturerat sätt utan en HTTP abstraktion som denna.
Använd Providers and Configuration istället för inbyggda
antaganden De flesta lösningar man bygger kommer att befinna sig i olika miljöer under sin livstid. De kommer leva
i en utvecklingsmiljö, en testmiljö, en produktionsmiljö etc. Alla dessa miljöer har troligen olika
konfiguration. Kanske kör servicarna man är beroende av på lokalt på en annan port i
utvecklingsmiljön, på en intern server i test och på en publik server i produktion. Något vi måste ta
hänsyn till.
Miljöer är bara en sak som förändras när vår kod används i olika scenarion. Kanske är en del av
konfigurationen användarbaserad? Kanske vill vi kunna tända och släcka features med featuretoggles?
Lösningen är att inte bygga in logik i koden för att automatiskt byta inställningar. Logik som ”om hosten
är localhost, använd endpoint X” etc. Använd istället providers och konfigurera servicarna som
används istället.
Konfigurationen kan antingen komma genom ett anrop till en ”konfigurations” endpoint när
applikationen startar upp, eller så kan konfigurationen finnas i sidan. Eller kanske en kombination.
När jag säger att konfigurationen finns i sidan, så menar jag att man renderar konfigurationen i ett
script-block i HTML sidan enligt:
<script src=”...”></script>
<script>
var appConfig = {
serviceBaseUrl: ’http://myserver.com/api/’
};
</script>
<script src=”.../MyModule.js”></script>
Den här appConfig variabeln kan sen kommas åt och användas för service configuration i Angular
modulernas config steg. Så här
angular.module(”MyModule”, [])
...
.config(httpServiceProvider) {
Augusti 2016
13
httpServiceProvider.setBaseUrl(appConfig.serviceBaseUrl);
}
Det går även att komma åt appConfig i all annan kod, men undvik detta. Det är ”illa nog” att config
funktionen förutsätter att det finns en magisk variabel. Att sprida ut det beroendet är inte bra.
Alternativt kan man även skapa en standard konfiguration som man avviker ifrån med hjälp av
Angular’s extend funktion.
angular.module(”MyModule”, [])
...
.config(httpServiceProvider) {
var defaultConfig = {
serviceBaseUrl: ‘/api/’,
myOtherConfig: ‘configValue’
}
var config = angular.extend(defaultConfig, appConfig || {});
httpServiceProvider.setBaseUrl(config.serviceBaseUrl);
}
På det här sättet så har man alltid värden satta till nått standardvärde, men möjligheten att skriva över
dem med en annan konfiguration vid behov.
Ibland kan det till och med vara vettigt att bygga in config för att skapa en fördröjning i HttpServicen.
Genom att göra det, så kan vi se hur applikationen kommer att uppföra sig i produktion där det finns
latency, istället för att bara se hur applikationen fungerar i utvecklingsmiljön där anropen till servern
är mer eller mindre försumbara.
Använd TypeScript TypeScript är ett superset till JavaScript, som transpileras till JavaScript. Det ger oss tillgång till features
som JavaScript inte erbjuder, samt features som JavaScript kommer erbjuda i framtiden, men som
webläsarna ännu inte hunnit implementera.
Bland annat ger TypeScript stöd för statisk typning. Något som kan hjälpa till på ett flertal sätt. För det
första ser det till att vi inte försöka använda fel typer när vi anropar funktioner. Det ser även till att vi
inte skickar in för många, eller för få variabler till constructors och funktioner. Till sist möjliggör det
även bättre verktygsstöd.
Genom statisk typning, så kan verktygen helt plötsligt ge utvecklaren en massa stöd som tidigare inte
varit möjligt pga JavaScripts lösa typning. Vi kan få IntelliSense funktionalitet som faktiskt fungerar.
Refactoring stöd. ”Go To” funktionalitet. Och en massa annat.
Augusti 2016
14
TypeScript ger oss även stöd för klasser och interface, vilket kan underlätta och strukturera koden för
oss.
Samtidigt är det JavaScript-kompatibelt, så om man behöver JavaScripts lösa typning, eller
funktionella funktionalitet, så är det fritt fram att använda den i TypeScript också. Så TypeScript ger
oss det bästa av både de statisk typade och löst typade världarna. Det är upp till oss att välja vilka
delar vi vill använda var.
JavaScript moduler JavaScript erbjuder möjligheten att använda moduler, något som kan underlätta massor. I ES2015
lanserades idén att använda export/import i filer för att på så vis göra en fil till en ”modul”. Andra
moduler kan sedan deklarera ett beroende på en annan modul, dvs filer kan deklarera andra filer som
de är beroende på. Så istället för att alltid behöva se till att alla filer laddas in i rätt ordning på sidan,
så kan man automatisera processen genom att titta på vilka moduler man behöver ladda in, och i
vilken ordning de behöver laddas in. TypeScript stödjer även det moduler på detta vis.
Problemet är att detta är en ES2015 feature, vilket gör att det inte stöds natively av webläsarna som
finns ute idag. Det är även så att utvecklare tidigare sett detta behov, och utvecklat egna lösningar för
modulär laddning. Så det finns ett antal olika modulsystem på marknaden. Något man behöver ta i
beaktande när man väljer bibliotek och ramverk att arbeta med.
Antingen så låter man ett verktyg bundla ihop alla filerna till en stor fil på servern och serverar den till
klienten, vilket är bra i produktion, eller så använder man ett JavaScript-bibliotek i webläsaren som
kan ladda filerna asynkront för oss, vilket är bra under utveckling. Eller, så gör man det ena under
utveckling, dvs använder individuella filer och ett bibliotek för att ladda dem, och det andra i
produktion. Exempel på verktyg för att bundla moduler är WebPack och Browserify, och för dynamisk
laddning av filer i webläsaren kan man använda System.js. Alla dessa tenderar att kunna hantera alla
de olika modultyperna.
Vettigt att känna till är även system.js-builder, som är ett verktyg för att bundla moduler med hjälp av
samma logik som System.js gör i webläsaren.
Modulärladdning av moduler gör saker mycket enklare. Det är lätt att råka glömma en fil som skall in
i HTML-sidan, eller inkludera filer i fel ordning. Moduler löser detta utan problem. Dessutom gör det
att det blir lätt att blanda lösa filer under utveckling, och bundlade och minifierade filer i produktion.
Det är dock bra att veta att om man väljer att använda moduler på detta vis, och inte använder
bundling, utan laddar de individuella filerna asynkront, så kan man inte ”bootstrappa” Angular som
vanligt. Istället måste man vänta tills alla filer laddats ner, och sedan exekvera bootstrappningen
manuellt.
Augusti 2016
15
Om man har en fil som heter app.js som ”entry point”, dvs som första fil att be om från servern, så
skulle den kunna se ut så här.
App.js
import { AppDirective } from './appdirective';
angular.module("MyModule", [])
.directive("myApp", AppDirective);
angular.element(document).ready(() => {
angular.bootstrap(document, [MyModule]);
});
Index.htm
<div data-my-app></div>
<script src="scripts/libs/system.js"></script>
<script>
System.config({ … });
System.import('app.js');
</script>
Det här exemplet använder ES2015 moduler med import/export syntax. Det behöver transpileras till
lämplig ES5 syntax för att fungera i en webläsare, men det är lättare att visa det så här.
Html sidan har lite kod som konfigurerar System.js för att det biblioteket ska förstå vad det ska göra.
Därefter så ombeds System.js att importera app.js. System.js ser då att app.js har ett beroende på
appdirective.js, och laddar ner den filen. Sen anges det som en del av System.js konfigurationen att
app.js har ett beroende till Angular också, så System.js laddar ner Angular också. När alla de filerna är
nerladdade, så laddas appdirective.js in i webläsaren, sedan Angular, och till sist app.js. Allt baserat på
hur de olika modulerna har beroenden till varandra.
Problemet med detta är att när Angular laddas in, så kommer det att försöka bootstrappa MyModule
om det finns ett ng-app attribut. Problemet med det är att MyModule är definierat i app.js som ännu
inte laddats in i webläsaren. Så det går inte. Så istället för att använda ngApp direktivet för att
bootstrappa Angular, så låter vi app.js innehålla koden för att göra det. Och det är koden som ser ut
så här
angular.element(document).ready(() => {
angular.bootstrap(document, [MyModule]);
});
Först använder vi Angular för att få tag i document elementet. Sen säger vi att när document är
färdigladdat, vilket det redan bör vara, så vill vi exekvera angular.bootstrap, och bootstrappa
Augusti 2016
16
document elementet med modulen MyModule. Denna processen kommer då gå igenom DOMen och
hantera alla Angular attribut som väntat.
Om författaren Chris Klug är Technical Dude på Novatrox Consulting med bland annat femton års erfarenhet av
arkitektur, systemutveckling och Azurespecifik arkitektur. Han är en omtyckt lärare, mentor och talare
vid konferenser runtom i världen, nu senaste [augusti 2016] i Sydney (Australien) och Redmond (USA).
Chris har dessutom de senaste fem åren erhållit status som Microsoft MVP inom ASP.NET samt som
Microsoft Azure insider.
Augusti 2016
17
Novatrox Group Till följd av en allt mer föränderlig värld med snabbare processer gör sig behovet av struktur,
informationsförsörjning och automatisering gällande. Skillnaden mellan att kunna möta
marknadens behov eller att totalt misslyckas beror på förmågan att kunna förhålla sig till dessa
snabba förändringar i en digital värld. Novatrox Group tillhandahåller resurser inom våra områden
Verksamhetsutveckling, Arkitektur, Systemutveckling samt Ledning & Styrning. Vi levererar både
resurskonsulter samt tar oss an kortare eller längre helhetsåtaganden gentemot våra kunder.
Arkitektur Inom arkitektur är Novatrox ett av Stockholms större företag med ett tiotal arkitekter. Vi hjälper organisationer att få ut mer från sina investeringar genom att stötta med struktur, erfarenhet och mentorskap.
Vi tillhandahåller tjänster inom analys och utredning, arkitektur- granskningar, arkitekturstyrning samt stöd vid strukturförflytt- ningar.
Verksamhetsutveckling Novatrox konsulter består av ett exklusivt team med unik spetskompetens och track-record inom förändringsledning och agil verksamhets- utveckling.
Vi tillhandahåller tjänster inom affärs- och verksamhetsutveckling, förändringsledning och processutveckling. Vi arbetar också med problemlösning för att få upp projekt eller team på banan igen.
Ledning & Styrning Novatrox ledningsteam säkerställer att projektens effekt- hemtagningar förverkligas. Teamet är certifierat i projekt- metodiker och agila metoder. Vi tillhandahåller tjänster inom projekt-, test- och förvaltnings- ledning. Vidare kan vi stötta med releasehantering samt driva och införa projektkontor.
Systemutveckling Novatrox är specialister inom .NET-platformen och frontend- utveckling med erfarenhet av molnlösningar, utbildning och coaching av team.
Till våra kunder tillhandahåller vi tjänster såsom föreläsningar, utbildningar, coaching, mjukvaruarkitektur samt systemut-veckling. Bland Novatrox konsulter finns Microsoft MVP’er , Azure Insiders. Vi har välkända talare vid konferenser runtom i världen.
Augusti 2016
18
Novatrox Group
Om Novatrox Group Novatrox Group är ett svenskt Stockholmsbaserat IT- och managementkonsultföretag baserat i
Stockholm. tillhandahåller resurser inom kompetensområdena Verksamhetsutveckling, IT-
Arkitektur, Systemutveckling samt Ledning & Styrning. Vi levererar både resurskonsulter samt tar
oss an kortare eller längre helhetsåtaganden gentemot våra kunder.
Våra kunder återfinns idag bland både små och stora organisationer samt offentlig verksamhet.
Christer Ogenstad Charlotte Grip VD Säljchef [email protected] [email protected] 070 – 418 43 88 076 – 273 54 83