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

Modernizing Large Frontends with Web Components

Sam Julien
January 31, 2020

Modernizing Large Frontends with Web Components

If you’re an enterprise software developer, you and your team probably struggle to know how or when to migrate their legacy code to current frameworks. Should you burn it all to the ground and start over (who has the time for that?) or let it sit forever, getting more outdated each day? Is there a third way?

Web Components help bridge that gap between legacy and modern code without having to go to either extreme. By creating custom reusable elements, teams are able to add a layer of indirection between the business logic and view layer of applications. This indirection lets legacy code and shiny, new frameworks work together in harmony.

In this talk, you’ll learn the current Web Components landscape and how to evaluate whether to use them for large legacy frontend migrations. You’ll also learn how to overcome architectural challenges of large scale refactoring by looking at a common use of Web Components: using Angular Elements to iteratively migrate AngularJS (1.x) to Angular (2+).

Sam Julien

January 31, 2020
Tweet

More Decks by Sam Julien

Other Decks in Technology

Transcript

  1. Sam Julien @samjulien Senior Developer Advocate Engineer at Auth0 GDE

    & Angular Collaborator UpgradingAngularJS.com, Thinkster, Egghead
  2. Frontend Philosophy Web Components AngularJS to Angular @samjulien View from

    the plane View from the parachute View from the grocery store
  3. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  4. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  5. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  6. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  7. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  8. connectedCallback() { console.log('Custom element added to page.'); } disconnectedCallback() {

    console.log('Custom element removed from page.'); } adoptedCallback() { console.log('Custom element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { console.log('Custom element attributes changed.'); } @samjulien
  9. connectedCallback() { console.log('Custom element added to page.'); } disconnectedCallback() {

    console.log('Custom element removed from page.'); } adoptedCallback() { console.log('Custom element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { console.log('Custom element attributes changed.'); } @samjulien
  10. connectedCallback() { console.log('Custom element added to page.'); } disconnectedCallback() {

    console.log('Custom element removed from page.'); } adoptedCallback() { console.log('Custom element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { console.log('Custom element attributes changed.'); } @samjulien
  11. connectedCallback() { console.log('Custom element added to page.'); } disconnectedCallback() {

    console.log('Custom element removed from page.'); } adoptedCallback() { console.log('Custom element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { console.log('Custom element attributes changed.'); } @samjulien
  12. connectedCallback() { console.log('Custom element added to page.'); } disconnectedCallback() {

    console.log('Custom element removed from page.'); } adoptedCallback() { console.log('Custom element moved to new page.'); } attributeChangedCallback(name, oldValue, newValue) { console.log('Custom element attributes changed.'); } @samjulien
  13. import { LitElement, html, property, customElement } from 'lit-element'; @customElement('hello-conf')

    export class HelloConf extends LitElement { @property() name = ‘Friends'; render() { return html`<p>Hello, ${this.name}!</p>`; } } <hello-conf name=“NDC London”></hello-conf> @samjulien
  14. import { LitElement, html, property, customElement } from 'lit-element'; @customElement('hello-conf')

    export class HelloConf extends LitElement { @property() name = ‘Friends'; render() { return html`<p>Hello, ${this.name}!</p>`; } } <hello-conf name=“NDC London”></hello-conf> @samjulien
  15. import { LitElement, html, property, customElement } from 'lit-element'; @customElement('hello-conf')

    export class HelloConf extends LitElement { @property() name = ‘Friends'; render() { return html`<p>Hello, ${this.name}!</p>`; } } <hello-conf name=“NDC London”></hello-conf> @samjulien
  16. import { LitElement, html, property, customElement } from 'lit-element'; @customElement('hello-conf')

    export class HelloConf extends LitElement { @property() name = ‘Friends'; render() { return html`<p>Hello, ${this.name}!</p>`; } } <hello-conf name=“NDC London”></hello-conf> @samjulien
  17. import { LitElement, html, property, customElement } from 'lit-element'; @customElement('hello-conf')

    export class HelloConf extends LitElement { @property() name = ‘Friends'; render() { return html`<p>Hello, ${this.name}!</p>`; } } <hello-conf name=“NDC London”></hello-conf> @samjulien
  18. The Elements Upgrade Strategy Set Up ⬆ Bottom-up components Create

    vanilla JS services with wrappers Convert routing and remove Elements @samjulien
  19. Custom Element index.html Custom Element Custom Element Polyfills Angular CLI

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

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

    Project AngularJS Project bundle-ce.js polyfills.js Monorepo
  22. @Component({ template: ` <table> // ... </table> ` }) export

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

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

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

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  26. export class AppModule { constructor(private injector: Injector) {} ngDoBootstrap() {

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

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

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

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

    const el = createCustomElement(CustomersTableComponent, { injector: this.injector }); customElements.define('customers-table-ce', el); } } @samjulien
  31. @Component({ template: ` <table> // ... </table> ` }) export

    class CustomersTableComponent { @Input() customers; constructor() {} } @samjulien
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. let lazySharedInstance: CustomerService | undefined; export class CustomerService { static

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

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

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

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

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

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

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

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

    CustomerService; constructor() { this.customerService = CustomerService.sharedInstance(); } } @samjulien
  52. @samjulien @Component({ // selector: ‘customers-table', template: ` <table> // ...

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

    </table> ` }) export class CustomersTableComponent {}
  54. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  55. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  56. "Web Components is a suite of different technologies allowing you

    to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.” - MDN Web Docs @samjulien
  57. The Elements Upgrade Strategy Set Up ⬆ Bottom-up components Create

    vanilla JS services with wrappers Convert routing and remove Elements @samjulien
  58. 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