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

ngrx - Best Practices

ngrx - Best Practices

5 Best Practices for ngrx that should be used in any larger web application.

Rainer Hahnekamp

April 28, 2020
Tweet

More Decks by Rainer Hahnekamp

Other Decks in Technology

Transcript

  1. About me… • Rainer Hahnekamp ANGULARarchitects.io • Angular Trainings and

    Consultancy RainerHahnekamp Public: Frankfurt, Munich, Vienna In-House: everywhere http://softwarearchitekt.at/workshops
  2. Load Status / Cache • Wie kann man die Anzahl

    der Endpoint Requests minimieren? • Muss sich jede Komponente um das Laden „ihres“ States selber kümmern (Deep Links!)? • Wann ist der State zur Verwendung bereit? • Wie verhindert man gleiche und parallele Ladevorgänge?
  3. Data Guard data.guard.ts export class DataGuard implements CanActivate { constructor(private

    store: Store<CustomerAppState>) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree { this.store.dispatch(CustomerActions.get()); return this.store .select(fromCustomer.isLoaded) .pipe(filter(isLoaded => isLoaded)); } } WICHTIG customer.module.ts RouterModule.forChild([ { path: 'customer', canActivate: [DataGuard], children: [ { path: '', component: CustomersComponent }, path: ':id', component: CustomerComponent, data: { mode: 'edit' } } ] }
  4. Load Status / Cache • Sinnvoll, wenn kompletter Datenbestand nur

    einmal geladen werden kann • Masterdaten • Aktueller User • DataGuard beachten • LoadStatus vs. Hacks (Expression has changed...),
  5. Modularität • Verantwortlichkeiten sollten klar zugeteilt sein • Bessere Instandhaltung

    • Schnellere Einarbeitung • Container & Presentation Components • Bessere Testbarkeit • Aufteilung in libs • nx für Abhängigkeitsregeln • Kein Spaghetticode by Design
  6. customer.component: Presentation customer.component.ts export class CustomerComponent implements OnInit { formGroup

    = new FormGroup({}); @Input() customer: Customer; @Output() save = new EventEmitter<Customer>(); @Output() remove = new EventEmitter<Customer>(); fields: FormlyFieldConfig[]; constructor() {} ngOnInit() { this.fields = [ formly.requiredText('firstname', 'Firstname'), formly.requiredText('name', 'Name'), formly.requiredSelect('country', 'Country', countries), formly.requiredDate('birthdate', 'Birthdate') ]; } submit(customer: Customer) { if (this.formGroup.valid) { this.save.emit(customer); } } }
  7. customer.component: Containers edit-container.component.ts export class EditContainerComponent implements OnInit { customer$:

    Observable<Customer>; constructor( private route: ActivatedRoute, private store: Store<CustomerAppState> ) {} ngOnInit(): void { const id = Number(this.route.snapshot.params.id); this.customer$ = this.store .select(fromCustomer.selectById, id) .pipe(map(customer => ({ ...customer }))); } edit(customer: Customer) { this.store.dispatch(CustomerActions.update({ customer })); } remove(customer: Customer) { if (confirm(`Really delete ${customer}?`)) { this.store.dispatch(CustomerActions.remove({ customer })); } } } add-container.component.ts export class AddContainerComponent { public customer = { id: 0, firstname: '', name: '', country: null, birthdate: null }; constructor(private store: Store<CustomerAppState>) {} add(customer: Customer) { this.store.dispatch(CustomerActions.add({ customer })); } }
  8. Facade / API • Ngrx wird nur innerhalb der lib

    verwendet • Außenkommunikation (zu Komponenten) mittels Methoden & Observables, statt Actions und Selectors • Nicht alle Actions oder Selektoren können aufgerufen werden • Bspw. load, loaded, added,... • Kapselung von Logik wie dem LoadStatus
  9. customer-store.service.ts (Facade) get customers$(): Observable<Customer[]> { this.checkForGet(); return this.store.select(fromCustomer.selectAll); }

    get isLoaded$(): Observable<boolean> { this.checkForGet(); return this.store.select(fromCustomer.isLoaded); } constructor(private store: Store<CustomerStore>) {} public getById(id: number) { return this.store .select(fromCustomer.selectById, id) .pipe(map(customer => ({ ...customer }))); } public add(customer: Customer) { this.store.dispatch(CustomerActions.add({ customer })); } public update(customer: Customer) { this.store.dispatch(CustomerActions.update({ customer })); } public remove(customer: Customer) { this.store.dispatch(CustomerActions.remove({ customer })); } private checkForGet() { if (!this.isGetDispatched) { this.store.dispatch(CustomerActions.get()); this.isGetDispatched = true; } }
  10. Problemstellung • Es kann nur ein Teil des gesamten Datenbestandes

    geladen werden • Abhängig von Filterkriterien • Suche • Paginator • LoadStatus nicht anwendbar • Keine Garantie, dass Listenansicht Element für Detailansicht beinhaltet • zB durch DeepLinks
  11. Caching und Detail • Detailformular benötigt spezielle Behandlung • Eigene

    Action • Check ob bereits vorhanden, ansonsten Backendcall • Caching von mehreren Kontexten möglich, jedoch komplex • Kosten/Nutzen Kalkül • Invalidierung durch Hinzufügen, Löschen
  12. Problemstellung • UI kann komplexere Datenstrukturen benötigen • Zugriff auf

    mehrere Feature-Stores notwendig • Inkompatible Datenstrukturen
  13. StateModel vs. ViewModel • Presentation Komponente spezifziert die für sie(!)

    beste Datenstruktur (=ViewModel) • Container Komponent ist für Transformation vom State zu ViewModel verantwortlich • CombineLatest garantiert, dass Daten von allen Stores vorhanden sind • Nur ein Input für ViewModel • Verhindert mehrere `| async` in Container Komponente
  14. •8. bis 10.6.2020 •Bis 15. 5.: Frühbucher Preise! (-33%: Frühbucherpreise

    + Remote-Rabatt) •Details und Anmeldung https://www.angulararchitects.io/workshop