Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Angular at Scale | Web Developer Chemnitz
Search
Gregor Woiwode
November 28, 2019
Programming
0
160
Angular at Scale | Web Developer Chemnitz
Gregor Woiwode
November 28, 2019
Tweet
Share
More Decks by Gregor Woiwode
See All by Gregor Woiwode
Pride and Prejudice - Template Driven Forms
gregonnet
0
86
Angular 17 - Rainaissance
gregonnet
0
270
Qwik for Angular Developers
gregonnet
0
160
Angular Material Test Harness
gregonnet
0
260
Qwik - Deliver instant apps at scale
gregonnet
0
380
Resilient UI Test Patterns
gregonnet
0
190
metaUI - Introduction
gregonnet
0
91
How to make tests more resilient
gregonnet
0
100
minsk-19-12-16
gregonnet
0
150
Other Decks in Programming
See All in Programming
「ちょっと古いから」って避けてた技術書、今だからこそ読もう
mottyzzz
10
6.7k
What's new in Spring Modulith?
olivergierke
1
150
チームの境界をブチ抜いていけ
tokai235
0
170
Software Architecture
hschwentner
6
2.3k
20251016_Rails News ~Rails 8.1の足音を聴く~
morimorihoge
1
110
Domain-centric? Why Hexagonal, Onion, and Clean Architecture Are Answers to the Wrong Question
olivergierke
2
840
[Kaigi on Rais 2025] 全問正解率3%: RubyKaigiで出題したやりがちな危険コード5選
power3812
0
130
Cloudflare AgentsとAI SDKでAIエージェントを作ってみた
briete
0
140
iOSエンジニア向けの英語学習アプリを作る!
yukawashouhei
0
190
Devoxx BE - Local Development in the AI Era
kdubois
0
130
monorepo の Go テストをはやくした〜い!~最小の依存解決への道のり~ / faster-testing-of-monorepos
convto
2
490
Building, Deploying, and Monitoring Ruby Web Applications with Falcon (Kaigi on Rails 2025)
ioquatix
4
2.1k
Featured
See All Featured
Music & Morning Musume
bryan
46
6.8k
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.1k
Docker and Python
trallard
46
3.6k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.5k
Art, The Web, and Tiny UX
lynnandtonic
303
21k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
234
17k
The Cult of Friendly URLs
andyhume
79
6.6k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
Git: the NoSQL Database
bkeepers
PRO
431
66k
Building Flexible Design Systems
yeseniaperezcruz
329
39k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
140
34k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.9k
Transcript
Angular at Scale …with Redux powered by NgRx
Gregor Woiwode
Gregor Woiwode co-IT.eu GmbH @GregOnNet
Kentan NgRx Ducks Schematics
@GregOnNet Topics Common Pitfalls Why, How & What of State
Management Redux Architecture NgRx platform
function iDoToMuch(input: any) { if (input.hasPropertyA) { !/* !!... !*/
if (input.hasAlsoPropertyA2) { !/* !!... !*/ } else if (input.hasAlsoPropertyA3) { input.propertyList.map(property !=> { !/* !!... !*/ }); } } if (input.hasPropertyB) { !/* !!... !*/ } input = updateInput(input); return input.fullFillsCondition ? changeInputTheOneWay(input) : changeInputTheOtherWay(input); } Nested Code Blocks Cyclometic Complexity of 10 Tests??? Complexity @GregOnNet
@GregOnNet
Modal Wrapper Monoliths Modal Stepper Questionnaire Button Group @GregOnNet
Modal Wrapper Monoliths Modal Stepper Questionnaire Button Group No clear
API Deeply nested Components Hard for others to understand Hard to maintain @GregOnNet
Monoliths Reduce Component Nesting Think in APIs Put a smile
on your colleagues’ face @GregOnNet
Learnings Applying valuable patterns is hard. It is nearly impossible
to know the whole Project you are working on. It is often ignored that requirements change over time. @GregOnNet
Decentralised state @GregOnNet State is spread across multiple components.
@GregOnNet Distant leaf components need access to state. Decentralised state
Centralised state Interact with single source @GregOnNet
Store Redux Component Action Reducers @GregOnNet Dispatch Processed Mutate Read
Redux Component Action Reducers @GregOnNet Read Write
Redux implements Command-Query-Segregation. @GregOnNet
Redux separates read- and write logic. @GregOnNet
Redux allows writing smaller modules. @GregOnNet
Redux decentralises your code. @GregOnNet
@GregOnNet interface Action { type: string } Action
@GregOnNet interface Action { type: string } Action type needs
to be unique Represents an interaction with UI or Data Takes an optional payload or metadata
@GregOnNet Action interface Action<T> { type: string payload: T }
Reducer Reducer A Reducer B Reduce @GregOnNet A reducer is
responsible for processing actions
Reducer Reducer A Reducer B Reduce @GregOnNet
Reducer Reducer B @GregOnNet Function
Store Principles @GregOnNet Read-Only Single Source of Truth Mutations are
made by pure functions
Pure function @GregOnNet function iAmPure(input: string): string { return `${input}1`;
}
Impure function @GregOnNet function iAmImpurePure(): string { const input =
document.querySelector('input'); return `${input.value}1`; }
Store-Slices @GregOnNet Reducer B Reducer A Reducer C
Store @GregOnNet Reducer B Reducer A Reducer C const state
= { featureA: { !/* !!... !*/ }, featureB: { !/* !!... !*/ }, featureC: { !/* !!... !*/ } } Store is an In-Memory data structure
@GregOnNet NgRx https://ngrx.io/
@GregOnNet @NgModule({ imports: [ StoreModule.forRoot(reducers, { }) !/* !!... !*/
}) export class AppModule { } StoreModule
@GregOnNet @NgModule({ imports: [ StoreModule.forRoot(reducers, { runtimeChecks: { } })
!/* !!... !*/ }) export class AppModule { } StoreModule
@GregOnNet StoreModule @NgModule({ imports: [ StoreModule.forRoot(reducers, { runtimeChecks: { strictActionImmutability:
true, strictActionSerializability: true, strictStateImmutability: true, strictStateSerializability: true } }) !/* !!... !*/ }) export class AppModule { }
@GregOnNet StoreModule - reducers export const reducers: ActionReducerMap<State> = {
counter: counterReducer, logger: loggerReducer } Reducers get combined feature name
@GregOnNet createAction export const increment = createAction( '[Counter] Increment by
one' ); !// createAction(); !// !// { !// type: '[Counter] Increment by one' !// }
@GregOnNet createAction & payload export const add = createAction( '[Counter]
Add value', props<{ payload: { value: number } }>() ); !// createAction({ payload: { value: 1 } }) !// !// { !// type:'[Counter] Add value', !// payload: { value: 1} !// }
@GregOnNet Read from the store export class SimpleCounterComponent { count$:
Observable<number>; constructor(private store: Store<State>) { this.count$ = this.store.pipe( select(state !=> state.counter.count) ); } }
@GregOnNet DEMO
Redux - Inter app communication Component Action Reducers @GregOnNet Dispatch
Processed Mutate Read
Redux - Side effects Component Action Reducers @GregOnNet Dispatch Action
Effects Dispatch
@GregOnNet @NgModule({ imports: [ !/* !!... !*/, EffectsModule.forRoot([CounterEffects]) }) export
class AppModule { } EffectsModule
Redux - Side effects @GregOnNet @Injectable() export class CounterEffects {
randomAdd = createEffect(() !=> this.actions.pipe( ofType(randomAdd) )); constructor(private actions: Actions) { } }
Redux - Side effects @GregOnNet @Injectable() export class CounterEffects {
randomAdd = createEffect(() !=> this.actions.pipe( ofType(randomAdd), switchMap((action !=> !/* e.g. call API !*/) )); constructor(private actions: Actions) { } }
Redux - Side effects @GregOnNet @Injectable() export class CounterEffects {
randomAdd = createEffect(() !=> this.actions.pipe( ofType(randomAdd), switchMap((action !=> !/* e.g. call API !*/), map((result !=> randomAddSuccess(result)) )); constructor(private actions: Actions) { } }
Redux - Side effects @GregOnNet @Injectable() export class CounterEffects {
randomAdd = createEffect(() !=> this.actions.pipe( ofType(randomAdd), switchMap((action !=> !/* e.g. call API !*/), ), { dispatch: true|false }); constructor(private actions: Actions) { } }
Redux - Side effects @GregOnNet @Injectable() export class CounterEffects {
randomAdd = createEffect(() !=> this.actions.pipe( ofType(randomAdd), switchMap((action !=> !/* call API !*/), map((result !=> randomAddSuccess(result)) ), { resubscribeOnError: true|false }); constructor(private actions: Actions) { } }
@GregOnNet DEMO
Selectors @GregOnNet this.store.pipe( select(state !=> state.counter.count) ); Inline selectors
Selectors @GregOnNet this.store.pipe( select(state !=> state.cou ); Break quickly when
Store changes Not reusable
Selectors @GregOnNet const featureCounter = createFeatureSelector<CounterState>('counter'); feature name Jump to
a feature
Selectors @GregOnNet export const count = createSelector(featureCounter, s !=> s.count);
Project data const featureCounter = createFeatureSelector<CounterState>('counter');
Selectors @GregOnNet Combine selectors aggregating data export const infoWithCurrentCount =
createSelector( infoMessage, count, (messages, total) !=> messages.map(message !=> `${message} (Total: ${total})` ) );
@GregOnNet Selectors simplify Store changes.
@GregOnNet are composable. Selectors
@GregOnNet use memoization. Selectors
@GregOnNet Selectors Memoization 1 selector calculate 2 selector use cache
3 selector calculate
@GregOnNet DEMO
What we have covered… Redux Architecture Actions & Reducers StoreDevtools
Effects Selectors @ngrx/entity @ngrx/router-store @ngrx/data
Closing thoughts On-Board your team patiently. Modeling application state has
lots of overlaps with Requirements Engineering. Redux makes your App more predictive.
Gregor Woiwode Thank you for having me! @GregOnNet https://bit.do/ng-scale-chemnitz https://stackblitz.com/edit/ngrx-8-playground