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

Angular 2 Data Binding

Angular 2 Data Binding

Avatar for Manfred Steyer

Manfred Steyer

March 20, 2017
Tweet

More Decks by Manfred Steyer

Other Decks in Programming

Transcript

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

    & Consultant • Focus: Angular • 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().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. NgModel • Erzeugt Objektgraph für Formular und Felder • Objekte

    synchronisieren sich mit Felder (HTML-Elemente oder Komponenten) • Objekte stoßen Validierung an
  40. Beispiel Page ▪ 95 <form> <input type="text" name="von" [(ngModel)]="von" required

    minlength="3"> […] </form> NgForm valid, dirty, … Control valid, dirty, … controls { } von
  41. Beispiel Page ▪ 96 <form #f="ngForm"> <input type="text" name="von" [(ngModel)]="von"

    required minlength="3"> […] </form> NgForm valid, dirty, … Control valid, dirty, … controls { } von
  42. Beispiel Page ▪ 97 <form #f="ngForm"> <input type="text" name="von" [(ngModel)]="von"

    required minlength="3"> <div *ngIf="!f?.controls?.von?.valid"> …Error… </div> </form> NgForm valid, dirty, … Control valid, dirty, … controls { } von
  43. 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
  44. 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 } }
  45. 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 } }
  46. 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 } }
  47. 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 } }
  48. Ins Steuerelement schreiben […] constructor( private _renderer: Renderer, private _elementRef:

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

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

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