Local States (with Apollo) By Oskar Bechtold Local States State? - - PowerPoint PPT Presentation
Local States (with Apollo) By Oskar Bechtold Local States State? - - PowerPoint PPT Presentation
Local States (with Apollo) By Oskar Bechtold Local States State? Observable Data Service Flux Redux NGRX Apollo NGXS Types of State A typical web application has the following six types of state:
Local States
- State?
- Observable Data Service
- Flux
- Redux
- NGRX
- Apollo
- NGXS
Types of State
A typical web application has the following six types of state:
- Server state
- Persistent state
- The URL and router state
- Client state
- Transient client state
- Local UI state
State Synchronization
- The persistent state and the server state store the same
information.
- So do the client state and the URL.
- We somehow have to synchronize them.
Ways to Solve the State Problem
- Angular Services and RxJS -> Observable Data Service
- Redux using @angular-redux/store (formlerly ng2-redux)
- Redux using @ngrx
- Apollo
- NGXS
Stores as Solution for Multiple Problems
Stores are a multi-responsibility solution:
- Component interaction via the Observable pattern.
- Client-side cache if needed, to avoid doing repeated Ajax
requests.
- Temporary UI state, as we fill in a large form or want to
store search criteria in a search form when navigating between router views.
- Solve the problem of allowing modification of client side
transient data by multiple actors.
Store Architecture
- Provide an Observable-like pattern for decoupled
component interaction.
- Provide a client container for temporary UI state.
- Provide a cache for avoiding excessive HTTP requests.
- Provide a solution for concurrent data modification by
multiple actors.
- Provide a hook for tooling.
- MVC and CRUD vs Event-sourcing, Commands and CQRS
Model View Controller and CRUD
https://martinfowler.com/bliki/CQRS.html
Command Query Responsibility Segregation
https://martinfowler.com/bliki/CQRS.html
Stores and Tooling
One of the biggest reasons for using a store it's the tooling ecosystem it provides. The tooling is amazing,
- time traveling debugging,
- being able to attach a store state to a bug report
- and hot reloading those are huge features.
Observable Data Services
Observable Data Services
The service, that can be named a store can be injected in any place where the data is needed:
export class App { constructor(private todoStore: TodoStore, private uiStateStore: UiStateStore) { } }
Use an Observable Data Service
<ul id="todo-list"> <li *ngFor="let todo of todoStore.todos | async" > ... </li> </ul>
Modify the Data of a Service
- nAddTodo(description) {
this.todoStore.addTodo(newTodo) .subscribe( res => {}, err => { this.uiStateStore.endBackendAction(); } ); }
Build an Observable Data Service
@Injectable() export class TodoStore { private _todos: BehaviorSubject<List<Todo>> = new BehaviorSubject(List([])); public readonly todos: Observable<List<Todo>> = this._todos.asObservable(); constructor(private todoBackendService: TodoBackendService) { this.loadInitialData(); } ... }
Writing an Action Method
addTodo(newTodo:Todo):Observable { let obs = this.todoBackendService.saveTodo(newTodo);
- bs.subscribe(
res => { this._todos.next(this._todos.getValue().push(newTodo)); }); return obs; }
Example
Example
Exmple taken from https://blog.nrwl.io/managing-state-in-angular-applications-22b75ef5625f
Example
Example
Types of State
- Backend manages the persistent state (the talks) and the
client state (the filters).
- The router manages the URL and the router state.
- WatchService manages the transient client state (watched
talks).
- The individual components manage the local UI state.
Example
Problems
- Syncing Persistent and Server State
- Syncing URL and Client State
Example
Mistakes
- No separated state management from computation and
- services. Backend talks to the server and manages state.
- No clearly defined synchronization strategy of the
persistent state and the server.
- No clearly defined synchronization strategy of client
state and URL.
- Model is mutable, which makes ensuring any sort of
guarantees difficult.
https://blog.nrwl.io/managing-state-in-angular-applications-22b75ef5625f
Separated State from Services
https://blog.nrwl.io/managing-state-in-angular-applications-22b75ef5625f
What About the Router?
Flux
The Original Facebook Chat Bug
that originated Flux
- Problem with the unread
messages counter
- Systematically
displaying incorrect results:
- users would see one
unread message, when they click the counter all the messages had already been read
What is Flux
- Application architecture from Facebook.
- Utilizing a unidirectional data flow.
- More of a pattern rather than a formal framework.
https://facebook.github.io/flux/docs/in-depth-overview.html
Redux
What is Redux
- Application state manager for JavaScript applications.
- Keeps the core principles of the Flux-architecture by
having a unidirectional data flow in your application.
- Where Flux applications traditionally have multiple
stores, Redux applications have only one global, read-only application state.
- State is calculated by "reducing" over a collection or
stream of actions.
How does Redux
- Prevent event soup scenarios caused by event buses.
- Redux store is a combination of the Command and the
Observable patterns.
- We dispatch an action into the store, and the store will
- perate on the data inside the store.
- Emitter of the action does not know what the store will
do with it.
- Receiver does not know what triggered the generation of
the new data.
- CQRS — Command Query Responsibility Segregation
Redux provides a solution by ensuring that:
- State is wrapped in a single store.
- Handles all updates and notifies all subscribers.
- No need to pass state through entire component tree.
- All changes are sequentially -> predictable end result
free from unexpected effects and race conditions.
- State is immutable
○ Change in state results in a totally new version of the state ○ More predictable ○ Look at any previous version of the state ○ Incredible debug experience.
Three Principles
- Single source of truth
○ The state of your whole application is stored in an object tree within a single store.
- State is read-only
○ The only way to change the state is to emit an action, an object describing what happened.
- Changes are made with pure functions
○ To specify how the state tree is transformed by actions, you write pure reducers.
https://www.dotnetcurry.com/reactjs/1356/redux-pattern-tutorial
https://medium.com/@aksudupa11/redux-sagas-714370b61692
Better video: https://gfycat.com/ThreadbareWeepyAlbino
You Might Not Need Redux
Dan Abramov, Co-author of Redux. People often choose Redux before they need it. “What if our app doesn’t scale without it?” Later, developers frown at the indirection Redux introduced to their code. “Why do I have to touch three files to get a simple feature working?” Why indeed! Finally, don’t forget that you can apply ideas from Redux without using Redux. For example, consider a React component with local state.
Flux vs Redux
https://stackoverflow.com/questions/32761316/flux-vs-redux-pros-and-cons-highlights
http://www.prathapkudupublog.com/2017/04/flux-vs-redux.html
@angular-redux
What is @angular-redux?
A set of npm packages to integrate redux store into your Angular 2+ applications.
- Change processing with RxJS observables.
- Compile time optimizations with NgModule and
Ahead-of-Time compilation.
- Integration with the Angular change detector.
- @angular-redux/store - Bindings between Redux and Angular
- @angular-redux/form - Bindings between Angular Forms and
your Redux state
- @angular-redux/router - Bindings between Angular Router
and your Redux state
NGRX
What is NGRX
Reactive State for Angular Store is RxJS powered state management for Angular applications, inspired by Redux. Store is a controlled state container designed to help write performant, consistent applications on top of Angular.
Key Concepts of NGRX
- Actions: Unique events dispatched from components and
services.
- Reducers: State changes are handled by pure functions
that take the current state and the latest action to compute a new state.
- Selectors: Pure functions to select, derive and compose
pieces of state.
Angular-redux vs NGRX
angular-redux/store is just bindings around the Redux API (uses Redux). ngrx/store is an RxJS powered state management for Angular applications (inspired by Redux, but do not use it).
src/app/counter.actions.ts
1. export enum ActionTypes { 2. Increment = '[Counter Component] Increment' , 3. Decrement = '[Counter Component] Decrement' , 4. Reset = '[Counter Component] Reset' , 5. } 6. 7. export class Increment implements Action { readonly type = ActionTypes .Increment; } 8. export class Decrement implements Action { readonly type = ActionTypes .Decrement; } 9. export class Reset implements Action { readonly type = ActionTypes .Reset; }
src/app/counter.reducer.ts
1. export const initialState = 0; 2. export function counterReducer (state = initialState , action: Action) { 3. switch (action.type) { 4. case ActionTypes .Increment: 5. return state + 1; 6. case ActionTypes .Decrement: 7. return state - 1; 8. case ActionTypes .Reset: 9. return 0; 10. default: 11. return state; 12. } 13. }
src/app/my-counter/my-counter.component.ts
1. export class MyCounterComponent { 2. count$: Observable <number>; 3. constructor (private store: Store<{ count: number }>) { 4. this.count$ = store.pipe(select('count')); 5. } 6. 7. increment () { this.store.dispatch(new Increment()); } 8. decrement () { this.store.dispatch(new Decrement()); } 9. reset() { this.store.dispatch(new Reset()); } 10. }
Also cool, Selectors
Selectors are pure functions used for obtaining slices of store state.
Effects
Provides an API to model event sources as actions. Effects:
- Listen for actions dispatched from Store.
- Isolate side effects from components, allowing for more
pure components that select state and dispatch actions.
- Provide new sources of actions to reduce state based on
external interactions such as network requests, web socket messages and time-based events.
Plugins
- Entity State adapter for managing record collections.
- Router Store.
Persistence
- Ngrx-store-persist
○ Whole store persisted
- Ngrx-store-localstorage
○ Setup with keys to store only parts of the store
Apollo
THE GRAPHQL PLATFORM
Do GraphQL Right
- Replace many inflexible
APIs with a single versatile query system.
- Decouple frontend and
backend development.
- Ship high quality apps
to more platforms faster.
GraphQL
- GraphQL is a query language for your API,
- and a server-side runtime for executing queries
- by using a type system you define for your data.
At its simplest, GraphQL is about asking for specific fields
- n objects
Query { hero { name } } JSON Result { "data": { "hero": { "name": "R2-D2" } } }
Fields
Query { hero { name # Queries can have comments! friends { name } } } JSON Result { "data": { "hero": { "name": "R2-D2", "friends": [ { "name": "Luke Skywalker" }, { "name": "Han Solo" }, { "name": "Leia Organa" } ] } } }
Arguments
Query { human(id: "1000") { name height } } JSON Result { "data": { "human": { "name": "Luke Skywalker", "height": 1.72 } } }
Mutations
mutation CreateReviewForEpisode($ep: Episode!, $review: ReviewInput!) { createReview(episode: $ep, review: $review) { stars commentary } }
https://www.howtographql.com/basics/1-graphql-is-the-better-rest/
GraphQL is the better REST
https://www.apollographql.com/docs/tutorial/introduction.html
Apollo GraphQL Query
const CurrentUserForProfile = gql` query CurrentUserForProfile { currentUser { login avatar_url } } `; this.querySubscription = this.apollo.watchQuery<any>({ query: CurrentUserForProfile }) .valueChanges .subscribe(({ data, loading }) => { this.loading = loading; this.currentUser = data.currentUser; });
Apollo GraphQL Mutation
const submitRepository = gql` mutation submitRepository { submitRepository(repoFullName: "apollographql/apollo-client") { createdAt } } `;
<
newRepository() { this.apollo.mutate({ mutation: submitRepository }).subscribe(); }
Apollo Cache
- apollo-cache-inmemory is the default cache
implementation.
- InMemoryCache is a normalized data store without the
dependency on Redux.
- Sometimes you may need to manipulate the cache directly,
such as updating the store after a mutation.
Local state management
- So far remote data from our GraphQL server.
- Access boolean flags and device API results from multiple
components in our app, without maintaining a separate NGRX or Redux store.
- Apollo cache can be the single source of truth for all
data in the client application.
- apollo-link-state allows you to store your local data
inside the Apollo cache alongside your remote data.
Setup
@NgModule({ // ... providers: [{ provide: APOLLO_OPTIONS, useFactory(httpLink: HttpLink) { const cache = new InMemoryCache(); const http = httpLink.create({ uri: "https://w5xlvm3vzz.lp.gql.zone/graphql" }); const local = withClientState({ cache, defaults, resolvers }); return { cache: new InMemoryCache(), link: local.concat(http) } }, deps: [HttpLink] }], // ... }) export class AppModule {}
Resolvers
export const resolvers = { Mutation: { toggleTodo: (_, variables, {cache, getCacheKey}) => { const id = getCacheKey({__typename: 'TodoItem', id: variables.id}); const fragment = gql` fragment completeTodo on TodoItem { completed } `; const todo = cache.readFragment({fragment, id}); const data = {...todo, completed: !todo.completed}; cache.writeData({id, data}); return null; }, }, };
Optimistic UI
const updateComment = gql` mutation updateComment($commentId: ID!, $commentContent: String!) { updateComment(commentId: $commentId, commentContent: $commentContent) { id __typename content } } `; submit({ commentId, commentContent }) { this.apollo.mutate({ variables: { commentId, commentContent },
- ptimisticResponse: {
__typename: 'Mutation', updateComment: { id: commentId, __typename: 'Comment', content: commentContent, }, }, }).subscribe(); }
Persistence
- Apollo-cache-persist
- Just tell it where to store the store
- In conflict with defaults
NGXS
NGXS
- State management pattern + library.
- CQRS like Redux and NGRX.
- But less boilerplate by using modern TypeScript features
such as classes and decorators.
Concepts
- Store
○ Global state container, action dispatcher and selector
- Actions
○ Class describing action to take
- State
○ Class definition of the state
- Select
○ State slice selectors
https://ngxs.gitbook.io/ngxs/concepts/intro
- Logger
- Devtools
- Storage
- Forms
- Web Socket
- Router
Plugins
Summary
- Observable Data Services
○ Easy, can get messy, CRUD
- Flux
○ Just a pattern, one store per module/component, lot’s to code, CQRS
- Redux
○ One store per app, write actions and reducers, awesome devtools, CQRS
- Apollo
○ GraphQl for queries and mutations, write resolvers, devtools, CRUD
- NGXS
○ One store, apparently less boilerplate code, redux devtools, CQRS
Code Examples
- https://stackblitz.com/edit/angular-local-state-ngrx
- https://stackblitz.com/edit/angular-local-state-ngxs
- https://stackblitz.com/edit/angular-local-state-apollo
- Enabled Devtools for all three:
- http://extension.remotedev.io/
- https://chrome.google.com/webstore/detail/apollo-client-d
eveloper-t/jdkknkkbebbapilgoeccciglkfbmbnfm
Sources
- https://almerosteyn.com/2016/08/redux-explained-again
- https://angular-2-training-book.rangle.io/handout/state-m
anagement/
- https://blog.angular-university.io/angular-2-redux-ngrx-r
xjs/
- https://blog.angular-university.io/how-to-build-angular2-
apps-using-rxjs-observable-data-services-pitfalls-to-avoi d/
- https://vsavkin.com/managing-state-in-angular-2-applicati
- ns-caf78d123d02
Sources
- https://blog.angular-university.io/angular-ngrx-store-and
- effects-crash-course/
- https://facebook.github.io/flux/docs/in-depth-overview.ht
ml
- https://redux.js.org/basics/dataflow
- https://ngrx.io/
- https://github.com/angular-redux/platform
- https://julienrenaux.fr/2017/02/16/from-redux-to-angular-
ngrxstore/