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

Bridging Parallel Universes: Upgrading with Angular Elements (Angular Denver 2019)

7beed3a6fa39e12c9e873b903e4d9244?s=47 Sam Julien
August 01, 2019

Bridging Parallel Universes: Upgrading with Angular Elements (Angular Denver 2019)

How do you jump between the AngularJS universe and the Angular universe without tearing the fabric of reality?

If you’ve got a giant AngularJS application, you’ve probably been stumped (and frustrated!) by how to get it moved to Angular. ngUpgrade doesn’t seem to be a good option for you and you roll your eyes at anyone who suggests rewriting this behemoth from scratch. What are you supposed to do?

Luckily, there’s a new kid on the block that’s here to help: Angular Elements. Angular Elements lets you use tiny Angular apps as reusable custom web elements, which happens to be an excellent strategy for migrating big applications from AngularJS.

Until now, though, there hasn’t been much real-world content on HOW to actually do this. Sam Julien, Mad Upgrade Scientist, is here to help!

In this talk, you’ll learn how to plan your migration with Angular Elements, how to migrate components and services, and tips on how to bundle your custom elements.

You’ll leave this talk feeling like the master of the upgrade multiverse!

7beed3a6fa39e12c9e873b903e4d9244?s=128

Sam Julien

August 01, 2019
Tweet

Transcript

  1. Bridging Parallel Universes Upgrading with Angular Elements

  2. Upgrading is hard.

  3. Choosing the right path is critical.

  4. Sam Julien @samjulien Technical Community Manager at Auth0 GDE &

    Angular Collaborator UpgradingAngularJS.com @samjulien
  5. The Four Paths Hybrid Routing Angular Elements ngUpgrade Rewrite @samjulien

  6. Angular Elements @samjulien

  7. What are Angular Elements? @samjulien

  8. What are Angular Elements? Custom Web Components Tiny Angular Apps

    “Fill in the Gaps” of Angular use cases @samjulien
  9. Why Angular Elements?

  10. Why not ngUpgrade?

  11. Where ngUpgrade Works Small to Medium Apps Low Complexity Modern

    AngularJS @samjulien
  12. But… @samjulien

  13. Tightly Coupled @samjulien

  14. @samjulien Dependency Injection

  15. @samjulien Dependency Injection Change Detection

  16. @samjulien Complex UIs Testing ☠

  17. Angular Elements Large Apps Legacy Patterns High Complexity @samjulien

  18. The Elements Upgrade Strategy @samjulien

  19. The Elements Upgrade Strategy Set Up ⬆ Bottom-up components Create

    vanilla JS services with wrappers Convert routing and remove Elements @samjulien
  20. None
  21. None
  22. Setting Up Angular Elements

  23. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project Monorepo
  24. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project Monorepo
  25. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project Monorepo
  26. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project Monorepo
  27. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project bundle-ce.js polyfills.js Monorepo
  28. Add Angular Elements @samjulien

  29. ng add @angular/elements @samjulien

  30. ngx-build-plus @samjulien

  31. @samjulien

  32. ng add ngx-build-plus @samjulien

  33. ng build --prod --single-bundle @samjulien

  34. cpr dist/custom-el ../legacy/dist/custom-el -d @samjulien

  35. ⚠ Polyfills @samjulien

  36. Upgrading Components

  37. @samjulien

  38. @samjulien

  39. @samjulien

  40. customers customers-table customerService @samjulien

  41. customers customers-table-ce customerService @samjulien

  42. @Component({ template: ` <table> // ... </table> ` }) export

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  43. @Component({ template: ` <table> // ... </table> ` }) export

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  44. @Component({ template: ` <table> // ... </table> ` }) export

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  45. @Component({ template: ` <table> // ... </table> ` }) export

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  46. Cool, but how do we use it?

  47. @NgModule({ entryComponents: [CustomersTableComponent], declarations: [CustomersTableComponent] // ...Providers, etc. }) @samjulien

  48. @NgModule({ entryComponents: [CustomersTableComponent], declarations: [CustomersTableComponent] // ...Providers, etc. }) @samjulien

  49. @NgModule({ entryComponents: [CustomersTableComponent], declarations: [CustomersTableComponent] // ...Providers, etc. }) @samjulien

  50. export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap() {

    const el = createCustomElement(CustomersTableComponent, { injector: this.injector }); customElements.define('customers-table-ce', el); } } @samjulien
  51. export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap() {

    const el = createCustomElement(CustomersTableComponent, { injector: this.injector }); customElements.define('customers-table-ce', el); } } @samjulien
  52. export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap() {

    const el = createCustomElement(CustomersTableComponent, { injector: this.injector }); customElements.define('customers-table-ce', el); } } @samjulien
  53. export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap() {

    const el = createCustomElement(CustomersTableComponent, { injector: this.injector }); customElements.define('customers-table-ce', el); } } @samjulien
  54. export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap() {

    const el = createCustomElement(CustomersTableComponent, { injector: this.injector }); customElements.define('customers-table-ce', el); } } @samjulien
  55. How do we pass data?

  56. AngularJS 1.7+ Helpers @samjulien

  57. ng-custom-element @samjulien

  58. Inputs

  59. ng-prop-* @samjulien

  60. @Component({ template: ` <table> // ... </table> ` }) export

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  61. <customers-table-ce ng-prop-customers=“$ctrl.customers”> </customers-table-ce> @samjulien

  62. <customers-table-ce ng-prop-customers=“$ctrl.customers”> </customers-table-ce> @samjulien

  63. <customers-table-ce ng-prop-customers=“$ctrl.customers”> </customers-table-ce> @samjulien

  64. How do get data out?

  65. Outputs

  66. @samjulien

  67. @samjulien

  68. @samjulien

  69. @samjulien

  70. customer-detail discount @samjulien

  71. customer-detail discount-ce @samjulien

  72. export class DiscountComponent { @Input() customerDiscount: any; @Output() updateDiscount =

    new EventEmitter(); selectedDiscount: any; // ...other code updateDiscountType() { this.updateDiscount.emit({ discount: this.selectedDiscount }); this.editDiscount = false; } } @samjulien
  73. export class DiscountComponent { @Input() customerDiscount: any; @Output() updateDiscount =

    new EventEmitter(); selectedDiscount: any; // ...other code updateDiscountType() { this.updateDiscount.emit({ discount: this.selectedDiscount }); this.editDiscount = false; } } @samjulien
  74. export class DiscountComponent { @Input() customerDiscount: any; @Output() updateDiscount =

    new EventEmitter(); selectedDiscount: any; // ...other code updateDiscountType() { this.updateDiscount.emit({ discount: this.selectedDiscount }); this.editDiscount = false; } } @samjulien
  75. export class DiscountComponent { @Input() customerDiscount: any; @Output() updateDiscount =

    new EventEmitter(); selectedDiscount: any; // ...other code updateDiscountType() { this.updateDiscount.emit({ discount: this.selectedDiscount }); this.editDiscount = false; } } @samjulien
  76. ng-on-* @samjulien

  77. <discount-ce ng-if="$ctrl.customer.getsDiscount" ng-prop-customer_discount="$ctrl.customer.discount" ng-on-update_discount="$ctrl.updateDiscount($event)" ></discount-ce> @samjulien

  78. <discount-ce ng-if="$ctrl.customer.getsDiscount" ng-prop-customer_discount="$ctrl.customer.discount" ng-on-update_discount="$ctrl.updateDiscount($event)" ></discount-ce> @samjulien

  79. <discount-ce ng-if="$ctrl.customer.getsDiscount" ng-prop-customer_discount="$ctrl.customer.discount" ng-on-update_discount="$ctrl.updateDiscount($event)" ></discount-ce> @samjulien

  80. <discount-ce ng-if="$ctrl.customer.getsDiscount" ng-prop-customer_discount="$ctrl.customer.discount" ng-on-update_discount="$ctrl.updateDiscount($event)" ></discount-ce> @samjulien

  81. How do we register multiple elements?

  82. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  83. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  84. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  85. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  86. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  87. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  88. const elements: any[] = [ [CustomersTableComponent, 'customers-table-ce'], [DiscountComponent, 'discount-ce'] ];

    for (const [component, name] of elements) { const el = createCustomElement(component, { injector: this.injector }); customElements.define(name, el); } @samjulien
  89. Tip: Group into Modules @samjulien

  90. Sharing Data with Services

  91. Here be dragons @samjulien

  92. @samjulien

  93. Wrap Shared JS Services @samjulien

  94. Vanilla JS Service

  95. Vanilla JS Service AngularJS Wrapper Angular Wrapper

  96. let lazySharedInstance: CustomerService | undefined; export class CustomerService { static

    sharedInstance() { if (!lazySharedInstance) { lazySharedInstance = new CustomerService(); } return lazySharedInstance; } } @samjulien
  97. let lazySharedInstance: CustomerService | undefined; export class CustomerService { static

    sharedInstance() { if (!lazySharedInstance) { lazySharedInstance = new CustomerService(); } return lazySharedInstance; } } @samjulien
  98. let lazySharedInstance: CustomerService | undefined; export class CustomerService { static

    sharedInstance() { if (!lazySharedInstance) { lazySharedInstance = new CustomerService(); } return lazySharedInstance; } } @samjulien
  99. let lazySharedInstance: CustomerService | undefined; export class CustomerService { static

    sharedInstance() { if (!lazySharedInstance) { lazySharedInstance = new CustomerService(); } return lazySharedInstance; } } @samjulien
  100. let lazySharedInstance: CustomerService | undefined; export class CustomerService { static

    sharedInstance() { if (!lazySharedInstance) { lazySharedInstance = new CustomerService(); } return lazySharedInstance; } } @samjulien
  101. @Injectable({ providedIn: 'root' }) export class AngularCustomerService { readonly customerService:

    CustomerService; constructor() { this.customerService = CustomerService.sharedInstance(); } } @samjulien
  102. @Injectable({ providedIn: 'root' }) export class AngularCustomerService { readonly customerService:

    CustomerService; constructor() { this.customerService = CustomerService.sharedInstance(); } } @samjulien
  103. @Injectable({ providedIn: 'root' }) export class AngularCustomerService { readonly customerService:

    CustomerService; constructor() { this.customerService = CustomerService.sharedInstance(); } } @samjulien
  104. @Injectable({ providedIn: 'root' }) export class AngularCustomerService { readonly customerService:

    CustomerService; constructor() { this.customerService = CustomerService.sharedInstance(); } } @samjulien
  105. export class AngularJSCustomerService { readonly customerService: CustomerService; constructor() { this.customerService

    = CustomerService.sharedInstance(); } } @samjulien
  106. export class AngularJSCustomerService { readonly customerService: CustomerService; constructor() { this.customerService

    = CustomerService.sharedInstance(); } } @samjulien
  107. export class AngularJSCustomerService { readonly customerService: CustomerService; constructor() { this.customerService

    = CustomerService.sharedInstance(); } } @samjulien
  108. customerService.getCustomers() @samjulien

  109. getCustomers(){ return from( this.customerService.getCustomers() ); } @samjulien

  110. getCustomers(){ return from( this.customerService.getCustomers() ); } @samjulien

  111. getCustomers(){ return from( this.customerService.getCustomers() ); } @samjulien

  112. getCustomers(){ return this.customerService.getCustomers() .then(() => { $rootScope.apply(); }); } @samjulien

  113. getCustomers(){ return this.customerService.getCustomers() .then(() => { $rootScope.apply(); }); } @samjulien

  114. getCustomers(){ return this.customerService.getCustomers() .then(() => { $rootScope.apply(); }); } @samjulien

  115. getCustomers(){ return this.customerService.getCustomers() .then(() => { $rootScope.apply(); }); } @samjulien

  116. Use inputs and outputs as much as possible. @samjulien

  117. Inputs & Outputs @samjulien

  118. Indirection @samjulien

  119. - / @samjulien

  120. NgRx & Redux @samjulien

  121. Angular Elements Large Apps Legacy Patterns High Complexity @samjulien

  122. Angular Elements Large Apps Legacy Patterns High Complexity @samjulien

  123. NgRx Large Apps High Complexity @samjulien

  124. Wrapping Up the Upgrade

  125. Add Component Selectors @samjulien

  126. @samjulien @Component({ // selector: 'customers-table' template: ` <table> // ...

    </table> ` }) export class CustomersTableComponent {}
  127. @samjulien @Component({ // selector: 'customers-table' template: ` <table> // ...

    </table> ` }) export class CustomersTableComponent {}
  128. @samjulien @Component({ selector: 'customers-table' template: ` <table> // ... </table>

    ` }) export class CustomersTableComponent {}
  129. Switch Routing @samjulien

  130. Drop Elements @samjulien

  131. Drop Elements Clean up modules Uninstall Elements and tools Remove

    unused polyfills @samjulien
  132. The Elements Upgrade Strategy Set Up ⬆ Bottom-up components Create

    vanilla JS services with wrappers Convert routing and remove Elements @samjulien
  133. Where to Go from Here Erin Coughlan: • AngularConnect 2018

    Talk • Devoxx Belgium 2018 Talk • create-ng1-wrapper Juri Strumpflohner • Egghead Course Manfred Steyer • Upgrading with Elements • Elements Series • A Deep Look at Elements • Beyond the Basics @samjulien
  134. samj.im/angular-denver @samjulien

  135. samj.im/angular-denver Thank you! @samjulien