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

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

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!

Sam Julien

November 21, 2019
Tweet

More Decks by Sam Julien

Other Decks in Programming

Transcript

  1. Bridging Parallel Universes
    Upgrading with Angular Elements

    View Slide

  2. Upgrading is hard.

    View Slide

  3. Choosing the right path is critical.

    View Slide

  4. Let’s get through it together.

    View Slide

  5. Sam Julien
    @samjulien
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  8. Sam Julien
    @samjulien
    Developer Advocate Engineer at Auth0
    GDE & Angular Collaborator
    UpgradingAngularJS.com, Thinkster, Egghead
    @samjulien

    View Slide

  9. @samjulien

    View Slide

  10. The Four Paths
    @samjulien

    View Slide

  11. The Four Paths
    Rewrite
    @samjulien

    View Slide

  12. The Four Paths
    ngUpgrade
    Rewrite
    @samjulien

    View Slide

  13. The Four Paths
    Hybrid Routing
    ngUpgrade
    Rewrite
    @samjulien

    View Slide

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

    View Slide

  15. Angular Elements
    @samjulien

    View Slide

  16. What are Angular Elements?
    @samjulien

    View Slide

  17. What are Angular Elements?
    Custom Web Components
    Tiny Angular Apps
    “Fill in the Gaps” of Angular use cases
    @samjulien

    View Slide

  18. Why Angular Elements?

    View Slide

  19. Why not ngUpgrade?

    View Slide

  20. Where ngUpgrade Works
    @samjulien

    View Slide

  21. Where ngUpgrade Works
    Small to Medium Apps
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  24. But…
    @samjulien

    View Slide

  25. Tightly Coupled
    @samjulien

    View Slide

  26. @samjulien
    Dependency Injection

    View Slide

  27. @samjulien
    Dependency Injection
    Change Detection

    View Slide

  28. @samjulien
    Complex UIs
    Testing ☠

    View Slide

  29. Angular Elements
    @samjulien

    View Slide

  30. Angular Elements
    Large Apps
    @samjulien

    View Slide

  31. Angular Elements
    Large Apps
    High Complexity
    @samjulien

    View Slide

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

    View Slide

  33. The Elements Upgrade Strategy
    @samjulien

    View Slide

  34. The Elements Upgrade Strategy
    Set Up
    ⬆ Bottom-up components
    Create vanilla JS services with wrappers
    Convert routing and remove Elements
    @samjulien

    View Slide

  35. View Slide

  36. View Slide

  37. Setting Up Angular Elements

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  51. Add Angular Elements
    @samjulien

    View Slide

  52. ng add @angular/elements
    @samjulien

    View Slide

  53. ngx-build-plus
    @samjulien

    View Slide

  54. @samjulien

    View Slide

  55. ng add ngx-build-plus
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  58. ⚠ Polyfills
    @samjulien

    View Slide

  59. Upgrading Components

    View Slide

  60. @samjulien

    View Slide

  61. @samjulien

    View Slide

  62. @samjulien

    View Slide

  63. customers
    customers-table
    customerService
    @samjulien

    View Slide

  64. customers
    customers-table-ce
    customerService
    @samjulien

    View Slide

  65. @Component({
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {
    @Input() customers;
    constructor() {}
    }
    @samjulien

    View Slide

  66. @Component({
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {
    @Input() customers;
    constructor() {}
    }
    @samjulien

    View Slide

  67. @Component({
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {
    @Input() customers;
    constructor() {}
    }
    @samjulien

    View Slide

  68. @Component({
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {
    @Input() customers;
    constructor() {}
    }
    @samjulien

    View Slide

  69. Cool, but how do we use it?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  78. How do we pass data?

    View Slide

  79. AngularJS 1.7+ Helpers
    @samjulien

    View Slide

  80. ng-custom-element
    @samjulien

    View Slide

  81. Inputs

    View Slide

  82. ng-prop-*
    @samjulien

    View Slide

  83. @Component({
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {
    @Input() customers;
    constructor() {}
    }
    @samjulien

    View Slide

  84. ng-prop-customers=“$ctrl.customers”>

    @samjulien

    View Slide

  85. ng-prop-customers=“$ctrl.customers”>

    @samjulien

    View Slide

  86. ng-prop-customers=“$ctrl.customers”>

    @samjulien

    View Slide

  87. How do get data out?

    View Slide

  88. Outputs

    View Slide

  89. @samjulien

    View Slide

  90. @samjulien

    View Slide

  91. @samjulien

    View Slide

  92. @samjulien

    View Slide

  93. @samjulien

    View Slide

  94. customer-detail
    discount
    @samjulien

    View Slide

  95. customer-detail
    discount-ce
    @samjulien

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  100. ng-on-*
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. How do we register multiple elements?

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  113. Tip: Group into Modules
    @samjulien

    View Slide

  114. Sharing Data with Services

    View Slide

  115. Here be dragons
    @samjulien

    View Slide

  116. @samjulien

    View Slide

  117. @samjulien

    View Slide

  118. Wrap Shared JS Services
    @samjulien

    View Slide

  119. Vanilla JS Service

    View Slide

  120. Vanilla JS Service
    AngularJS Wrapper
    Angular Wrapper

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  133. customerService.getCustomers()
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  143. Inputs & Outputs
    @samjulien

    View Slide

  144. Indirection
    @samjulien

    View Slide

  145. -
    /
    @samjulien

    View Slide

  146. NgRx & Redux
    @samjulien

    View Slide

  147. Angular Elements
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  150. NgRx
    Large Apps
    High Complexity
    @samjulien

    View Slide

  151. Wrapping Up the Upgrade

    View Slide

  152. Add Component Selectors
    @samjulien

    View Slide

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

    // ...

    `
    })
    export class CustomersTableComponent {}

    View Slide

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

    // ...

    `
    })
    export class CustomersTableComponent {}

    View Slide

  155. @samjulien
    @Component({
    selector: ‘customers-table',
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {}

    View Slide

  156. Switch Routing
    @samjulien

    View Slide

  157. Drop Elements
    @samjulien

    View Slide

  158. Drop Elements
    Clean up modules
    Uninstall Elements and tools
    Remove unused polyfills
    @samjulien

    View Slide

  159. The Elements Upgrade Strategy
    Set Up
    ⬆ Bottom-up components
    Create vanilla JS services with wrappers
    Convert routing and remove Elements
    @samjulien

    View Slide

  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

    View Slide

  161. samj.im/elements-upgrade
    @samjulien

    View Slide

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

    View Slide