Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Scalable Angular Applications

Scalable Angular Applications

Denis Kyashif

November 13, 2018
Tweet

More Decks by Denis Kyashif

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. Operators const squaresOfEvenNumbers = interval(10) .pipe( filter(x => x %

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

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

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

    2 === 0), map(x => x * x) ); squaresOfEvenNumbers.subscribe(console.log); // Output 0, 4, 16, 36...
  12. 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
  13. 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
  14. course-details.component.ts export class CourseDetailsComponent { @Input() course: Course; @Output() delete

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

    = new EventEmitter<Course>(); constructor() { } onDeleteClick(course: Course) { this.delete.emit(course); } ... }
  16. 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); } ... }
  17. 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> ...
  18. 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> ...
  19. Facade A facade is an object that serves as a

    front-facing interface masking more complex underlying or structural code.
  20. 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)); } ... }
  21. 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)); } ... }
  22. 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)); } ... }
  23. 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
  24. 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) { } } ...
  25. 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) { } } ...
  26. 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; } }
  27. 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; } }
  28. 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; } }
  29. course-board.component.ts export class CourseBoardComponent implements OnInit { ... selectedCourse$: Observable<Course>;

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

    constructor(private courses: CoursesStateService) { } ngOnInit() { ... this.selectedCourse$ = this.courses.getSelectedCourse(); } ... }
  31. 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))
  32. 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))
  33. 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))
  34. 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))
  35. 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))
  36. 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))
  37. 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))
  38. 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))
  39. 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. @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
  41. @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
  42. @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
  43. @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
  44. 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: '' }; ... }
  45. 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}`); } }
  46. 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}`); } }
  47. 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
  48. 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
  49. 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); } ... }
  50. 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); } ... }
  51. Core Module App Module Feature A Module Feature B Module

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

    Predictable state management (Redux) • Increased load capacity (immutability, detect changes on push)
  53. 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)
  54. 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)
  55. 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
  56. 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