Scalable Angular Applications

Scalable Angular Applications

7a3f4cf57e03f53e7b37c8ef84338460?s=128

Denis Kyashif

November 13, 2018
Tweet

Transcript

  1. 3.
  2. 11.
  3. 12.
  4. 13.
  5. 14.
  6. 15.
  7. 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
  8. 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
  9. 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
  10. 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
  11. 20.
  12. 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
  13. 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
  14. 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
  15. 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...
  16. 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...
  17. 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...
  18. 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...
  19. 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
  20. 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
  21. 39.

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

    = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  22. 40.

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

    = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  23. 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); } ... }
  24. 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> ...
  25. 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> ...
  26. 45.

    Facade A facade is an object that serves as a

    front-facing interface masking more complex underlying or structural code.
  27. 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)); } ... }
  28. 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)); } ... }
  29. 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)); } ... }
  30. 56.
  31. 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
  32. 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) { } } ...
  33. 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) { } } ...
  34. 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; } }
  35. 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; } }
  36. 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; } }
  37. 71.

    course-board.component.ts export class CourseBoardComponent implements OnInit { ... selectedCourse$: Observable<Course>;

    constructor(private courses: CoursesStateService) { } ngOnInit() { ... this.selectedCourse$ = this.courses.getSelectedCourse(); } ... }
  38. 72.

    course-board.component.ts export class CourseBoardComponent implements OnInit { ... selectedCourse$: Observable<Course>;

    constructor(private courses: CoursesStateService) { } ngOnInit() { ... this.selectedCourse$ = this.courses.getSelectedCourse(); } ... }
  39. 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))
  40. 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))
  41. 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))
  42. 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))
  43. 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))
  44. 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))
  45. 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))
  46. 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))
  47. 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))
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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: '' }; ... }
  53. 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}`); } }
  54. 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}`); } }
  55. 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
  56. 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
  57. 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); } ... }
  58. 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); } ... }
  59. 107.
  60. 110.

    Core Module App Module Feature A Module Feature B Module

    Feature C Module Shared Module Shared Module App Injector Lazy Module Injector
  61. 113.

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

    Predictable state management (Redux) • Increased load capacity (immutability, detect changes on push)
  62. 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)
  63. 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)
  64. 118.
  65. 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
  66. 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