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

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

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!

Sam Julien

August 01, 2019
Tweet

More Decks by Sam Julien

Other Decks in Technology

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. Sam Julien
    @samjulien
    Technical Community Manager at Auth0
    GDE & Angular Collaborator
    UpgradingAngularJS.com
    @samjulien

    View Slide

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

    View Slide

  6. Angular Elements
    @samjulien

    View Slide

  7. What are Angular Elements?
    @samjulien

    View Slide

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

    View Slide

  9. Why Angular Elements?

    View Slide

  10. Why not ngUpgrade?

    View Slide

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

    View Slide

  12. But…
    @samjulien

    View Slide

  13. Tightly Coupled
    @samjulien

    View Slide

  14. @samjulien
    Dependency Injection

    View Slide

  15. @samjulien
    Dependency Injection
    Change Detection

    View Slide

  16. @samjulien
    Complex UIs
    Testing ☠

    View Slide

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

    View Slide

  18. The Elements Upgrade Strategy
    @samjulien

    View Slide

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

    View Slide

  20. View Slide

  21. View Slide

  22. Setting Up Angular Elements

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. Add Angular Elements
    @samjulien

    View Slide

  29. ng add @angular/elements
    @samjulien

    View Slide

  30. ngx-build-plus
    @samjulien

    View Slide

  31. @samjulien

    View Slide

  32. ng add ngx-build-plus
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  35. ⚠ Polyfills
    @samjulien

    View Slide

  36. Upgrading Components

    View Slide

  37. @samjulien

    View Slide

  38. @samjulien

    View Slide

  39. @samjulien

    View Slide

  40. customers
    customers-table
    customerService
    @samjulien

    View Slide

  41. customers
    customers-table-ce
    customerService
    @samjulien

    View Slide

  42. @Component({
    template: `

    // ...

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

    View Slide

  43. @Component({
    template: `

    // ...

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

    View Slide

  44. @Component({
    template: `

    // ...

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

    View Slide

  45. @Component({
    template: `

    // ...

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

    View Slide

  46. Cool, but how do we use it?

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. How do we pass data?

    View Slide

  56. AngularJS 1.7+ Helpers
    @samjulien

    View Slide

  57. ng-custom-element
    @samjulien

    View Slide

  58. Inputs

    View Slide

  59. ng-prop-*
    @samjulien

    View Slide

  60. @Component({
    template: `

    // ...

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

    View Slide

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

    @samjulien

    View Slide

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

    @samjulien

    View Slide

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

    @samjulien

    View Slide

  64. How do get data out?

    View Slide

  65. Outputs

    View Slide

  66. @samjulien

    View Slide

  67. @samjulien

    View Slide

  68. @samjulien

    View Slide

  69. @samjulien

    View Slide

  70. customer-detail
    discount
    @samjulien

    View Slide

  71. customer-detail
    discount-ce
    @samjulien

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  76. ng-on-*
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  81. How do we register multiple elements?

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  89. Tip: Group into Modules
    @samjulien

    View Slide

  90. Sharing Data with Services

    View Slide

  91. Here be dragons
    @samjulien

    View Slide

  92. @samjulien

    View Slide

  93. Wrap Shared JS Services
    @samjulien

    View Slide

  94. Vanilla JS Service

    View Slide

  95. Vanilla JS Service
    AngularJS Wrapper
    Angular Wrapper

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  108. customerService.getCustomers()
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  117. Inputs & Outputs
    @samjulien

    View Slide

  118. Indirection
    @samjulien

    View Slide

  119. -
    /
    @samjulien

    View Slide

  120. NgRx & Redux
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  123. NgRx
    Large Apps
    High Complexity
    @samjulien

    View Slide

  124. Wrapping Up the Upgrade

    View Slide

  125. Add Component Selectors
    @samjulien

    View Slide

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

    // ...

    `
    })
    export class CustomersTableComponent {}

    View Slide

  127. @samjulien
    @Component({
    // selector: 'customers-table'
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {}

    View Slide

  128. @samjulien
    @Component({
    selector: 'customers-table'
    template: `

    // ...

    `
    })
    export class CustomersTableComponent {}

    View Slide

  129. Switch Routing
    @samjulien

    View Slide

  130. Drop Elements
    @samjulien

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  134. samj.im/angular-denver
    @samjulien

    View Slide

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

    View Slide