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

Manfred Steyer

October 10, 2017
Tweet

More Decks by Manfred Steyer

Other Decks in Programming

Transcript

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

    & Consultant • Focus: Angular • Gründer: angular-akademie.com • Google Developer Expert (GDE) Page ▪ 2 Manfred Steyer
  2. 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
  3. Komponenten-Baum in Angular 2 Page ▪ 8 Komponente für App

    Komponente (z. B. Liste) Komponente (z. B. Eintrag) Komponente (z. B. Eintrag)
  4. 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
  5. Property-Binding Page ▪ 10 model item item {{ item.title }}

    {{ item.title }} [http://victorsavkin.com/post/110170125256/change-detection-in-angular-2]
  6. Event-Bindings (One-Way, Bottom/Up) • Kein Digest um Events zu versenden

    • Aber: Events können Daten ändern  Property Binding Page ▪ 12
  7. 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
  8. 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>
  9. Recap • Property-Binding: One-Way; Top/Down • Event-Binding: One-Way; Bottom/Up •

    Two-Way-Binding? • Two-Way = Property-Binding + Event-Binding Page ▪ 17
  10. Property und Event-Bindings Page ▪ 19 <input [ngModel]="from" (ngModelChange)="from =

    $event"> Property + Change Geänderter Wert <input [(ngModel)]="from">
  11. Beispiel: flug-card Page ▪ 39 public basket = {}; […]

    basket[3] = true; basket[4] = false; basket[5] = true;
  12. 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>
  13. Beispiel: flug-card export class FlugCard { @Input() item: Flug; @Input()

    selected: boolean; select() { this.selected = true; } deselect() { this.selected = false; } }
  14. 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>
  15. Komponente registrieren Page ▪ 45 @NgModule({ imports: [ CommonModule, FormsModule,

    SharedModule ], declarations: [ AppComponent, FlugSuchenComponent, FlugCardComponent ], providers: [ FlugService ], bootstrap: [ AppComponent ] }) export class AppModule { }
  16. 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>
  17. flug-card Page ▪ 49 flug-card item selected > > >

    selectedChange flug basket[flug.id]
  18. 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>
  19. Angular traversiert den gesamten Baum bei jedem Event (Standard) flights

    flight flight {{ flight.id }} {{ flight.id }} FlightSearch Card Card
  20. OnPush flights flight flight {{ flight.id }} {{ flight.id }}

    FlightSearch Card Card Nur geprüft wenn “benachrichtigt”
  21. Input ändern flights flight flight {{ flight.id }} {{ flight.id

    }} FlightSearch Card Card flightold === flightnew
  22. 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)
  23. 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';
  24. 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
  25. Immutables let newFlightDate = new Date(oldFlightDate.getTime() + ONE_MINUTE * 15);

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

    let newFlight = { …oldFlight, date: newFlightDate.toISOString() }; TypeScript 2.1
  27. 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) ];
  28. 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;
  29. 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
  30. Immutables und Angular flights flight flight {{ flight.id }} {{

    flight.id }} FlightSearch Card Card Change
  31. 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'); };
  32. 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
  33. Hot Observable Page ▪ 84 var o = Observable.create((observer) =>

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

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

    BehaviorSubject Speichert letzten Wert ReplaySubject Speichert mehrere Werte
  36. Observables mit OnPush Page ▪ 88 flights flight$ flight$ {{

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

    flight.id }} {{ flight.id }} FlightSearch Card Change Card
  38. 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
  39. 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
  40. 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 } }
  41. 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 } }
  42. 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 } }
  43. 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 } }
  44. Ins Steuerelement schreiben […] constructor( private _renderer: Renderer, private _elementRef:

    ElementRef) { } writeValue(value: any): void { // Format value this._renderer.setElementProperty( this._elementRef.nativeElement, 'value', value); } […]
  45. Eigene Formular-Controls @Component({ ... }) export class DateControlComponent implements ControlValueAccessor

    { registerOnChange(fn): void { ... } registerOnTouched(fn): void { ... } writeValue(value: any) { // value ins Steuerelement schreiben } }
  46. 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 } }
  47. Zusammenfassung Page ▪ 115 Property Bindings: top/down Event Bindings: bottom/up

    @Input und @Output Two-Way-Bindings OnPush mit Immutables und Observables ControlValueAccessor