Skip to content

Instantly share code, notes, and snippets.

@Gabri
Last active June 18, 2021 16:16
Show Gist options
  • Save Gabri/01a4b6f73a2e62ee1c3e694e50e2e50e to your computer and use it in GitHub Desktop.
Save Gabri/01a4b6f73a2e62ee1c3e694e50e2e50e to your computer and use it in GitHub Desktop.
[Angular & Friends] #angular #node #rxjs #ngrx

Angular & Friends

Start local FE (connected to local be)

ng serve --configuration=local-dev

Angular Heroes Tutorial

https://angular.io/tutorial

A workspace contains the files for one or more projects

ng new angular-tour-of-heroes

It also creates the following workspace and starter project files:

  • A new workspace, with a root folder named angular-tour-of-heroes.
  • An initial skeleton app project in the src/app subfolder.
  • Related configuration files.

Serve the application

cd angular-tour-of-heroes
ng serve --open

Create the heroes component

ng generate component heroes

The ngOnInit() is a lifecycle hook. Angular calls ngOnInit() shortly after creating a component. It's a good place to put initialization logic.

[(ngModel)] is Angular's two-way data binding syntax.

Every component must be declared in exactly one NgModule.

Create a new service

ng generate service hero

If a property should be bind to the template, ie the messageService, the property must be public because you're going to bind to it in the template.

Routing

In Angular, the best practice is to load and configure the router in a separate, top-level module that is dedicated to routing and imported by the root AppModule. By convention, the module class name is AppRoutingModule and it belongs in the app-routing.module.ts in the src/app folder.

ng generate module app-routing --flat --module=app

A typical Angular Route has two properties:

  • path: a string that matches the URL in the browser address bar.
  • component: the component that the router should create when navigating to this route.

The following line adds the RouterModule to the AppRoutingModule imports array and configures it with the routes in one step by calling RouterModule.forRoot():

imports: [ RouterModule.forRoot(routes) ],

Next, AppRoutingModule exports RouterModule so it will be available throughout the application.

exports: [ RouterModule ]

The <router-outlet> tells the router where to display routed views. The RouterOutlet is one of the router directives that became available to the AppComponent because AppModule imports AppRoutingModule which exported RouterModule. The ng generate command you ran at the start of this tutorial added this import because of the --module=app flag.

<a routerLink="/heroes">Heroes</a>

A routerLink attribute is set to "/heroes", the string that the router matches to the route to HeroesComponent. The routerLink is the selector for the RouterLink directive that turns user clicks into router navigations. It's another of the public directives in the RouterModule.

The ActivatedRoute holds information about the route to this instance of the HeroDetailComponent. This component is interested in the route's parameters extracted from the URL. The "id" parameter is the id of the hero to display.

The location is an Angular service for interacting with the browser. You'll use it later to navigate back to the view that navigated here.

const id = Number(this.route.snapshot.paramMap.get('id'));

The route.snapshot is a static image of the route information shortly after the component was created.

The paramMap is a dictionary of route parameter values extracted from the URL. The "id" key returns the id of the hero to fetch.

Route parameters are always strings. The JavaScript Number function converts the string to a number, which is what a hero id should be.

HttpClient

In general, an observable can return multiple values over time. An observable from HttpClient always emits a single value and then completes, never to emit again. This particular HttpClient.get() call returns an Observable<Hero[]>; that is, "an observable of hero arrays". In practice, it will only return a single hero array.

RxJs in angular

Attenzione che i Subscribe vanno poi fatti unsubscribe in ngOnDestroy a meno che non uso async come pipe che lo fa in automatico

{{data$ | async | json}}

Neseted call http

fare http.get e poi .pipe( switchMap(seconda chiamata), switchMap(terza chiamata dopo la seconda e posso girare il result della chiamata prec)). Potrebbe essere meglio il mergeMap o concatMap (avendo anche certezza dell'ordine) poiché altrimenti se le chiamate sono veloci posso essere cancellate alcune

SwitchMap prende un array ed emette un observable per ognuno, quindi potrei considerando users come un array di User così posso usare map prendendo una property del singolo User (mentre senza lo switchMap non potrei perché è un array)

SwitchMap(users => users)
.map(user => user.name)

shareReplay(1) o share() converte da cold a hot Observable quindi diventa Multicast (singleton e quindi eventi condivisi senza ripartire dall'inizio)

Subject Observable speciale ed è hot. in subscriber prende i valori dal prossimo next.

BehaviorSubject Observable simile a Subject ma prende anche un val di default. i subscriber prendo il valore attuale e i successivi o anche il def se non ha ancora emesso valori. Di solito si usa questo (e non Subject). ha anche il metodo getValue (ma da usare con parsimonia)

ReplaySubject recupera lo storico dei valori nella subject, prende anche un paramentro ed è un numero che mi dice gli ultimi N valori da prendere

OT : chrome extension Lighthouse che da report su SEO, accessibilità, performance

npm install -g json-server meglio però come dev dependencies npm install -D json-server

Moduli

ng g m features/login --route login --module app modulo e lazy login già utilizzabile

Se creo un modulo di routing per poter poi usare il tag router-outlet occorre che il modulo esporti il RouterModule, in questo modo tutti quelli che importeranno AppRoutingModule avranno accesso anche al RouterModule

Creiamo moduli anche per poter usare il lazy loading (usando il loadChildren)

Non si possono caricare lo stesso componente in più moduli (se c'è importazione doppia) -> allora devo wrapparlo in un modulo e utilizzare il modulo. Anche Material ha ogni componente in un suo modulo (così posso importare il singolo modulo e componente che mi serve).

Per fare in modo che la dependencies injection carichi implementazioni diverse occorre esplicitare nei providers del modulo o componente: { provide: ConfigService, useClass: ConfigServiceAlternative } il default: { provide: ConfigService, useClass: ConfigService } che sarebbe equivalente a: ConfigService C'è una gerarchia degli injector e non è tutto su root

@HostBinding('className') consente di mettere classe nella root tag del component, oppure @HostBinding class = 'nomeClasse', ma posso anche cambiare il contenuto @HostBinding innerText = 'new content'

HostListener mi consente, se sono in una direttiva posso ad esempio mettermi in ascolto di un evento, tipo click:

@HostListener('click')
clickMe() {
    consol.log('clickme')
}

Renderer2 mi da accesso a metodi di basso livello simili a jQuery tipo setStyle, appendChild

Direttive strutturali (con * davanti) hanno anche il vantaggio (mettendo l'asterisco, anche su nostre direttive) è che vengano generati wrap tag html (non rederizzati)

ChangeDetectionStrategyOnPush

Caso di servizio iniettato in più punti dell'app e viene cambiata una sua property, se il servizio è iniettato nei componenti potrei scatenare l'aggiornamento con change detection su tutti i nodi dipendenti. Meglio iniettare il servizio nella view padre e passare eventualmente i parametri che servono come @Input in questo modo usiamo la strategia onPush correttamente.

-> Quindi occorre componente smart (container) e gli altri dummy (dumb) sotto di lui (stateless). In questo modo puoi usare onPush su tutta la ChangeDetectionStrategyOnPush

Oppure alternativa con ngRx e il suo unico state manager, con i seguenti vantaggi:

  • isolation (anche per separare dev tra arch e ui)
  • scalability
  • readability
  • maintainability
  • UI is statetless (no injections, state or side effects)
    • Performance with onOush strategy
    • easy to test containers components connects UI and Data

Contro:

  • more code to write
  • drillig props
    • molti @Input e @O

Reducer : funzioni che consentono di aggiornare solo porzioni specifiche dello store (divisi quindi per tipologie/dominio). aggiorna lo store, solitamente, in base al risultato dall'effetto non dell'azione (effetto che magari ha prima comunicato col BE, dobbiamo aspettare il risultato)

Posso avere anche uno store lazy

Redux rules:

  • single source of truth: the store
  • State is Read Only
  • Changes are made with pure function

Posso avere anche un Reducer che gestisce altri reducer più specifici

Side Effects per gesitre azioni async

Inizializzazione dello stato:

StoreModule.forRoot(reducers)

Actions

Dispatch di azioni

createAction passando nome e payload (possono anche non avere params)

store.dispatch(editProfile({ user: formData}))

NB: Solitamente (e di default) un Effetto deve sempre fare dispatch di una Action

Ogni volta che viene emessa un azione restituisco un Observable

Effects

createEffect() Un effetto deve ritornare un Observable di una Action

Reducers

appy Immutability and deve essere pura Stesso input -> stesso output (iin questo modo non abbiamo problemi con il time travel dei redux tools)

createReducer()

Selectors

funzione che acquisisce tutto lo stato e decide quale nodo tornare

createSelector() gli si possono passare fino a 8 selettori e combinarli

createFeatureSelector per crearly lazy

Suggerimento per la UI : tailwind (https://tailwindcss.com/) Facile crearsi ui kit con questo (poi ce ne sono già fatti ma a pagamento)


Possiamo fare anche dei Reducers per gestire singoli aspetti (e comuni a più oggetti): tipo errorReducer, pendingReducer. Ma posso anche gestirli su singolo oggetto, tipo itemErrorReducer. Stessa cosa anche per i Selector.

Si potrebbe avere anche uno UIState per gestire lo stato della ui (modali aperte/chiuse, pannelli ecc)

ngAfterViewInit

// approccio push vs pull
Pull : approccio imperativo e lo stato deve essere aggiornato manualmente
Buona soluzione per azioni che non vengono ripetute
Observable può chiamare la callback n volte (a differenza della promise)
Push : + complicato da capire, richiede tools, consumer viene notificato quando lo stato cambia (meno bug, meno code, consigliato quando i dati cambiano)
Reactive programming (branca della programamzione funzionale, architettura Push)
Usato soprattutto per applicazioni event driven (stream)
funzione pura : stesso input -> stesso output (+ altre caratteristiche tipo l'output è derivato solo dai parametri in input)
Immutabilità e cambio valori (ricreo un oggetto nuovo, clonandolo e modificando una proprietà)
user = Object.assign({}, user, {name: 'Jhon'})
Il cambio di valore potrebbe non comprendere il cambio di stato, mentre una nuova allocazione di memoria sì (usando '===')
In un Observable se avviene un complete o un error -> non emetteranno altri valori
In RxJS observable are cold, or unicast by default
NB: tentare di evitare subscribe dentro subscribe
Operatori:
of
from
interval e timer (con tempo di delay iniziale)
fromEvent
reduce (per avere output serve che l'obseervable sia completed)
tap (utile per debuggare nella catena di observable)
scan (mostra tutti i passaggi diversamente da reduce)
distinct (può prendere in input la funzione comparator)
distinctUntilChanged (utile nei form, elimina i duplicati solo rispetto al valore precedente)
take (i primi N)
first (== take(1)), ma posso passare anche funzione di filtro -> quindi il primo che risolve la condizione
last
takeLast (gli ultimi N valori)
takeWhile
takeUntil
skip (salta i primi N)
skipWhile (in base a condizione)
buffer (molto utile con sequence di dati veloci, accumulo i valori e poi aggiorno)
debouceTime (utile ad esempio per ricerca di testo, prende in input il tempo prima di emettere il valore)
delay (simulo una latenza, tipo sleep)
ajax ('rxjs/ajax')
catchError
retry
merge (crea Observable partendo da un lista di observable, quindi basta un solo subscribe)
combineLatest (rispetto a merge ho un solo risultato come array dei risultati, comodo per chiamate simultanee, mette sempre l'ultimo valore di ognuno, restituisce un valore se tutti hanno emesso almeno un valore)
forkJoin (simile a combineLatest ma restituisce il valore solo se tutti hanno hanno completato)
zip
withLatestFrom
@see rxmarbles.com, rxjs.dev, learnrxjs.io
Convenzione: var che contiene un Observable ha in fondo un $ : click$ = fromEvent(document, 'click')
switchMap (passare da un Observable a un altro. le richieste intermedie sono cancellate in favore dell'ultimo valore)
mergeAll (sottoscrive tutti gli Observable della catena)
mergeMap (prende uno o + Observable, lo sottoscrive e ritorna il risultato)
concatMap (garantisce ordine e mette in coda tutti gli observable facendo partire il successivo al termine della precedente)
exhaustMap (ignorate tutte le richieste successive fino a che la prima non completa. utile per click compulsivi)
iif (operatore ternario)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment