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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Gregor Woiwode
November 28, 2019
Programming
170
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Angular at Scale | Web Developer Chemnitz
Gregor Woiwode
November 28, 2019
More Decks by Gregor Woiwode
See All by Gregor Woiwode
Pride and Prejudice - Template Driven Forms
gregonnet
0
100
Angular 17 - Rainaissance
gregonnet
0
300
Qwik for Angular Developers
gregonnet
0
180
Angular Material Test Harness
gregonnet
0
320
Qwik - Deliver instant apps at scale
gregonnet
0
430
Resilient UI Test Patterns
gregonnet
0
230
metaUI - Introduction
gregonnet
0
120
How to make tests more resilient
gregonnet
0
130
minsk-19-12-16
gregonnet
0
170
Other Decks in Programming
See All in Programming
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
220
不変条件と整合性境界—ビジネスが決める設計判断と実現パターン / Invariants and Consistency Boundaries
nrslib
14
6k
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
610
Vue × Nuxt × Oxc どこまで使える?実運用の現在地
andpad
0
310
軽量Java基盤の設計 DIコンテナに頼らない、長期保守と1秒起動の実現 JJUG CCC 2026 Spring
macha64
0
610
Make SRE Operations Easier with Azure SRE Agent
kkamegawa
0
9.1k
フロントエンドとバックエンドで「1文字」を揃えよう
youkidearitai
PRO
0
770
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6.6k
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
990
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7.1k
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
140
The NotImplementedError Problem in Ruby
koic
1
980
Featured
See All Featured
The SEO identity crisis: Don't let AI make you average
varn
0
510
Amusing Abliteration
ianozsvald
1
210
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
190
Designing for humans not robots
tammielis
254
26k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
230
23k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
170
Hiding What from Whom? A Critical Review of the History of Programming languages for Music
tomoyanonymous
2
870
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
320
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.5k
Context Engineering - Making Every Token Count
addyosmani
9
990
Faster Mobile Websites
deanohume
310
32k
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
160
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