Slide 1

Slide 1 text

ngrx Best Practices angulararchitects.io Angular Meetup Graz 29.4.2020 Rainer Hahnekamp (@rainerhahnekamp)

Slide 2

Slide 2 text

About me… • Rainer Hahnekamp ANGULARarchitects.io • Angular Trainings and Consultancy RainerHahnekamp Public: Frankfurt, Munich, Vienna In-House: everywhere http://softwarearchitekt.at/workshops

Slide 3

Slide 3 text

#1 LoadStatus / Cache

Slide 4

Slide 4 text

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?

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

Data Guard data.guard.ts export class DataGuard implements CanActivate { constructor(private store: Store) {} canActivate( next: ActivatedRouteSnapshot, state: RouterStateSnapshot ): | Observable | Promise | 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' } } ] }

Slide 8

Slide 8 text

Load Status / Cache • Sinnvoll, wenn kompletter Datenbestand nur einmal geladen werden kann • Masterdaten • Aktueller User • DataGuard beachten • LoadStatus vs. Hacks (Expression has changed...),

Slide 9

Slide 9 text

#2 Modularity Container & Presentation Components

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

customer.component: Presentation customer.component.ts export class CustomerComponent implements OnInit { formGroup = new FormGroup({}); @Input() customer: Customer; @Output() save = new EventEmitter(); @Output() remove = new EventEmitter(); 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); } } }

Slide 13

Slide 13 text

customer.component: Containers edit-container.component.ts export class EditContainerComponent implements OnInit { customer$: Observable; constructor( private route: ActivatedRoute, private store: Store ) {} 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) {} add(customer: Customer) { this.store.dispatch(CustomerActions.add({ customer })); } }

Slide 14

Slide 14 text

#3 Facade / API

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

customer-store.service.ts (Facade) get customers$(): Observable { this.checkForGet(); return this.store.select(fromCustomer.selectAll); } get isLoaded$(): Observable { this.checkForGet(); return this.store.select(fromCustomer.isLoaded); } constructor(private store: Store) {} 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; } }

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

#4 Context

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

#5 StateModel vs. ViewModel

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Problemstellung • UI kann komplexere Datenstrukturen benötigen • Zugriff auf mehrere Feature-Stores notwendig • Inkompatible Datenstrukturen

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

• Sourcecode: https://github.com/rainerhahnekamp/ngrx-best-practices • Blogserie folgt • https://angulararchitects.io • @rainerhahnekamp

Slide 29

Slide 29 text

•8. bis 10.6.2020 •Bis 15. 5.: Frühbucher Preise! (-33%: Frühbucherpreise + Remote-Rabatt) •Details und Anmeldung https://www.angulararchitects.io/workshop