Ivy's hidden features / Ivy's best kept secret

Ivy's hidden features / Ivy's best kept secret

Component and directive features will enable new architectural patterns for Angular applications. The features option gives us reusable logic without inheritance or classes so that we can mix multiple traits into our components and directives.

Presented at:
- NG Poland conference, November 2019
- Angular Warsaw's Angular Online Event #3, May 2020

Companion GitHub repository:
https://github.com/LayZeeDK/ngx-ivy-features

Article:
https://indepth.dev/component-features-with-angular-ivy/

4f349dbe1d48627445735f7e2c818c97?s=128

Lars Gyrup Brink Nielsen

November 21, 2019
Tweet

Transcript

  1. 4.

    Ivy feature types • Directive features • Component features •

    Host features for bootstrapped components IVY'S BEST KEPT SECRET
  2. 5.

    Username feature Add username observable to component import { ɵComponentDef,

    ɵɵdirectiveInject } from '@angular/core'; import { select, Store } from '@ngrx/store'; export function withUsername() { return (componentDef: ɵComponentDef<any>) => { const originalFactory = componentDef.factory; componentDef.factory = () => { const component = originalFactory(componentDef.type); const store = ɵɵdirectiveInject(Store); component.username$ = store.pipe(select('username’)); return component; }; }; } IVY'S BEST KEPT SECRET
  3. 6.

    Username feature Add username observable to component import { ɵComponentDef,

    ɵɵdirectiveInject } from '@angular/core'; import { select, Store } from '@ngrx/store'; export function withUsername() { return (componentDef: ɵComponentDef<any>) => { const originalFactory = componentDef.factory; componentDef.factory = () => { const component = originalFactory(componentDef.type); const store = ɵɵdirectiveInject(Store); component.username$ = store.pipe(select('username')); return component; }; }; } IVY'S BEST KEPT SECRET
  4. 7.

    Username feature Add username observable to component @Component({ features: [

    withUsername(), ], selector: 'app-profile', templateUrl: './profile.component.html', }) export class ProfileComponent { username$: Observable<string>; } IVY'S BEST KEPT SECRET
  5. 8.

    Component features today Features are supported by the Ivy runtime

    Not yet available as an option for the Component and Directive decorator factories MyComponent.ɵcmp = ɵɵdefineComponent({ features: [myFeature], }); IVY'S BEST KEPT SECRET
  6. 9.

    Component features today Features are supported by the Ivy runtime

    Not yet available as an option for the Component and Directive decorator factories MyComponent.ɵcmp = ɵɵdefineComponent({ features: [myFeature], }); export function componentFeatures(features) { return ({ ɵcmp }) => { // At runtime, before bootstrap Promise.resolve().then(() => { // Add features to component definition Object.assign(ɵcmp, { features }); // Apply features features.forEach(feature => feature(ɵcmp)); }); }; } IVY'S BEST KEPT SECRET
  7. 10.

    Component features today Features are supported by the Ivy runtime

    Not yet available as an option for the Component and Directive decorator factories @Component({ selector: 'app-profile', templateUrl: './profile.component.html', }) @componentFeatures([ withUsername(), ]) export class ProfileComponent { username$: Observable<string>; } IVY'S BEST KEPT SECRET
  8. 11.

    Host features Enable lifecycle hooks for bootstrapped components import {

    ɵLifecycleHooksFeature, ɵrenderComponent, } from '@angular/core'; ɵrenderComponent(AppComponent, { hostFeatures: [ ɵLifecycleHooksFeature, ], }); IVY'S BEST KEPT SECRET
  9. 12.

    Internal Ivy features Ivy applies these internal features to directives

    and components: ɵɵNgOnChangesFeature ɵɵProvidersFeature ɵɵInheritDefinitionFeature ɵɵCopyDefinitionFeature IVY'S BEST KEPT SECRET
  10. 14.

    Observe state from store Mixed component code smells: ❎ UI

    depends on state ❎ UI has querying details ❎ UI has a container object, @Component({ selector: 'app-todo-list', template: ` <ul> <li *ngFor="let todo of todos$ | async"> {{todo.description}} </li> </ul> `, }) export class TodoListComponent { todos$: Observable<Todos> = this.store.pipe(select('todos')); constructor(private store: Store<State>) {} } IVY'S BEST KEPT SECRET container not the actual data structures
  11. 15.

    Observe state from store Presentational component with component feature ☑️

    UI has no dependencies ❎ UI has querying details ❎ UI has a container object, @Component({ features: [ fromStore({ todos$: 'todos' }), ], selector: 'ivy-todo-list', template: ` <ul> <li *ngFor="let todo of todos$ | async"> {{todo.description}} </li> </ul> `, }) export class TodoListComponent { todos$: Observable<Todos>; } IVY'S BEST KEPT SECRET not the actual data structures
  12. 16.

    Observe state from store Presentational component with component feature ☑️

    UI has no dependencies ☑️ UI is passed data ❎ UI has a container object, @Component({ features: [ todosFromStore('todos$'), ], selector: 'ivy-todo-list', template: ` <ul> <li *ngFor="let todo of todos$ | async"> {{todo.description}} </li> </ul> `, }) export class TodoListComponent { todos$: Observable<Todos>; } IVY'S BEST KEPT SECRET not the actual data structures
  13. 17.

    Observe state from store Presentational component with simple data structure

    @Component({ selector: '[ivyTodoItem]', template: ` {{todo.description}} `, }) export class TodoItemComponent { @Input('ivyTodoItem') todo: Todo; } IVY'S BEST KEPT SECRET
  14. 18.

    Observe state from store Presentational component with component feature ☑️

    UI has no dependencies ☑️ UI is passed data ☑️ UI has the actual data @Component({ features: [ todosFromStore('todos$'), ], selector: 'ivy-todo-list', template: ` <ul> <li *ngFor="let todo of todos$ | async" [ivyTodoItem]="todo"></li> </ul> `, }) export class TodoListComponent { todos$: Observable<Todos>; } IVY'S BEST KEPT SECRET structures
  15. 19.

    Observe state from store Presentational component with component feature ❎

    List UI has a container @Component({ features: [ todosFromStore('todos$'), ], selector: 'ivy-todo-list', template: ` <ul> <li *ngFor="let todo of todos$ | async" [ivyTodoItem]="todo"></li> </ul> `, }) export class TodoListComponent { todos$: Observable<Todos>; } IVY'S BEST KEPT SECRET object, not the actual data structures
  16. 20.

    Observe state from store Presentational component with component feature and

    simple data structures ☑️ List UI has the actual data @Component({ features: [ withTodos(), ], selector: 'ivy-todo-list', template: ` <ul> <li *ngFor="let todo of todos" [ivyTodoItem]="todo"></li> </ul> `, }) export class TodoListComponent { @Input() todos: Todos; } IVY'S BEST KEPT SECRET structures
  17. 22.

    Dispatch actions to store Mixed component code smells: ❎ UI

    depends on state ❎ UI creates actions ❎ UI dispatches actions @Component({ selector: 'app-add-todo-form', template: ` <input #description> <button (click)="onAdd(description.value)"> Add </button> `, }) export class AddTodoFormComponent { constructor(private store: Store<State>) {} onAdd(description: string) { this.store.dispatch(addTodo(description)); } } IVY'S BEST KEPT SECRET container
  18. 23.

    Dispatch actions to store Presentational component emitting primitive value @Component({

    selector: 'app-add-todo-form-ui', template: ` <input #description> <button (click)="add.emit(description.value)"> Add </button> `, }) export class AddTodoFormComponent { @Output() add = new EventEmitter<string>(); } IVY'S BEST KEPT SECRET
  19. 24.

    Dispatch actions to store Container component creating and dispatching actions

    ☑️ UI has no dependencies ☑️ UI emits simple data ☑️ UI doesn’t dispatch actions import { addTodo } from './todo.actions'; @Component({ selector: 'app-add-todo-form', template: ` <app-add-todo-form-ui (add)="onAddTodo($event)"></app-add-todo-form-ui> `, }) export class AddTodoFormContainerComponent { constructor(private store: Store<State>) {} onAddTodo(description: string) { this.store.dispatch(addTodo(description)); } } IVY'S BEST KEPT SECRET structures
  20. 25.

    Dispatch actions to store Container component creating and dispatching actions

    ❎ Container component import { addTodo } from './todo.actions'; @Component({ selector: 'app-add-todo-form', template: ` <app-add-todo-form-ui (add)="onAddTodo($event)"></app-add-todo-form-ui> `, }) export class AddTodoFormContainerComponent { constructor(private store: Store<State>) {} onAddTodo(description: string) { this.store.dispatch(addTodo(description)); } } IVY'S BEST KEPT SECRET exists only to translate component-specific events to actions
  21. 26.

    Dispatch actions to store Presentational component with component feature ☑️

    UI has no dependencies ☑️ UI emits simple data ☑️ UI doesn’t dispatch actions import { addTodo } from './todo.actions'; @Component({ features: [ toStore({ add: addTodo }), ], selector: ‘ivy-todo-form', template: ` <input #description> <button (click)="add.emit(description.value)"> Add </button> `, }) export class TodoFormComponent { @Output() add = new EventEmitter<string>(); } IVY'S BEST KEPT SECRET structures
  22. 27.

    Dispatch actions to store Presentational component with component feature ❎

    UI knows about the action import { addTodo } from './todo.actions'; @Component({ features: [ toStore({ add: addTodo }), ], selector: 'ivy-todo-form', template: ` <input #description> <button (click)="add.emit(description.value)"> Add </button> `, }) export class TodoFormComponent { @Output() add = new EventEmitter<string>(); } IVY'S BEST KEPT SECRET creator
  23. 28.

    Dispatch actions to store Presentational component with component feature ☑️

    UI only emits events @Component({ features: [ toAddTodoAction('add'), ], selector: 'ivy-todo-form', template: ` <input #description> <button (click)="add.emit(description.value)"> Add </button> `, }) export class TodoFormComponent { @Output() add = new EventEmitter<string>(); } IVY'S BEST KEPT SECRET
  24. 30.

    Use cases for features • Route parameters, route data, query

    parameters • Replace container components • Local store for local UI state • Observable lifecycle events • Convert observables to event emitters • Advanced (requires working with Ivy instructions): Observable UI events (click, keypress, and so on) • Manage subscriptions and call markDirty or detectChanges IVY'S BEST KEPT SECRET
  25. 31.

    Higher-order components with Ivy component features • No inheritance •

    No class decorators • No property decorators • No extra package dependencies • No custom WebPack configuration • Component features are tree-shakable IVY'S BEST KEPT SECRET
  26. 32.

    Feature limitations • Features are not public in the Angular

    API yet • Feature declarations can’t vary at runtime • Only one feature declaration list per component or directive IVY'S BEST KEPT SECRET
  27. 33.

    Get in touch IVY'S BEST KEPT SECRET bit.do/ivy-features-slides bit.do/ivy-features github.com/LayZeeDK/ngx-ivy-features

    github.com/LayZeeDK twitter.com/LayZeeDK www.linkedin.com/in/larsgbn/ larsbrinknielsen@gmail.com