Slide 1

Slide 1 text

Ivy’s best kept secret

Slide 2

Slide 2 text

”ivy features” on Google Search IVY'S BEST KEPT SECRET

Slide 3

Slide 3 text

Ivy features Overview IVY'S BEST KEPT SECRET

Slide 4

Slide 4 text

Ivy feature types • Directive features • Component features • Host features for bootstrapped components IVY'S BEST KEPT SECRET

Slide 5

Slide 5 text

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) => { 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

Slide 6

Slide 6 text

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) => { 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

Slide 7

Slide 7 text

Username feature Add username observable to component @Component({ features: [ withUsername(), ], selector: 'app-profile', templateUrl: './profile.component.html', }) export class ProfileComponent { username$: Observable; } IVY'S BEST KEPT SECRET

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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; } IVY'S BEST KEPT SECRET

Slide 11

Slide 11 text

Host features Enable lifecycle hooks for bootstrapped components import { ɵLifecycleHooksFeature, ɵrenderComponent, } from '@angular/core'; ɵrenderComponent(AppComponent, { hostFeatures: [ ɵLifecycleHooksFeature, ], }); IVY'S BEST KEPT SECRET

Slide 12

Slide 12 text

Internal Ivy features Ivy applies these internal features to directives and components: ɵɵNgOnChangesFeature ɵɵProvidersFeature ɵɵInheritDefinitionFeature ɵɵCopyDefinitionFeature IVY'S BEST KEPT SECRET

Slide 13

Slide 13 text

Observe state from store Case study IVY'S BEST KEPT SECRET

Slide 14

Slide 14 text

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: `
  • {{todo.description}}
`, }) export class TodoListComponent { todos$: Observable = this.store.pipe(select('todos')); constructor(private store: Store) {} } IVY'S BEST KEPT SECRET container not the actual data structures

Slide 15

Slide 15 text

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: `
  • {{todo.description}}
`, }) export class TodoListComponent { todos$: Observable; } IVY'S BEST KEPT SECRET not the actual data structures

Slide 16

Slide 16 text

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: `
  • {{todo.description}}
`, }) export class TodoListComponent { todos$: Observable; } IVY'S BEST KEPT SECRET not the actual data structures

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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: `
`, }) export class TodoListComponent { todos$: Observable; } IVY'S BEST KEPT SECRET structures

Slide 19

Slide 19 text

Observe state from store Presentational component with component feature ❎ List UI has a container @Component({ features: [ todosFromStore('todos$'), ], selector: 'ivy-todo-list', template: `
`, }) export class TodoListComponent { todos$: Observable; } IVY'S BEST KEPT SECRET object, not the actual data structures

Slide 20

Slide 20 text

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: `
`, }) export class TodoListComponent { @Input() todos: Todos; } IVY'S BEST KEPT SECRET structures

Slide 21

Slide 21 text

Dispatch actions to store Case study IVY'S BEST KEPT SECRET

Slide 22

Slide 22 text

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: ` Add `, }) export class AddTodoFormComponent { constructor(private store: Store) {} onAdd(description: string) { this.store.dispatch(addTodo(description)); } } IVY'S BEST KEPT SECRET container

Slide 23

Slide 23 text

Dispatch actions to store Presentational component emitting primitive value @Component({ selector: 'app-add-todo-form-ui', template: ` Add `, }) export class AddTodoFormComponent { @Output() add = new EventEmitter(); } IVY'S BEST KEPT SECRET

Slide 24

Slide 24 text

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: ` `, }) export class AddTodoFormContainerComponent { constructor(private store: Store) {} onAddTodo(description: string) { this.store.dispatch(addTodo(description)); } } IVY'S BEST KEPT SECRET structures

Slide 25

Slide 25 text

Dispatch actions to store Container component creating and dispatching actions ❎ Container component import { addTodo } from './todo.actions'; @Component({ selector: 'app-add-todo-form', template: ` `, }) export class AddTodoFormContainerComponent { constructor(private store: Store) {} onAddTodo(description: string) { this.store.dispatch(addTodo(description)); } } IVY'S BEST KEPT SECRET exists only to translate component-specific events to actions

Slide 26

Slide 26 text

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: ` Add `, }) export class TodoFormComponent { @Output() add = new EventEmitter(); } IVY'S BEST KEPT SECRET structures

Slide 27

Slide 27 text

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: ` Add `, }) export class TodoFormComponent { @Output() add = new EventEmitter(); } IVY'S BEST KEPT SECRET creator

Slide 28

Slide 28 text

Dispatch actions to store Presentational component with component feature ☑️ UI only emits events @Component({ features: [ toAddTodoAction('add'), ], selector: 'ivy-todo-form', template: ` Add `, }) export class TodoFormComponent { @Output() add = new EventEmitter(); } IVY'S BEST KEPT SECRET

Slide 29

Slide 29 text

Conclusion IVY'S BEST KEPT SECRET

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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/ [email protected]