Redux, RxJS oraz

koncept AppSandbox'a czyli

Angular na sterydach

Rafał Brzoska, Łukasz Max Kokoszka

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

Redux, RxJS oraz

koncept AppSandbox'a

 

czyli

 

Angular na sterydach

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)

RxJS

Redux

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

Go & code! :-)

linkedin.com/in/lukasz-max-kokoszka

twitter.com/rafal_brzoska

linkedin.com/in/rafalbrzoska

Dziękujemy! :-)

linkedin.com/in/lukasz-max-kokoszka

twitter.com/rafal_brzoska

linkedin.com/in/rafalbrzoska

FDD2018 - Angular App Sandbox

By Rafał Brzoska

FDD2018 - Angular App Sandbox

  • 98