AngularJS [1st edition]

Klucz to klasyczna historia noir z romansem i intrygą osadzona w futurystycznym, niemal nieludzkim świecie. Leszek, wyrz

311 14 6MB

German Pages 354 [357] Year 2014

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
000......Page 1
_001......Page 2
_002......Page 3
_003......Page 4
_004......Page 5
_005......Page 6
_006......Page 7
_007......Page 8
_008......Page 9
_009......Page 10
_010......Page 11
001......Page 12
002......Page 13
003......Page 14
004......Page 15
005......Page 16
006......Page 17
007......Page 18
008......Page 19
009......Page 20
010......Page 21
011......Page 22
012......Page 23
013......Page 24
014......Page 25
015......Page 26
016......Page 27
017......Page 28
018......Page 29
019......Page 30
020......Page 31
021......Page 32
022......Page 33
023......Page 34
024......Page 35
025......Page 36
026......Page 37
027......Page 38
028......Page 39
029......Page 40
030......Page 41
031......Page 42
032......Page 43
033......Page 44
034......Page 45
035......Page 46
036......Page 47
037......Page 48
038......Page 49
039......Page 50
040......Page 51
041......Page 52
042......Page 53
043......Page 54
044......Page 55
045......Page 56
046......Page 57
047......Page 58
048......Page 59
049......Page 60
050......Page 61
051......Page 62
052......Page 63
053......Page 64
054......Page 65
055......Page 66
056......Page 67
057......Page 68
058......Page 69
059......Page 70
060......Page 71
061......Page 72
062......Page 73
063......Page 74
064......Page 75
065......Page 76
066......Page 77
067......Page 78
068......Page 79
069......Page 80
070......Page 81
071......Page 82
072......Page 83
073......Page 84
074......Page 85
075......Page 86
076......Page 87
077......Page 88
078......Page 89
079......Page 90
080......Page 91
081......Page 92
082......Page 93
083......Page 94
084......Page 95
085......Page 96
086......Page 97
087......Page 98
088......Page 99
089......Page 100
090......Page 101
091......Page 102
092......Page 103
093......Page 104
094......Page 105
095......Page 106
096......Page 107
097......Page 108
098......Page 109
099......Page 110
100......Page 111
101......Page 112
102......Page 113
103......Page 114
104......Page 115
105......Page 116
106......Page 117
107......Page 118
108......Page 119
109......Page 120
110......Page 121
111......Page 122
112......Page 123
113......Page 124
114......Page 125
115......Page 126
116......Page 127
117......Page 128
118......Page 129
119......Page 130
120......Page 131
121......Page 132
122......Page 133
123......Page 134
124......Page 135
125......Page 136
126......Page 137
127......Page 138
128......Page 139
129......Page 140
130......Page 141
131......Page 142
132......Page 143
133......Page 144
134......Page 145
135......Page 146
136......Page 147
137......Page 148
138......Page 149
139......Page 150
140......Page 151
141......Page 152
142......Page 153
143......Page 154
144......Page 155
145......Page 156
146......Page 157
147......Page 158
148......Page 159
149......Page 160
150......Page 161
151......Page 162
152......Page 163
153......Page 164
154......Page 165
155......Page 166
156......Page 167
157......Page 168
158......Page 169
159......Page 170
160......Page 171
161......Page 172
162......Page 173
163......Page 174
164......Page 175
165......Page 176
166......Page 177
167......Page 178
168......Page 179
169......Page 180
170......Page 181
171......Page 182
172......Page 183
173......Page 184
174......Page 185
175......Page 186
176......Page 187
177......Page 188
178......Page 189
179......Page 190
180......Page 191
181......Page 192
182......Page 193
183......Page 194
184......Page 195
185......Page 196
186......Page 197
187......Page 198
188......Page 199
189......Page 200
190......Page 201
191......Page 202
192......Page 203
193......Page 204
194......Page 205
195......Page 206
196......Page 207
197......Page 208
198......Page 209
199......Page 210
200......Page 211
201......Page 212
202......Page 213
203......Page 214
204......Page 215
205......Page 216
206......Page 217
207......Page 218
208......Page 219
209......Page 220
210......Page 221
211......Page 222
212......Page 223
213......Page 224
214......Page 225
215......Page 226
216......Page 227
217......Page 228
218......Page 229
219......Page 230
220......Page 231
221......Page 232
222......Page 233
223......Page 234
224......Page 235
225......Page 236
226......Page 237
227......Page 238
228......Page 239
229......Page 240
230......Page 241
231......Page 242
232......Page 243
233......Page 244
234......Page 245
235......Page 246
236......Page 247
237......Page 248
238......Page 249
239......Page 250
240......Page 251
241......Page 252
242......Page 253
243......Page 254
244......Page 255
245......Page 256
246......Page 257
247......Page 258
248......Page 259
249......Page 260
250......Page 261
251......Page 262
252......Page 263
253......Page 264
254......Page 265
255......Page 266
256......Page 267
257......Page 268
258......Page 269
259......Page 270
260......Page 271
261......Page 272
262......Page 273
263......Page 274
264......Page 275
265......Page 276
266......Page 277
267......Page 278
268......Page 279
269......Page 280
270......Page 281
271......Page 282
272......Page 283
273......Page 284
274......Page 285
275......Page 286
276......Page 287
277......Page 288
278......Page 289
279......Page 290
280......Page 291
281......Page 292
282......Page 293
283......Page 294
284......Page 295
285......Page 296
286......Page 297
287......Page 298
288......Page 299
289......Page 300
290......Page 301
291......Page 302
292......Page 303
293......Page 304
294......Page 305
295......Page 306
296......Page 307
297......Page 308
298......Page 309
299......Page 310
300......Page 311
301......Page 312
302......Page 313
303......Page 314
304......Page 315
305......Page 316
306......Page 317
307......Page 318
308......Page 319
309......Page 320
310......Page 321
311......Page 322
312......Page 323
313......Page 324
314......Page 325
315......Page 326
316......Page 327
317......Page 328
318......Page 329
319......Page 330
320......Page 331
321......Page 332
322......Page 333
323......Page 334
324......Page 335
325......Page 336
326......Page 337
327......Page 338
328......Page 339
329......Page 340
330......Page 341
331......Page 342
332......Page 343
333......Page 344
334......Page 345
335......Page 346
336......Page 347
337......Page 348
338......Page 349
339......Page 350
340......Page 351
341......Page 352
342......Page 353
343......Page 354
344......Page 355
345......Page 356
346......Page 357
Recommend Papers

AngularJS [1st edition]

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Philipp Tarasiewicz

·

Robin Böhm

u ar Eine praktische Einführung in das JavaScript-Framework

dpunkt.verlag

AngularJS

Ü ber die Autoren Philipp Tarasiewicz ist im Web groß geworden und arbeitet als

freiberuflicher Technologieberater, Autor, Sprecher und Coach. Seit einigen Jahren hat er sich auf den Bereich Enterprise JavaScript, ins­ besondere AngularJS, spezialisiert und unterstützt Unternehmen bei der Aus- und Fortbildung ihrer Mitarbeiter wie auch beim Ramp-up neuer Projekte. Gemeinsam mit Sascha Brink und Robin Böhm betreibt er das deutsche Portal zu AngularJS (AngularJS.DE).

Robin Böhm ist leidenschaftlicher Softwareentwickler, Berater und

Autor im Bereich der Webtechnologien und speziell zu Enterprise JavaScript. Er beschäftigt sich seit einigen Jahren intensiv mit der Erstellung clientseitiger Webapplikationen und unterstützt Unter­ nehmen sowohl bei der Aus- und Fortbildung von Mitarbeitern als auch bei der Umsetzung von Projekten. Außerdem ist er Mitgründer des Portals AngularJS.DE.

Zu diesem Buch- sowie zu vielen weiteren dpunkt.büchernkönnen Sie auch das entsprechende E-Book im PDF-Format herunterladen. Werden Sie dazu einfach Mitglied bei dpunkt.plus+: www.dpunkt.de/plus

Philipp Tarasiewicz Robin Böhm ·

AngularJS Eine praktische E i nführung i n das JavaScript-Framework

Philipp Tarasiewicz [email protected] Robin Böhm [email protected]

Lektorat: Rene Schönfeldt Copy Editing: Annette Schwarz, Ditzingen Satz: Da-TeX, Leipzig Herstellung: Frank Heidt Umsch laggestaltung: Helmut Kraus, www.exclam.de Druck und Bindung: M.P. Media-Print Informationstechnologie GmbH, 3 3 1 00 Paderborn

Bibliograf ische Information der Deutschen Nationalbibl iothek Die Deutsche Nationalbibl iothek verzeichnet diese Publikation in der Deutschen Nationalbibliografie; detaillierte bibliografische Daten sind im Internet ü ber http://dnb.d-nb.de abrufbar.

ISBN 978- 3-864 90- 1 54-6

1. Auflage 201 4 Copyright© 2014 dpunkt.verlag GmbH WieblingerWeg 1 7 691 23 Heidelberg

Die vorliegende Publik ation ist u rheberrechtlich geschützt. Alle Rechte vorbehalten. Die Verwendung der Texte und Abbildungen, auch auszugsweise, ist ohne die schriftliche Zustimmung des Verlags u rheberrechtswidrig und daher strafbar. Dies gilt insbesondere fü r die Vervielfä ltigung, Ü bersetzung oder die Verwendung in elektron ischen Systemen. Es wird darauf hingewiesen, dass die im Buch verwendeten Soft- und Hardware-Bezeichnungen sowie Markennamen und P roduktbezeichnungen der jeweiligen Firmen im Allgemeinen Wa renzeichen-, marken- oder patentrechtlichem Schutz unterliegen. Alle Angaben und P rogramme in diesem Buch wurden mit größter Sorgfalt kontrolliert. Weder Autor noch Verlag können jedoch fü r Schäden haftbar gemacht werden, die in Zusammenhang mit der Verwendung dieses Buches stehen. 5 4 3 21 0

Philipp Tarasiewicz: Für Thaddeus, Sylwia, Patrick und Laura

Robin ßöhm: Für Heike, Ernst, Kathi, Roswitha und Lisa

vii

Inhaltsverzeichnis

1

AngularJ S Schnellstart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

101

Zwei-Wege-Datenbindung: Boilerplate-Code war gestern 0 0 0

1

1.2

Direktiven: Eigene HTML-Eiemente und Attribute 0 0 0 0 0 0 0 0 0 0 0 0

5

1.3

Filter: Formatierte Ausgaben im Handumdrehen

00000000000

12

2

G rund lagen und Konzepte des Frameworks . . . . . . . . . . . . .

19

201

Leitkonzepte 0 0 0 0 0 0 0 0 0 0 0

202

0

0

1

00000000000000000 0000000000 00

19

20101

Modei-View-Controller oder Modei-View-ViewModel? 0

19

20102

Die Zwei-Wege-Datenbindung und Scopes 0 0 0 0 0 0 0 0 0 0 0

23

201.3

Inversion of Control und Dependency ln jection 0 0

25

201.4

Testbarkeit 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0

0

0

0 0 0

0 0

000

27

00000

0

Anwendungsbausteine 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0

29

00 00000 00000 0 0000 000000000000 0 0000 0 0000

29

0

0 0

0

0

20201

Module 0 0 0 0

20202

Controller 0 0 0 0 0

0 0

202.3

Models 0 0 0 0

0 0

000

0 0 0

202.4

Routen 0 0 0 0

0 0

0 00 00 00000 00000000000000000000000

20 2.5

Ansichten, Templates und Expressions 0 0 0 0 0 0

20206

Filter 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

0

0 0

0

0

0000000000000000000000000000000000

Services 0 0 0 0 0

20208

Direktiven 0 0 0 0

0 0

0 0

31

000

31

0000000

33

0 0 0

0000

36

000000 0000000000000000 000000000000000

42

0

20 2.7

000000000000000000000000000000000

30

0

0 0

0000000000 0000 00000 0 0000000000

49

3

Das BookMon key-Projekt . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

75

301

Auf geht's, ab geht's: Projekt- und Prozessvorstellung 0 0

000

75

302

Voraussetzungen 0 0 0

0000 00 00000 00000 0000 00000000000000000

76

3.3

Die Projektumgebung aufsetzen

000 00000 00 0000 00000000000

77

3.4

Pro jektstart: Detailansicht eines Buches 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

81

3.4 01

Das Template für die Detailansicht mit Expressions 0 0 0 0

83

3.4 0 2

Die ngHref-Direktive 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

84

3.4.3

Das Template mit der ngßind-

0 0

0 0

000

0 0 0

0 0 0

0

0 0

0

0

0

und ngßindTemplate-Direktive 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

84

3.4.4

Das Anwendungsmodul definieren 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

86

3.4.5

Die erste Route 0 0

87

3.4.6

Ein eigener Scope mit dem BookDetailsCtri-Controller

89

3.4 07

Der erste Test 0 0

90

0 0

0 0

000

0 0

000 000000000000 0 000000000000

000 00000 0000000 00000 00000 0000000000

viii

I n h a ltsverzeichnis

3.5

Listenansicht für Bücher............... ..... ............ ....

98

Als Erstes der Test ......... ..........................

99

3.5.1 3.5.2

Die Infrastruktur für die Listenansicht ........... ..... 1 04

3.5.3

Der BooklistCtri-Controller ..................... ..... 1 OS

3.5.4

Die ngRepeat-Direktive: Ausgabe eines Arrays im Template .. ............................... ....... 1OS

3.6

3.5.5

Der orderBy-Filter: Sortierung festlegen .............. 1 10

3.5.6

Der filter-Filter: Daten bequem filtern ................ 11S

Navigieren innerhalb der Anwendung ...................... 123 3.6.1

Die Standardroute mit $routeProvider.otherwise() .... 1 2 3

3.6.2

Als Erstes der Test ......... .......................... 124

3.6.3

Navigation mittels Hashbang-URLs................... 1 2 6

3.6.4

Die ngCiick-Direktive: Auf Klick-Events reagieren ...... 1 3 0

3.6.S

Der $1ocation-Service: Interaktionen mit der Adresszeile .......................... ........ 13 1

3.7

Der erste Service............................... .... ........ 13 4 3.7.1

Als Erstes der Test .................... .... ..... .... .. 13 4

3.7.2

Der BookDataService: Datenzugriffe kapsein.......... 1 4 1

3.7.3

Den BookDataService einbinden ..................... 1 4 6

3.7.4

Der $routeParams-Service: URL-Parameter auslesen ... 1 48

4

Die Anwendung erweitern

4.1

Der Administrationsbereich .............................. .. 1S3

. . . . . . . . . . . . . . . . . . . . . . . . . . . .

153

4.1.1

Als Erstes der Test ......... .......................... 1S4

4.1.2

Die CRUD-Operationen des BookDataService ......... 1S8

4.1.3

Die ngShow- und ngHide-Direktive: Inhalte bedingt anzeigen und ausblenden ...... ...... ............... 1 6 2

4.1.4

Formularverarbeitung und Validierung mit dem FormController und NgModeiController ............... .... 1 6 S

4.1.5

4.2

4.1.6

Die Funktion zum Editieren eines Buches ............. 181

4.1.7

Die Funktion zum Löschen eines Buches.............. 182

Kategorisierung durch Tags ...... ..... ................... .. 187 4.2.1

4.3

Templates mit der nglnclude-Direktive einbinden ..... 178

Das Datenmodell um Tags erweitern . ................ 188

4.2.2

Als Erstes der Test ................................. .. 190

4.2.3

Die Tokenfield-Direktive: Tags anlegen ............... 198

4.2.4

Die Tags-Direktive: Tags anzeigen .................. .. 2 07

Einen REST Web Service anbinden .......................... 2 1 1 4.3.1

Das BookMonkey-Backend ..... ...... ............... 212

4.3.2

HTIP-Kommunikation mit dem $http-Service ........ 213

4.3.3

Als Erstes der Test ................................... 2 1 6

4.3.4

$http im BookDataService nutzen .................... 2 2 1

4.3.5

Die Anwendung wiederinstandsetzen ................ 2 2 4

Inha ltsverzeichnis

5

Projektverwaltung und Automatisierung

5.1

Node.js: Die Ablaufumgebung für die Werkzeuge ... ......... 2 3 3

5.2

Frontend-Abhängigkeiten mit Bower verwalten ............ 237

5.3

5.4

5.5

6 6.1

233

.

5.2.1

Bower konfigurieren ................................ 2 4 1

5.2.2

Eigene Pakete mit Bower verwalten ................. 2 4 2

5.2.3

Ein privates Register erstellen ........................ 2 4 2

5.2.4

Mögliche Probleme mit Proxy-Servern

.

.

.............. 2 4 3

Aufgaben mit Grunt automatisieren ........................ 2 4 4 5.3.1

Aufgaben konfigurieren ............................. 2 4 5

5.3.2

Sinnvolle Pakete für die Entwicklung ................ 2 5 5 .

Tests mit Karma automatisiert ausführen ................... 2 59 .

5.4.1

Konfiguration ....................................... 260

5.4.2

Die wichtigsten Parameter ........................... 261

5.4.3

Initiale Karma-Konfiguration generieren .............. 263

5.4.4

Karma-Erweiterungen nutzen. ...................... 26 5

5.4.5

Tests direkt in WebStarm ausführen .................. 26 5

.

5.4.6

Test-Frameworks .................... ................ 267

5.4.7

Continuous Integration . .

. . .

... .

. .

.. .

. .

... . . .. .

. . . . . .

268

Yeoman: Ein definierter Workflow. ......................... 270 .

5.5.1

Was ist Yeoman? ...

. . .

.. .

. .

... .

. .

.. . . . . . ... .

. .

.. .

5.5.2

Yeoman installieren

. . .

.. .

. .

... .

. .

.. .

. .

... .

5.5.3

Anwendungsbausteinen generieren

5.5.4

Yo für AngularJS-Pro jekte

5.5.5

Generierte Grunt-Konfiguration ...................... 279

Debugg i n g

. . .

... .

. .

.

.. .

. .

. . . .. .

. . .

. .

270 27 1

................ 27 1 . .

... . . .. .

. . .

..

.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

27 2

283

Chrome Developer Tools ......................... ..... ..... 283 6.1.1

6.2

. . . . . . . . . . . . . . .

Der Elemente-Tab ................................... 284

6.1.2

Die Konsole ......................................... 285

6.1.3

Der Sources-Tab: JavaScript-Code debuggen ......... 287

Batarang: Einsicht in die laufende AngularJS-Anwendung .... 289 6.2.1

Scopes untersuchen ... ..... ...... ... ............... 290

6.2.2

Ausführungszeiten von Funktionen vermessen ....... 292

6.2.3

Serviceabhängigkeiten untersuchen ................ 293

.

.

6.3

Die WebStorm-IDE . ........................ ................ 295

7

Antworten auf häufig gestellte Fragen

7.1

AngularJS-Module: Wie strukturieren wir Anwendungen mit Modulen?

. . . . . . . . . . . . . . . . . .

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.. .

... .

..

7.1.1

Module in AngularJS

7.1.2

Wann ist Modularisierung sinnvoll?

7.1.3

Ordnerstruktur

. .

... .

. .

. .

.. .

. .

. .

. . . . . . . . . . . . . . . . .

. . . .

. .. .

. . . . . . . . . . . . . . . . .

. . . . . . . . . .

..

. .

... .

. .

299 299 299 30 1 304

ix

X

Inha ltsverzeich n i s

702

7.3

7.4

I ndex

0

306

70201

Asynchronität und nichtblockierende Aufrufe 0 0 0 0 0 0 0

306

Promises: Wie gehen wir mit Asynchronität um?

0

0

00

0 0

0

000

0

0

0

70202

Was sind Promises? 0 0

0

0

0

000 00000000000000000000

702.3

Promises in AngularJS

0 0

0

000

702.4

Promises in AngularJS testen

0

0

00

0

307

0

0000000000000000000000

0

3 11

0

0

0

0

0000 000000000000000000

3 15

AngularJS und RequireJS: Ist diese Kombination sinnvoll? 0 0 0 0

3 18

7.301

Was ist RequireJS?

0

0

7.30 2

Ein Beispiel mit RequireJSo

0

0

000

0

0

000

0

3 19

7.3.3

AngularJS und RequireJS

0

0

0000

0

0

00000000000000000000

322

7.3.4

Testen mit RequireJS 0 0 0 0

0 0

0

000

0

0

000000000000000000 00

3 27

7.3.5

Die Antwort 0 0

0 0

00 000

0

0

0

0 0 0

0

0 0 0 0

0 0

00 0

000

000 00000 0 000000000000000

0 0

0

0

0000000000 0000

0000000000

0

0

0

3 18

000000

331

Mobile: Unterstützt AngularJS mobile Endgeräte? 0 0 0 0 0 0 0 0 0 0 0

333

7.4 0 1

Touch-Events unterstützen

334

7.4 02

Die Swipe-Direktiven

7.4.3

Der $swipe-Service 0 0 0 0 0 0 0 0 0 0

0

00

0

0 0

0

0000 0000000000000000000 00

000000 0

0

00

0 0

0000

0

0

000

0

0

000

0

0

0 0 0 0

0

0

0

0000000

0 0 0

00

0 0 0

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

3 35 3 36

341

1

1

AngularJS Schnellstart

D ieses K apitel soll i n Form von k urzen Beispielen die mächtigsten Features von AngularJS demonstrieren. Außerdem wollen wir ein ers­ tes Gefühl dafür vermitteln, wie typische Quellcodefragmente i n ei­ ner AngularJS-Anwendung a ussehen. Die Beispiele sind a bsichtlich sehr einfach gehalten, um sie ohne tiefe Kenntnisse des Frameworks verste­ hen und ausführen zu können. Wir werden nicht alle Einzelheiten er­ k lären, sondern die Erklärungen mit Absicht recht k urz halten. Wenn Sie das Buch durchgearbeitet haben, werden Sie die Beispiele in ihren E inzelheiten verstehen.

1 .1

Zwei-Wege-Date n b i n d u n g : Boilerplate-Code wa r gestern

E ines der ausschlaggebenden Features, die für AngularJS sprechen, ist sicherlich die Zwei- Wege-Datenbindung (Two-Way D ata-Bindi ng). Die Zwei-Wege-Datenbindung sorgt dafür, dass sich Änderungen im Datenmodell automatisch auf die entsprechenden Elemente i n der An­ sicht a uswirken und sich Benutzerinteraktionen innerhalb der Ansicht a uch automatisch in dem D atenmodell widerspiegeln. D urch diesen a utomatischen Abgleich in beide Richtungen sprechen wir von einer Zwei-Wege-Datenbindung. Somit können wir uns sehr v iel >>Klebe­ Code



Listing 1 - 1 Zwei-Wege­ Datenbindung mit einem Eingabefeld

1

2

AngularJS Schnel lstart

Name :

Hel l o { {yourName } } !



Ohne auf die Einzelheiten detailliert einzugehen, sehen wir, dass Lis­ ting 1-1 eine einfache HTML-Datei enthält, die ein i nput-Eingabefeld beinhaltet und eine Ausgabe in Form einer h l-Überschrift definiert. Wenn wir dieses Beispiel nun in einem Browser ausführen (siehe Ab­ bildung 1-1 ), dann stellen wir fest, dass sich d ie Überschrift jedes Mal a utomatisch aktualisiert, wenn wir den Text in dem Eingabefeld än­ dern. Der Mechanism us, der das für uns übernimmt, ist also die Zwei­ Wege-Datenbindung. Abb. 1 - 1 Ausgabe des Beispiels aus Listing 7-7 in Chrome

C

Cl localhost:9000/2way_binding_simple.htm l � "{:J

{oi)

_

Name: AngularJS

Hello AngularJS! ng-mode/ ist eine Direktive.

Für d ie Herstellung der Zwei-Wege-Datenbindung ist i n diesem Beispiel das Attribut ng-mode 1 verantwortlich. Oder anders erklärt: Das, was für HTML-Kenner aussieht wie ein unbekanntes HTML-Attri but, ist i n der Welt von AngularJS eine sogenannte Direktive. Die ngMo­ de/-Direktive stellt eine Zwei-Wege-Datenbi ndung zwischen dem Ein­ gabefeld und der Variable yourName her. Damit ändert s ich der Wert dieser Variable automatisch entsprechend der Eingabe im E ingabefeld. Wo die Variable yourName definiert ist, ist zunächst einmal unerheblich.' Zum Verständnis dieses Beispiels fehlt j etzt noch die Erklärung zu dem Ausdruck in den doppelten geschweiften Klammern innerhalb des 1 Für Ungeduldige: AngularJS nutzt das Vie wM o de l-Konzept zur Realisie­ rung der Zwei-Wege-Datenbindung. Dabei tragen die sogenannten ViewMo­ dels innerhalb von AngularJS den Namen Scopes. Die Variable yourName ist also innerhalb eines solchen Scopes definiert.

1 .1

hl-Tags. Dabei handelt es sich um eine sogenannte Expression. Das ist der AngularJS-Mechanismus, mit dem wir Ausgaben produzieren können. I nsbesondere erlauben uns Expressions, Variablenwerte a us­ zugeben. Sie unterliegen dabei ebenfalls der Zwei-Wege-Datenbindung. Wenn sich also der Wert der Variable yourName verändert, dann wird die Expression neu ausgewertet, was schließlich dazu führt, dass sich die Ansicht automatisch aktualisiert. Auf diese Weise ü berträgt das Frame­ work den Inhalt des Eingabefeldes automatisch in die Überschrift. Wir sollten an dieser Stelle noch die ng-app-Direktive erwähnen, die in diesem Beispiel das htm 1 -Tag annotiert. Mithilfe dieser Direktive teilen wir AngularJS mit, welchen Teil des D O M (Document Object Model) das Framework als AngularJS-Anwendung betrachten soll. Da­ durch dass wir die Direktive hier an das html -Tag schreiben, teilen wir AngularJS also mit, dass unsere Anwendung auf dem kompletten DOM operieren soll, weil das html -Tag unser Wurzelknoten ist. Wir könnten die ng-app-Direktive a ber auch an einen tiefer verschachtelten DOM­ Knoten schreiben und nur für diesen entsprechenden Unterbaum des D O M eine AngularJS-Anwendung a usweisen. Somit würde das Frame­ work a lle Ausdrücke außerhalb dieses Unterbaums unangetastet lassen und ignorieren. Wir können a uch mehrere Geschwister-Knoten des D O M mit der ng-app-Direktive a nnotieren, um in einem HTML-Dokument mehre­ re autonome AngularJS-Anwendungen zu definieren. Für den Großteil der Applikationen hat diese Verwendung wenig Sinn, weil die einzel­ nen Anwendungen z unächst einmal keine Möglichkeiten besitzen, um miteinander zu interagieren. Dennoch sollten wir diesen Aspekt hier erwähnen, weil die offizielle Webseite zu AngularJS2 diese Eigenschaft a usnutzt, um eine Vielzahl von in sich geschlossenen Beispielen z u prä­ sentieren. Ei n zweites Beispiel

Durch das Feature der Zwei-Wege-Datenbindung lassen sich bereits viele Anwendungsfälle elegant umsetzen, ohne eine einzige Zeile Java­ Script-Quellcode schreiben zu m üssen. Um das Ganze m it einem wei­ teren Beispiel zu belegen, sch auen wir uns den nachfolgenden Color­ Picker an. Ein Color-Picker ist eine UI-Komponente, die dem Benutzer die Auswahl einer Farbe erleichtert, indem sie ihm ein sofortiges visuel­ les Feedback bezüglich der a ktuellen Belegung der RGBA-Werte liefert.

2

3

Zwei-Wege-Datenbindung: Boilerplate-Code war gestern

http://angularjs.org/

Expressions

ng-app definiert eine AngularJS-Anwendung.

Mehrere AngularJS-An wendungen in einem HTML-Dokument

4

Listing 1-2 Zwei-Wege­ Datenbindung mit HTML5-Schiebereglern

1

AngularJS Schnellstart

< ! DOCTYPE h tml >



R :
G :
B :
A : < i nput type = " range" name = " col or_a " mi n = " O " max= " l " step= " O . O l " ng -model = " a " >



E ine e infache Umsetzung solch eines Color-Pickers sehen wir in Lis­ ting 1-2. Vom Grundaufbau entspricht das Beispiel dem Beispiel da­ vor. Wir definieren vier HTMLS-Schieberegler, i ndem wir innerhalb des i nput -Tags diesmal dem type-Attri but den Wert range zuweisen. D rei Schieberegler brauchen wir für die Steuerung der Rot-, Grün- und Blau-Werte unseres Color-Pickers und einen weiteren für die Steuerung des Alpha-Kanals. Mit den Attributen mi n und max können wir den Minim um- und Maxim umwert des Reglers festlegen. Das s tep-Attribut gibt die Schrittweite an. Auch in d iesem Beispiel n utzen wir die ngMode/-Direktive, um zwi­ schen den Sch iebereglern und den entsprechenden Scope-Variablen r, g, b und a eine Zwei-Wege-Datenbindung herzustellen. Weiterhin können wir erkennen, dass auch Expressions zum E insatz kommen. D iesmal produzieren wir mit den Expressions aber keine direkte Ausgabe, son­ dern setzen damit die einzelnen Komponenten der backg round-co 1 o r­ CSS-Eigenschaft des div-Elements, i n dem die Farbvorschau gerenden werden soll. Das Ergebnis ist recht beeindruckend. Dadurch dass wir zwischen den vier Reglern und den vier Einzelkomponenten der rgba -Eigenschaft

1 .2

!! localhost:9000/2way_binc C

5

D i rektiven: Eigene HTML-Eiemente und Attribute

x

Cl localhost:9000/2way_binding_colorpicker.html

auf Basis der Scope-Variablen r, g, b und a eine Zwei-Wege­ D atenbindung geschaffen h aben, haben wir mit übersichtlichem Aufwand einen simplen Color-Picker m it Live-Farbvorschau erstellt (siehe Abbildung 1-2 ) . Jedes Mal, wenn wir einen der vier Regler ver­ schieben, ändert sich entsprechend die Farbe in unserem di v-Element für die Farbvorschau. Im Vergleich z u dem Beispiel davor gibt es hier allerdings noch eine kleine Erweiterung. Wir nutzen die nglnit-Direktive, um die Variablen r, g, b und a mit Initialwerten z u belegen.

Abb. 1-2 Ausgabe des Color-Picker-Beispiels aus Listing 7-2 in Chrome

ng-init zur lnitialisierung von Scope-Variablen in einem Template

H i nweis zur n g l n it-Direktive

Die Definition von Initialwerten innerhalb eines Templates mithilfe von ng-i ni t gehört in größeren Projekten zwar nicht zum guten Ton, weil wir damit einen Teil der Logik ins Template verlagern. Für unsere Zwecke können wir diese Variante aber nutzen, um in dem einfachen Beispiel das Schreiben des äquivalenten JavaScript-Codes zu vermeiden.

1.2

D i rektive n : Eigene HTM L-Eiemente u n d Attri bute

Ein weiteres mächtiges Feature und a ußerdem eines der Alleinstell u ngs­ merkmale von AngularJS sind Direktiven. Wie wir bereits in den beiden vorangegangenen Beispielen gesehen haben, sind Direktiven a llgegen­ wärtig in AngularJS. Das Framework selber baut sehr stark a u f diesem Konzept a uf. Mithilfe von Direktiven erweitern wir HTML um eigene Elemente und Attribute. D iesen Mechanism us können wir für viele interessan­ te Dinge verwerten. Wir können uns z. B. für unsere Anwendung eine

1

6

AngularJS Sch nellstart

eigene Tag-Sammlung definieren, mit der wir große Teile der Anwen­ dung deklarativ beschreiben können. Oder wir könnten eine eigene wie­ derverwendbare Bibliothek schreiben, die HTML um eine Vielzahl spe­ zieller VI-Komponenten erweitert, die in der Domäne unserer Applika­ tion unverzichtbar sind. Denkbar sind Tags wie , oder a uch < i nput auto-compl ete = " da t a " >. Die entsprechende Logik wür­ den wir dann innerhalb der Direktiven kapseln. Ein Beispiel

In dem nachfolgenden Beispiel erfinden wir ein -Tag, das die Logik aus dem Color-Picker-Beispiel kapseit und somit wiederver­ wendbar macht. D a be i haben wir uns dafür entschieden, dass unsere D irektive zwei Anforderungen erfüllen m uss. Es soll eine Möglichkeit geben, die initiale Farbe des Color-Pickers zu konfigurieren. A ußerdem wollen wir bei Farbänderungen von der Direktive benachrichtigt wer­ den, um darauf in irgendeiner Form reagieren zu können. Das Tem pl ate in Listing 1-3 zeigt, wie wir unsere eigene -Direktive den Anforderungen entsprechend nutzen wollen. Listing 1-3 Verwendung der colorPicker-Direktive

< ! DOCTYPE h tml >



Angul a rJS Schnel l s tart Col orPi c ker





Unsere D i rektive wird genau wie die Standard-HTML-Tags benutzt, i ndem wir die gewöhnliche Tag-Syntax verwenden und einige selbstde­ finierte Attribute setzen. Im Einzelnen sind das die Attribute i n i t - r, i n i t-g, i n i t - b, i n i t - a und on-change. Entsprechend unserer Anfor­ derung nutzen wir die i ni t-Attribute, um den initialen R GB- und

1 .2

7

D i rektiven: Eigene HTML-Eiemente und Attribute

Alpha-Wert des Color-Pickers zu konfigurieren. M ithilfe des On-c hange­ Attributs spezifizieren wir eine Callback-Funktion3 , die die Direktive im Falle eine Farbänderung aufrufen soll. Somit haben wir von a u­ ßen die Möglichkeit, a uf eine Farbveränderung zu reagieren. Wir tei­ len der D irektive a lso m it, dass onCo 1 orChange ( ) die Callback-Funktion ist, die die Direktive aufrufen soll . Definiert ist diese Funktion in dem Mai nCtrl -Controller. AngularJS weiß, dass dieser Controller in dem DOM-Unterbaum des -Tags gelten soll, weil wir d ieses Tag mit der ng-contro l l er-Direktive annotiert haben. Was das genau heißt, soll uns an dieser Stelle zunächst nicht weiter interessieren. In dem Template in L isting 1-3 nutzen wir a ußerdem die ngApp­ D i rektive anders als in den Beispielen davor, nämlich mit der Anga­ be eines zu ladenden Moduls. Wird - wie in den Beispielen davor kein Modul angegeben, lädt AngularJS für uns ein Default-Modul. Für kleine Beispiele ist das vollkommen ausreichend, aber in größeren An­ wendungen mit mehreren Modulen funktioniert diese Methode nicht, weil dem Framework nicht klar ist, welches Modul geladen werden soll, nachdem der Browser das DOM vollständig geladen h at. Wir wol­ len uns ab sofort an Best Practices halten und teilen AngularJS in Lis­ ting 1-3 m it, dass das Framework das col orPi c kerApp-Modul l aden soll, sobald der Browser das D O M geladen hat. Entsprechend müssen wir dieses Modul und den zuvor genannten Ma i nCtrl -Controller i n unserem JavaScript-Code definieren. var Col orPi c kerApp

=

angul a r . modu l e ( ' col orPi c kerApp ' , [] ) ;

col orPi c kerApp . control l er ( ' Ma i nCtrl ' , funct i on ( $ s cope) $ scope . onCol orChange funct i on ( r , g , b , a ) { consol e . l og ( ' onCo l o rChange ' , r , g , b , a ) ; }; }) ; =

Interessant an dieser Definition ist die Tatsache, dass AngularJS dem Ma i nCt rl -Controller einen Scope ü bergibt. Den Begriff des Scopes ( Geltungsbereich) haben wir i n dem ersten Beispiel bereits eingeführt. Ein Scope definiert alle Variablen und Funktionen, die in einem bestimmten Kontext gültig sein sollen, und ist weiterhin maßgeblich dafür verant­ wortl ich, dass in d iesem Kontext die Zwei-Wege-Datenbindung genutzt 3Eine Callback-Funktion ist eine Funktion, die eine Komponente A einer Komponente B übergibt, damit A auf bestimmte Ereignisse innerh a l b von B rea­ gieren kann. Dabei n immt B die Cal l back-Funktion entgegen und ruft sie beim Auftreten bestimmter Ereignisse auf, um A darüber z u informieren. Bildlich ge­ sprochen ruft die Komponente B die Komponente A im Falle eines bestimmten Ereignisses zurück (deswegen >>Callback«).

Listing 1-4 Definition des colorPickerApp-Moduls inkl. MainCtri­ Controller

Scope

1

8

Angu larJS Schnellstart

werden kann. In dem Framework gibt es einige Komponenten, für die im Falle eines Aufrufs ein neuer Scope erzeugt wird. Ein Controller ist eine dieser Komponenten. Somit übergibt AngularJS dem Control­ ler seinen Scope. Auf diesen Scope können wir innerhalb des Control­ lers mittels des übergebenen $scope-Parameters zugreifen. Hiermit de­ finieren wir in dem Scope des Mai nCt r 1 -Controllers die angesprochene Callback-Funktion onCo l orChange ( ) , die die vier Parameter r, g, b und a erwartet und von unserer col orPi cker-Direktive bei Farbveränderungen aufgerufen wird. Bei einer Farbveränderung wollen wir a lso lediglich einen Text in der Konsole des Browsers a usgeben. Das D i rektiven-Template

Definition der colorPicker-Direktive

Listing 1-5 Definition des Templates der colorPicker-Direktive

Nun definieren wir die eigentliche co l orPi c ker- Direktive. Dazu fangen wir mit dem Tem plate an, das sich hinter dem -Tag ver­ bergen soll. R :
G : < i nput type = " range" mi n = " O " max = " 2 5 5 " step= " l " ng -model = " g " >
B :
A :

Wie wir in Listing 1-5 unschwer erkennen können, ist das Template identisch mit dem Template aus dem Color-Picker-Beispiel, bei dem wir keine eigene Direktive definiert haben ( siehe L isting 1-2) . Die Direktivendefinition

Kommen wir nun zu dem i nteressanten Teil des Beispiels, der eigentli­ chen D i rektivendefinition.

1 .2

Direktiven: Eigene HTML-Eiemente und Attribute

col orPi c kerApp . d i rect i ve ( 1 COl orPi cker 1 , funct i on ( ) { return { s cope : r : 1 @i n i t R 1 , g : 1 @i n i tG 1 , b : 1 @ i n i tß 1 , a : 1 @i n i tA 1 , onChange : 1 & 1 }. restri ct : 1 E 1 , temp l ateUrl : 1 Col orPi c kerTempl ate . html 1 , l i n k : funct i on ( s cope ) { var COLORS = [ 1 r 1 , 1 9 1 , 1 b 1 , 1 a 1 ] ;

9

Listing 1-6 Definition der colorPicker-Direktive

COLORS . forEach ( funct i on (val ue) { s cope . $watch (val u e , funct i on ( newVa l ue , ol dVal ue) i f (newV a l ue !== ol dVal ue) { i f (angul a r . i s Funct i on ( s cope . onChange ) ) { scope . onChange ( generateCo l o rChangeObj ect ( ) ) ;

}); }); var generateCol o rChangeObject var obj = { } ;

funct i on ( ) {

COLORS . forEach ( functi on ( v a l ue) obj [ va 1 ue] s cope [ v a 1 ue] ; }) ; =

ret urn obj ; }; }; }) ;

Wir wollen die D i rektivendefinition in L isting 1-6 nicht im Detail be­ sprechen, sondern nur auf einige Kernaspekte eingehen, um ein Gefühl dafür zu vermitteln, wie eigene Direktiven implementiert werden kön­ nen. Wir führen eine neue Direktive mit der d i recti ve { ) -Funktion von AngularJS ein. Der erste Parameter gibt dabei den Namen unserer Direktive an. Dieser Name ist unmittelbar dafür verantwortlich, wie das entsprechende HTML-Tag bzw. HTML-Attribut heißen wird, das die D i rektive in unseren Templates repräsentiert. Dabei lässt sich bereits

Namenskonventionen bei Direktiven

1

10

AngularJS Schnellstart

in d iesem Beispiel eine Besonderheit erkennen. Das HTML-Tag unse­ res Color-Pickers l autet und der D irektivenname ist co1 orPi c ker. Für die Abbildung von dem D irektivennamen zum HTML­ Eiement bzw. HTML-Attribut verwendet AngularJS eine interne Regel. Regel zur Benen n u n g von Direktiven

Wenn der Name einer Direktive aus mehreren Wörtern besteht, geben wir ihn mit der Game/Case-Notation an. Die Abbildung zum entsprechen­ den HTML-Tag bzw. HTML-Attribut erfolgt, indem statt der CameiCase­ Notation die SnakeCase-Notation verwendet wird. Als Trennzeichen sind dabei der Doppelpunkt ( » : « ) , Bindestrich ( ) und U nterstrich (»_> Boilerplate-Code

Listing 2-6 Ein Template mit einer einfachen Expression

x Wie wir in dem Beispiel in Listing 2-6 erkennen können, ist eine Ex­ nre sion ein Ausdruck, den wir innerhalb eines Tempiares in doppelten �e chweiften Klammern aufschreiben. Innerhalb dieser Klammern kön­ '1en wir eine Sub-Menge von JavaScript verwenden, um Ausgaben zu ..., roduzieren. Dabei sollten wir erwähnen, dass der Ausdruck zur Eva­ _uierung nicht einfach nur durch ein JavaScript eval ( ) eva l uiert wird. omit ergeben sich für Expressions einige Besonderheiten:

l.

Die Auswertung erfolgt n icht wie bei eval () gegen das globale wi ndow-Objekt, sondern gegen den Scope, der im Kontext des Tem­ piares gültig ist.

, Expressions, die zu undefi ned oder nul l eva l uieren, produzieren kei­ ne Ausgabe, weil potenzielle Exceptions wie ReferenceError oder Ty­ peError a bgefangen und ignoriert werden. �-

Kontrollfluss-Anweisungen wie Fallunterscheidungen oder Schleifen können nicht benutzt werden.

tr können in Expressions demnach auch einfache Berechnungen ..:urchführen (siehe Listing 2-7).


o i v>

tr haben bereits erwähnt, dass Expressions in erster Linie den Zweck -:ullen, Scope-Daten in Tempiares auszugeben. In Verbindung mit der Z .,·ei-Wege-Datenbindung heißt das, dass AngularJ S dafür sorgt, dass 'le Expression automatisch neu evaluiert wird, wenn sich der entspre­ - ende Scope-Wert verändert hat. Somit aktualisiert das Framework .a .. -omatisch unsere Ansichten, wenn sich die zugrunde liegenden Da­ -'1 verändert haben.



Listing 2-7 Eine einfache Berechnung in einer Expression

36

2

Grundlagen und Konzepte des Frameworks

Was Expressions so richtig mächtig macht, ist die Tatsache, dass wir sogenannte Formatierungsfilter nutzen können, um Ausgaben zu formatieren. Eine genauere Erläuterung folgt im anschließenden Unter­ kapitel. 2.2.6

Fi lter

In AngularJS gibt es zwei Arten von Filtern. Auf der einen Seite haben wir die im letzten Kapitel erwähnten Formatierungsfilter. Auf der an­ deren Seite befinden sich die Collection-Filter, die in Verbindung mit der ngRepeat-Direktive eingesetzt werden können, um Collections zu filtern bzw. zu transformieren. Collections sind in diesem Zusammen­ hang entweder Arrays oder Objekt-Hashes. Formatierungsfilter

Wie bereits angesprochen, nutzen wir Formatierungsfilter i nnerhal b von Expressions, um den evaluierten Ausdruck nachträglich z u forma­ tieren oder zu transformieren. Dazu benutzen wir in Expressions die Pipe-Syntax. Listing 2-8 Eine Expression mit angewandtem Filter

Hel l o , { { user . n ame I u ppercase } } !
F i ve years ago you were { { user . age - 5 } } years ol d .



In dem obigen Beispiel wird auf den eva l uierten Ausdruck user. name der uppercase-Filter angewandt. Dieser Filter ist Tei l der Filtersamm­ lung, die AngularJS mitbringt, und führt dazu, dass die eingegebene Zeichenkette in Großbuchstaben transformiert wird. Die resultierende Ausgabe in der Ansicht wäre diesem Template zufolge also: Listing 2-9 Resultat des uppercase-Filters

Hel l o , JOHN DOE ! F i ve years ago you were 22 yea rs ol d .

Neben dem uppercase-Filter bringt AngularJS noch weitere nützliche Formatierungsfilter mit, die a l lerdings erst später vorgestellt werden. Darüber hinaus gibt uns das Framework die Möglichkeit, a uch eigene Formatierungsfilter zu definieren.

2.2

Anwendungsbausteine

angul a r . modu l e ( ' myApp ' ) . fi l te r ( ' a l terna t i ngCase ' , funct i on () { ret u rn funct i on ( i nput ) { var output tmp ;

37

Listing 2- 1 0 Unser alternatingCase-Filter

for (var i = 0 ; i < i nput . l engt h ; i ++) { tmp = i nput . charAt ( i ) ; i f ( i % 2 === 0) { output += tmp . toUpperCase ( ) ; e l se { output += tmp . to lowerCase ( ) ;

return outpu t ; }; }) ;

l:ber den Sinn unseres a l ternati ngCase Filters kann man sich streiten. Er oll hier für uns lediglich einen didaktischen Zweck erfü l len. Nun, Jlles, was der F ilter macht, ist bei einer eingegebenen Zeichenkette für eden Buchstaben zwischen Groß- und Kleinschreibung zu alternieren. Die Program mierschnittstelle für Formatierungsfilter ist dabei sehr ein­ :Jch. Wir erhalten per Funktionsparameter den evaluierten Ausdruck ..1er Expression und können nun innerhalb unserer Filterfu nktion jegli­ -he Art von Verarbeitung auf Basis der Eingabe vornehmen. Zu guter �.etzt müssen wir innerhalb der Filterfunktion eine Ausgabe zurück­ _eben, d ie schließlich zur Anzeige gebracht wird. Wir nutzen unseren : ternat i ngCase Filter genauso wie die internen Formatierungsfilter von \ngularJS. -

'

-

< : i v>

Hel l o , { { user . name I a l ternati ngCase } } !
Fi ve yea rs ago you were { { user. age - 5 } } yea rs ol d . < p> � d i v>

Listing 2- 1 1 Unseren a/ternatingCase-Fi/ter verwenden

1e Ausgabe ist wie zu erwarten : Hel l o , JoHn dOe ! Fi ve years ago you were 22 years o l d .

Listing 2- 12 Resultat unseres a/ternatingCase-Filters

38

2

Grundlagen und Konzepte des Frameworks

Collection-Filter

Neben den Formatierungsfiltern, die innerhalb von Expressions benutzt werden, gibt es in AngularJ S auch Collection-Filter. Vom API her sind die Collection-Filter zwar genauso aufgebaut wie die Formatierungsfil­ ter, a ber wir benutzen sie in einem anderen Kontext, nämlich in Kom­ bination mit der ng-repeat-Direktive. An dieser Stelle müssen wir noch n icht unbedingt wissen, was Di­ rektiven sind und was wir mit ihnen anstellen können. Wir sollten al­ lerdings erwähnen, dass die ng-repeat-Direktive dazu genutzt wird, um innerhal b eines Tempiares die Elemente eines Arrays oder eines Objekt­ Hashes a uszugeben. Wenn man so will, ist ng-repeat a lso das deklara­ tive Gegenstück zu Schleifen. Listing 2- 13 Verwendung von ng-repeat

Fami 1 y members of { { user . name } } :

{ { member } }

In dem oberen Beispiel referenziert die Scope-Variable fami 1 y ein Array mit Zeichenketten, das die Namen der Familienmitglieder enthält. Die ng-repeat-Direktive sorgt nun dafür, dass in der Ansicht für jedes Ele­ ment in dem Array ein entsprechendes -Tag gerendert wird. Somit haben wir a ls Resultat eine Ansicht, in der die Familienangehörigen von John Doe angezeigt werden. Der HTML-Code, der sich nach der Auf­ lösung der ng- repeat-Direktive zur Laufzeit ergibt, sieht a lso in etwa so a us: Listing 2- 14 Resultierender HTML-Code der Ansicht

Fami 1 y members of John Doe :

James Doe C 1 ari ssa Doe Ted Doe

Die Frage, die sich unmittelbar stellt, ist: Können wir diese Ausgabe filtern, sodass nur eine Tei lmenge der Collection zur Ausgabe kommt? Gena u an dieser Stelle kommen die Collection-Filter ins Spiel. Sie sor­ gen dafür, dass die Collection entsprechend bestimmter Kriterien gefil­ tert wird oder i hre Elemente auf eine bestimmte Art und Weise trans­ formiert werden. In Verbindung mit ng- repeat nutzen wir dafü r wieder die Pipe-Notation. Das folgende Beispiel demonstriert den fi 1 ter-Filter

2.2

Anwendung sbausteine

39

on AngularJS. Zugegebenermaßen ist d ieser Filter nicht sehr einfalls­ ·eich benannt worden, aber er erfüllt seinen Zweck.

Fami 1 y members of { { u ser . name } } :

{ { member } }

< di v>

Listing 2- 15 Verwendung von Filtern innerhalb von ng-repeat

"ir teilen dem fi 1 ter-Filter a lso mit, dass wir nur die Familienmitglie­ ..:er a usgeben wollen, die das Wort clarissa im Namen enthalten. Dabei nrerscheidet der Filter nicht zwischen Groß- und K leinschreibung. Es gibt noch weitere Collection-Filter im Standardrepertoire von -\ngularJS. Darunter fallen der 1 i m i tTo- und orderßy-Filter, die einer et­ as einfallsreicheren Namensgebung unterliegen. Wie sich diese beiden .- drer auf die Ausgabe einer Collection a uswirken, sollte anhand des -a mens klar sein. Dennoch werden wir sie später im Buch in unserer neispielapplikation nochmals a u fgreifen und genauer erklären. Wenn d iese Filter für unsere Anwendungsfälle n icht a usreichen, ha­ . n wir natürlich a uch die Möglichkeit, weitere Collection-Filter zu Jefinieren. Im Folgenden definieren wir einen eigenen Collection-Filter > Look and Fee! und . Wobei bei dem col orPi cker aufgrund der [enge möglicher Trennzeichen noch weitere Aufrufkombinationen 'l t tehen, nämlich: und . Wie eingangs im Schnellstart-Kapitel bereits angerissen, gibt es zwei eitere valide Möglichkeiten, Angular]S-Direktiven a u fzurufen. Und var einerseits mit dem Präfix data- und andererseits mit dem Präfix x - . er Hintergrund ist, dass ältere Browser selbst definierte Tags bzw. At·-·bute nur dann zulassen, wenn sie eines dieser Präfixe besitzen. Dem­ �ach könnten wir z. B. die spreadsheet-Direktive in unseren Tempiares Jch mittels oder nutzen. Das gilt �arürlich auch gleichermaßen für die fiktive chart-Direktive wie a uch ..:r unseren colorPicker.





·

> >_

H i nweis zur Benenn u n g von D i rektiven

Es ist wichtig, diese Namenskonvention zu kennen und zu verstehen, um bei der Erstellung eigener Direktiven keine bösen Überraschungen zu er­ eben. Insbesondere ist es nicht empfohlen, eigene Direktiven so zu be­ nennen, dass sie mit den impliziten Konventionen kol lidieren. Schnell ver­ gisst man, dass AngularJS weitere Aufrufmöglichkeiten für uns definiert, und wundert sich dann, warum eine eigene Direktive namens xSpreads­ heet nicht über das Tag erreichbar ist. Erst der Aufruf mittels führt uns zu dem gewünschten Ergebnis.

51

Listing 2-25 Definition der colorPicker-Direktive

52

2

Grundlagen und Konzepte des Frameworks

Definition mithi lfe einer Link-Funktion

AngularJ S erwartet, dass die Factory-Funktion, d ie wir als zweiten Parameter übergeben, entweder eine Funktion oder ein sogenanntes Direktiven-Definitions-Objekt zurückgibt. Wenn wir eine Funktion zu­ rückgeben, dann stellt diese Funktion die sogenannte Link-Funktion der Direktive dar. I m Falle des Direktiven-Definitions-Objekts können wir durch eine Menge von Eigenschaften sehr feingranular steuern, wie sich die Direktive verhalten soll. Somit stellt die Rückgabe einer Funktion ( Link-Funktion) den einfachen Fall und die Rückgabe eines Direktiven-Definitions-Objekts entsprechend den komplexen Fall dar. Listing 2-26 Definition der colorPicker-Direktive durch Rückgabe einer Link-Funktion

angul a r . modu l e ( ' myApp ' ) . d i recti ve ( ' col orPi c ker ' , funct i on ( ) { retu rn funct i on ( scope , e l ement , attrs ) { conso l e . l og ( 11 I t ' s me , co l orPi c ker ! 11 ) ; } }) ;

In Listing 2-26 betrachten wir zunächst einmal den einfachen Fall, in­ dem wir in der Factory-Funktion unserer colorPicker-Direktive eine Funktion, also die sogenannte Link-Funktion, zurückgeben. Die Link­ Funktion wird für jede Instanz unserer Direktive genau einmal aufge­ rufen. Somit haben wir innerhalb der Link-Funktion z. B. die Möglich­ keit, Event-Ha ndler zu registrieren, DOM-Manipulationen zu vollzie­ hen und sonstige Routinen auszuführen, die instanzbehaftet sind. Kurz­ um: Die Link-Funktion erlaubt es uns, für jede Instanz einer Direkti­ ve eine Initialisierungslogik a uszuführen. AngularJS übergibt der Link­ Funktion beim Aufruf mindestens drei Parameter: 1.

scope - eine Referenz auf den Scope, der für diese Direktive gültig

ist 2. el ement

-

eine Referenz auf das DOM-Element, a u f das die Direktive

wirkt 3 . attrs - eine Referenz auf ein Objekt, mit dessen Hilfe wir auf die HTML-Attribute des DOM-Elements zugreifen können Die Implementierung unserer Link-Funktion in Listing 2-26 macht n ichts, a ußer eine Ausgabe in d ie Konsole des Browsers zu schreiben. Um diese minimale Direktive nun in Aktion zu sehen, müssen wir noch ein kleines Tempiare schreiben, das die Direktive nutzt. Listing 2-27 Nutzung der colorPicker-Direktive in einem Template



2.2

Anwendungsbausteine

53

Li ring 2-27 zeigt solch ein beispielhaftes Template, das unsere mi­ nimale colorPicker-Direktive nutzt. Wie wir in dem Tempiare erken­ nen können, haben wir mit unserer minima len Direktivendefin ition ein neu es HTML-Attribute geschaffen. Die Nutzung innerhalb des di v­ Tags ist dabei willkürlich, und somit könnten wir unser neues Attribut auch in Kombination mit jedem anderen Tag verwenden. Wenn wir al o eine Direktive implementieren, indem wir innerhalb der Factory­ Funktion lediglich die Link-Funktion zurückgeben, dann erzeugen wir damit standardmäßig ein neues HTML-Attribut. Um das Beispiel nun ausführbar zu machen, müssen wir natürlich noch einen minimalen Ap­ plikationsrahmen schaffen, indem wir insgesamt drei Dateien erstellen. 1.

i ndex . html : Einstiegspunkt und Basis-Tempiare unserer Anwendung app . j s : Moduldefinition unserer Anwendung

"' col or-pi c ker . j s : Definition unserer colorPicker-Direktive < ! DOCTYPE h tml >

});

});

con t r o l l e r :

' templates/book_de tai l s . html ' , ' BookDetailsCtrl '

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

:

J

[

Pfadparameter in Routen

Zugriff auf Pfadparameter über $routeParams

. . .

]

An der Route /books/: i sbn, die zu der Detailansicht eines Buches führt, können wir außerdem noch eine kleine Besonderheit erkennen. Mit einem vorangestellten Doppelpunkt können wir sogenannte Pfadpa­ rameter spezifizieren. Bei dieser Route ist die : i sbn also ein Pfadpa­ rameter. Das bedeutet, dass dieser Teil der URL variabel sein darf und trotzdem auf die konfigurierte Kombination aus Tempiare und Controller abgebildet wird. Über die : i sbn werden wir also in einem weiteren Entwicklungsschritt auf die Detailinformationen des Buche zugreifen können, das die entsprechende i sbn besitzt. I nnerha lb de BookDeta i l sCt r 1 -Conrrollers erfolgt der Zugriff dann ü ber den sogenannten $routeParams-Service.

3 .4

3 .4.6

Projektstart: Deta i l a nsicht eines Buches

89

Ein eigener Scope m it dem BookDetailsCtri-Controller

In der rudimentären BookDetai l sCtrl -Implementierung, die wir in Lis­ mg 3-8 sehen, i nteressiert uns die i sbn allerdings noch n icht. Vielmehr • ollen wir i n einem ersten Schritt die Scope-Variable book mit einem Buch-Objekt belegen, sodass wir eine erste lauffähige Version der Deailansicht erzeugen können. Wir erinnern uns an dieser Stelle, dass em Controller für einen DOM-Ausschnitt einen eigenen Scope defi­ 'llert. Das gilt somit auch für unseren BookDeta i l sCtrl -Cont rolle r . I m DOM-Ausschnitt des Tempiares { book_det a i l s . h tml ) erstellt AngularJS Jl o bei jedem Aufruf der Route einen neuen Scope. Auf diesen Scope önnen wir innerhalb der Konstruktorfunktion des Controllers zugrei­ ·en, indem wir uns über den Parameter $s cope mithilfe von Dependency • 'l)ection den Scope übergeben lassen. In dem Scope belegen wir sch l ieß­ -:h die Variable book mit einem Buch-Objekt, das alle Eigenschaften .:mhält, die wir im Tempiare mit der ngBind- bzw. ngBindTemplate­ rektive ausgeben. : - pp . contro l l er ( ' Boo kDeta i l sCtrl ' , funct i on ( $ s cope) { S scope . boo k { title ' JavaSc r i pt für Enterpri s e - Entwi c k l er ' , subt i t l e ' Profess i onel l p rog rammi e ren i m B rowser und auf dem Server ' , i s bn ' 978-3 -89864- 728- 1 ' ' abst ract ' J avaSc r i pt i st l ängst n i cht mehr n u r für k l a s s i sche Webprog rammi erer i nteres sant . ' , numPages 302 , ' Ol i ver Och s ' , author publ i s her : name ' dpunkt . verl ag ' , u rl : ' http : //dpun kt . de/ ' } L ); =

enn wir in unserem Browser nun eine URL aufrufen, die zu der de­ .:..,lerten Route passt, dann sollten wir eine erste Version der Detailan­ ._ht erhalten ( Abbildung 3-5). Eine mögliche URL ist: : : p : //l oca l host : 8080/#/boo ks/123 •

· arürlich können wir anstatt der 123 auch beliebige andere Bei­ ... elwerte als Pfadparameter übergeben. Dadurch dass wir den i sbn­ ·adparameter für die Erstellung der Detailansicht noch n icht beachten, .rd immer dieselbe Detailansicht a usgegeben.

$scope

Listing 3-8 Implementierung des BookDetailsCtri-Con­ trollers

90

3

Abb. 3-5 Ausgabe der Detailansicht eines Buches in Chrome

Das BookMon key-Projekt

() 0

0JsookMonkey c

X

localhost 8080/#/books/123

BookMonkey JavaScript für Euterprise-Entwickler Professionell programmieren im Browser und auf dem Server 978-3-89864-728- 1 302



ISB N :



Seiten:



Autor: Oliver Ochs



Verlag: dpunkt.verlag

JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.

3 .4.7

Aufteilung der Tests

Unit-Tests

Der erste Test

Wie bereits erwähnt, liegt es uns sehr am Herzen, den geneigten Leser dieses Buches für den Ansatz der testgetriebenen Entwicklung zu begeis­ tern . Aus dieser Perspektive betrachtet haben wir daher bereits einen ersten Fehler gemacht, weil wir den Test nicht vor der tatsächlich Im­ plementierung geschrieben haben. Dennoch wollen wir an d ieser Stelle nachträglich einen ersten einfachen Test sch reiben, um zu zeigen, dass die Ausgaben im Template mithilfe der ngBind- bzw. ngBindTemplate­ Direktive tatsächlich das gleiche Resultat produzieren wie die A usgaben der Expressions. Für die Tests haben wir bereits in unserem bookmonkeyHauptverzeichnis ein Verzeichnis mit dem Namen test erstellt. In dem test-Verzeichnis erstellen wir nun zwei weitere Unterverzeich­ nisse mit den Namen un i t und e2e. In dem uni t-Verzeichnis werden wir ab sofort unsere Unit-Tests verwalten, während wir in dem e2e-Verzeichnis die sogenannten E2E-Tests a blegen werden. Zusammengefasst nutzen wir Unit-Tests, um kleine Applikationseinheiten isoliert von dem Rest der Anwendung testen zu können . Sol­ che Applikationseinheiten sind in der Welt von AngularJS z. B. Con­ troller, Services, Direktiven oder Filter. Wir versetzen diese Einheiten in eine bestimmte Situation und führen einen Teil ihrer Logik aus. An­ schließend überprüfen wir, ob das Resultat d ieser Ausführung u nseren Erwartungen entspricht. Um einen echten isolierten Test schreiben zu können, werden die Abhängigkeiten der Applikationseinheiten für ge-

3 .4

Projektstart: Detailansicht eines Buches

wöhnlich gemockt. Das bedeutet, dass wir die echte I mplementierung einer Abhängigkeit durch ein sogenanntes Mock-Objekt a ustauschen. Ein Mock-Objekt ist ein Objekt, das für die zu testende Applikati­ onseinheit das gleiche öffentliche API bereitstellt, jedoch anstatt einer tatsäeblichen Implementierung eine I mplementierung anbietet, die hart kodierte Werte zurückliefen. Anhand dieser hart kodierten Werte kön­ nen wir also festlegen, wie sich eine Abhängigkeit im Testfall zu ver­ halten hat. AngularJS bringt für interne Komponenten bereits eigene :\ lock-lmplementierungen mit, sodass uns an dieser Stelle eine Menge Arbeit beim Testen a bgenommen werden kann. In E2E-Tests hingegen betrachten wir unsere Anwendung sozusaoen als Blackbox. Wir haben keinerlei Zugriff auf den internen Zustand unseres JavaScript-Codes und können somit auch keine Erwartungen auf Basis dieses Zustands formulieren. Vielmehr können wir nur Er­ wartungen formu l ieren, die sich auf den Zustand des DOM beziehen . .\ lithilfe einer bequemen Domain Specific Language ( DSL) haben wir die Möglichkeit zu steuern, wie sieb ein potentieller Benutzer verhalten oll. Auf diese Weise können wir die Anwendung in eine bestimmte Si­



    < l i ng-bi nd- templ ate="Autor : { { book . author } } " c l a s s = " bm-boo k -autho r " >

    Verl ag :

    91

    E2E-Tests

    Listing 3-9 Erweiterung unseres Templates um aussagekräftige CSS-Kiassen

    92

    3

    Das BookMon key-Projekt





    Listing 3-9 zeigt das Markup unseres Templates, nachdem wir die Er­ weiterungen durchgeführt haben. Wie wir erkennen können, haben wir die HTML-Elemente, die die maßgeblichen Buchinformationen enthal­ ten, um aussagekräftige CSS-Klassen ergänzt (z. B. bm-book- t i t l e). Das erlaubt uns i m E2E-Test, kurze CSS-Selektoren zu formulieren, um d ie­ se HTML-Elemente referenzieren und ihren Inhalt überprüfen zu kön­ nen. Bevor wir uns mit dem eigentlichen E2E-Test befassen, erstellen wir in dem zuvor erzeugten e2e-Verzeichnis ein Unterverzeichnis mit dem Namen temp l ates. Hier legen wir die Datei book_deta i l s . s pec . j s an, in der wir den E2E-Test für die Detailansicht eines Buches definieren werden. H i nweis z u Dateinamen und Struktur für Tests

    Es hat sich ••eingebürgert«, alle Dateien, die Tests enthalten, so zu be­ nennen, dass sie die Endung . s pec . j s besitzen. Außerdem sollte inner­ halb des test-Ve rzeichnisses für die Tests eine Struktur aufgebaut wer­ den, die äquivalent zu der eigentlichen Applikationsstruktur in dem app­ Verzeichnis ist. Listing 3- 1 0 E2E-Test zur Überprüfung der Template-Ausgaben

    descri be ( " E2 E : book deta i l s v i ew " , funct i on ( ) { beforeEach ( funct i on ( ) { browser ( ) . navi gateTo ( ' / ' ) ; }) ; i t ( ' shou l d show the correct book det a i l s ' , functi on ( ) { b rowser ( ) . navi g ateTo ( ' #/ books/978-3 -89864-728- 1 ' ) ; expect ( e l ement ( ' . bm-boo k - t i t l e ' ) . html ( ) ) . toBe ( ' J avaSc r i pt für Enterpri s e - Entwi c k l er ' ); expect ( e l ement ( ' . bm-boo k - s u bt i t l e ' ) . html ( ) ) . toBe ( ' Profess i onel l p rog rammi e ren i m Browser und auf dem Server ' ); expect ( e l ement ( ' . bm-book - i s bn ' ) . html ( ) ) . toBe ( ' I SBN : 978-3 -89864-728- 1 ' );

    3.4

    93

    Projektstart: Detailansicht eines Buches

    expec t ( e l ement ( ' . bm-book-num- pages ' ) . html ( ) ) . toBe ( ' Sei ten : 302 ' ); expect ( e 1 ement ( ' . bm-boo k -autho r ' ) . h tml ( ) ) . toBe ( ' Auto r : Ol i ver Och s ' };

    expect ( e 1 ement ( ' . bm-book -pub 1 i sher- n ame ' ) . html ( ) ) . toBe ( ' dpunkt . verl ag ' }; expect ( e l ement ( ' . bm-book -publ i sher- name ' ) . at t r ( ' h ref ' ) ) . toBe ( ' http : //dpun k t . de/ ' ); expect ( e 1 ement ( ' . bm- book -abst ract ' ) . html ( ) ) . toBe ( ' JavaScri pt i st l ängst n i cht mehr nur fü r ' + ' k l a s s i sche Webprog rammi erer i nteres sant . ' };

    l1 ring 3- 1 0 können wir den E2E-Test für die Detailansicht ei­

    Buches sehen ( book_deta i l s . s pec . j s ) , der prüft, ob die Daten aus .., cope richtig a usgegeben wurden. Die Leser, die mit dem Test­ - mework ]asmine8 bereits vertraut sind, werden vermutlich sofort Parallelen wiederentdecken. Und in der Tat ist es so, dass die von � _ularJS bereitgestellte DSL für E2E-Tests ( a uch bekannt unter dem n:1en ngScenario) in Anlehnung an Jasmine konzipiert wurde. An er teile sei schon einmal vorweggenommen, dass wir hinterher die '"' ·-Tests mit Jasmine spezifizieren werden. Doch zunächst wollen wir er t einmal wieder unserem E2E-Test widmen, den wir mithilfe von ·enario definiert haben . i nweis auf das Protractor-Framework

    .... as Projekt Protractor9 ist ein Framework zur Definition von E2E-Tests. =:s arbeitet ähnlich wie das hier behandelte ngScenario. Allerdings setzt :s auf die WebDriver-Spezifikation auf, die uns deutlich mehr Kontrolle _:Jer die Steuerung des Browsers bietet. ln der offiziellen Dokumentation AngularJS ist erwähnt, dass Protractor Karma als E2E-Umgebung in .-er Zukunft ablösen wird. Unterschiede gibt es in der Konfiguration der -:stumgebung und an einigen Stellen des API. Die hier beschriebenen nzepte zur Definition von E2E-Tests können jedoch analog angewen­ :s· werden und sind somit generell gültig.

    '1trp://pivota l .github.io/j asmine/

    ngScenario in Anlehnung an Jasmine

    94

    3

    describe() für Test-Suite

    Testfa/1 mit it()

    expect()

    beforeEach()

    Das BookMon key-Projekt

    Grundsätzlich sind E2E-Tests syntaktisch genauso aufgebaut wie UnitTests. Außen starten wir mit einem descri be ( ) -Aufruf, der die Test­ Suite, also eine Sammlung von Testfäl len, definiert. Als ersten Parame­ ter vergeben wir für die Test-Suite einen Namen. Der zweite Parameter enthält eine anonyme Funktion, innerhalb der wir die Testfälle definieren. Ein einzelner Testfall wird mit der i t ( ) -Funktion spezifiziert, die als ersten Parameter eine Zeichenkette erwartet, die das erwarte­ te Verhalten in natürlicher Sprache ausdrückt. Als zweiten Parameter übergeben wir wiederum eine anonyme Funktion, in der schließlich der Testfall definiert ist. Charakteristisch für solch einen Testfall ist, dass er zunächst eine Vorbedingung schafft, dann die zu testende Funktio­ nalität a usführt und schließend mittels einem oder mehreren expect ( ) Aufrufen die erwarteten Nach bedingungen formuliert. Wenn mehrere Testfälle die gleiche Vorbedingung benötigen oder wir eine bestimm­ te Logik vor jedem Testfall a usführen wollen, dann können wir dazu die beforeEach ( ) -Funktion verwenden, die als einzigen Parameter eine Funktion erwartet, die vor jedem Testfall a usgeführt wird. In unserer E2E-Test-S uite in Listing 3- 1 0 n utzen wir die beforeEach ( ) -Funktion dazu, u m vor jedem Testfall zur Startseite u nserer Anwendung zu navi­ gieren. Der konkrete Aufruf sieht dazu folgendermaßen a us: browser ( ) . na v i gateTo ( ' / ' )

    I n dem momentanen Entwicklungsstadium ist das unsere einzige Vor­ bedingung, die potenziell für alle Testfälle in dieser Test-Suite relevant sein könnte. Dadurch dass wir aktuell nur einen Testfa l l definiert ha­ ben, könnten wir diese Vorbedingung a uch unmittelbar in diesen Test­ fall einkodieren. Allerdings können in der zukünftigen Entwicklung po­ tenziell noch weitere Testfälle hinzukommen, die diese Vorbedingung benötigen. Vorausschauend, wie wir sind, haben wir deswegen schon einmal diesen beforeEach ( ) -Aufruf eingebaut. In unserem einzigen Testfall navigieren wir mithilfe des folgenden Aufrufs zu der gleichen URL, die wir bereits manuell im Browser ge­ öffnet haben. Allerdings geben wir d ieses Mal als Pfadparameter eine realistische ISBN an, um diesen Testfall für die zukünftige Entwicklung vorzubereiten. Schließlich werden wir in einem der nächsten Schritte die per URL ü bergebene ISB verwenden, u m die Detailinformationen zu dem entsprechenden Buch anzuzeigen. browser ( ) . na v i g ateTo ( ' #/books/978-3 -89864-728- 1 ' } ;

    Innerhalb unserer Applikation sollten jetzt entsprechend unserer Rou­ tenkonfiguration der BookDeta i 1 sCtrl -Controller und das Template für 9 https://github.com/angu lar/protractor

    3.4

    Projektstart: Deta i lan sicht ei nes Buches

    die Detailansicht ( templ atesjbook_deta i l s . html ) geladen werden. An­ schließend sollten die Buchinformationen im DOM verfügbar sein. Wir können also anfangen, mittels der expect ( ) -Funktion unsere Nach­ bedingungen zu formulieren. Aufgrund dessen, dass wir in unserem Template die entsprechenden HTML-Elemente mit aussagekräftigen CSS-Kiassen versehen haben, können wir jetzt ziemlich bequem mit der e l ement ( ) -Funktion in Kombination mit dem entsprechenden CSSelektor (z. B . . bm-boo k - t i t l e ) die nötigen DOM-Elemente referenzie­ ren. Was nun folgt, ist der Aufruf eines sogenannten Matchers. Ein Y latcher hat innerhalb von ngScenario die gleiche Semantik wie in Jas­ mine. Er abstrahiert von der eigentlichen Prüfungslogik, indem er die Prüfung durch genau einen Funktionsau fruf repräsentiert. Dabei gibt es ziemlich rudimentäre Mateher wie den toße ( ) -Matcher, der lediglich einen Wertevergleich mit Beachtung des Typs ( ) durchführt. Aber auch komplexere Mateher wie z. B. der toConta i n ( ) -Matcher werden \·on ngScenario bereitgestellt. Dieser Mateher überprüft bei einer Zei­ chenkette, ob sie Teil einer anderen Zeichenkette ist. Den toContai n ( ) ­ .'datcher können wir a ber a uch verwenden, u m sicherzustellen, dass ein be timmtes Element in einem Array enthalten ist. Eine vollständige Lis­ te aller Mateher und eine solide Beschreibung des kompletten API von ngScenario finden wir in der offiziellen Dokumentation10 . Wir haben nun eine erste Test-Suite definiert. Um die E2E-Tests der uire auszuführen, benötigen wir jetzt noch eine Ablaufumgebung. Im .-\ngularJS-Umfeld hat sich Karma als Testablau fumgebung etabliert, weil das Projekt praktisch daraus entstanden ist, dass die AngularJS­ Ent wickler fü r AngularJS selbst auf der Suche nach einem geeigneten \\'erkzeug waren . Karma haben wir zu Beginn des Kapitels bereits inralliert und nutzen es fortan als Ablaufumgebung für unsere Unit- wie .weh E2E-Tests.

    95

    element()

    Mateher

    ===

    H i nweis zu Karma

    Dem einen oder anderen ist eventuell die Testablaufumgebung Testa­ cular bekannt. Testacular wurde in Karma umbenannt, nachdem es die offizielle Testumgebung für das AngularJS-Framework geworden ist.

    Cm die in Listing 3- 1 0 definierte Test-Suite auszuführen, benötigen wir ·ur Ka rma jedoch noch eine Konfigurationsdatei. Welche Konfigurati'1 möglichkeiten uns Karma bietet, soll uns an dieser Stelle erst einmal .,Kht weiter interessieren. Dieser Aspekt wird in dem entsprechenden - apitel näher betrachtet (vgl. Abschnitt 5.4). Falls wir unsere Projekt­ . ·ruktur exakt so erstel lt haben wie bisher beschrieben, können wir pro10 h ttp://docs.angu larj s.orglgu ide/dev_gu ide.e2e-testi ng

    Karma als Ablaufumgebung für Tests

    96

    3

    Das BookMon key-Projekt

    blemlos die Konfigurationsdatei aus dem offiziellen GitHub-Repository zum Buch verwenden. Dazu kopieren wir die Datei k a rma-e2e . conf . j s i n das bookmonkey- Verzeichnis. Und wenn wir gerade dabei sind, kön­ nen wir zusätzlich a uch noch die Datei karma . conf . j s in dasselbe Ver­ zeichnis übertragen . Die Datei karma . conf . j s ist dabei die Konfigurati­ on für die Ausführung unserer Unit-Tests, die wir im späteren Verlauf noch brauchen werden, während sich in der Datei karma -e2e . conf . j s die Konfiguration für die Ausführu ng der E2E-Tests befindet. Um die E2E-Test-Suite für die Detailansicht eines Buches nun a us­ zuführen, wechseln wir in das Hauptverzeichnis der BookMonkey­ Anwendung ( bookmonkey) und starten Karma mit dem folgenden Befehl: karma s ta rt karma -e2e . conf . j s H i nweis zur Ausführung von E2E-Tests

    Laut unserer Konfiguration für E2E-Tests ( k a rma -e2e . conf . j s) geht Karma davon aus, dass die BookMonkey-Anwendung über http : I1 1 oca l hos t : 80801 zu erreichen ist. Das bedeutet, dass unser http-se rver-Modul vor der Ausführung von Karma weiterhin auf Port 8080 laufen und unsere Applikation ausliefern muss. Karma lädt die Anwendung über diese URL und führt anschließend die E2 E-Tests aus.

    Wenn wir keinen Fehler eingebaut haben, sollte Karma uns ein Feed­ back über die erfolgreiche Ausführung eines Testfalls geben. Ch rome 30 . 0 . 1 59 9 : Executed 1 of 1 SUCCESS ( 0 . 908 secs I 0 . 699 sec s )

    Ziel dieses E2E-Tests war, z u zeigen, dass w i r m i t der ngBind- bzw. ng­ BindTemplate-D i rektive genauso Ausgaben im Tempiare produzieren können wie mithilfe einfacher Expressions. Um diese Behauptung nun zu verifizieren, können wir unser Tem­ piare für die Detailansicht eines Buches nochmals auf die Ausgabe mit Expressions zurückstellen. Dabei sollten wir jedoch beachten, dass wir die Auszeichnungen der HTML-Elemente mit den entsprechenden CSS­ Klassen nicht vergessen. Anderenfalls werden unsere CSS-Selektoren aus dem E2E-Test n icht mehr funktionieren. Das Resultat sehen w1r in Listing 3 - 1 1 . Listing 3- 1 1 Template-Ausgaben produzieren wir testweise mit Expressions.

    { { book . t i t l e } } { { book . subti t l e } }

      I SBN : { { book . i s bn } } Sei ten : { { book . n umPages } }

      3.4

      Projektstart: Detaila nsicht e i nes Buches

      Auto r : { { book . author } }

      Verl ag :

      { { book . publ i sher . n ame } }



      { { book . abstract } }



      Wenn wir den E2E-Test jetzt noch einmal a usführen, werden wir erken­ nen, dass er trotz der Umstellung auf einfache Expressions weiterhin grün bleibt. Also haben wir an dieser Stelle u nser Ziel erreicht. Zusammenfassung

      Wir nutzen sogenannte Expressions, um in einem Tempiare Ausga­ ben zu produzieren. I nsbesondere können wir mithilfe von Expressions die aktuellen Werte von Variablen aus dem Scope ausgeben, der für das Tem­ piare im aktuellen Zustand gültig ist. Expressions u nterliegen der Zwei-Wege-Datenbindung. Das bedeu­ tet, dass sie neu ausgewertet werden, wenn sich der Wert der aus­ gegebenen Scope-Variable verändert. Wir sollten möglichst versuchen, aus Performance-Gründen die ng­ Bind- bzw. ngBindTemplate-Direktive den Expressions vorzuzie­ hen. Mithilfe der ngHref-Direktive können wir Hyperlinks erzeugen, die Expressions enthalten. AngularJS sorgt dann dafür, dass der Link erst aktiv wird, wenn das Template verarbeitet und die entspre­ chenden Expressions evaluiert wurden. Mit dem $ routeProv i der können wir in einem con fi g ( ) -Block unsere Anwendungsrouten konfigur ieren. Die ngView-Direktive annotiert das HTML-Element, Routen-Tempiares geladen werden sollen.

      111

      das die

      Die sogenannten E2E-Tests erla uben uns, ein simuliertes Benut­ zerverhalten für unsere Anwendung zu skripten und auf Basis des DOMs gewisse Erwartungen zu formulieren.

      97

      98

      3

      Das BookMon key-Projekt

      Für E2E-Tests bringt AngularJS ein eigenes Test-Framework mit dem Namen ngScenario mit.

      3.5

      Listenansicht fü r Bücher

      Nachdem wir eine rudimentäre Detailansicht eines Buches erfolgreich erstellt haben, wollen wir uns im nächsten Schritt um die Listenan­ sicht für Bücher k ümmern. Die Listenansicht soll eine Übersicht von Büchern anzeigen, bevor der Benutzer mit einem Klick auf einen be­ stimmten Bucheintrag zu der Detailansicht gelangt. Dabei könnten wir die Listenansicht für mehrere Zwecke verwenden. So könnten wir dem Benutzer z. B. die Suchergebnisse einer Büchersuche präsentieren ( vgl. Abbildung 3-6). Aber a uch h i nterher im Administrationsbereich könnte die Listenansicht zum Einsatz kommen, u m dem Admin die Verwaltung zu erleichtern. Abb. 3-6 Skizze der geplanten Listenansicht

      Boo kMon key

      I

      Suche A utor

      ISBN

      Oliver Ochs

      978-3-89864-728- 1

      Node.js & Co.

      Golo Roden

      978-3-89864-728- 1

      CoffeeScriQ!

      Andreas Schu bert

      978-3-89864-728-1

      Name

      JavaScrigt für Entergrise-Entw.

      -- -

      --

      -- -

      ----

      � --

      Der zentraler Aspekt dieses Unterkapitels ist dabei der Umgang mit der ngRepeat-Direktive. Mithilfe dieser D i rektive können wir listenartige Strukturen wie z. B. Arrays ziemlich einfach in einem Template ausge­ ben. Dabei bietet uns die ngRepeat-Direktive eine ganze Reihe an Funk­ tionen, um diese Ausgaben filtern oder ordnen zu können. Auch für die Listenansicht formulieren wir wieder eine User Story. Als Nutzer Michael möchte ich mir eine Liste von Büchern ansehen, um mir eine Ü bersicht zu verschaffen.

      3.5

      3.5.1

      Listenansicht für Bücher

      99

      Als Erstes der Test

      Wie bereits erwähnt wollen wir ab sofort mit dem Test-First­ Ansatz weitermachen. Deswegen widmen wir uns zunächst den Tests für die Listenansicht. Dazu erstellen wir in dem Verzeichnis testle2eltemp 1 atesl die Datei book_1 i s t . s pec . j s, in der wir wieder eine E2E-Test-Suite definieren. descri be ( " E2 E : book 1 i st v i ew " , funct i on () { II Defi ne the a rray of book s i n the expected order . II Sorted by t i t 1 e . var expectedßoo ks [ =

      {

      t i t 1 e : 1 CoffeeScri pt i sbn : 1 978-3-86490-050- 1 1 , author : 1 Andreas Schubert I ,

      1

      }, { t i t 1 e : 1 J avaSc ri pt für Enterpri se -Entwi c k 1 er 1 , i sbn : 1 978-3-89864- 728- 1 1 , author : 1 01 i ver Ochs 1 }, {

      t i t 1 e : 1 Node . j s & C o . 1 , i sbn : 1 978-3-89864-829-5 1 , autho r : 1 Go 1 o Roden 1

      ]; II Deri ve an a rray that on 1 y cont a i ns t i t 1 es II for eas i er expectati on checks . var orderedTi t 1 es expectedßooks . map ( functi on ( boo k ) return book . t i t 1 e ; }); =

      [ . .] .

      ); l 1 ring 3- 1 2 zeigt den grundlegenden Aufbau der Test-Suite. Bevor wir

      .11e eigentlichen Testfälle schreiben, definieren wir uns zunächst einige Hdfskonstrukte, die h interher die I mplementierung der Testfälle verein­ ·a.:hen. Das maßgebliche H ilfskonstrukt ist an dieser Stelle das Bücher­ \rray expectedßooks. In diesem Array definieren wir für jedes Buch, das 1r in der Listenansicht als Eintrag erwarten, ein Buch-Objekt mit den

      Listing 3- 12 Vorbereitungen für den E2E-Test zur Überprüfung der Listenansicht

      1 00

      3

      Das BookMon key-Projekt

      E igenschaften t i t l e, i sbn und author. Insbesondere ist bei dieser Defini­ tion wichtig, dass wir die Buch-Objekte in der erwarteten Reihenfolge notieren, damit wir schließlich im Testfall auf bequeme Weise über­ prüfen können, ob die Listenansicht die Bucheinträge richtig geordnet hat. Hier haben wir uns dafür entschieden, die Reihenfolge lexikalisch anband des Buchtitels festzulegen. Außerdem leiten wir von dem ex p ected B ooks -Array mithilfe der map { ) -Funktion ein zweites Array orderedTi t l es ab, in dem wir le­ diglich die Buchtitel in der gleichen Reihenfolge a blegen. Auch das o rderedT i t l es-Array ist ein Hilfskonstrukt für die anschließenden Test­ fälle. Listing 3- 13 Der beforeEach()-8/ock der E2E-Test-Suite zur Überprüfung der Listenansicht

      descri be ( " E2 E : book l i st v i ew " , funct i on () { [ . .] .

      beforeEach ( functi on ( ) { browser ( ) . navi gateTo ( ' #/books ' ) ; b rowser ( ) . rel oad ( ) ; }); [ . . .] });

      I n Listing 3- 1 3 sehen wir die Fortsetzung u nserer Test-Suite aus Lis­ ting 3- 1 2. Genauso wie bei dem E2E-Test für die Detailansicht eines Buches nutzen wir hier einen beforeEach ( ) -Biock, um die Vorbedi ngung für unsere Tests herzustellen. In diesem Fall definieren wir, dass Kar­ ma vor jedem Testfall zu der Route /books navigieren und anschließend das Dokument aktualisieren soll. Die Aktualisierung ist nötig, weil wir i n einigen zukünftigen Testfällen den Zustand der Ansicht verändern werden. Somit stellen wir mit der Aktualisierung sicher, dass sich die Ansicht vor der Ausführung jedes Testfalls in ihrem Ursprungszustand befindet. Die alleinige Navigation zu der Route /boo ks reicht h ier nicht a us, weil der Ursprungszustand in der Regel nicht hergestellt wird, wenn wir zwischen denselben URLs navigieren. Listing 3- 1 4 Testfall für die Überprüfung der richtigen Anzahl von Büchern

      descri be ( " E2 E : book l i s t v i ew " , functi on ( ) { [. . .]

      var sel ector

      ' tabl e . bm-book - l i st t r ' ;

      3.5

      Listenansicht für Bücher

      101

      i t ( ' shou l d s how the correct n umber of books ' , funct i on () { expect ( repeate r ( sel ector ) . count ( ) ) . toEq u a l ( expectedßooks . l engt h ) ; }); [. . .] }) ;

      Weiter geht

      es

      Listing 3-14. Da Wir den CSS-Selektor in mehreren Testfällen benötigen, lagern ihn in eine Variable sel ector aus. wir Mit der i t { ) -Funktion schreiben wir nun unseren ersten Testfall, der prüft, ob sich in der Listenansicht tatsächlich so viele Listenein­ träge befinden, wie wir Buch-Objekte in dem expectedßooks-Array definiert haben . Dazu nutzen wir einen sogenannten Repeater. Der Repeater ist ein Konstrukt, das uns ngScenario bereitstellt, um Informationen aus tabellenartigen DOM-Strukturen, die meistens mithilfe der ngRe­ peat-Direktive erzeugt wurden, bequemer a uslesen zu können. Solch eine tabellenartige DOM-Struktur ist - wer hätte es gedacht - z. B. eine HTML-Tabelle. Charakteristisch für solch eine Struktur ist, dass sich ein bestimmtes DOM-Muster mehrfach wiederholt. Eine HTMLTabelle ist also ein perfektes Beispiel für solch eine Struktur, weil jede Zeile () meistens die gleiche Anzahl von Spalten ( ) besitzt und omit eine gleichbleibende Struktur aufweist. Anband des CSS-Selekrors, den wir dem Repeater als Parameter übergeben, haben wir also implizit festgelegt, dass die Listenansicht a u f einer HTML-Tabelle basieren soll. A u f diese Weise können wir mit dem Repeater ziemlich einfach auf Basis des DOM ermitteln, wie viele Bucheinträge unsere L istenansicht beinhaltet. Die entsprechende Repeater-Funktion dafü r lautet count ( ) . Somit können wir nun unsere Erwartung für diesen Test formulieren. Weiter geht es mit dem nächsten Test, der überprüft, ob die Ein­ träge in der Listenansicht richtig geordnet sind, näml ich lexikalisch ab­ teigend anband des Buchtitels. m

      tabl e . bm-book- l i st t r

      Repeater

      count() bei Repeatern

      1 02

      Listing 3- 15 Testfall für die Überprüfung der richtigen Reihenfolge

      3

      Das BookMon key-Projekt

      descri be { " E2 E : book l i s t v i ew " , funct i on ( ) { [. . .]

      i t ( ' shou l d show the books i n the p roper order ' , functi on ( ) { II Are t hey i n the expected order II ( ascendi ng sorted by t i t l e ) ? expect ( repeater (sel ector ) . col umn ( ' boo k . t i t l e ' ) ) . toEqual ( o rderedT i t l es ) ; }); [. . .] }); column() bei Repeatern

      Der entsprechende Testfall ist i n L isting 3- 1 5 zu sehen. Erneut n ut­ zen wir einen Repeater, um die entsprechenden Informationen bequem aus dem DOM lesen zu können. Aber diesmal kommt die col umn ( ) ­ Funktion des Repeaters zum Einsatz, an der wir erneut erkennen kön­ nen, dass ein Repeater praktisch das ergänzende Testkonstrukt zur ngRepeat-Direktive ist. Die col umn { ) - Funktion erwartet als Parame­ ter einen Datenbindungsausdruck, a lso genau das Fragment, das wir a uch in Verbindung mit der ngBind-Direktive nutzen würden, u m eine Tempiare-Ausgabe zu produzieren. Wir legen an dieser Stelle fest, dass dieser Ausdruck book . t i t l e lauten soll. Somit werden wir hinterher in der Implementierung zum Erzeugen der Ausgabe mit dem folgenden Ausdruck arbeiten müssen: ng-bi nd= " book . t i t l e "

      Mithi l fe der co l umn ( ) -Funktion können wir also ziemlich einfach die Werte der Tabellenspalte auslesen, in der der Buchtitel steht. D ie Funk­ tion liefert uns die entsprechenden Spaltenwerte aller Zeilen als Ar­ ray. Daher können wir an d ieser Stelle mit dem toEqual ( ) -Matcher ver­ gleichen, ob das zurückgelieferte Array unserem orderedTi t l es-Array entspricht. Wenn d iese Erwartung erfüllt ist, dann bedeutet das, dass die Listeneinträge der Sortierung entsprechen, die wir manuell in dem expectedßooks-Array festgelegt haben, weil das o rderedTi t l es-Array aus dem expectedßoo ks-Array a bgeleitet wurde.

      3.5

      Listenansicht für Bücher

      1 03

      H i nweis zum toEquai ()-Matcher

      Der toEqua l () -Matcher führt einen Vergleich auf Basis der AngularJS­ Funktion angu l a r . equa l s ( ) durch. Diese Funktion nutzt einen etwas komplexeren Algorithmus, um die Gleichheit zwei er Objekte zu überprü­ fen. Unter anderem prüft sie bei Arrays, ob die einzelnen Elemente über­ einstimmen. Diese Tatsache erlaubt u ns in Kombination mit den Repea­ tern eine relativ komplexe Erwartung ziemlich einfach aufzuschreiben.

      Was jetzt noch fehlt, bevor wir zur eigentlichen Implementierung der Listenansicht voranschreiten, ist der letzte Testfall, der überprüft, ob neben dem Buchtitel auch die anderen Buchinformationen (Autor und ISBN) richtig a usgegeben wurden. Diesen Testfall sehen wir in Listing 3 - 1 6 . descri be ( " E2 E : book l i st v i ew " , funct i on ( ) l [. . .]

      Listing 3-16 Testfall für die Überprüfung der richtigen Listeninhalte

      i t ( ' shou l d s how the correct book i nfo rma t i on ' , funct i on ( ) I I Do t h e other book deta i l s ( i sbn , author) match? for ( v a r i 0 , n = expectedBooks . l engt h ; i < n; i ++) expect ( repeater (sel ector) . row ( i ) ) . toEqua l ( =

      [

      expectedßooks [ i ] . t i t l e , expectedBooks [ i ] . author , expectedßooks [ i ] . i sbn ); } }) ; }) ;

      Die Besonderheit an diesem Testfall ist, dass wir einen neuen Aspekt des Repeaters nutzen, nämlich die row ( ) -Funktion. Der Name sagt eigentlich schon alles. D iese Funktion erwartet als Parameter den Index einer Tabellenzeile und gibt uns als Rückgabe die Spaltenwerte dieser Zeile i n Form eines Arrays. Daher können wir mithilfe einer for-Schleife relativ einfach die E rwartung ausdrücken, dass der Buchtitel, der Aurar und die ISBN in einer Zeile i mmer äquivalent zur entsprechenden Definition im expectedßoo ks-Array sein müssen. Damit hätten wir also .weh die letzte Erwartung an die Listenansicht definiert. Darum können wir jetzt versuchen, den Erwartungen entsprechend eine Implementierung zu erstellen. Vorher sollten wir die E2E-Tests allerdings nochmals

      row() bei Repeatern

      1 04

      3

      Das BookMon key-Projekt

      ausführen und sicherstellen, dass die Tests aus der soeben erstellten Test-Suite fehlschlagen, weil noch keine I mplementierung existiert. karma start karma-e2e . con f . j s

      Das entsprechende negative Feedback sollte i n etwa so aussehen: C h rome 30 . 0 . 1 599 : Executed 4 of 4 (3 FA I LED) ( 1 . 2 1 7 secs I 0 . 9 7 1 secs)

      Unser E2E-Test für die Detailansicht ist weiterhin »grün « , während die drei Tests, die wir soeben definiert haben, fehlschlagen. In der Ausgabe können wir außerdem nachlesen, welche Tests im Detail fehlgeschlagen sind. 3.5.2

      Die Infrastruktur für die Listenansicht

      Nachdem wir die E2E-Tests für die Listenansicht definiert haben, wol­ len wir zunächst noch die Infrastruktur schaffen, bevor wir mit der eigentlichen I mplementierung loslegen. Das bedeutet, dass wir die ent­ sprechenden Dateien anlegen und in unserer app . j s eine zusätzliche Route konfigurieren müssen. Wir fangen mit der Route an. Dazu müs­ sen wir die app . j s folgendermaßen erweitern ( Listing 3- 1 7) : Listing 3- 1 7 Eine neue Route für die Listenansicht

      v a r bmApp = angu l a r . modu l e ( 1 bmApp 1 , [ 1 ngRoute 1 ] ) ; bmApp . confi g ( funct i on ( $ routeProv i de r ) { $ routeProv i der . when ( 1 /boo ks / : i sbn 1 , { temp l ateUrl : 1 temp l ates /book_deta i l s . html 1 , cont rol l er : I Boo kDeta i l sCt r l 1 }) . when ( 1 /books temp l ateUrl : 1 temp l ates /book_l i s t . h tml 1 , control l er : 1 Book li stCtrl 1 }); }); 1

      ,

      Mithilfe der when-Funktion konfigurieren wir eine weitere Route, so­ dass alle Aufrufe von /books zu der Listenansicht führen, die natür­ lich wieder ein Tempiare (book_l i st . html ) und einen eigenen Controller ( Bookl i stCtrl - Co nt roller) besitzt. Somit müssen wir entsprechend die­ se beiden Dateien erzeugen und in unsere i ndex . h tml mittels zusätz­ l icher -Tags einbinden. Die Datei book_l i s t . html (Template) erstellen wir in dem Verzeichnis appjtempl ates/, während wir für den BookL i stCtrl -Controller ebenfalls eine separate Datei mit dem Namen book_l i s t . j s in dem Verzeichnis appjsc ri pt s/control l ers/ erstellen.

      3.5

      3.5.3

      Listenansicht für Bücher

      1 05

      Der BooklistCtri-Controller

      Die Implementierung des Boo k l i stCtr 1 -Controllers ist genauso wie beim BookDeta i 1 s C t r1 Controller bisher noch recht simpel gehalten ( Listing 3- 1 8 ) . -

      bmApp . contro1 1 e r ( 1 Bookli stCtr1 1 , funct i on ( $ scope) { $ s cope . boo ks [ { t i t 1 e : 1 J avaSc ri pt für Enterpri se- Entwi c k 1 er 1 , i sbn : 1 978-3-89864- 728- 1 1 , author : 1 01 i ver Ochs 1 , [ .] }, =

      .

      {

      .

      t i t 1 e : 1 Node . j s & Co . i sbn : 1 978-3-89864-829-5 1 , author : Go 1 o Roden 1 , [. . .] 1 ,

      1

      }

      {

      '

      t i t 1 e : 1 CoffeeScri pt 1 , i sbn : 1 978-3-86490-050- 1 1 , author : 1 And reas Schubert 1 , [ . ] .

      });

      .

      ];

      Wir belegen die Scope-Variable boo ks mit einem Array von Buch­ Objekten. Bevor wir diese Informationen in einem zukünftigen Schritt vom Server laden werden, geben wir uns an dieser Stelle vorerst mit einer statischen Definition zufrieden. Wir sollten außerdem beachten, dass in Listing 3- 1 8 zugunsren der Übersichtlichkeit a bsichtlich die Buchinformationen ausgelassen wurden, die für die Listenansicht nicht relevant sind. Nachdem wir in Listing 3- 1 8 eine Liste von Büchern definiert ha­ ben, wollen wir diese nun in unserem Template a usgeben. 3.5.4

      Die ng Repeat-Direktive: Ausgabe eines Arrays im Template

      Um listenartige Struktu ren, also z. B. At-rays, in einem Tempiare auszu­ geben, verwenden wir die ngRepeat-Direktive. Neben der reinen Ausga­ be bietet uns AngularJS mit dieser D i rektive a ußerdem d ie Möglichkeit,

      Listing 3-18 Der BookListCtri-Contro/ler

      1 06

      3

      Das BookMon key-Projekt

      die zugrunde liegende Liste zu filtern, zu transformieren oder zu ord­ nen. All diese Aspekte wollen wir uns nun der Reihe nach anschauen. Dazu implementieren wir zunächst die einfachste Version u nseres Templates, sodass zumindest schon die drei Bücher aus dem Array a us­ gegeben werden. Diese Version des Tempiares ist i n Listing 3- 1 9 zu sehen. Listing 3- 1 9 Das Template für die Listenansicht mit Benutzung der ngRepeat-Direktive

      ngRepeat als Attribut





      Genauso wie im E2E-Test spezifiziert, bauen wir die Listenansicht in Form einer HTML-Tabelle ( ) a uf. D iese Tabelle annotieren wir a ußerdem mit der CSS-Kiasse bm- book - 1 i st . Auch das haben wir im E2E-Test so spezifiziert. Nun kommt der interessante Teil ins Spiel, nämlich die N utzung der ngRepeat-Direktive. Wie wir in Listing 3- 1 9 nachvollziehen können, verwenden wir die ngRepeat-Di rektive als At­ tribut in Verbindung mit dem HTML-Element, das bei der Ausgabe des books -Arrays mehrfach erzeugt werden soll. Den DOM-Unterbaum des HTML-Elements einschließlich des HTML-Eiements selbst betrachtet die Direktive dabei als Template, welches schl ießlich für jedes Element aus dem zugrunde liegenden Array einmal instanziert wird. In diesem Fall wollen wir a lso erreichen, dass für jedes Buch-Objekt aus dem books-Array eine Tabellenzeile ( ) gerendert wird, weil wir die Di­ rektive in Verbindung mit dem -Tag verwenden. Die ngRepeat- D i rekti ve erwartet dabei als Wert einen strukturier­ ten Ausdruck wie in d iesem Fall z. B. book i n books. Sie beschreibt, wel­ ches Array a usgegeben werden soll und wie die Schleifenvariable heißen soll, mittels der wir in jeder Iteration Zugriff auf das aktuell a usgege­ bene Buch-Objekt erhalten. Wir spezifizieren demnach, dass das a us­ zugebende Array von der Scope-Variable book s referenziert wird, und benennen die Schleifenvariable book. Somit haben wir bei jeder Iteration über die Schleifenvariable book einen Zugriff auf die einzelnen Buchin­ formationen wie Buchtitel ( boo k . t i t l e ) , Autor { boo k . author ) und ISB { boo k . i sbn ) . Dieser Zusammenhang wird i n Abbildung 3-7 noch ein­ mal verdeutlicht. Außerdem können wir in dieser Abbildung erkennen, in welchem DOM-Ausschnitt der Scope des Bookl i stCtrl -Controllers gültig ist.

      3.5

      � � - - back < t able



      � . r �- - - >

      II

      class= " bm-bo ok - l i s t " >

      b t} t) k 1 i� t .

      :-;;

      - ;;;:,� ---------:

      bmApp . c o n t r o l l e r ( ' BookListCtrl ' ,

      < tbody>

      < t r ng- .::e peat= " book i n books " >

      --;;;::;:,;;;:_;,:;;

      1 07

      Listenansicht für Bücher

      :

      function

      -

      Entenrl P E

      ( $ scope)

      -

      Abb. 3-7 ngRepeat-Oirektive gibt books-Array aus dem Controller-Scope aus.

      ,,

      $ s cope . books



      (



      )





      {

      '"' •

      _

      . f e

      =

      [ t

      c: r

      t

      pt · , )

      I.

      tw

      "' er • 1 ) ,

      ;

      )I;

      < / tbody>

      < / table>

      Folglich können wir wieder mit der ngßind-Direktive a rbeiten, um die tatsächliche Ausgabe zu produzieren. Die Beziehung zwischen der ngRepeat-Direktive und dem Repeater aus dem E2E-Test können w1r in Abbildung 3-8 noch einmal genau nachvollziehen. L

      >

      -

      table c l a s s = " bm-book- l i s t " >

      I I I

      :

      '

      ?._

      I I 1

      II

      .l\u .. zuq h•J•·k

      var it

      selector

      ( ' [ ... )

      ' ,

      =

      . ·p('o:

      .

      ' table . bm-book-list tr ' ;

      function ( I

      I

      : --------------, expect (

      1

      _ _ -...

      < / td> < / td>

      < / tr>

      t

      3

      '=============.J

      L

      t



      2

      8

      Resul .. Ier��dt:: fr..;. ga.be VI'Jn nq-r..,.pedt

      1

      8 Scope

      .... ! - -

      ------

      < / t d > < / td>

      < l t => < / tbody> < / t ab l e >

      In Abbildung 3-9 können wir außerdem erkennen, dass AngularJS in j edem der durch die ngRepeat-Direktive erzeugten Scopes die Scope­ Variable book definiert. Sie referenziert das entsprechende Buch-Objekt.

      3.5

      Listenansicht für Bücher

      1 09

      H i nweis zur Ausgabe von Objekten mittels n g-repeat

      ln JavaScript verwenden wir oftmals Objekte, um Hashtable-artige Struk­ turen abzubilden. Auch solche Objekte können mithilfe der ngRepeat­ Direktive ausgegeben werden . Allerdings lassen sich in diesem Fall die Filterfunktionantäten der Direktive, die wir in einem der nächsten Schrit­ te nutzen werden, nicht problemlos verwenden. Zu dieser Problema­ tik haben wir auf AngularJS.DE einen Artikel verfasst, der den H inter­ grund dieser Einschränkung näher beleuchtet. Die URL zum Artikel lau­ tet: h t t p : I I angu 1 a rj s . del a rt i ke 1 I angu 1 a rj s - ng - repeat .

      Um die Listenansicht nun aufzurufen, rufen wir im Browser die folgen­ de URL auf: http : ll1 oca1 host : 8080I # I books

      Wenn wir keinen Feh ler gemacht haben, sollten wir die Listenansicht sehen (vgl. Abbildung 3- 1 0): 0

      0

      � BookMonkey c

      Abb. 3- 1 0 Ausgabe unserer Listenansicht für Bücher in Chrome

      X

      localhost. 8080 /# / books

      BookMonkey JavaScript für Enterprise-Entw ickler Oliver Ochs

      978-3-89864-728- 1

      Golo Roden

      978-3-89864-829-5

      Node.js & Co. CoffeeScript

      Andreas Schubert 978-3-86490-050-1

      Wenn wir die gleiche Ausgabe erhalten wie in Abbildung 3- 1 0, dann müsste logischerweise jetzt auch einer der drei E2E-Tests »grüngrün

      3.7

      Der erste Service

      1 41

      Der eigentliche Karma-Aufruf, um die Unit-Tests a uszuführen, un­ terscheidet sich dabei bis auf die Angabe einer anderen Konfiguration nicht weiter von dem Aufruf für unsere E2E-Tests. karma s tart karma . conf . j s

      Wenn wir keinen Fehler gemacht haben, sollte uns Karma das Feedback über die fehlgeschlagenen Unit-Tests geben. Ch rome 30 . 0 . 1 599 : Executed 5 o f 5 ( 5 FA I LED) E RROR ( 0 . 727 secs I 0 . 022 secs)

      )Jeben der zusammengefassten Ausgabe bekommen wir zu jedem Test­ fall auch einen detaillierten Stack Trace und eine Begründung, warum der Testfall feh lgeschlagen ist. Dabei kann es sich um die Verletzung ei­ ner von uns formulierten Erwartung oder einen tatsächlichen Fehler im Programmablauf h andeln. In unserem Fall sollte jeder der fünf Testfälle aus demselben Grund fehlschlagen. Erro r : Un known prov i de r : BookDataServ i ceProvi der

      \\'ie bei dem FormController la utet a uch bei dem NgMode!Con­ :roller d ie Antwort: mithilfe des name-Attributs. Es gibt jedoch bei dem NgModelController einen kleinen Unterschied. Während uns -\ngularJS bei dem FormController mithilfe des name-Attributs die

      Validierung in Kombination mit der ngModei-Direktive Validierungszustand des NgMode/Controllers

      1 72

      4

      Die Anwendung erweitern

      Instanz unter der als Wert a ngegebenen Variable ( hier: book Form) in den aktuell gültigen Scope legt, funktioniert dieser Mechanis­ mus bei dem NgModelController so, dass die Instanz als Eigen­ schaft innerhalb des übergeordneten FormControllers publiziert wird. Den Namen der Eigenschaft können wir wieder mit dem Attribut­ wert festlegen ( hier: t i t l e ) . Das bedeutet schließlich, dass wir mit dem Ausdruck boo k Form . t i t l e auf die NgModeiController-Instanz für das Buchtitel-Feld zugreifen können. Somit lassen sich z.B. mit book Form . t i t l e . $d i rty oder boo k Fo rm . t i t l e . $ i nv a l i d die entsprechen­ den Zustandseigenschaften erfragen. Mithilfe dieser beiden Eigenschaf­ ten zeigen wir bedingt eine Fehlermeldung an. Dazu verwenden wir erneut die ngShow-Direktive. Die Anforderung, die wir auf d iese Wei­ se formulieren, lautet: Wenn der Benutzer mit dem Buchtitel-Feld be­ reits interagiert hat ( boo k Form . t i t l e . $d i rty) und einer der verwende­ ten Validatoren ( h ier: req u i red) die Eingabe als n icht valide erachtet ( bookForm . t i t l e . $ i nv a l i d ), dann zeige eine entsprechende Fehlermel­ dung an. H i nweis z u m FormController u n d Ng ModeiController

      Äquivalent zu den fünf Zustandseigenschaften $pri s t i ne, $d i rty, $val i d, $ i nval i d u nd $error sorgen der FormController wie auch der NgModeiController dafür, dass für das Formular bzw. seine Komponen­ ten auch entsprechende CSS-Kiassen gesetzt werden. Wenn z.B. die $di rty- und $va l i d-Eigenschaften den Wert t rue besitzen, dann an no­ tiert der NgModeiController das jeweilige -Eiement mit den CSS­ Kiassen ng-di rty und ng-va l i d. Das bedeutet, dass wir für diese CSS-Kiassen entsprechende Re­ geln definieren können, um dem Benutzer neben der Fehlermeldung ein visuelles Feedback in Bezug auf die Validierung zu geben. Wir werden darauf nicht mehr weiter eingehen. Wenn Sie das BookMonkey-Projekt aus dem offiziellen GitHub-Repository auschecken, werden Sie erken­ nen, dass wir diese CSS-Regeln beispielhaft in der Datei ma i n . cs s defi­ niert haben. Diese Regeln sorgen dafür, dass valide Eingaben mit einem grünen Hintergrund im Eingabefeld gekennzeichnet werden, während in­ valide Eingaben einen roten Hintergrund erhalten.

      Wenn wir uns Listing 4- 1 5 anscha uen, können wir erkennen, dass das Eingabefeld für die ISBN ebenfalls mit dem req u i red-Validator annotiert ist. Auch in diesem Fall weisen wir AngularJS an, die NgModeiController-Instanz i nnerhalb der FormController-Instanz zu publizieren. Somit können wir - wie zuvor a uch - mit dem entspre­ chenden Ausdruck book Form . i sbn . $d i rty && boo k Fo rm . i sbn . $ i nval i d ei­ ne bedingte Fehlermeldung anzeigen.

      4.1

      Der Ad m i n i strationsbereich

      Admi n i strati onsbere i c h

      Neues Buch a n l egen Buch ed i t i eren

      [. . .J

      B i tte geben S i e ei ne I SBN ei n .
      [ . . .J

      In Listing 4- 1 5 verwenden wir außerdem die ngDisabled-Direktive, um hinterher im Editieren-Modus die ISB nicht mehr verändern zu können. Neben der required-Direktive bringt AngularJS von Haus aus noch weitere Validator-D irektiven mit, die mit Absicht nach den entspre­ chenden HTML5-Attributen für die native Validierung benannt sind, um einen reibungslosen Umstieg zu ermöglichen. Im Einzelnen gibt es \'alidator-Direktiven für die folgenden Attribute: req u i red - definiert eine Eingabe als obligatorisch pattern

      -

      Überprüfung der Eingabe anhand eines regulären Aus­

      drucks mi n - Festlegen eines Minimumwertes für Zahlenwerte max - Festlegen eines Maximumwertes für Zahlenwerte mi n l ength - Festlegen einer Mindestlänge für Eingaben max l ength - Festlegen eines Höchstlänge für Eingaben

      .-\ ußerdem stellt das Framework a uch Validator-Direktiven für die neu­ en HTML5-Eingabefelder bereit.

      1 73

      Listing 4- 1 5 Nutzung der ngDisab/ed-Direktive

      1 74

      4

      Die Anwendung erweitern

      - Überprüfung auf Zahlenwerte - Überprüfung, ob die E ingabe einer URL ent­

      spricht - Überprüfung, ob die Eingabe emer E­

      Mai l-Adresse entspricht

      Weitere Validatoren

      Listing 4- 16 Der min-Validator bei Eingabefeldern für Zahlen

      In L isting 4- 1 6 sehen wir den Template-Ausschnitt, der die Elemen­ te für das Seitenzahl-Eingabefeld definiert. Wir können erkennen, dass wir in diesem Fal l zusätzlich zum req u i red-Validator noch zwei weitere Validatoren verwenden. Das ist zum einen der Validator, der durch das spezielle -Eingabefeld in Kraft tritt, und zum anderen der mi n-Validator, der überprüft, ob der eingegebene Zahlen­ wert einem Mindestwert entspricht. Das bedeutet also, dass der Aus­ druck boo k Form . n umPages . $ i nval i d von diesen drei Validatoren abhän­ gig ist. Admi n i s t ra t i onsbere i c h

      Neues Buch anl egen Buch ed i t i eren

      [ . . .] < i nput type = 11 n umbe r 11 mi n = 11 1 11 p l acehol der=11 Sei ten . . . 11 n g -model = 11 boo k . numPages 11 name = 11 n umPages 11 requ i red> D a s B u c h m u s s mi n . ei ne Sei te entha l ten . B i tte geben S i e ei ne g ü l t i ge Sei tenzahl ei n .
      [. . .]

      Bedingte Fehlermeldungen mit der $error-Eigenschaft

      Wir wollen nun eine allgemeine Fehlermeldung ausgeben, wenn der requ i red- oder n umber- Valida tor verletzt wurde. Falls allerdings der mi n -

      4.1

      Der Ad m i n i strationsbereich

      1 75

      \'alidator nicht erfüllt sein sollte, wollen wir stattdessen eine spezielle Fehlermeldung anzeigen, die den Benutzer darauf hinweist, dass er eine höhere Anzahl von Seiten eingeben sol l . Dazu können wir die $erro r­ Eigenschaft des NgModeiControllers nutzen. Wie bereits erwähnt, referenziert die $error-Eigenschaft ein Feh­ lerobjekt, dessen Objekteigenschaften die Zeichenketten-Repräsen­ rarionen der Validararen sind, d ie im aktuellen Zustand die Eingabe al invalide erachten. Im Falle des NgModeiControllers sind die Wer­ e dieser Objekteigenschaften a llerdings keine Arrays von Formular. omponenten, sondern Boolean-Werte, die angeben, ob der jeweilige \'alidator die Eingabe a ls invalide a nsieht ( t rue) oder nicht ( fa l se). Demnach können wir u nsere spezielle Fehlermeldung abhängig von dem Ausdruck book Form . n umPages . $error . mi n anzeigen bzw. ausblen­ den. Wir n utzen dazu w ieder das bekannte Muster des gegenseitigen :\usschlusses, indem wir die ngShow- und ngHide-Direktive einsetzen. Admi n i strati ons berei c h

      Neues Buch anl egen Buch edi t i eren

      [ . . .] < i nput type= " url " pl aceho l der="Websei te des Verl ags . . . " ng -model = " book . publ i s her . u rl " name = " publ i s herUrl " req u i red> Bi tte geben S i e ei ne g ü l t i ge URL ei n . Bi tte geben S i e d i e Websei te des Verl ags ei n .


      [. . .]

      Ahnlieh sieht es mit dem Eingabefeld aus, in das wir die URL zur Ver­ lagsseite eingeben (siehe Listing 4- 1 7) . In diesem Fall wollen wir die

      Listing 4-17 Die uri-Validator bei URL-Eingabefeldern

      1 76

      4

      Die Anwendung erweitern

      spezielle Fehlermeldung a l lerdings abhängig vom url - Val idator a nzei­ gen. Ansonsten folgt diese Definition demselben Muster wie die Defini­ tion des Eingabefeldes für die Seitenzahl. Was jetzt noch fehlt, u m das Feature zum Anlegen eines Buches fertigzustellen, ist der Admi n NewBookCtrl -Controller, für den wir bereit die Datei admi n_new_book . j s erstellt haben. In Listing 4- 1 8 können wir nachvollziehen, dass wir uns von AngularJS - wie ü blich - per De­ pendency Injection den Boo kDataServ i ce übergeben lassen. Diesen Ser­ vice verwenden wir in der Funktion s ubmi tAct i on ( ) , um das neue Buch­ Objekt abzuspeichern, wenn der Benutzer auf den Button mit dem La­ bel » Buch anlegen « geklickt hat. Außerdem rufen wir in dieser Funk­ tion die goToAdmi nli s tV i ew ( ) -Funktion auf, u m nach dem Speicher­ vorgang wieder zur Admin-Listenansicht zu gelangen. Hierzu nutzen wir i nnerhalb der goToAdmi nl i stVi ew ( ) -Funktion den bereits bekannten $1 ocat i on-Service. Listing 4- 18 Der AdminNewBookCtri­ Controller

      bmApp . contro l l er ( ' Admi nNewBoo kCtrl ' , funct i on ($scope , $ l ocat i on , BookDataServ i ce ) $scope . book {}; $ scope . su bmi tßtn label = ' Buch a n l egen ' ; =

      $scope . su bmi tAct i on = funct i on ( ) { BookDataServ i ce . storeBoo k ( $ s cope . book ) ; goToAdmi n l i s t V i ew ( ) ; }; $ scope . cancel Act i on = funct i on ( ) goToAdmi n l i stV i ew ( ) ; }; var goToAdmi n l i stV i ew = funct i on ( ) { $ l ocat i on . path ( ' /admi n /boo ks ' ) ; }; }); Trick bei der Verarbeitung von Formularen

      Der ausschlaggebende Grund dafür, dass wir die submi tAc t i on ( ) Funktion mit so wenigen Zeilen Quellcode i mplementieren können, ist die Tatsache, dass wir in unserem Template mit der Formularde­ finition in den ngMode/-Direktiven die Zwei-Wege-Datenbindung zu den Objekteigenschaften des book -Objekts herstellen. Das führt nämlich dazu, dass sich das book-Objekt immer weiter mit den nötigen Infor­ mationen füllt, während der Benutzer da Formular ausfüllt. Schließ­ l ich befinden sich also alle Buch-Informationen in dem book -Objekt und somit kann das Objekt unmittelbar der storeBoo k ( ) -Funktion de BookDataServ i ce übergeben werden. Eigentlich müssen wir das book,

      4.1

      Der Ad m i n i strationsbereich

      1 77

      bjekt im Scope noch nicht einmal initialisieren ($scope . book = { } ) . �\ngularJS würde die Initialisierung für uns übernehmen, sobald der Benutzer mit der ersten Formular-Komponente interagiert. Allerdings ..., i br es viele 3 rd-Party-Direktiven, die mit nicht i n itialisierten Objekten mcht u mgehen können. Deswegen sollten wir die Initialisierung den­ noch in d ie eigene Hand nehmen. H i nweis zur Datenbindung mit der n g Modei - D i re ktive

      Wir empfehlen bei der Verwendung der ngModei-Direktive immer einen Punkt im Ausdruck zu haben (z.B. n g-mode 1 =" boo k . t i t 1 e " ) . Einerseits hat das den Vorteil, dass wir auf diese Weise sehr einfach ein Objekt (z.B. book) sukzessiv mit den entsprechenden Daten befüllen können (vgl. Template für das Formular zum Anlegen/Editieren eines Buches). Andere rseits können wir damit unangenehme Seiteneffekte vermeiden, die aufgrund der prototypischen Vererbung von Scopes entstehen.

      \\'enn wir bis hierhin keinen Fehler gemacht haben, dann sollte es nun möglich sein, neue Bücher anzulegen. Das Formu lar sollte dabei in etwa o aussehen wie in Abbildung 4-4. 0 0

      0 BookMo n key c

      local host 8080/#/ad m i n / books/n ew

      BookMonkey Administrationsbereich �eues B uch anlegen

      Abbrechen

      X

      Buth

      ar

      egE''l

      Abb. 4-4 Ausgabe der Ansicht mit dem Formular zum A nlegen eines neuen Buches in Chrome

      1 78

      4

      Die Anwendung erweitern

      Wenn wir mit dem Formular interagieren, können wir feststellen, dass a uch die von uns definierten Validatoren greifen und in den invaliden Zuständen somit die angegebenen Fehlermeldungen angezeigt werden. 4.1 .5 Direktes Feedback während der Eingabe

      Abb. 4-5 Skizze der Ansicht zum Anlegen eines neuen Buches mit Vorschau

      Tem plates mit der ngl nclude-Direktive einbinden

      Nachdem wir das Formular zum Anlegen/Editieren eines Buches er­ stellt haben, wollen wir uns nun der Vorschau widmen. Der Benutzer soll beim Eingeben der Buch-Informationen ein unmittelbares Feedback dazu erhalten, wie die entsprechende Detailansicht aussehen wird ( vgl. Abbildung 4-5 ) . BookMonkey Administrationsbereich Neues Buch anlegen

      I JavaScript für Enterpri. . . Professionell programi. . .

      � I

      978-3-89864-72

      [ [ �e

      JavaScript ist längst ni...

      =:J

      302

      r Ochs



      I

      dpu nkt.verlag

      G,

      up://www.dpunkt.de Abbrechen

      J

      Vorschau JavaScript für Enterprise-Entvvickler Professionell programmieren im Browser und auf dem Server ISBN: 978-3-89864-728-1 Pages: 302 Author: Oliver Ochs Publisher: dpunkt.verlag

      IJl

      JavaScript ist längst nicht mehr nur für klassische Webprogrammierer interessant.

      Buch anlegen

      Durch die Zwei-Wege-Datenbindung lässt sieb das Vorschau-Feature verhältnismäßig einfach rea l isieren, indem wir die Scope-Variablen, zu denen wir mit der ngMode/-Direktive eine Datenbindung herstel­ len, an einer anderen Stelle im Template ausgeben. Wenn wir auf da Schnellstart-Kapitel ( vgl. Kapitel l ) zurückblicken, dann sollten wir er­ kennen, dass wir nach diesem Muster bereits zwei k leine Anwendungs­ beispiele mit Vorscha u gesehen haben. Zum einen h aben wir bei dem einfachen Beispiel mit dem -Feld den eingegebenen Wert mithil­ fe einer Expression unmittelbar wieder a usgegeben ( vgl. Abschnitt 1 . 1 ). Aber auch unser Color-Picker folgte genau diesem Muster ( vgl. Kapi­ tel 2.2. 8 ) . Das bedeutet, dass wir a uch hier dieses Muster nutzen könn­ ten, um das Vorschau-Feature zu implementieren.

      4.1

      Der Admin istrationsbereich

      Allerdings können wir uns einen Großteil des Template-Codes spa­ ren, wenn wir das Template für die Detailansicht an dieser Stelle wie­ derverwenden. Schließlich haben wir dort bereits festgelegt, wie die De­ railansicht aussehen soll . Ausschlaggebend für diese Art der Wiederver­ wendung ist die sogenannte nglnclude-Direktive. Admi n i strati onsbere i c h

      < h 3 ng-hi de= " i s Edi tMode " >Neues Buch anl egen Buch ed i t i eren

      1 79

      Wiederverwendung eines Templates mit der nglnc/ude-Direktive

      Listing 4- 1 9 Die nglnc/ude-Direktive

      [. . .J

      < d i v>

      Vorschau

      < d i v>

      ''ie wir in Listing 4- 1 9 nachvollziehen können, können wir mithilfe der nglnclude-Direktive i n ein Tempiare ein anderes Template einfü.._en . Dazu müssen wir bei der Verwendung dieser Direktive den relati' en Pfad zu dem Tempiare angeben, das an der entsprechenden Stelle emgefügt werden soll. Wir müssen dabei beachten, dass die nglncludeDirektive erwartet, dass der relative Pfad mittels einer Scope-Variable .wgegeben wird. Statische Pfade müssen daher - wie hier - in einfachen \nführungszeichen übergeben werden. H i nweis zu u n serem Vorschau-Feature

      Damit die Vorschau rechts neben unserem Formular gerendert wird und einen Rahmen besitzt, haben wir die entsprechenden HTML-Eiemente mit einigen CSS-Kiassen ( sp l i t - s c reen, s i mp l e-border und padded ) an­ notiert. Auf die genauen CSS-Regeln werden wir hier nicht weiter einge­ hen. I nteressierte finden den entsprechenden Stylesheet in dem offiziel­ len GitHub-Repository zum Buch.

      \\'enn wir a uch bei diesem Schritt keinen Fehler gemacht haben, sollte die Ausgabe im Browser in etwa so aussehen wie in Abbildu ng 4-6.

      Ein Template in einem anderen Template einbinden

      1 80

      4

      Abb. 4-6 Ausgabe derAnsicht mit dem Formular zum Anlegen eines neuen Buches in Chrome (inklusive Live-Vorschau)

      Die Anwendung erweitern

      0 0

      X

      � BookMonkey c

      localhost 8080/#/ad m i n / books / new

      BookMonkey Administrationsbereich Neues Buch anlegen

      Vorschau

      JavaScript effektiv Wollen Sie javaScript wirklich beherrschen?

      68 Dinge, d ie ein guter Ja vaScript-Entwickler wissen sollte • •

      Abbrechen

      Buch anlegen

      • •

      ISBN: 978-3-86490- 1 27-0 Seiten: 240 Autor: David Herman Verlag: dpunkt.verlag

      Wollen Sie JavaScript wirklich beherrschen?

      Voraussicht zahlt sich aus!

      Wäh rend wir i m Formular unsere Eingaben tätigen, sollten wir rechts in der Vorschau unmittelbar die getätigten Eingaben erkennen können. Ganz maßgeblich dafür, dass wir dieses Feature so einfach realisieren konnten, ist die Tatsache, dass wir uns im Vorfeld darüber Gedanken gemacht haben, wie wir die Scope-Variablen benennen. Dadurch dass wir im Template für die Detailansicht a uch mit einem book-Objekt ar­ beiten und konsequent darauf geachtet haben, dass alle Objekteigen­ schaften ( t i t l e, subt i t l e usw . ) durchgängig identisch benannt sind, können wir das Template ohne Probleme wiederverwenden.

      4.1

      4.1 .6

      Der Adm i n i strationsbereich

      1 81

      Die Funktion zum Ed itiere n eines Buches

      Unsere BookMonkey-Anwendung soll dem Admin a uch eine Möglich­ keit bieten, bestehende Bücher zu editieren. In der Admin-Listenansicht haben wir dazu bereits den entsprechenden Hyperlink eingefügt, der zu dem Formular zum Editieren eines Buches führt. Somit müssen wir erneut u nsere Routenkonfiguration i n der app . j s aktualisieren und die Route ladmi nlboo k s l : i sbnled i t einfügen ( vgl. Listing 4-20) . Wenn wir diese Route in unserer Applikation öffnen, sollen das Template aus der Datei boo k_form . html und der Admi n Ed i t BookCtrl -Controller geladen werden. Das bedeutet, dass wir bei der Editieren-Funktion das Templa­ te für das Formular zum Anlegen eines neuen Buches wiederverwenden. Die nötigen Vorkehrungen haben wir i n dem Template bereits getroffen ( vgl. Abschnitt 4. 1 . 4 ) . v a r bmApp

      =

      angul a r . modu l e ( ' bmApp ' , [ ' ngRoute ' ] ) ;

      bmApp . confi g ( funct i on ( $ routeProvi der ) {

      Listing 4-20 Routen-Konfiguration für das Formular zum Editieren eines Buches

      [. . .] I * Admi n routes * I [. . .] . when ( ' ladmi nlbooksl : i sbnledi t ' , templ ateUrl : ' temp l ates ladmi n lbook_form . html ' , cont rol l er : ' Admi n Ed i t BookCtrl ' }) [. . .] });

      Für den Admi nEd i tBookCtrl -Controller müssen wir in unserem Verzeich­ nis lapplsc ri ptslcontrol l ersl noch die Datei admi n_ed i t_boo k . j s erstel­ len. Die Implementierung sehen wir in Listing 4-2 1 . bmApp . cont ro 1 1 er ( ' Admi n Ed i t Boo kCtrl ' , functi on ( $ s cope , $routeParams , $ l ocat i on , BookDataServ i ce ) $scope . i sEdi tMode t ru e ; $scope . s ubmi tBtn label = ' Buch edi t i eren ' ; =

      var i s bn = $routePa rams . i s bn ; $scope . book = Book DataServ i ce . getBookBy l s bn ( i sbn) ;

      Listing 4-2 1 Der AdminEditßookCtri­ Controller

      1 82

      4

      Die Anwendung erweitern

      $s cope . s u bmi tAct i on funct i on ( ) { Book0ataServ i ce . u pdateBoo k ($scope . boo k ) ; goToAdmi n l i s t V i ew ( ) ; }; =

      $scope . cancel Act i on functi on ( ) goToAdmi n L i st V i ew ( ) ; }; =

      var goToAdmi n l i s t V i ew funct i on ( ) { $ l ocat i on . path ( ' /admi n/books ' ) ; }; =

      });

      Prinzipiell ist der Admi n Ed i t BookCtrl - Cont ro l l er nach dem gleichen Muster aufgebaut wie der Admi n NewBookCtrl - Controller Zu den we­ sentl ichen Unterschieden gehört die Definition der Scope-Variable i s Edi tMode. D iese Variable setzen wir auf t rue, um im Tempiare die nötigen HTML-Elemente anzuzeigen bzw. a uszublenden und da Feld für die ISBN zu deaktivieren. Weiterhin weisen wir der Scope­ Variable submi tßt n label die Zeichenkette » Buch editieren« zu, um die Beschriftung des Buttons, der bei einem Klick die s ubmi tActi an ( ) ­ Funktion aufruft, entsprechend festzulegen. Wir müssen außerdem wissen, welches Buch editiert werden soll. Daher greifen wir er­ neut mithilfe des $ routePa rams-Service auf die ISBN zu, die per URLPfadparameter übergeben wird. Mithilfe der ISBN holen wir uns mittel Boo kDataServ i ce . getBookBy i sbn ( ) das entsprechende Buch-Objekt und referenzieren es mit der Scope-Variable book . Auf diese Weise stellen wir eine Vorbelegung unseres Formulars sicher. Schließlich müssen wir beim Aufruf der s ubmi tAct i on ( ) -Funktion das Buch-Objekt mithilfe von BookDataServ i ce . updateßook ( ) aktualisieren. Das ist an dieser Stelle al­ les, um das Editieren-Feature zu implementieren. Das Resultat können wir in Abbildung 4-7 erkennen. .

      Vorbelegung des Formulars

      4.1 .7

      Die Funktion zum Löschen eines Buches

      Neben dem Anlegen und Editieren von Büchern ist natürlich a uch da Löschen für einen Admin eine wichtige Funktion. Die eigentliche An­ sicht ist dabei recht einfach, denn sie besteht aus einer Frage und zwei Schaltflächen, eine zum Bestätigen und eine zum Abbrechen ( vgl. Ab­ bildung 4-8 ) . Auch für d i e Löschfunktion haben w i r bereits in der Admin­ Listenansicht einen entsprechenden Hyperlink gesetzt. Somit müs­ sen wir unsere app . j s um die dort verwendete Route erweitern

      4.1

      IZJ sookMonkey

      () 0

      c

      Der Ad m i n i strati onsbereich

      X

      local host 8080 /#/ad m i n / books/ 9 78- .

      . .

      0

      ....

      ®

      BookMonkey

      _

      1 83

      Abb. 4-7 Ausgabe der Ansicht mit dem Formular zum Editieren eines Buches in Chrome (inklusive Live-Vorschau)

      Administrationsbereich Vorschau

      Buch editieren

      )avaScript effektiv 6 8 Dinge, die ein gutE

      JavaScript effektiv

      978 3 86490- 12 7-0

      Wollen Sie JavaScript wirklich beherrschen�

      68 Dinge, d ie ein guter J avaScript-Entwickler

      240

      wissen sollte

      David Herman dpunkt.verlag http://dpunkt.de/ Abbrechen Buch editieren

      • • • •

      ISB N : 978-3-86490- 1 27-0 Seiten: 240 Autor: David Herman Verlag: dpunkt.verlag

      Wollen Sie JavaScript wirklich beherrschen?

      BookMonkey Adm i nistrationsbereich Soll das Buch "Node.js

      & Co." wirklich gelöscht

      werden?

      [

      löschen

      [ Abbrechen

      Abb. 4-8 Skizze der Ansicht zum Löschen eines Buches

      1 84

      4

      Die Anwendung erweitern

      ( vgl. Listing 4-22 ) . Wenn wir also in unserer Applikation die Rou­ te ladmi nlbooksl : i s bnlde l ete aufrufen, dann sollen das Template book del ete . html und der Admi nDe l eteßookCtrl -Contro ller geladen wer­ den. Listing 4-22 Routen-Konfiguration für den Dialog zum Löschen eines Buches

      var bmApp = angu l a r . modu l e ( ' bmApp ' , [ ' ngRoute ' ] ) ; bmApp . con f i g ( fu nct i on ( $ routeProvi der) { [ 0 0 0] I * Admi n routes [ 0 0]

      *

      I



      . when ( ' ladmi n lbooks l : i s bn ldel ete ' , temp l ateUrl : ' templ ates ladmi n lbook_del ete . html ' , contro l l er : ' Admi n Del eteßookCtrl ' }) [ 0 0 0] });

      Wie zuvor auch müssen w i r dafür die entsprechenden Dateien erzeugen. Für das Template erstellen wir in dem Verzeichnis lappltemp l atesladmi nl die Datei boo k_del ete . html . Den Inhalt der Datei können wir in Listing 4-23 erkennen. Listing 4-23 Das Template für den Dialog zum Löschen eines Buches

      Admi n i s t rat i ons berei eh

      Sol l das Buch " ; { { book . t i t l e } } " ; w i rkl i ch gel öscht werden?

      Lösc hen Abbrechen

      In dem Template nutzen wir nur Mechanismen, die wir bereits bespro­ chen haben. Wir stellen dem Admin die Frage, ob das Buch wirklieb gelöscht werden soll, und erwarten, dass er den Löschvorgang bestätigt oder abbricht. Für die beiden Aktionen definieren wir jeweils einen But­ ton. Wenn der Admin den Löschvorgang bestätigt, dann rufen wir die Funktion del eteßook () auf und übergeben die ISBN. Bei einem Abbruch wird die Funktion cancel ( ) aufgerufen. Auch der Admi nDe l eteßookCt r 1 -Controller besteht aus emer ü bersichtlichen Anzahl von Codezeilen. Wir erstellen für den Con-

      4.1

      Der Ad m i n i strationsbereich

      1 85

      rroller die Datei admi n_del ete_boo k . j s im bekannten Verzeichnis 'appjs c ri pts/control l ersj. Wie alle anderen JavaScript-Dateien unse­ rer Anwendung a uch müssen wir die admi n_del ete_boo k . j s mit einem -Tag in u nserer i ndex . h tml einbinden. bmApp . contro l l er ( ' Admi nDe l eteBookCtrl ' , functi on ( $ scope , $ routePa rams , $ l ocat i on , BookDataServ i ce ) v a r i s bn $ routePa rams . i sbn ; $scope . book BookDataServ i ce . getBookBy l s bn ( i s bn) ; =

      =

      $ scope . de l eteBook funct i on ( i sbn ) { Boo kDataServ i ce . de l eteBookBy l sbn ( i sbn) ; goToAdmi nli st V i ew ( ) ; }; =

      $scope . cance l funct i on ( ) goToAdmi nli s t V i ew ( ) ; }; =

      var goToAdmi nli s t V i ew functi on ( ) { $ l ocat i on . path ( ' /admi n/book s ' ) ; }; =

      );

      In Listing 4-24 sehen wir die Implementierung des Admi n Del eteBookCt rl ­ Lontrollers. Wie beim Admi n Ed i t Boo kCtrl -Controller lesen wir m it­ f-tilfe des $routePa rams-Service die I SBN des Buches a us, das geehr werden soll . Wir übergeben die ISBN der Servicefunktion 3ookDat aServ i c e . getBookBy l sbn () und referenzieren das zurückgegebe­ '1e Buch-Objekt mithilfe der Scope-Variable boo k, damit wir im Tem­ "'late auf den Buchtitel und die ISBN zugreifen können. Zu gu­ ·er Letzt definieren wir die beiden Funktionen del eteBook ( ) und :ancel ( ) , die aufgerufen werden, wenn wir auf den entsprechenden Button klicken. Die Funktion de l eteBook ( ) ruft intern die Funktion 3ookDataServi c e . de l e teBoo k Byl sbn () auf, um das Buch tatsächlich zu löhen, wenn der Admin den Löschvorgang bestätigt hat. Anschl ießend \ erden wir mithilfe des $1 ocati an- Service zur Admin-Listenansicht wei­ ·ergeleitet. Letztere Aktion erfolgt a uch im Falle eines Abbruchs. In Abbildung 4-9 können wir uns a nschauen, wie der Löschdialog Ju sieht. Um ihn a ufzurufen, müssen wir in der Admin-Listenansicht Juf den Hyperlink zum Löschen des entsprechenden Buches klicken . .: m Klick auf » Löschen > Digest already in progress« -Fehler entgegenzuwirken .

      Implementierung der

      bmApp . di rect i ve ( 1 tokenfi e 1 d 1 , functi on ( BookDataServi ce ) { return { [. . .] l i n k : funct i on ( s cope , e l em) { [ . ]

      Listing 4-35 Oie tokenfield-Direktive

      .

      .

      func t i on i n i t ( ) { i f ( angu l a r . i sDefi ned ( scope . tokenfi el d . tags ) ) i f ( s cope . tokenfi e l d . tags . l ength > 0 ) { II t h i s c a l l emi t s an 1 a fterCreateToken 1 event II and t h i s wou l d i mpl y a 1 d i gest a l ready II i n progreS S 1 wi thout the i n i t i a l i z ed fl ag el em . tokenfi e l d ( 1 setTokens 1 , s cope . tokenfi e l d . tags ) ;

      e l se { scope . t o kenfi el d . tags

      i n i t i a l i zed

      [] ;

      true ;

      i ni t () ;

      }) ;

      Der Hintergrund ist, dass unsere tokenfield-Direktive mit zwei Fällen ordentlich u mgehen muss, denn es gibt zwei potenzielle Aufrufszenari­ os. Einerseits werden wir die Direktive während des Editierens eines Bu­ ches verwenden. Andererseits kommt sie ebenfalls zum Einsatz, wenn wir ein neues Buch erstellen. In dem ersten Fall ist es sehr wahrschein­ l ich, dass das Buch-Objekt zuvor bereits mit bestimmten Tags annotiert wurde. Das bedeutet, dass wir in d iesem Fall dafür sorgen müssen, dass das Tokenfield-Plugin mit den bestehenden Tags vorbelegt wird, damit es die entsprechenden Tokens erzeugen kann. Der Aufruf sieht folgen­ dermaßen aus: el em . tokenfi el d ( 1 setTokens 1 , s cope . tokenfi el d . tags) ;

      init()-Funktion

      206

      4

      Die Anwendung erweitern

      Die Auswirkung dieses Aufrufs ist aber der G rund dafür, dass wir den » Digest already in progress« -Fehler erhalten würden, wenn wir nicht mit dem i n i t i al i z ed -Flag arbeiten würden. Denn das Vorbele­ gen des Tokenfield-Plugins mit diesem Aufruf führt a uch dazu, dass das Plugin für jedes erstellte Token das afterC reateToken-Event a ussen­ det. Und wir erinnern uns: Als Reaktion auf dieses Event rufen wir die addToken ( ) -Funktion auf, die intern mittels s cope . $appl y ( ) ein manu­ elles Dirty Checking anstößt. Das bedeutet, dass wir während eines D irty-Checking-Zyklus ( Link-Funktion ) ein weiteres D irty Checking anstoßen und somit den erwähnten Fehler erha lten würden. I ndem wir das i n i t i a l i z ed -Flag erst auf t rue setzen, nachdem wir die potenzielle Vorbelegung durchgeführt haben, ist dieser Fehler nun ausgeschlossen. Listing 4-36 Nutzung der tokenfie/d-Direktive in dem Template mit dem Formular zum Anlegen!Editieren eines Buches

      Admi ni strat i ons berei eh

      [. . .]

      [ ] . . .

      [. .]

      Vorschau

      .

      Das ist an dieser Stelle alles, u m das Tokenfield-Plugin rei bungslos in die Welt von AngularJ S einzubinden . Wenn wir unsere tokenfield­ Direktive nun entsprechend u nserer Defin ition in dem Tempiare mit dem Formular zum Anlegen/Editieren eines Buches { book_form . html ) verwenden ( vgl. Listing 4-36), dann sollten wir nun ein Token­ Eingabefeld sehen (vgl. Abbildung 4- 1 1 ), das sich nahtlos in den Zy­ k l us der Zwei-Wege-Datenbindung integriert. Mit d iesem Eingabefeld können wir nun sehr bequem ein Buch in Kategorien eintei len, i ndem wir es mit dem entsprechenden Tag versehen. Außerdem sollten die de­ finierten Unit-Tests nun a uch » grün « werden. Noch deutlicher wird das Ergebnis, wenn wir im nächsten Unterka­ pitel die tags-Direktive zum Ausgeben von Tags i mplementiert haben.

      4.2

      �BookMonkey

      0 0 0

      c

      Kategorisierung d u rch Tag s

      X

      localhost 8080/#/ad m i n / boo k s / 9 7 8 - 3 -8 64 . .

      0

      "

      0

      _

      BookM on key Ad min istrationsbereich B u c h ed itieren

      Vorschau

      CoffeeScript Einfach JavaScript

      978-3-86490-050-1 CoffeeScript ist eine junge, kleine

      200

      CoffeeScri pt Ei nfach J avaScript •

      ISBN:

      978-3-86490-050-1

      Seiten:

      Andreas Schubert dpunkt.verlag

      200



      Autor: Andreas Schubert



      Verlag: dr unkt.verlag

      http://dpunkt.de/ coffeescript Abbrechen

      web Buch editieren

      CoffeeSc ript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird. Zurück

      Denn dann werden sich die Auswirkungen unserer Tag-Eingaben un­ mittelbar in der Vorschau widerspiegeln. 4.2.4

      Die Tags-Direktive: Tags a nzeigen

      Neben einer Eingabemöglichkeit für Tags müssen wir natürlich auch einen Mechanismus erstellen, der die eingegebenen Tags wieder aus­ geben kann. Wie bereits erwähnt, wollen wir auch dazu wieder eine Direktive implementieren. Die tags-Direktive soll ein neues HTML-Element definieren. Weiterhin soll es möglich sein, über ein HTML-Attribut tag-data eine Datenquelle für die Tag-Ausgabe festzulegen. Bei der Datenquelle wol­ len wir uns auf ein Array von Zeichenketten einigen . Listing 4-37 zeigt das Template für die Detailansicht eines Buches ( book_detai 1 s . h tml ) mit der Verwendung unserer tags-Direktive. Das tag-data-Attribut setzen

      207

      Abb. 4- 1 1 Ausgabe des Formulars zum Anlegen!Editieren eines Buches mit tokenfie/d-Oirektive in Chrome

      208

      4

      Die Anwendung erweitern

      wir auf boo k . tags, um der Direktive das Array mit den Buch-Tags zu übergeben. Listing 4-37 Nutzung der tags-Direktive in der Detailansicht

      Die Implementierung der tags-Direktive

      Listing 4-38 Die tags-Direktive

      [ . . .]





      Z u rück

      11

      Wie wir uns sicherlich vorstellen können, ist die Implementierung unserer tags-Direktive recht übersichtlich. Dafür erstellen wir zunächst in dem Verzeichnis /app/scri pts/di rect i ves/ die Datei tags . j s, in der wir die Direktive implementieren werden. Den Inhalt dieser Datei sehen wir in Listing 4-3 8 . bmApp . d i rect i ve ( 1 tags 1 , funct i on ( ) { retu rn { restri ct : 1 E 1 , t emp l ateUrl : 1 COmponent_templ ates/di rect i ves /tags . html 1 , s cope : { tagData : = 1 1

      }) ;

      Auch hier definieren wir die Direktive mithilfe eines Direktiven­ Definitions-Objekts (DDO). Die re stri ct E igenschaft setzen wir auf die Zeichenkette 1 E 1 , um AngularJS mitzuteilen, dass wir mit dieser Definition ein HTML-Eiement definieren wollen . Außerdem legen wir mithilfe der temp l ateUrl -Eigenschaft fest, dass u nsere Direktive ein eige­ nes Tempiare besitzen soll. Weiterhin müssen wir der scope-Eigenschaft ein Objekt mit der tagData -Eigenschaft zuweisen. Mit dieser Eigen­ schaft ermöglichen wir unserer Direktive den Zugriff auf das tag-data­ Attribut. Das Ganze entspricht den bekannten Namenskonventionen ( vgl. Abschnitt 2.2. 8 ) . Dadurch dass wir eine Zwei-Wege-gebundene Referenz auf das übergebene Array erhalten wollen, legen wir für die tagData -Eigen schaft den Wert = fest. Wir werden sehen, dass diese -

      1

      1

      4.2

      Kategorisierung durch Tags

      Zwei-Wege-Verbindung a usschlaggebend dafür ist, dass sich die Tag­ Ausgabe unserer Direktive automatisch aktualisiert, wenn sich der In­ halt des Arrays ändert. Nun müssen wir noch das Template definieren. Dazu erste!len wir im app-Verzeichnis u nserer Anwendu ng das Verzeichnis component_templ ates. In diesem Verzeichnis erstellen wir außerdem noch das Unterverzeichnis di rec t i ves. Dort legen wir schließlich die Datei tag s . html an. Das ha ben wir in Listing 4-38 mit der temp l ateUrl Eigenschaft so definiert. Das Tempiare unserer tags-Direktive sehen wir in Listing 4-39. Für die Ausgabe des Zwei-Wege-gebundenen Arrays s cope . tagData nutzen wir einen alten Bekannten: die ngRepeatDirektive. Die Schleifenvariable tag geben wir mit einer gewöhnlichen Expression { { tag } } aus und sorgen mit der CSS-Kiasse bm- t ag dafür, dass unsere Tags einen blauen Hintergrund und a bgerundete Ecken erhalten. Auf die genaue CSS-Regel gehen wir hier nicht weiter ein. { { tag } }

      Da die ngRepeat-Direktive für jedes Element aus dem Array a utoma­ tisch das entsprechende -Element erzeugt und die Ausgabe im Falle einer Array-Manipulation ebenfalls automatisch aktualisiert, wer­ den wir nun in der Vorscha u des Formulars für das Anlegen/Editieren eines Buches ein direktes Feedback erhalten, wenn wir mithilfe der to­ kenfield-Direktive neue Tags einpflegen bzw. entfernen. Wir erinnern uns: In dem Tempiare für das Formular zum Anlegen/Editieren eines Buches verwenden wir das Tempiare für die Detaila nsicht wieder, um die Live-Vorschau zu erstellen. Das ist alles, um eine Möglichkeit zur Ausgabe von Tags zu schaf­ fen. Wenn wir keinen Fehler gemacht haben, sollte die Ansicht zum Anlegen/Editieren eines Buches nun so a ussehen wie Abbildung 4 - 1 2. Wenn wir jetzt Tags hinzufügen oder entfernen, dann wird sich a uch die Live-Vorschau entsprechend aktualisieren. Zusammenfassung

      In Unit-Tests werden die internen Mechan ismen von AngularJ S n icht automatisch a usgeführt, um dem Entwickler d i e volle Kon­ trolle über den Testablauf zu geben. Das bedeutet, dass wir beim Testen von Direktiven einige D inge manuell erledigen müssen, die das Framework bei der normalen Ausführung automatisch durchführen würde.

      209

      Das Template für die tags-Direktive

      Ausgabe der Tags mithilfe der ngRepeat-Direktive

      Listing 4-39 Das Template der tags-Direktive

      210

      Abb. 4- 12 Ausgabe der Live-Vorschau des Formulars zum Anlegen!Editieren eines Buches mit tags-Direktive in Chrome

      4

      Die Anwendung erweitern

      () ()

      IZJ sookMonkey

      X

      x

      localhost:4730/api/books

      local host 8080/#/ad m i n / books / 9 78 - 3 - 86 4

      0

      �) -

      BookMon key Ad m i n i strationsbereich B uch ed itieren

      Vorschau ·------------------------------------------

      CoffeeScript Einfach JavaScript

      978-3-86490-050-1 CoffeeScript ist eine junge, kleine

      CoffeeSc ript Ei nfach JavaScri pt ISBN:

      200

      978-3-86490-050-1

      Seiten: 200

      Andreas Schubert



      dpunkt. verlag

      Autor: Andreas Schubert Verlag:

      J u

      http://dpunkt.de/ coffeescript Abbrechen

      web Buch editieren

      CoffeeScript ist eine junge, kleine Programmiersprache, die nach JavaScript übersetzt wird.

      So müssen wir insbesondere das Tempiare manuell kompilieren, in dem die Direktive verwendet wird. Dazu nutzen wir den $compi 1 e­ Service. Beim Kompilieren wird AngularJS die zu testende Direktive erkennen und somit unter anderem die Link-Funktion der Direktive a usführen. Wir kompilieren ein Template immer gegen einen Scope. In Unit­ Tests können wir einen neuen Scope erzeugen, indem wir uns von AngularJS den $ rootScope per Dependency Injection übergeben las­ sen und auf dem $ rootScope die $new ( ) -Funktion aufrufen. In Unit-Tests für Direktiven müssen wir oftmals die genaue Be­ schaffenheit des DOM überprüfen. Über angul a r . el ement ( ) können wir dazu jqLite nutzen . Das ist die eigene j Query-Implementierung, die AngularJS mitbringt. Die Einschränkung ist jedoch, dass jq-

      4.3

      21 1

      Einen REST Web Service a n b i nden

      Lite nur eine Untermenge der Funktionen der richtigen jQuery­ lmplementierung bereitstellt. Falls wir in unserer i ndex . h tml die richtige j Query-lmplementierung vor AngularJS importiert haben, wird AngularJS das feststellen und jeden Aufruf von ang u l a r . el ement ( ) an die richtige Implementie­ rung weiterdelegieren. Mithi lfe von Direktiven ist es ziemlich einfach, bestehende j Query-Plugins und andere 3 rd-Party-Bibliotheken in die Welt von AngularJS einzubinden. Jedoch müssen wir beachten, dass wir ein Plugin rei bungslos in den Zyklus der Zwei-Wege-Datenbindung integrieren. Dazu müssen wir bei jeder Scope-Manipulation, die i nnerhalb einer Callback-Funktion durchgeführt wird, die innerhalb einer 3 rd­ Party-Bibliothek zur Ausführung kommt, dafür sorgen, dass wir mittels scope . $appl y ( ) das D i rty Checking manuell anstoßen.

      4.3

      Einen R EST Web Service a n bi nden

      Wenn wir ehrlich sind, ist unsere Anwendung bisher noch ziemlich langweilig, weil jeder Benutzer seinen Datenausschnitt im eigenen Browser besitzt u nd es keine übergeordnete Datenverwaltung gibt. Das bedeutet, dass es i nsbesondere n icht möglich ist, Buchinformationen zwischen zwei laufenden Anwendungen zu synchronisieren und auf den jeweils anderen Datenausschnitt zuzugreifen. Diese Einschränkung wollen wir in diesem Kapitel aufheben, indem wir u nsere Appl ikationsdaten zentral auf einem Server verwalten. Die Kommunikation mit einem Backend gehört dabei zu den typischen Anwendungsfällen bei Single-Page-Anwendungen. Wie wir beim Projektstart bereits erwäh nt haben, nutzen wir dazu eine Backend-Implementierung, die wir auf Basis von Node.js erstellt haben. Mithilfe des Express-Frameworks6 stellen wir für unsere AngularJS-Anwendung einen Endpunkt zur Verfügung, auf den u nsere Applikation über HTTP zugreifen kann. Dabei haben wir versucht, die Backend-Schnittstelle - soweit es für unser Beispiel praktika bel ist möglichst >> R ESTful « zu gestalten. Somit stellt unser Backend einen sogenannten R EST Web Service bereit. Auch in diesem Schritt definieren wir die Anforderung mithilfe einer User Story. 6 http://expressjs.com/

      Kommunikation mit einem Backend Node.js Backend

      RESTfu/ Web Service

      212

      4

      Die Anwendung erweitern

      Als Nutzer Michael möchte ich meine Daten auf einem zentralen Server speichern, um diese von mehreren Endgeräten aus abrufen zu können.

      4.3 . 1

      Die REST-Ressourcen

      Das BookMonkey-Backend

      An dieser Stelle wollen wir das Backend kurz vorstellen. Um den Fo­ kus weitestgehend a u f die Backend-Schnittstelle zu lenken, werden wir die I mplementierung allerdings n icht im Detai l besprechen. Wer sich mit Node.js und dem Express-Framewerk intensiver auseinandersetzen möchte, dem sei an d ieser Stelle das Buch ,, ode.js und Co. > Post-Build­ Action« die Option » Publish JUnit Test Result Report« a uswählen. Hier können wir nun unsere Ausgabedatei mit den Testergebnissen aus­ wählen, die dann nach jedem D u rchlauf eingelesen und angefügt wer­ den. Zusätzlich können Schwellenwerte definiert werden, die angeben, ab wann der komplette Build als fehlgeschlagen zu werten ist. Zusam menfassung

      Karma ist ein Werkzeug, das auf der Node.js-Plattform basiert. Es ermöglicht uns das automatisierte Ausführen von Tests. 28 https://wi k i . j enk ins-ci.orgldisplay/jENKINS/xUnit + Plugin

      Listing 5-4 1 Mit Karma einen JUnit-Report generieren

      Testergebnisse in Jenkins importieren

      270

      5

      Projektverwaltung und Automatisierung

      Hierzu startet Karma einen eigenen Webserver, auf den sich dte Testgeräte verbinden müssen. Somit ist jeder Browser auf jedem Endgerät, der über das Netzwerk auf diesen Server zugreifen kann. ein potenzieller Ort für die Testausführung. Auf diese Weise kann d ie A usführung auch auf mobilen Endgeräten sehr einfach gewähr­ leistet werden. Die Konfiguration erfolgt über eine einfache Datei tm JSO Format, die standardmäßig karma . conf . j s heißt. Es gibt eine Integration in die WebStorm-IDE. Wir haben die Möglichkeit, verschiedene Test-Frameworks einzu­ binden, die wir für die Spezifikation unserer Testfälle verwenden können. D ie bekanntesten Frameworks i m JavaScript-Bereich ha­ ben wir kurz vorgestellt. Kanna bietet uns Mechanismen an, die uns eine einfache Integrati­ on i n ein CI-System ermöglichen. Wir haben die Möglichkeit, über Plugins einen Export der Ergeb­ nisse in eine XML-Datei anzustoßen. Diese Datei d ient als Schnitt­ stelle zu weiteren Systemen. Anhand eines Beispiels haben wir die Integration in ein bestehende System mit Maven und ]enkins dargestellt.

      5.5 5.5.1

      Umfassende Unterstützung beim Aufbau der Projektstruktur

      Yeoman: E i n defi n ierter Workflow Was ist Yeoman ?

      Yeoman29 ist ein in der Praxis getesteter Workflow, der es uns erlaubt. sich vollständig auf die Entwicklung der eigentlichen Anwendung zu konzentrieren. Durch eine geschickte Kombination von Generatoren und der Werkzeuge Bower und Grunt kann die Umsetzung des eigent­ lichen Anwendungsfa l ls fokussiert werden. Begleitende Nebenaufgaben wie das Festlegen einer sinnvollen Ordnerstruktur, die Verwaltung von Abhängigkeit oder das Heraussuchen der genauen Syntax für Anwen­ dungskomponenten überlassen wir dabei den entsprechenden Werkzeu­ gen. Auch für Aufgaben, die die Entwicklungsinfrastruktur betreffen, gibt es in Yeoman eine Lösung. Dazu gehört insbesondere die Bereit­ stellung eines Webservers und die Erzeugung eines Build-Prozesses.

      29 http://yeoman.io/

      5.5

      5.5.2

      Yeoman: Ein defi n ierter Workflow

      271

      Yeoman i nstal lieren

      Yeoman ist ein Node.js-Modul. Wir können dieses Werkzeug also mithilfe von npm installieren:

      lnstallation über npm

      npm i ns t a l l -g yo

      Die Installation beinhaltet neben dem Generator Yo die Werkzeuge Bower und Grunt, weil d iese a ls direkte Abhängigkeiten von Yeoman eingetragen sind. Somit sind wir nach der erfolgreichen Installation in der Lage, über die Kommandozeile die Befehle yo, bower und g runt zu nutzen. D ie offizielle I nstallationsanleitung ist ebenfa l ls online verfüg­ bar30 . Um den Generator Yo tatsächlich nutzen zu können, benötigen wir zunächst noch einen Generator für das Framework, mit dem wir arbeiten wollen. D iese Generatoren sind ebenfa ll s Node.js-Module und können somit a uch über npm installiert werden. Beispielhaft ist an die­ ser Stelle die Installation des Generators für AngularJS aufgeführt: npm i ns t a l l - g generator-angul a r

      E s gibt natürlich noch viele weitere Generatoren, d i e w i r installieren können. Wir haben einerseits die Möglichkeit, ü ber npm direkt nach Generatoren zu suchen. Andererseits können wir uns auch in dem of­ fiziellen GitHub-Projekt31 die Liste der Generatoren ansehen. Um mit­ hilfe von npm eine entsprechende Suchanfrage zu starten, können wir den nachfolgenden Befehl verwenden: npm search generator-%name%

      5.5.3

      Anwendungsbausteinen generieren

      Wir haben i n jedem neuen Projekt zunächst die Aufgabe, die grundle­ genden Strukturen für das Projekt zu erstellen. Dazu gehört bei Weban­ wendungen in erster Linie die Erstellung von bestimmten Dateien wie z.B. einer i ndex . h tml und üblicherweise Verzeichnissen für Bilder, CSS­ und JavaScript-Dateien (die sogenannten Assets} . Allerdings ist es so, dass es kein Patentrezept zur Erstellung dieser grundlegenden Struktu­ ren gibt. Ganz im Gegenteil: Häufig stehen wir als Entwickler vor der Qual der Wahl, die oft durch persönliche Vorlieben beeinflusst wird. Die einen Entwickler führen mit a s sets ein Zwischenverzeichnis ein. Andere nennen dieses Verzeichnis res oder verzichten komplett darauf. Wie dem auch sei, wenn wir ehrlich sind, sind diese wiederkehrenden 30 http://yeoman.io/gettingstarted. html 31 https://girhub.com/yeoman/

      Bootstrapping eines Projektes

      272

      5

      Inspiriert durch Rails

      Eng/. »Scaffo/ding«

      Es gibt verschiedene Generatoren.

      Projektverwaltung und Automatisierung

      Aufgaben mühselig, fehleranfä llig und führen zu vielen unterschiedli­ chen Projektstrukturen. Warum können wir diese Aufgaben dann nicht einfach automatisieren und homogenisieren ? Durch Rails i nspiriert, entstand das Projekt Yo32 , das sich der Lösung dieser Aufgabe für clientseitige Webanwendungen widmet. Yo ist ein Kommandozeilenwerkzeug, mit dem wir die Grundstrukturen für neue Projekte automatisch und einheitlich generieren können. Die De­ finitionen h ierfür werden in sogenannten Generatoren bereitgestellt. Yo bringt bereits Generatoren für viele aktuell relevanten Frameworks mit. Dazu gehört a uch, wie bereits erwähnt, ein Generator für AngularJS33 • Tatsächlich ist der AngularJS-Generator einer der ersten Generatoren, die in diesem Projekt entwickelt wurden. Neben dem Generator für AngularJS sind u nter anderem Generatoren für die folgenden Frame­ works verfügbar: Ember.js Backbone.js jQuery Jasmine Mocha Twitter Flight Die Benutzung eines Generators hat den positiven Nebeneffekt, da s die Projektstrukturen immer einheitlich sind und sich somit neue Ent­ wickler in bestehenden Projekten schneller zurechtfinden . 5.5.4

      Generierung von AnWendungsbausteinen

      Yo für AngularJS-Projekte

      Der Generator für AngularJS war einer der ersten Generatoren, die in­ nerhalb des Yeoman-Projektes entstanden sind. Er bietet uns die Mög­ lichkeit, die typischen AngularJS-Anwendungsbausteine zu generieren u nd automatisch in unsere i nde x . h tml einzutragen . Außerdem wird für jeden erzeugten Anwendungsbaustein sofort eine Test-Suite erstellt. Wir werden in diesem Kapitel die verschiedenen Sub-Generatoren besprechen . Zunächst schauen wir uns jedoch in Listing 5-42 die grund­ legende Ordnerstruktur an, die der AngularJS-Generator erstellt. Auf der ersten Ebene haben wir durch die Verzeichnisse app und test eine grundlegende Trennung in Anwendungs- und Testdateien. I nnerhalb 32 http://yeoman . io/ 33 https://github.com/yeoman/generator-angular

      5.5

      Yeoman: Ein defi n ierter Workflow

      273

      des app-Verzeichnisses haben wir eine weitere Unterteilung in Skript-, Stylesheet und Template-Dateien. In dem scri pts-Verzeichnis differen­ ziert der Generator noch einmal zwischen den verschiedenen Anwen­ dungsbausteinen, die AngularJS mitbringt ( Controller, Services, Direk­ tiven, usw. ) . app/ scri pts/ cantro l l ers / decorators / d i rec t i ves/ fi l ters/ serv i ces/ s tyl es/ v i ews/ tes t / s pec/ contro l l ers/ decorators / d i rec t i ves/ fi l ters/ serv i ces/

      Innerhalb des test-Verzeichnisses gibt es das Unterverzeichnis spec, das die Spezifikation der verschiedenen Anwendungsbausteine unserer Anwendung enthält. Das Zwischenverzeichnis spec existiert aus dem Grund, dass es hierzu para l lel noch ein Verzeichnis e2e geben kann, in dem unsere E2E-Tests definiert werden. Da sich diese Art von Tests a ufgrund ihrer stark fachlich getriebenen Definition nicht so leicht ge­ nerieren lassen, m üssen wir uns an dieser Stelle selber um die Erstellung kümmern. H inweis zur Modularisierung i n größeren Projekten

      Die Verzeichnisstruktur, die der Generator generiert, ist für kleine Projek­ te oder Module geeignet. in größeren Projekten sollten wir das Modul­ system von AngularJS intensiv nutzen, um unsere Anwendung zu struk­ turieren und in fachliche Komponenten zu unterteilen. Leider bietet sich die generierte Verzeichnisstruktur des AngularJS-Generators nur für so­ genannte Single-Module-Anwendungen an. Es ist jedoch möglich, inner­ halb von einzelnen Modulen den Generator dennoch einzusetzen. Die Verwaltung von mehreren Modulen kann mit Yo bisher noch nicht au­ tomatisch durchgeführt werden. Wie wir unsere Anwendungen generell mithilfe von Modulen strukturieren können, besprechen wir in U nterkapi­ tel 7. 1 .

      Listing 5-42 Generierte Verzeichnisstruktur des AngularJS-Generators

      274

      S

      Projektverwaltung und Automatisierung

      Der AngularJS-Generator beinhaltet verschiedene automatisierte Rou­ tinen, die über die Kommandozeile aufgerufen werden können. Wir können dem Werkzeug yo den gewünschten Generator per Parameter übergeben. In u nserem Fall nutzen wir h ierfür den Parameter ang u l ar. yo angul a r Bootstrap eines AngularJS-Projektes

      Listing 5-43 Ausschnitt der möglichen AngularJSGenerator-Befehle

      Geben w i r keine weitere Spezialisierung a n , legt u n s y o nun d i e grund­ legende Projektstruktur für eine AngularJS-Anwendung an. Durch einen Doppelpunkt getrennt, können wir einen der vorhandenen Sub­ Generatoren a uswählen, um spezifische Anwendungsbausteine zu ge­ nerieren. Möchten wir z.B. einen neuen Controller hinzufügen, können wir yo mit dem Parameter ang u l a r : control l er ausführen. Über einen weiteren Parameter können wir den Name des zu generierenden Con­ trollers definieren. Insgesamt sind die folgenden Sub-Generatoren ver­ fügbar: yo yo yo yo yo yo yo yo

      angu l a r [app l i ca t i onName] angul a r : contro l l er Con t ro l l erName angul a r : decorator Serv i ceName angul a r : d i rect i ve d i rec t i veName angu l a r : f i l ter fi l terName angul a r : route routeName angu l a r : serv i ce serv i ceName angul a r : v i ew v i ewName

      H i nweis zur Nutzung von CoffeeScript

      Für Entwickler, die ihre Applikationen lieber in CoffeeScript schreiben, gibt es die Möglichkeit, über den Parameter - -coffee die entsprechen­ den CoffeeScript-Vorlagen zu nutzen. Außerdem ist es möglich, über den Parameter - -mi nsa fe nur Vorlagen zu benutzen, die auch nach einer Mi­ nifizierung des Quellcodes weiterhin funktionieren. Das sollte allerdings normalerweise durch den Build-Prozess erledigt werden, sodass wir uns um die Minifizierung während der Entwicklung keine Gedanken machen sollten.

      Namen der Komponenten in CameiCase

      Eine Besonderheit, die wir bei der Ben utzung des Generators beachten sollten, ist die Namenskonvention von Komponenten. Der per Parame­ ter übergebene Name für unsere Komponente wird innerhalb der Vor­ lage mithilfe der Funktion camel i ze ( ) 34 aus der JavaScript-Bibliothek Underscore.js in die CamelCase-Konvention transformiert. Das be­ deutet, dass die AngularJS-Komponente in unserer Anwendung z.B. 34 https://gi thu b.com/epel i/underscore.string

      5.5

      Yeoman: Ein defi n ierter Workflow

      Mei nCont ro l l erCtrl heißt, wenn wir einen Controller mit dem Komman­ dozeilenaufruf yo ang u l a r : cont rol l e r mei n -contro l l er generieren. Die erzeugte Datei wird a llerdings den Dateinamen m e i n - contro l l er . j s tra­ gen und unter appjscri ptsjcontrol l ers/ zu finden sein. Das kann unter Umständen zu Verwirrung führen. Deswegen sollten wir d iese Konven­ tionen kennen. Neben der Erstellung der gewünschten Komponente kümmert sich der Generator ebenfalls darum, dass die erzeugte Datei automatisch an der korrekten Stelle in unserer i ndex . h tml eingetragen wird. Da AngularJS von Anfang an unter dem Aspekt der Testbarkeit entwickelt wurde, ist es n icht überraschend, dass der Generator uns ebenfalls die benötigten Testdateien für unsere Komponenten generiert. �-------------------�

      app(defau lt)

      ( ( (

      ) ) )

      add common

      add main

      add test env

      !( !(

      directive

      '

      hPok

      filler



      (

      .

      Automatische Erweiterung der index.html

      Abb. S-3 Übersicht über die Bausteine eines Yeoman-Generators

      '

      controller

      service route rewrite app js



      275





      )

      ,

      � - ------- - - - - - - - - - - - �

      )

      hook

      :i

      view

      �------------------------------1

      , ' ' ' ' '

      add template

      ( : (�============� : ------ - ---- - - -

      :

      add lest template add to Index

      '- - - -

      -

      -

      -

      -

      - -

      -

      -

      -

      ---

      yo angular

      Mithilfe des Befehls yo angu l a r können wir eine neue AngularJS­ Anwendung erstellen. I ntern wird hierbei der Su b-Generator angu l ar : app aufgerufen, der als Standard definiert ist, falls kein S ub-Generator explizit angegeben wird. Mit einem optionalen Parameter können wir den Name der Anwendung festlegen. Wenn wir den Generator ohne Namen a usführen, wird der Name des aktuellen Ordners a ls Anwendungsname gewählt. In beiden Fällen wird an den Anwendungsnamen die Zeichenkette App angefügt, sodass wir mit dem Parameter UserMan ager eine AngularJ S-Anwendung mit dem Namen UserManagerApp erhalten. Mit der Ausführung dieses Sub-Generators

      Sub-Generatoren können übereinen Doppelpunkt angegeben werden.

      276

      5

      Projektverwaltung und Automatisierung

      wird eine erste lauffähige Anwendung generiert. Somi t sind unre· anderem die folgenden üblichen Initialisierungsaufgaben erledigt. Grundlegende Ordnerstruktur i ndex . h tml mit bereits eingebundenem AngularJS

      Erste Route inklusive Tempiare und Controller Allgemeine Dateien wie ein 404-Template oder eme . htaccess­ Datei Testumgebung für Unit- und Integrationstests Basiskonfiguration für die Abhängigkeitsverwaltung Darüber hinaus können wir die folgenden Module optional einbinden: Twitter Bootstrap Angular Resource Angular Cookies Angular Sanitize Dazu stellt uns Yo beim Generieren jeweils eine entsprechende Frage, die wir mithilfe einer Eingabeaufforderungen beantworten können. Am Ende stößt das Skript den Befehl npm i n s t a l l an, mit dem die Abhängig­ keiten für den Build-Prozess installiert werden. Diese sind in der Datei pac kage . j son definiert. Auf d iesen Aspekt werden wir im nächsten Un­ terkapitel genauer eingehen. H inweis zu Grunt i n nerh a l b von Yeoman

      Neben den Projektstrukturen für unser Projekt generiert uns yo auch die Datei Gruntfi l e. j s, die eine Entwicklungs- und Build-Umgebung für un­ ser Projekt definiert. Auf die Möglichkeiten des Grunt-Servers innerhalb von Yeoman gehen wir am Ende dieses Kapitels noch einmal detaillierter ein.

      yo angular:controller

      Mit dem Sub-Generator cantro l l er können wir einen neuen Control­ ler erstellen. Der Name wird wie üblich mith ilfe des ersten Parameter übergeben. Zu beachten ist h ier, dass d ieser Name in der Vorlage ei­ nerseits durch die Underscore.js-Funktion c l ass i fy () modifiziert und

      5.5

      Yeoman: E i n defi n ierter Workflow

      277

      die Zeichenkette >> Ctrl Sch1.bert 97 Ol• Ochs 'i C R.xicn Elements

      x



      Network

      aP�" 'b•d.pp''

      ��� �� -. �

      Sry e s

      Compured

      elefl'ent . style {

      }

      »

      +



      > /h · n ·· r us e r &qen t s t y le s he�· dlv { r ji •" • • >-< : • • : AalltnlH nH i on sbe re H h< /h 2>

      c�crtpt srcc" scqpts/con t r o t\ers/adlll ln nsw book;, !S"> • •. Y

      . . •·

      ng-model=

      "searchText'' c lass=" n g-scope ng-pristine ng-vat id''> •

      AngularjS

      Computed

      »

      S f i rs t : true sid: ··ees Slndex: 0

      ,.. -

      M. • Node . J s & Co.

      978-3