![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5408380/pasted-from-clipboard.png)
Frontend Wolves
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5335798/wilki.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5395742/_MG_6688__2___1_.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5395754/pasted-from-clipboard.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5409619/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5409622/giphy_1_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5409648/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5409678/giphy_1_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5410946/giphy_1_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5410953/giphy_1_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5411060/news.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5444081/tumblr_ou83tt2PWf1qfrfyho1_400_1_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5411036/giphy_4_.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5411050/giphy_1_.gif)
Jak ogarnąć architekturę rozbudowanej wielomodułowej aplikacji dzięki
Angular'owi i NgRx
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5437752/34b49f51ce1daed841b4d26676d829a5.jpg)
Z czego wzięła się cała nasza rozkmina?
Jeden produkt o spójnym UX
Różnorodność wymagań
Rozproszony development
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5415456/16028840.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5415484/dam.jpg)
Fantastyczna czwórka
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409185/angular.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409186/redux.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409192/rxjs.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5409452/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5409456/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5425130/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5425133/pasted-from-clipboard.png)
Angular
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409185/angular.png)
TypeScript
Angular CLI
Components
Routing (Guards, Lazy Loading)
Angular Module (NgModule)
Angular Module (NgModule)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5427086/pasted-from-clipboard.png)
Angular Modules
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5427086/pasted-from-clipboard.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409192/rxjs.png)
Callback vs Promise vs Observable
Functional Programming
Reactive Programming
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5427086/pasted-from-clipboard.png)
RxJS
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409192/rxjs.png)
Observable
Observer
Subscription
Operator
Subject
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5427086/pasted-from-clipboard.png)
Redux
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409186/redux.png)
Store
Side effect
View
Action
Reducer
Action
NgRx
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5120850/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409192/rxjs.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5415446/57795199_1271315872_11.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409186/redux.png)
NgRx
Biblioteka Angular
@store, @effects, @router-store, @store-devtools, @schematics
ObservableStore
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5415456/16028840.png)
Silnie typowane Akcje
NgRx
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409192/rxjs.png)
subscription
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5120850/pasted-from-clipboard.png)
Store
Effect
View
Action
Reducer
Action
Component
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5409192/rxjs.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5427122/Gargamel_SV.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5427131/react.gif)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5427211/armagedon.jpg)
Dodatkowe wyzwania
Kontrola developmentu
Kontrola stanu
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5415519/Волки.png)
Deployment i budowanie
S jak Sandbox
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5410886/smurf-sandbox.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5410943/wilk.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5410944/bulb-512.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5410961/pasted-from-clipboard.png)
Core
Store
Sandbox
Feature #1
Feature #3
Feature #2
Services
Shared components
Services
Services
Services
Components
Components
Components
Store
Store
Store
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5427262/armagedon-balloon.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5427373/wilk-przestraszony-cien.png)
Sandbox
Sandbox
Sandbox
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445771/pasted-from-clipboard.png)
app.module.ts
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445771/pasted-from-clipboard.png)
app.sandbox.ts
app
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
store
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
core
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
features
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445771/pasted-from-clipboard.png)
feature1.module.ts
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445771/pasted-from-clipboard.png)
feature1.sandbox.ts
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
store
feature1
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445771/pasted-from-clipboard.png)
feature2.module.ts
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445771/pasted-from-clipboard.png)
feature2.sandbox.ts
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
store
feature2
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5445768/pasted-from-clipboard.png)
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
![](https://s3.amazonaws.com/media-p.slid.es/uploads/305497/images/5415589/b2e7d53c7c47a2360f42f16414d9c0cd.png)
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)))
)
)
);
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5444253/giphy_1_.gif)
Go & code! :-)
linkedin.com/in/lukasz-max-kokoszka
twitter.com/rafal_brzoska
linkedin.com/in/rafalbrzoska
![](https://s3.amazonaws.com/media-p.slid.es/uploads/618884/images/5444257/wolves.png)
4dev Katowice - Angular App Sandbox
By Rafał Brzoska
4dev Katowice - Angular App Sandbox
- 748