
Frontend Wolves



Rafał Brzoska
- Senior Software Developer
- JS, TS, Angular
- Organizator Angular Silesia
- Angular Trainer, Prelegent
- Bottega IT Mind
Łukasz Max Kokoszka
- Senior Software Developer
- JS & Frontend
- Internal Trainer, Prelegent










Jak ogarnąć architekturę rozbudowanej wielomodułowej aplikacji dzięki
Angular'owi i NgRx

Z czego wzięła się cała nasza rozkmina?
Jeden produkt o spójnym UX
Różnorodność wymagań
Rozproszony development

Wymagania architektoniczne
Szkielet (Core)
Globalizacja (g11n)
Bezpieczeństwo
UX
Pozostałe elementy wspólne
Feature #1
Uwierzytelnienie, Autoryzacja, Dane
Lokalizacja, Internacjonalizacja
Wspólne komponenty i kontenery
Routing, Serwisy, Storage
Moduły biznesowe
Feature #2

Fantastyczna czwórka







Angular

TypeScript
Angular CLI
Components
Routing (Guards, Lazy Loading)
Angular Module (NgModule)
Angular Module (NgModule)

Angular Modules

Lazy Loaded FeatureModule
Services
Components
Directives
Eagery Loaded FeatureModule
Services
Components
Directives
App Module
Services
Components
Directives
Shared Module
Components
Directives
// src/app/app-routing.module.ts
// ...
const routes: Routes = [
// ...
{
path: 'lazyLoading',
canActivate: [ LoadTranslationsForFeatureGuard ],
loadChildren: 'app/features/lazy-loading/lazy-loading.module#LazyLoadingModule'
},
{
path: 'eagerlyLoading',
canActivate: [ LoadTranslationsForFeatureGuard ],
component: EagerComponent
}
];
@NgModule({
imports: [ RouterModule.forChild(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule { }
RxJS

Callback vs Promise vs Observable
Functional Programming
Reactive Programming

RxJS

Observable
Observer
Subscription
Operator
Subject

Redux

Store
Side effect
View
Action
Reducer
Action
NgRx




NgRx
Biblioteka Angular
@store, @effects, @router-store, @store-devtools, @schematics
ObservableStore

Silnie typowane Akcje
NgRx

subscription

Store
Effect
View
Action
Reducer
Action
Component




Dodatkowe wyzwania
Kontrola developmentu
Kontrola stanu

Deployment i budowanie
S jak Sandbox




Core
Store
Sandbox
Feature #1
Feature #3
Feature #2
Services
Shared components
Services
Services
Services
Components
Components
Components
Store
Store
Store


Sandbox
Sandbox
Sandbox


app.module.ts

app.sandbox.ts
app

store

core

features


feature1.module.ts

feature1.sandbox.ts

store
feature1


feature2.module.ts

feature2.sandbox.ts

store
feature2





actions
reducers
effects
selectors
states
Sandbox
Budowa Sandboxa
@Injectable()
class AppSandbox {
constructor(
private appState$: Store<AppState>,
private actionsSubj: ActionsSubject,
private childSandbox: ChildSandbox) {}
dataStream$ = this.appState$.select(dataSelector);
}Components
Store
Data streams
Dispatchers
Special (observable) dispatchers
@Injectable()
class AppSandbox {
constructor(
private appState$: Store<AppState>,
private actionsSubj: ActionsSubject,
private childSandbox: ChildSandbox) {}
dataStream$ = this.appState$.select(dataSelector);
doSomething(newData) {
this.appState$.dispatch(new DoSomethingAction(newData))
}
}@Injectable()
class AppSandbox {
constructor(
private appState$: Store<AppState>,
private actionsSubj: ActionsSubject,
private childSandbox: ChildSandbox) {}
dataStream$ = this.appState$.select(dataSelector);
doSomething(newData) {
this.appState$.dispatch(new DoSomethingAction(newData))
}
doSomethingSpecial$$(): Observable<string> {
return getDispatchObservable(
this.actionsSubject,
this.appState$,
SampleActionTypes.DoSomethingSpecialSuccess,
SampleActionTypes.DoSomethingSpecialFailure,
new DoSomethingSpecialAction()
);
}
}@Injectable()
class AppSandbox {
constructor(
private appState$: Store<AppState>,
private actionsSubj: ActionsSubject,
private childSandbox: ChildSandbox) {}
}Services
Services
@Injectable()
class AppSandbox {
constructor(
private appState$: Store<AppState>,
private actionsSubj: ActionsSubject,
private childSandbox: ChildSandbox) {}
dataStream$ = this.appState$.select(dataSelector);
doSomething(newData) {
this.appState$.dispatch(new DoSomethingAction(newData))
}
doSomethingSpecial$$(): Observable<string> {
return getDispatchObservable(
this.actionsSubject,
this.appState$,
SampleActionTypes.DoSomethingSpecialSuccess,
SampleActionTypes.DoSomethingSpecialFailure,
new DoSomethingSpecialAction()
);
}
doSomethingElse(data) {
this.childSandbox.doSomethingElse(data);
}
}Re-exports
// src/app/component/sample.component.ts
// ...
@Component({
selector: 'sample',
templateUrl: './sample.component.html', styleUrls: ['./sample.component.scss']
})
export class SampleComponent implement OnInit, OnDestroy {
dataStream$: Observable<any>;
constructor(private appSandbox: AppSandbox, private childSandbox: ChildSandbox) {
this.dataStream$ = this.appSandbox.dataStream$;
}
}
// src/app/component/sample.component.ts
// ...
@Component({
selector: 'sample',
templateUrl: './sample.component.html', styleUrls: ['./sample.component.scss']
})
export class SampleComponent implement OnInit, OnDestroy {
dataStream$: Observable<any>;
constructor(private appSandbox: AppSandbox, private childSandbox: ChildSandbox) {
this.dataStream$ = this.appSandbox.dataStream$;
}
ngOnInit() {
this.routerSubscription = this.appSandbox.routeParams$.pipe(take(1))
.subscribe((p: Params) => {}, () => {});
}
}
// src/app/component/sample.component.ts
// ...
@Component({
selector: 'sample',
templateUrl: './sample.component.html', styleUrls: ['./sample.component.scss']
})
export class SampleComponent implement OnInit, OnDestroy {
dataStream$: Observable<any>;
constructor(private appSandbox: AppSandbox, private childSandbox: ChildSandbox) {
this.dataStream$ = this.appSandbox.dataStream$;
}
ngOnInit() {
this.routerSubscription = this.appSandbox.routeParams$.pipe(take(1))
.subscribe((p: Params) => this.appSandbox.doSomething(p.param1), () => {});
}
buttonClicked() {
this.childSandbox.doSomethingElse();
}
ngOnDestroy() {
this.routerSubscription.unsubscribe();
}
}
// src/app/component/sample.component.ts
// ...
@Component({
selector: 'sample',
templateUrl: './sample.component.html', styleUrls: ['./sample.component.scss']
})
export class SampleComponent implement OnInit, OnDestroy {
dataStream$: Observable<any>;
constructor(private appSandbox: AppSandbox, private childSandbox: ChildSandbox) {
this.dataStream$ = this.appSandbox.dataStream$;
}
ngOnInit() {
this.routerSubscription = this.appSandbox.routeParams$.pipe(take(1))
.subscribe((p: Params) => this.appSandbox.doSomething(p.param1), () => {});
}
}
// src/app/component/sample.component.ts
// ...
@Component({
selector: 'sample',
templateUrl: './sample.component.html', styleUrls: ['./sample.component.scss']
})
export class SampleComponent implement OnInit, OnDestroy {
dataStream$: Observable<any>;
constructor(private appSandbox: AppSandbox, private childSandbox: ChildSandbox) {
this.dataStream$ = this.appSandbox.dataStream$;
}
ngOnInit() {
this.routerSubscription = this.appSandbox.routeParams$.pipe(take(1))
.subscribe((p: Params) => this.appSandbox.doSomething(p.param1), () => {});
}
buttonClicked() {
this.childSandbox.doSomethingElse();
}
}
Gratisy
Mało zależności w komponentach
Agregacja zależności
Uproszczone testowanie

Problemy
Duży przyrost kodu NgRx
Ograniczone wsparcie ze strony narzędzi
Puchnący Sandbox
Ciężkie wejście dla nowych osób w projekcie
Pułapki RxJS i NgRx
// src/app/store/reducers/example.reducer.ts
// ...
export function sampleReducer(
state = initialState,
action: SampleActions
): SampleState {
switch (action.type) {
case SampleActionTypes.DoSomethingSpecialSuccess:
return {
...state,
exampleStateSlice: action.data
};
}
}
}// src/app/store/effects/example.effects.ts
// ...
@Injectable()
export class SampleEffects {
constructor(
private actions$: Actions, private dataRepository: DataRepository
) {}
@Effect()
doSomething$ = this.actions$.pipe(
ofType(SampleActionTypes.DoSomethingSpecialSuccess),
switchMap((action: DoSomethingAction) =>
this.dataRepository.getDataFromServer(action.payload.id, action.payload.params).pipe(
map((data) => new DoSomethingSpecialSuccess(data)),
catchError((error: string) => of(new DoSomethingSpecialFailure(error)))
)
)
);
}

Go & code! :-)
linkedin.com/in/lukasz-max-kokoszka
twitter.com/rafal_brzoska
linkedin.com/in/rafalbrzoska

4dev Katowice - Angular App Sandbox
By Rafał Brzoska
4dev Katowice - Angular App Sandbox
- 796