Bridging Parallel Universes: Upgrading from AngularJS with Angular Elements (AngularMix)

7beed3a6fa39e12c9e873b903e4d9244?s=47 Sam Julien
November 21, 2019

Bridging Parallel Universes: Upgrading from AngularJS with Angular Elements (AngularMix)

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

November 21, 2019
Tweet

Transcript

  1. Bridging Parallel Universes Upgrading with Angular Elements

  2. Upgrading is hard.

  3. Choosing the right path is critical.

  4. Let’s get through it together.

  5. Sam Julien @samjulien @samjulien

  6. Sam Julien @samjulien Developer Advocate Engineer at Auth0 @samjulien

  7. Sam Julien @samjulien Developer Advocate Engineer at Auth0 GDE &

    Angular Collaborator @samjulien
  8. Sam Julien @samjulien Developer Advocate Engineer at Auth0 GDE &

    Angular Collaborator UpgradingAngularJS.com, Thinkster, Egghead @samjulien
  9. @samjulien

  10. The Four Paths @samjulien

  11. The Four Paths Rewrite @samjulien

  12. The Four Paths ngUpgrade Rewrite @samjulien

  13. The Four Paths Hybrid Routing ngUpgrade Rewrite @samjulien

  14. The Four Paths Hybrid Routing Angular Elements ngUpgrade Rewrite @samjulien

  15. Angular Elements @samjulien

  16. What are Angular Elements? @samjulien

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

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

  19. Why not ngUpgrade?

  20. Where ngUpgrade Works @samjulien

  21. Where ngUpgrade Works Small to Medium Apps @samjulien

  22. Where ngUpgrade Works Small to Medium Apps Low Complexity @samjulien

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

    AngularJS @samjulien
  24. But… @samjulien

  25. Tightly Coupled @samjulien

  26. @samjulien Dependency Injection

  27. @samjulien Dependency Injection Change Detection

  28. @samjulien Complex UIs Testing ☠

  29. Angular Elements @samjulien

  30. Angular Elements Large Apps @samjulien

  31. Angular Elements Large Apps High Complexity @samjulien

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

  33. The Elements Upgrade Strategy @samjulien

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

    vanilla JS services with wrappers Convert routing and remove Elements @samjulien
  35. None
  36. None
  37. Setting Up Angular Elements

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

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

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

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

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

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

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

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

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

    Project AngularJS Project bundle-ce.js Monorepo
  47. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project bundle-ce.js Monorepo
  48. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project bundle-ce.js polyfills.js Monorepo
  49. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

    Project AngularJS Project bundle-ce.js polyfills.js Monorepo
  50. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

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

  52. ng add @angular/elements @samjulien

  53. ngx-build-plus @samjulien

  54. @samjulien

  55. ng add ngx-build-plus @samjulien

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

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

  58. ⚠ Polyfills @samjulien

  59. Upgrading Components

  60. @samjulien

  61. @samjulien

  62. @samjulien

  63. customers customers-table customerService @samjulien

  64. customers customers-table-ce customerService @samjulien

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  79. AngularJS 1.7+ Helpers @samjulien

  80. ng-custom-element @samjulien

  81. Inputs

  82. ng-prop-* @samjulien

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

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

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

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

  87. How do get data out?

  88. Outputs

  89. @samjulien

  90. @samjulien

  91. @samjulien

  92. @samjulien

  93. @samjulien

  94. customer-detail discount @samjulien

  95. customer-detail discount-ce @samjulien

  96. 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
  97. 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
  98. 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
  99. 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
  100. ng-on-* @samjulien

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

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

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

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

  105. How do we register multiple elements?

  106. 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
  107. 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
  108. 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
  109. 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
  110. 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
  111. 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
  112. 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
  113. Tip: Group into Modules @samjulien

  114. Sharing Data with Services

  115. Here be dragons @samjulien

  116. @samjulien

  117. @samjulien

  118. Wrap Shared JS Services @samjulien

  119. Vanilla JS Service

  120. Vanilla JS Service AngularJS Wrapper Angular Wrapper

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  143. Inputs & Outputs @samjulien

  144. Indirection @samjulien

  145. - / @samjulien

  146. NgRx & Redux @samjulien

  147. Angular Elements @samjulien

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

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

  150. NgRx Large Apps High Complexity @samjulien

  151. Wrapping Up the Upgrade

  152. Add Component Selectors @samjulien

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

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

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

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

  157. Drop Elements @samjulien

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

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

    vanilla JS services with wrappers Convert routing and remove Elements @samjulien
  160. 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
  161. samj.im/elements-upgrade @samjulien

  162. samj.im/elements-upgrade Thank you! @samjulien