Share
Embed
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
-
About me… • Rainer Hahnekamp ANGULARarchitects.io • Angular Trainings and
Consultancy RainerHahnekamp Public: Frankfurt, Munich, Vienna In-House: everywhere http://softwarearchitekt.at/workshops -
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? -
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' } } ] } -
Load Status / Cache • Sinnvoll, wenn kompletter Datenbestand nur
einmal geladen werden kann • Masterdaten • Aktueller User • DataGuard beachten • LoadStatus vs. Hacks (Expression has changed...), -
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 -
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); } } } -
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 })); } } -
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 -
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; } } -
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 -
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 -
Problemstellung • UI kann komplexere Datenstrukturen benötigen • Zugriff auf
mehrere Feature-Stores notwendig • Inkompatible Datenstrukturen -
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 -
•8. bis 10.6.2020 •Bis 15. 5.: Frühbucher Preise! (-33%: Frühbucherpreise
+ Remote-Rabatt) •Details und Anmeldung https://www.angulararchitects.io/workshop