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

Transcript

29 slides
  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

More Decks by Rainer Hahnekamp

Other Decks in Technology