Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Angular Databinding unter der Motorhaube

Angular Databinding unter der Motorhaube

Talk from Angular Days in Berlin (Germany), October 2017

15934fa2aa7b2ce21f091e9b7cffa856?s=128

Manfred Steyer
PRO

October 10, 2017
Tweet

Transcript

  1. Angular Datenbindung unter der Motorhaube Manfred Steyer SOFTWAREarchitekt.at ManfredSteyer ManfredSteyer

  2. Über mich … • Manfred Steyer • SOFTWAREarchitekt.at • Trainer

    & Consultant • Focus: Angular • Gründer: angular-akademie.com • Google Developer Expert (GDE) Page ▪ 2 Manfred Steyer
  3. Inhalt • Datenbindung näher betrachtet • Komponenten mit Bindings •

    Performancetuning mit Immutables, Observables und OnPush • Mit dem ValueAccessor in die Datenbindung von Formularen eingreifen • Bestehende Datenbindung beeinflussen (Datum, Zahlen etc.) • Eigene Formular-Komponenten erstellen
  4. Didaktik • Präsentation • Live Coding

  5. Datenbindung näher betrachtet Page ▪ 5

  6. Architektur-Ziele von Angular 2 Performance Komponenten Vorhersagbarkeit

  7. Data-Binding in Angular 1.x Page ▪ 7 Model Model Directive

  8. Komponenten-Baum in Angular 2 Page ▪ 8 Komponente für App

    Komponente (z. B. Liste) Komponente (z. B. Eintrag) Komponente (z. B. Eintrag)
  9. Regeln für Property-Bindings • Daten fließen von oben nach unten

    (top/down) • Parent kann Daten an Children weitergeben • Children können keine Daten an Parent weitergeben • Abhängigkeits-Graph ist ein Baum • Angular benötigt nur einen Digest um Baum mit GUI abzugleichen Page ▪ 9
  10. Property-Binding Page ▪ 10 model item item {{ item.title }}

    {{ item.title }} [http://victorsavkin.com/post/110170125256/change-detection-in-angular-2]
  11. Event-Bindings (One-Way, Bottom/Up) Page ▪ 11 {{ item.title }} {{

    item.title }} Event-Handler Event-Handler
  12. Event-Bindings (One-Way, Bottom/Up) • Kein Digest um Events zu versenden

    • Aber: Events können Daten ändern  Property Binding Page ▪ 12
  13. Property- und Event-Bindings Page ▪ 13 Property-Binding ausführen Event-Handler ausführen

    Ereignis tritt ein App ist bereit Alle Handler ausgeführt Properties gebunden
  14. View Page ▪ 16 <button [disabled]="!von || !nach" (click)="search()"> Search

    </button> <table> <tr *ngFor="let flight of flights"> <td>{{flight.id}}</td> <td>{{flight.date}}</td> <td>{{flight.from}}</td> <td>{{flight.to}}</td> <td><a href="#" (click)="selectFlight(flight)">Select</a></td> </tr> </table> <td [text-content]="flight.id"></td>
  15. Recap • Property-Binding: One-Way; Top/Down • Event-Binding: One-Way; Bottom/Up •

    Two-Way-Binding? • Two-Way = Property-Binding + Event-Binding Page ▪ 17
  16. Property und Event-Bindings Page ▪ 18 <input [ngModel]="from" (ngModelChange)="update($event)">

  17. Property und Event-Bindings Page ▪ 19 <input [ngModel]="from" (ngModelChange)="from =

    $event"> Property + Change Geänderter Wert <input [(ngModel)]="from">
  18. Komponenten mit Bindings Page ▪ 37

  19. Beispiel: flug-card Page ▪ 38

  20. Beispiel: flug-card Page ▪ 39 public basket = {}; […]

    basket[3] = true; basket[4] = false; basket[5] = true;
  21. Beispiel: flug-card in flug-suchen.html Page ▪ 40 <div *ngFor="let f

    of fluege"> <flug-card [item]="f" [selected]="basket[f.id]"> </flug-card> </div>
  22. flug-card Page ▪ 41 flug-card item selected > > flug

    basket[flug.id]
  23. Beispiel: flug-card @Component({ selector: 'flug-card', templateUrl: './flug-card.html' }) export class

    FlugCard { […] }
  24. Beispiel: flug-card export class FlugCard { @Input() item: Flug; @Input()

    selected: boolean; select() { this.selected = true; } deselect() { this.selected = false; } }
  25. Template <div style="padding:20px;" [ngStyle]="{'background-color': (selected) ? 'orange' :'lightsteelblue' }" >

    <h2>{{item.from}} - {{item.to}}</h2> <p>Flugnr. #{{item.id}}</p> <p>Datum: {{item.date | date:'dd.MM.yyyy'}}</p> <p> <button *ngIf="!selected" (click)="select()">Auswählen</button> <button *ngIf="selected" (click)="deselect()">Entfernen</button> </p> </div>
  26. Komponente registrieren Page ▪ 45 @NgModule({ imports: [ CommonModule, FormsModule,

    SharedModule ], declarations: [ AppComponent, FlugSuchenComponent, FlugCardComponent ], providers: [ FlugService ], bootstrap: [ AppComponent ] }) export class AppModule { }
  27. DEMO Page ▪ 46

  28. Event-Bindings

  29. flug-card mit Event selectedChange Page ▪ 48 <div *ngFor="let f

    of fluege"> <flug-card [item]="f" [selected]="basket[f.id]" (selectedChange)="basket[f.id] = $event"> </flug-card> </div>
  30. flug-card Page ▪ 49 flug-card item selected > > >

    selectedChange flug basket[flug.id]
  31. Beispiel: flug-card export class FlugCard { @Input() item: Flug; @Input()

    selected: boolean; @Output() selectedChange = new EventEmitter<boolean>(); select() { this.selected = true; this.selectedChange.next(this.selected); } deselect() { this.selected = false; this.selectedChange.next(this.selected); } } <div *ngFor="let f of fluege"> <flug-card [item]="f" [selected]="basket[f.id]" (selectedChange)="basket[f.id] = $event"> </flug-card> </div>
  32. Performance- Tuning mit OnPush

  33. Angular traversiert den gesamten Baum bei jedem Event (Standard) flights

    flight flight {{ flight.id }} {{ flight.id }} FlightSearch Card Card
  34. DEMO

  35. OnPush flights flight flight {{ flight.id }} {{ flight.id }}

    FlightSearch Card Card Nur geprüft wenn “benachrichtigt”
  36. OnPush aktivieren @Component({ […] changeDetection: ChangeDetectionStrategy.OnPush }) export class FlightCard

    { […] @Input() flight; }
  37. Über Änderung “benachrichtigen” • Input ändern • Mit Observable “event”

    triggern
  38. Input ändern flights flight flight {{ flight.id }} {{ flight.id

    }} FlightSearch Card Card flightold === flightnew
  39. Immutables

  40. Immutables • Unveränderbare Objekte • Wenn sich darunterliegende Daten ändern

     neues Immutable erzeugen • Änderungen können einfach entdeckt werden: • oldObject === newObject • Mit und ohne Bibliotheken (wie immutable.js)
  41. Readonly in TypeScript (2.0) export interface Flight { readonly id:

    number; readonly from: string; readonly to: string; readonly date: string; } let f: Flight = { id: 7, from: 'here', to: 'there', date: '...' }; f.to = 'somewhere else';
  42. Immutables const ONE_MINUTE = 1000 * 60; let oldFlights =

    this.flights; let oldFlight = oldFlights[0]; // Flight to change! let oldFlightDate = new Date(oldFlight.date); // Date to change
  43. let newFlightDate = new Date(oldFlightDate.getTime() + ONE_MINUTE * 15); Immutables

  44. Immutables let newFlightDate = new Date(oldFlightDate.getTime() + ONE_MINUTE * 15);

    let newFlight = { id: oldFlight.id, from: oldFlight.from, to: oldFlight.to, date: newFlightDate.toISOString() };
  45. Immutables let newFlightDate = new Date(oldFlightDate.getTime() + ONE_MINUTE * 15);

    let newFlight = { …oldFlight, date: newFlightDate.toISOString() }; TypeScript 2.1
  46. Immutables let newFlightDate = new Date(oldFlightDate.getTime() + ONE_MINUTE * 15);

    let newFlight = { …oldFlight, date: newFlightDate.toISOString() }; let newFlights = [ newFlight, ...oldFlights.slice(1, this.flights.length) ];
  47. Immutables let newFlightDate = new Date(oldFlightDate.getTime() + ONE_MINUTE * 15);

    let newFlight = { …oldFlight, date: newFlightDate.toISOString() }; let newFlights = [ newFlight, ...oldFlights.slice(1, this.flights.length) ]; this.flights = newFlights;
  48. Auf Änderungen prüfen console.debug("Array: " + (oldFlights == newFlights)); //

    false console.debug("#0: " + (oldFlights[0] == newFlights[0])); // false console.debug("#1: " + (oldFlights[1] == newFlights[1])); // true
  49. Immutables und Angular flights flight flight {{ flight.id }} {{

    flight.id }} FlightSearch Card Card Change
  50. DEMO

  51. Observables

  52. Was sind Observables? • Repräsentieren asynchrone Daten, die im Verlauf

    der Zeit veröffentlicht werden
  53. Observer „Senke“ Observable „Quelle“ Operator (z. B. map)

  54. Observable Observable .subscribe( (result) => { … }, (error) =>

    { … }, () => { … } ); Observer
  55. Beispiel this .http .get("http://www.angular.at/api/...") .map(flights => flights.bookings) .subscribe( (bookings) =>

    { … }, (err) => { console.error(err); } );
  56. Eigenes Observable var observable = Observable.create((sender) => { sender.next(4711); sender.next(815);

    //sender.error("err!"); sender.complete(); }); Asynchron, Ereignis-gesteuert var subscription = observable.subscribe(…); subscription.unsubscribe(); return () => { console.debug('Bye bye'); };
  57. Cold vs. Hot Observables Page ▪ 83 Cold • Standard

    • Punkt zu Punkt • Pro Empfänger ein Sender • Sender startet erst bei Anmeldung Hot • Punkt zu Multipunkt • Sender startet auch ohne Anmeldungen
  58. Hot Observable Page ▪ 84 var o = Observable.create((observer) =>

    { observer.next(42); observer.error("err!"); }).publish(); o.connect();
  59. Subjects Subject .subscribe( (result) => { … }, (error) =>

    { … }, () => { … } ); Observer Daten/ Benachrichtigung von außen
  60. Subjects Page ▪ 86 Subject Hot & verteilt empfangene Daten

    BehaviorSubject Speichert letzten Wert ReplaySubject Speichert mehrere Werte
  61. Observables und OnPush

  62. Observables mit OnPush Page ▪ 88 flights flight$ flight$ {{

    flight$.id }} {{ flight$.id }} FlightSearch Card Card
  63. Observables mit OnPush Page ▪ 89 flights$ flight flight {{

    flight.id }} {{ flight.id }} FlightSearch Card Change Card
  64. An Observable binden? <flight-card [item]="flight | async" […]> </flight-card>

  65. DEMO

  66. Keine „Alles-oder-Nichts-Lösung“ • Kann bei Bedarf eingesetzt werden • Wenn

    durchgängiger Einsatz angestrebt • Werfen Sie einen Blick auf Redux • Implementierung für Angular: @ngrx/store
  67. Datenbindung und Formulare

  68. Fragestellungen • Wie kann Angular die unterschiedlichen Steuerelemente berücksichtigen? •

    Textfeld, Dropdown, Checkbox, Radiobutton, eigene, … • Wie kann man die Datenbindung beeinflussen? • Date-Objekt zur Ausgabe in deutsches Datum umwandeln • Deutsches Datum nach Änderung in Date-Objekt überführen
  69. ControlValueAccessor

  70. ControlValueAccessor Initialisierung

  71. ControlValueAccessor Änderung

  72. ControlValueAccessor Neuer Wert

  73. Beispiel: DateValueAccessor <input [(ngModel)]="date" name="date" date>

  74. DateValueAccessor @Directive({ selector: '[date]' }) export class DateValueAccessor implements ControlValueAccessor

    { registerOnChange(fn: Function): void { … } registerOnTouched(fn: Function): void { … } writeValue(value: any): void { // value ins Steuerelement schreiben } }
  75. DateValueAccessor @Directive({ selector: '[date]' }) export class DateValueAccessor implements ControlValueAccessor

    { registerOnChange(fn: (value: any) => void): void { … } registerOnTouched(fn: () => void): void { … } writeValue(value: any): void { // value ins Steuerelement schreiben } }
  76. DateValueAccessor @Directive({ selector: '[date]' }) export class DateValueAccessor implements ControlValueAccessor

    { onChange = (value: any) => {}; onTouched = () => {}; registerOnChange(fn: (value: any) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } writeValue(value: any): void { // value ins Steuerelement schreiben } }
  77. DateValueAccessor @Directive({ selector: '[date]' providers: [{provide: NG_VALUE_ACCESSOR, useExisting: DateValueAccessor, multi:

    true}] }) export class DateValueAccessor implements ControlValueAccessor { onChange = (value: any) => {}; onTouched = () => {}; registerOnChange(fn: (value: any) => void): void { this.onChange = fn; } registerOnTouched(fn: () => void): void { this.onTouched = fn; } writeValue(value: any): void { // value ins Steuerelement schreiben } }
  78. Ins Steuerelement schreiben […] constructor( private _renderer: Renderer, private _elementRef:

    ElementRef) { } […]
  79. Ins Steuerelement schreiben […] constructor( private _renderer: Renderer, private _elementRef:

    ElementRef) { } writeValue(value: any): void { // Format value this._renderer.setElementProperty( this._elementRef.nativeElement, 'value', value); } […]
  80. Auf Eingaben reagieren @HostListener('blur') blur() { this.onTouched(); } @HostListener('input', ['$event.target.value'])

    input(value) { […] this.onChange(value); }
  81. DEMO

  82. Eigene Formular-Controls @Component({ ... }) export class DateControlComponent implements ControlValueAccessor

    { registerOnChange(fn): void { ... } registerOnTouched(fn): void { ... } writeValue(value: any) { // value ins Steuerelement schreiben } }
  83. Eigene Formular-Controls @Component({ ... }) export class DateControlComponent implements ControlValueAccessor

    { constructor(private c: NgControl) { c.valueAccessor = this; } registerOnChange(fn): void { ... } registerOnTouched(fn): void { ... } writeValue(value: any) { // value ins Steuerelement schreiben } }
  84. DEMO

  85. Zusammenfassung Page ▪ 115 Property Bindings: top/down Event Bindings: bottom/up

    @Input und @Output Two-Way-Bindings OnPush mit Immutables und Observables ControlValueAccessor
  86. Kontakt [mail] manfred.steyer@SOFTWAREarchitekt.at [blog] SOFTWAREarchitekt.at [twitter] ManfredSteyer