title | filename | tags | description | lang | slideOptions | ||||
---|---|---|---|---|---|---|---|---|---|
Présentation de Angular JS, EPFL VPSI — Décembre 2019 |
prez-Angular.md |
AngularJS, EPFL |
View the slide with "Slide Mode". |
fr |
|
Décembre 2019
Tous les garçons cool l'ont !
- AngularJS par la pratique
- Programme / courbe d'apprentissage
- The occasional diatribe
Si vous aimez... | vous aimerez... |
---|---|
☁️ les nuages ☁️ | stackblitz.com |
Votre laptop | node.js + cli.angular.io |
Emacs | M-x package-install ⏎ tide ⏎ |
... vous ne savez pas | Essayez |
💡
- Commencer par la vue «mock» (avec le HTML souhaité, mais aucun comportement)
- Pas besoin de
<form>
«Moustacher» le mot Angular
de Hello, Angular
depuis une variable d'instance du contrôleur.
🔧 Avec Chrome et Augury, c'est suffisant pour bidouiller «à la main» :
<input type="text" (input)="console.log('bah!')">
... Mais Angular ? Pas toujours.
@Component{...}
export class AppComponent {
console = window.console
}
Comment savoir quoi mettre dans (input)="..."
?
Le débogueur du navigateur est aussi votre ami.
<input type="text" (input)="inputChanged()">
@Component{...}
export class AppComponent {
inputChanged() {
debugger;
}
}
💡
Tous les «tuyaux» vus pour l'exercice 1 restent valables dans la vraie vie.
- Division du travail (HTML, CSS, JS)
- Buts intermédiaires
- Outillage : débogueur du navigateur + Augury
https://stackblitz.com/edit/epfl-angular-exercice1
Note:
- app.component.ts : le
Component
- app.module.ts : I is serious framwerk. I haz modules.
- Une brève plongée dans les templates et les événements
- Les dépendances dans
package.json
, et en particulier les@types
refactorer pour créer HelloComponent
<h1>Hello, {{ name }}!</h1>
Pour le JavaScript TypeScript, à vous de jouer !
Si vous aviez deviné que la solution serait à l'adresse
https://stackblitz.com/edit/epfl-angular-exercice2
... alors vous avez gagné !
Révisions JS (
npm
, ES2015,import
/export
)TypeScript : types... et décorateurs
Angular : templates, événements... Et bien plus encore !
Note:
- À présent chacun doit installer Angular sur son laptop, si pas déjà fait
Pré-requis : ng test
est «vrai rouge» (pas de crash)
→ Si nécessaire, se resynchroniser depuis go/workshop-angular-exercices
// ...
describe('AppComponent', () => {
// ...
it('updates the title', () => {
})
})
// ...
describe('AppComponent', () => {
// ...
it('updates the title', () => {
// ...
})
})
describe
, it
← le BDD de Jasmine
// ...
describe('AppComponent', () => {
// ...
fit('updates the title', () => {
// ...
})
})
💡 fdescribe
existe également (f
comme “focused”)
// ...
describe('AppComponent', () => {
// ...
it('updates the title', () => {
const fixture = TestBed.createComponent(AppComponent);
})
})
// ...
describe('AppComponent', () => {
// ...
it('updates the title', () => {
const fixture = TestBed.createComponent(AppComponent);
const input = fixture.nativeElement.
querySelector('input');
// ...
})
})
it('updates the title', () => {
const fixture = TestBed.createComponent(AppComponent);
debugger;
// ...
})
fixture
fixture.nativeElement
- ... est un élément DOM
- ... Donc
MDN est votre ami
it('updates the title', () => {
const fixture = TestBed.createComponent(AppComponent);
const input = fixture.nativeElement.
querySelector('input');
input.value = 'World';
input.dispatchEvent(new Event('input'));
// ...
})
Note: Le fait que nous utilisons les API MDN rend la compatibilité des tests sous IE / Edge nettement plus... hasardeuse ? À vrai dire je n'en sais rien, nous sommes quand même en 2019.
it('updates the title', () => {
const fixture = TestBed.createComponent(AppComponent);
const input = fixture.nativeElement.
querySelector('input') as HTMLInputElement;
input.value = 'World';
input.dispatchEvent(new Event('input'));
// ...
expect(fixture.nativeElement.
querySelector('h1').textContent).toContain('World');
})
expect
, toContain
← moar Jasmine
// ... // ???
fixture.detectChanges();
// ...
describe('AppComponent', () => {
// ...
it('updates the title', () => {
const fixture = TestBed.createComponent(AppComponent);
const input = fixture.nativeElement.
querySelector('input') as HTMLInputElement;
input.value = 'World';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(fixture.nativeElement.
querySelector('h1').textContent).toContain('World');
})
})
¹ Ou plus précisément, son absence
- Peaufinez vos recherches sur Google ("2019", "Angular 7", "Angular 8")
- Ne vous éloignez pas trop des sentiers battus ! Votre app risquerait (par exemple) de ne plus compiler en prod'
- Malgré cela, attendez-vous à des changements incompatibles (ex : adoption de
async
/await
et Puppeteer pour les tests unitaires)
stackblitz.com/edit/epfl-angular-exercice4-jquery
... Ça ne marche pas très bien
ng new --defaults --no-routing search-as-you-type cd search-as-you-type ng test
ng generate component search-as-you-type ng test
💡
ng create
change les dépendances, il faut donc stopper/redémarrer les tests en continu (commandeng test
).
Faisons une mini-app pour les tests manuels :
- Tout effacer dans
src/app/app.component.html
; remplacer par<app-search-as-you-type></app-search-as-you-type>
ng serve
@ http://localhost:4200/ng test
: revenir à la barre verte (solution)
-
Une «carrosserie» graphique
- Fournit : les mots-clefs de recherche
- Reçoit : les résultats
- Un service de recherche
-
La tuyauterie pour les relier :
RxJS
... À ce stade de notre apprentissage, commençons par un test manuel, et voyons en second lieu comment l'automatiser.
<input type="text" #searchterm (keyup)="search(searchterm.value)">
- angular.io/guide/testing
- ... et Google
-
By.css(...)
-
.triggerEventHandler()
(exemple) -
spyOn
(exemple)
it("shows results");
search(value: string) {
console.log(value); // TODO: peut mieux faire.
}
... et les Subject
s
import { Subject } from 'rxjs';
// ...
private searchString = new Subject<string>();
// ...
search(value: string) {
this.searchString.next(value);
}
export class SearchAsYouTypeComponent implements OnInit {
// ...
ngOnInit() {
this.searchString.subscribe(console.log);
}
}
import { debounceTime, tap } from 'rxjs/operators';
// ...
export class SearchAsYouTypeComponent implements OnInit {
// ...
private searchString = new Subject<string>();
searchStringDebounced = searchString.pipe(
debounceTime(400),
tap(console.log)
);
}
.pipe()
import { tap, debounceTime,
distinctUntilChanged } from 'rxjs/operators';
export class SearchAsYouTypeComponent implements OnInit {
searchStringDebounced = this.searchString.pipe(
debounceTime(400),
distinctUntilChanged(),
tap(console.log)
);
}
🚑 Le diff
... Plus rien.
Les Observable
s sont paresseux. Tant qu'on n'y .subscribe()
pas, ils ne font rien.
<p>Current search term:</p>
<pre>{{searchStringDebounced | async }}</pre>
| async
rend la dernière valeur d'un Observable
, n'a pas besoin d'être importé, et est auto-unsubscribe.
Implémenter le search-as-you-type et présenter votre approche.
- Ne pas s'attarder sur la présentation des résultats (
console.log()
ou bien<pre>{{ searchResults | async | json}}</pre>
) - Rappel : la version jQuery contient une fonction
searchEPFL()
prête à copier-coller.
Avec l'opérateur switchMap
(démo sur rxmarbles.com), on peut
- créer un nouvel
Observable
pour chaque valeur de l'Observable
«parent»; - fusionner les valeurs.
💡 La fonction «switchMappée» peut renvoyer une Promise
à la place d'un Observable
.
Note: Ce n'est pas la solution parfaite pour l'instant.
En savoir plus sur switchMap
et son utilité pour le search-as-you-type : blog.angular-university.io/rxjs-higher-order-mapping/ (§ “Search TypeAhead”)
- RxJS :
Observable
s, opérateurs,Observable
s d'Observable
s - Functional reactive programming
- Mieux que
les Promisesrien (jQuery et asynchronie incontrôlée) - Beaucoup de progrès à faire
- Rendu des résultats
- Limitation / annulation des requêtes
- Couverture de tests
Au cours de cette partie,
- nous explorerons l'API HTTP d'Angular, qui propose des requêtes annulables
- nous verrons comment séparer (et tester séparément) le code «graphique» et le client de l'API
constructor(private http : HttpClient) {}
Réécrire la fonction searchEPFL(q)
en termes de @angular/common/http
.
→ blog.angular-university.io/angular-http
⊕ | ⊖ |
---|---|
|
Les tests sont cassés (solution) |
Du point de vue du consommateur, l'injection de dépendances
- fournit automagiquement des singletons à tout endroit de la hiérarchie des composants,
- permet d'utiliser des instances séparées pour le test et la production (⇒ tests hermétiques)
-
Couverture de tests
-
Erreurs côté serveur (500 / timeouts) — Mocking avec
HttpTestingController
- Refactorer pour extraire le pipe-line comme un nouvel opérateur RxJS, et le mettre sous tests (“marble testing”)
-
Erreurs côté serveur (500 / timeouts) — Mocking avec
-
Injection de dépendances en tant que producteur (ex :
I18NService
)
Note:
- Sujets potentiellement intéressants pour la journée 3
- Partir du
premier exemple de la doc de ng-select
- Écouter l'événement
search
(plutôt que chercher une seule fois dansngOnInit()
)
- Tuyauter ce qui doit l'être pour rechercher dans l'API de l'EPFL
.pipe(map())
pour transformer les.sciper
en.id
Même chose que l'exercice 6, mais sans le cloud.
- Raccrochage au
debounceTime
etc. des exercices précédents - (Optionnel) Réutiliser l'idée d'un
DataService
injecté comme fait dans le StackBlitz