Scalable Angular Applications

Scalable Angular Applications

7a3f4cf57e03f53e7b37c8ef84338460?s=128

Denis Kyashif

November 13, 2018
Tweet

Transcript

  1. Scalable Angular Applications Denis Kyashif

  2. @deniskyashif deniskyashif deniskyashif.github.io

  3. None
  4. Scalability

  5. Scalability •Dynamic Requirements

  6. Scalability •Dynamic Requirements •Increasing load

  7. Scalability •Dynamic Requirements •Increasing load •Growing complexity

  8. Scalability •Dynamic Requirements •Increasing load •Growing complexity •Developer turnover /

    Increasing team size
  9. Software Design

  10. Source: https://martinfowler.com/bliki/DesignStaminaHypothesis.html The Design Stamina Hypothesis

  11. None
  12. None
  13. End Goal •Modular application •Predictable state management •Async data flow

    handling •Increased load capacity •Easy to test
  14. None
  15. None
  16. class Queue<T> { constructor(private data: T[] = []) { }

    enqueue(item: T) { return this.data.push(item); } dequeue = () : T => this.data.shift() } const n: number = 5; const queue = new Queue<number>(); queue.enqueue(n); queue.enqueue('5'); // Error TypeScript
  17. class Queue<T> { constructor(private data: T[] = []) { }

    enqueue(item: T) { return this.data.push(item); } dequeue = () : T => this.data.shift() } const n: number = 5; const queue = new Queue<number>(); queue.enqueue(n); queue.enqueue('5'); // Error TypeScript
  18. class Queue<T> { constructor(private data: T[] = []) { }

    enqueue(item: T) { return this.data.push(item); } dequeue = () : T => this.data.shift() } const n: number = 5; const queue = new Queue<number>(); queue.enqueue(n); queue.enqueue('5'); // Error TypeScript
  19. class Queue<T> { constructor(private data: T[] = []) { }

    enqueue(item: T) { return this.data.push(item); } dequeue = () : T => this.data.shift(); } const n: number = 5; const queue = new Queue<number>(); queue.enqueue(n); queue.enqueue('5'); // Error TypeScript
  20. None
  21. A set of libraries for composing asynchronous and event-based programs

    using observable sequences.
  22. const numbers = Observable.create(observer => { observer.next(1); observer.next(2); observer.next(3); setTimeout(()

    => { observer.next(4); // happens asynchronously }, 1000); }); numberEmitter.subscribe(console.log); // 1, 2, 3, ... 4 Observables
  23. const numbers = Observable.create(observer => { observer.next(1); observer.next(2); observer.next(3); setTimeout(()

    => { observer.next(4); // happens asynchronously }, 1000); }); numberEmitter.subscribe(console.log); // 1, 2, 3, ... 4 Observables
  24. const numbers = Observable.create(observer => { observer.next(1); observer.next(2); observer.next(3); setTimeout(()

    => { observer.next(4); // happens asynchronously }, 1000); }); numberEmitter.subscribe(console.log); // 1, 2, 3, ... 4 Observables
  25. Operators const squaresOfEvenNumbers = interval(10) .pipe( filter(x => x %

    2 === 0), map(x => x * x) ); squaresOfEvenNumbers.subscribe(console.log); // Output 0, 4, 16, 36...
  26. Operators const squaresOfEvenNumbers = interval(10) .pipe( filter(x => x %

    2 === 0), map(x => x * x) ); squaresOfEvenNumbers.subscribe(console.log); // Output 0, 4, 16, 36...
  27. Operators const squaresOfEvenNumbers = interval(10) .pipe( filter(x => x %

    2 === 0), map(x => x * x) ); squaresOfEvenNumbers.subscribe(console.log); // Output 0, 4, 16, 36...
  28. Operators const squaresOfEvenNumbers = interval(10) .pipe( filter(x => x %

    2 === 0), map(x => x * x) ); squaresOfEvenNumbers.subscribe(console.log); // Output 0, 4, 16, 36...
  29. Observable Promise Emits multiple values over a period of time

    Emits a single value at a time Can be lazy Not lazy Can be cancelled Cannot be cancelled
  30. Sample Application

  31. Source: git.io/fpqkN

  32. Sample Architecture

  33. UI Components Facade State Management Data Access

  34. UI Components Facade State Management Data Access

  35. Container • Passes data to the presentational component • Handles

    events raised by presentational components • Interacts with the business layer Presentational • Takes data only as an @Input() • Delegates the event handling to the container via @Output() Components
  36. Container Component Facade Presentational Component @Output() @Input() Components Actions Data

  37. Source: git.io/fpqkN

  38. Course Board Course List Course 1 Course 2 Course Details

  39. course-details.component.ts export class CourseDetailsComponent { @Input() course: Course; @Output() delete

    = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  40. course-details.component.ts export class CourseDetailsComponent { @Input() course: Course; @Output() delete

    = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  41. course-board.component.ts export class CourseBoardComponent implements OnInit { courses$: Observable<Course[]>; selectedCourse$:

    Observable<Course>; constructor(private courses: CoursesStateService) { } ngOnInit() { this.courses.load(); this.courses$ = this.courses.getCourseList(); this.selectedCourse$ = this.courses.getSelectedCourse(); } delete(course: Course) { this.courses.delete(course); } ... }
  42. course-board.component.html ... <div> <app-course-list [courses]="courses$ | async" ...></app-course-list> </div> <div>

    <app-course-details [course]="selectedCourse$ | async" (delete)="delete($event)"> </app-course-details> </div> ...
  43. course-board.component.html ... <div> <app-course-list [courses]="courses$ | async" ...></app-course-list> </div> <div>

    <app-course-details [course]="selectedCourse$ | async" (delete)="delete($event)"> </app-course-details> </div> ...
  44. UI Components Facade State Management Data Access

  45. Facade A facade is an object that serves as a

    front-facing interface masking more complex underlying or structural code.
  46. Client Classes Subsystem Classes

  47. Client Classes Subsystem Classes Facade

  48. courses.state.service.ts @Injectable({ providedIn: 'root' }) export class CoursesStateService { constructor(private

    store: Store<fromCourses.State>) { } load() { this.store.dispatch(new CourseActions.Load()); } getCourseList(): Observable<Course[]> { return this.store.pipe(select(fromCourses.getCourseList)); } getSelectedCourse(): any { return this.store.pipe(select(fromCourses.getSelectedCourse)); } create(course: Course) { this.store.dispatch(new CourseActions.Create(course)); } ... }
  49. courses.state.service.ts @Injectable({ providedIn: 'root' }) export class CoursesStateService { constructor(private

    store: Store<fromCourses.State>) { } load() { this.store.dispatch(new CourseActions.Load()); } getCourseList(): Observable<Course[]> { return this.store.pipe(select(fromCourses.getCourseList)); } getSelectedCourse(): any { return this.store.pipe(select(fromCourses.getSelectedCourse)); } create(course: Course) { this.store.dispatch(new CourseActions.Create(course)); } ... }
  50. courses.state.service.ts @Injectable({ providedIn: 'root' }) export class CoursesStateService { constructor(private

    store: Store<fromCourses.State>) { } load() { this.store.dispatch(new CourseActions.Load()); } getCourseList(): Observable<Course[]> { return this.store.pipe(select(fromCourses.getCourseList)); } getSelectedCourse(): any { return this.store.pipe(select(fromCourses.getSelectedCourse)); } create(course: Course) { this.store.dispatch(new CourseActions.Create(course)); } ... }
  51. Predictable State Management

  52. UI Components Facade State Management Data Access

  53. Or how I stopped worrying and learned to love

  54. Source: https://css-tricks.com/learning-react-redux/

  55. Redux •Single Source of Truth •State is Read-Only •Changes are

    made with Pure Functions
  56. ngrx

  57. State Management Store Reducers Effects Selectors Actions

  58. Redux Terms State – an object that stores the state

    of the app Store – an immutable object that holds the state of the app View – a DOM representation of the state Action – an object describing an event. Used to send information to the store. Reducer – a pure function producing a new state out by given action and current state Selector – a function used to read from the store Effect – a middleware used to run side effects in isolation
  59. Component Action Creator Store Reducer dispatch(action) applyReducers(action, state) next(state) newState

  60. Component Action Creator Store Reducer dispatch(action) applyReducers(action, state) next(state) newState

  61. Component Action Creator Store Reducer dispatch(action) applyReducers(action, state) next(state) newState

  62. Component Action Creator Store Reducer dispatch(action) applyReducers(action, state) next(state) newState

  63. Component Action Creator Store Reducer dispatch(action) applyReducers(action, state) next(state) newState

  64. Component State Service (Facade) Store Reducer select(course) dispatch(new SelectAction(course))) applyReducers(action,

    state) next(state) New State
  65. courses.state.ts export interface CoursesState { readonly courseList: Course[]; readonly selectedCourse?:

    Course; }
  66. course.actions.ts export enum CourseActionTypes { Load = '[COURSE] Load', Create

    = '[COURSE] Create' ... } export class Load implements Action { readonly type = CourseActionTypes.Load; constructor(public payload: string = '') { } } export class Create implements Action { readonly type = CourseActionTypes.Create; constructor(public payload: Course) { } } ...
  67. course.actions.ts export enum CourseActionTypes { Load = '[COURSE] Load', Create

    = '[COURSE] Create' ... } export class Load implements Action { readonly type = CourseActionTypes.Load; constructor(public payload: string = '') { } } export class Create implements Action { readonly type = CourseActionTypes.Create; constructor(public payload: Course) { } } ...
  68. courses.reducer.ts ... export function coursesReducer( state: CoursesState = initialState, action:

    CourseActionsUnion): CoursesState { switch (action.type) { case CourseActionTypes.LoadSuccess: return { ...state, courseList: action.payload } case CourseActionTypes.CreateSuccess: return { ...state, courseList: [...state.courseList, action.payload] }; case CourseActionTypes.Select: return { ...state, selectedCourse: action.payload }; default: return state; } }
  69. courses.reducer.ts ... export function coursesReducer( state: CoursesState = initialState, action:

    CourseActionsUnion): CoursesState { switch (action.type) { case CourseActionTypes.LoadSuccess: return { ...state, courseList: action.payload } case CourseActionTypes.CreateSuccess: return { ...state, courseList: [...state.courseList, action.payload] }; case CourseActionTypes.Select: return { ...state, selectedCourse: action.payload }; default: return state; } }
  70. courses.reducer.ts ... export function coursesReducer( state: CoursesState = initialState, action:

    CourseActionsUnion): CoursesState { switch (action.type) { case CourseActionTypes.LoadSuccess: return { ...state, courseList: action.payload } case CourseActionTypes.CreateSuccess: return { ...state, courseList: [...state.courseList, action.payload] }; case CourseActionTypes.Select: return { ...state, selectedCourse: action.payload }; default: return state; } }
  71. course-board.component.ts export class CourseBoardComponent implements OnInit { ... selectedCourse$: Observable<Course>;

    constructor(private courses: CoursesStateService) { } ngOnInit() { ... this.selectedCourse$ = this.courses.getSelectedCourse(); } ... }
  72. course-board.component.ts export class CourseBoardComponent implements OnInit { ... selectedCourse$: Observable<Course>;

    constructor(private courses: CoursesStateService) { } ngOnInit() { ... this.selectedCourse$ = this.courses.getSelectedCourse(); } ... }
  73. course-board.component.html ... <div> <app-course-details [course]="selectedCourse$ | async"> </app-course-details> </div> ...

  74. course-board.component.html ... <div> <app-course-details [course]="selectedCourse$ | async"> </app-course-details> </div> ...

  75. Managing Side Effects

  76. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  77. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  78. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  79. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  80. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  81. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  82. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  83. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  84. Component State Service (Facade) Store Reducer login(credentials) dispatch(new LoginAction(credentials)) applyReducers(action,

    state) next(state) New State Effect Auth API login(credentials) onSuccess(res) dispatch(new LoginSuccessAction(user))
  85. @Injectable() export class AuthEffects { @Effect() login$ = this.actions$.pipe( ofType<Login>(AuthActionTypes.Login),

    map(action => action.payload), exhaustMap((auth: Auth) => this.authApi.login(auth).pipe( map(user => new LoginSuccess({ username: auth.username }))) )); constructor(private actions$: Actions, private authApi: AuthApiService) { } } auth.effects.ts
  86. @Injectable() export class AuthEffects { @Effect() login$ = this.actions$.pipe( ofType<Login>(AuthActionTypes.Login),

    map(action => action.payload), exhaustMap((auth: Auth) => this.authApi.login(auth).pipe( map(user => new LoginSuccess({ username: auth.username }))) )); constructor(private actions$: Actions, private authApi: AuthApiService) { } } auth.effects.ts
  87. @Injectable() export class AuthEffects { @Effect() login$ = this.actions$.pipe( ofType<Login>(AuthActionTypes.Login),

    map(action => action.payload), exhaustMap((auth: Auth) => this.authApi.login(auth).pipe( map(user => new LoginSuccess({ username: auth.username }))) )); constructor(private actions$: Actions, private authApi: AuthApiService) { } } auth.effects.ts
  88. @Injectable() export class AuthEffects { @Effect() login$ = this.actions$.pipe( ofType<Login>(AuthActionTypes.Login),

    map(action => action.payload), exhaustMap((auth: Auth) => this.authApi.login(auth).pipe( map(user => new LoginSuccess({ username: auth.username }))) )); constructor(private actions$: Actions, private authApi: AuthApiService) { } } auth.effects.ts
  89. auth.reducer.ts export function authReducer(state: AuthState = initialState, action: AuthActionsUnion) {

    switch (action.type) { case AuthActionTypes.Login: return { ...state, loginPending: true, errorMessage: '' }; case AuthActionTypes.LoginSuccess: return { isLoggedIn: true, currentUser: action.payload, loginPending: false, errorMessage: '' }; ... }
  90. UI Components Facade State Management Data Access

  91. HTTP WebRTC WebSockets JSON XML Text Payloads Gateways Data Access

  92. courses.api.service.ts @Injectable({ providedIn: 'root' }) export class CoursesApiService { private

    baseUrl = '/api/courses'; constructor(private http: HttpClient) { } get() : Observable<Course[]> { return this.http.get<Course[]>(this.baseUrl); } getById(id: string) : Observable<Course> { return this.http.get<Course>(`this.baseUrl/${id}`); } }
  93. courses.api.service.ts @Injectable({ providedIn: 'root' }) export class CoursesApiService { private

    baseUrl = '/api/courses'; constructor(private http: HttpClient) { } get() : Observable<Course[]> { return this.http.get<Course[]>(this.baseUrl); } getById(id: string) : Observable<Course> { return this.http.get<Course>(`this.baseUrl/${id}`); } }
  94. Redux DevTools

  95. Change Detection

  96. By default the change detection goes through every node of

    the component tree to see if it changed, and it does it on every browser event. Change Detection Rerefence: https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c
  97. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  98. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  99. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  100. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  101. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  102. Immutable Objects If a component depends only on its input

    properties, and they are immutable, then this component can change if and only if one of its input properties changes. Rerefence: https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c
  103. course-details.component.ts @Component({ ... changeDetection: ChangeDetectionStrategy.OnPush }) export class CourseDetailsComponent {

    @Input() course: Course; @Output() delete = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  104. course-details.component.ts @Component({ ... changeDetection: ChangeDetectionStrategy.OnPush }) export class CourseDetailsComponent {

    @Input() course: Course; @Output() delete = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  105. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  106. App_ChangeDetector CourseBoard_ChangeDetector CourseList_ChangeDetector CourseDetails_ChangeDetector

  107. Modules

  108. Core Module App Module Feature A Module Feature B Module

    Shared Module
  109. Lazy Loading

  110. Core Module App Module Feature A Module Feature B Module

    Feature C Module Shared Module Shared Module App Injector Lazy Module Injector
  111. Properties • Modular project (Feature modules and lazy loading)

  112. Properties • Modular project (Feature modules and lazy loading) •

    Predictable state management (Redux)
  113. Properties • Modular project (Feature modules and lazy loading) •

    Predictable state management (Redux) • Increased load capacity (immutability, detect changes on push)
  114. Properties • Modular project (Feature modules and lazy loading) •

    Predictable state management (Redux) • Increased load capacity (immutability, detect changes on push) • Async data flow handling (RxJS)
  115. Properties • Modular project (Feature modules and lazy loading) •

    Predictable state management (Redux) • Increased load capacity (immutability, detect changes on push) • Async data flow handling (RxJS) • Easy to test (DI and easy to mock service layer)
  116. Sample Tech Stack •TypeScript •RxJS •ngrx/store •ngrx/effects •ImmutableJS

  117. This does not replace common sense!

  118. None
  119. Reims Cathedral, France

  120. It is better to have a system omit certain anomalous

    features and improvements, but to reflect one set of design ideas, than to have one that contains many good but independent and uncoordinated ideas. Frederick P. Brooks, “The Mythical Man-Month” Conceptual Integrity
  121. References • Redux Docs https://redux.js.org/introduction • @ngrx docs https://ngrx.github.io/ •

    Managing State in Angular Applications https://blog.nrwl.io/managing-state-in-angular-applications-22b75ef5625f • Change Detection in Angular https://vsavkin.com/change-detection-in-angular-2-4f216b855d4c • On Push Change Detection and Immutability https://blog.mgechev.com/2017/11/11/faster-angular-applications-onpush-change-detection-immutable- part-1/ • Making Architecture Matter by Martin Fowler https://www.youtube.com/watch?v=DngAZyWMGR0
  122. @deniskyashif deniskyashif deniskyashif.github.io Thank You!