Lock in $30 Savings on PRO—Offer Ends Soon! ⏳
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
270
Qwik - Deliver instant apps at scale
gregonnet
0
390
Resilient UI Test Patterns
gregonnet
0
200
metaUI - Introduction
gregonnet
0
93
How to make tests more resilient
gregonnet
0
100
minsk-19-12-16
gregonnet
0
160
Other Decks in Programming
See All in Programming
非同期処理の迷宮を抜ける: 初学者がつまづく構造的な原因
pd1xx
1
660
リリース時」テストから「デイリー実行」へ!開発マネージャが取り組んだ、レガシー自動テストのモダン化戦略
goataka
0
110
S3 VectorsとStrands Agentsを利用したAgentic RAGシステムの構築
tosuri13
6
290
開発に寄りそう自動テストの実現
goyoki
1
680
分散DBって何者なんだ... Spannerから学ぶRDBとの違い
iwashi623
0
180
俺流レスポンシブコーディング 2025
tak_dcxi
13
8.2k
AWS CDKの推しポイントN選
akihisaikeda
1
240
Microservices rules: What good looks like
cer
PRO
0
850
Full-Cycle Reactivity in Angular: SignalStore mit Signal Forms und Resources
manfredsteyer
PRO
0
110
Integrating WordPress and Symfony
alexandresalome
0
130
TypeScriptで設計する 堅牢さとUXを両立した非同期ワークフローの実現
moeka__c
6
2.9k
CSC509 Lecture 14
javiergs
PRO
0
220
Featured
See All Featured
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
61k
Optimising Largest Contentful Paint
csswizardry
37
3.5k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.3k
Optimizing for Happiness
mojombo
379
70k
It's Worth the Effort
3n
187
29k
Docker and Python
trallard
46
3.7k
GitHub's CSS Performance
jonrohan
1032
470k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
55
3.1k
4 Signs Your Business is Dying
shpigford
186
22k
StorybookのUI Testing Handbookを読んだ
zakiyama
31
6.4k
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