question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Support for dynamic Angular Elements / Webcomponents (with Renderers)

See original GitHub issue

🚀 feature request

Relevant Package

The functionality relates to the @angular/platform-browser package as this is the package which implements DomRendererFactory2 and DefaultDomRenderer2

Description

Angular Elements and Webcomponents in general represent great solution for various real world use cases like…

  • sub-applications - ability to deploy library independently which will update all consumers without need to rebuild these consumer SPAs because lib is only referenced by url (as Angular Element bundle)
  • microfrontends ( runtime-configurable )

Usual approach to consuming Angular Elements and Web components leaves us with 2 options:

  1. eager loading + standard Angular template binding <some-element [prop]="value">
  2. lazy loading + imperative component creation and props / event binding document.createElement('some-element');

The library @angular-extensions/elements solves this by enabling both lazy loading and standard Angular template binding <some-element *axLazyElement="bundle.js" [prop]="value"> and this works with both ViewEngine and IVY…

Dynamic element use case

In previous section we’re committing to element being <some-element> by hard-coding it into template of the consumer Angular component. What if we wanted to support loading element configuration at runtime (eg from backend) to enable fully dynamic microfrontends?

As it turns out this works too (at least for the ViewEngine) so it is possible to write <ax-lazy-element *axLazyElementDynamic="'some-element'; url: 'bundle.js'" [prop]="value"></ax-lazy-element>. The bundle will be lazy loaded, and then the element will be rendered as <some-element>. The only thing needed to do that is to override value of the tagName in the TemplateRef of the *axLazyElemendDynamic directive and Angular will render desired element supporting our use case.

Now this does NOT work with IVY since in IVY template is an function. Rob @robwormald gave me hint that it should be possible to override Renderer (or use custom) to hook into rendering and override element there.

The Issue

It is possible to achieve that with render BUT:

  • currently both DomRendererFactory2 and DefaultDomRenderer2 are private so it is NOT possible to extend them and hence I would need to re-implement the whole renderer which sounds pretty excessive and hard to maintain
  • PLUS as far as I am aware the render API changed older NRWL article so there is no more RootRenderer ?

Current solution

Play around with live demo (which serves both lib and demo and rebuilds on changes to any of them) using this one liner git clone https://github.com/angular-extensions/elements.git elements && cd elements && npm ci && npm start once it runs, please navigate to Examples > Dynamic to see it in action.

@Directive({
  selector: '[axLazyElementDynamic]'
})
export class LazyElementDynamicDirective implements OnInit {
  @Input('axLazyElementDynamic') tag: string;
  @Input('axLazyElementDynamicUrl') url: string;

  constructor(
    @Inject(DOCUMENT) private document: Document,
    private renderer: Renderer2,
    private cdr: ChangeDetectorRef,
    private template: TemplateRef<any>,
    private elementsLoaderService: LazyElementsLoaderService
  ) {}

  ngOnInit() {
    this.elementsLoaderService
      .loadElement(this.url)
      .then(() => {
        this.vcr.clear();
        const originalCreateElement = this.renderer.createElement;
        this.renderer.createElement = (name: string, namespace: string) => { // temporary override 
          if (name === 'ax-lazy-element') {
            name = this.tag;
          }
          return this.document.createElement(name);
        };
        this.vcr.createEmbeddedView(this.template);
        this.renderer.createElement = originalCreateElement;
        this.cdr.markForCheck();
      })
  }
}

Describe the solution you’d like

Probably being able to easily extend default renderer and being able to register it like we’re used to with interceptors or control value accessors.

I would like to give people option (or do it for them behind the scenes) to do

@NgModule({
   import: [LazyElementsModule],
  providers: [{ provide: Renderer, useClass: LazyElementsRenderer }]
})
export class AppModule {}

export class LazyElementsRenderer  extends DefaultDomRenderer2  {
  
 constructor(...args) {
     this.super(...args);
  }

  createElement() {
      // override logic and change tag name if necessary
  }
}

Describe alternatives you’ve considered

I spent quite some time trying to override tag inside of template function without success

Issue Analytics

  • State:open
  • Created 4 years ago
  • Reactions:7
  • Comments:17 (13 by maintainers)

github_iconTop GitHub Comments

1reaction
angular-robot[bot]commented, Jun 25, 2021

Thank you for submitting your feature request! Looks like during the polling process it didn’t collect a sufficient number of votes to move to the next stage.

We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular’s scope, we’d encourage you to collaborate with the community on publishing it as an open source package.

You can find more details about the feature request process in our documentation.

1reaction
tomastrajancommented, Jun 4, 2021

For reference, here is the current impl which allows dynamically override rendered tag with IVY

        this.vcr.clear();

        const originalCreateElement = this.renderer.createElement;

        this.renderer.createElement = (name: string, namespace: string) => {
          if (name === 'ax-lazy-element') {
            name = this.tag;
          }
          return this.document.createElement(name);
        };

        this.viewRef = this.vcr.createEmbeddedView(this.template);
        this.renderer.createElement = originalCreateElement;
        this.cdr.markForCheck();

Read more comments on GitHub >

github_iconTop Results From Across the Web

Angular elements overview
Easy dynamic content in an Angular application, Transforming a component to a custom element provides a straightforward path to creating dynamic HTML content...
Read more >
Angular Elements, Part I - ANGULARarchitects
A dynamic dashboard in four steps with Web Components ... This blog post is part of an article series. ... Beginning with version...
Read more >
Web Components with Angular Elements: Beyond the Basics
In a snap Angular Elements provides Angular components as framework-independent web components.But it is only later that the really ...
Read more >
How to talk with Web Components in React and Angular
Web Components is a web standard for defining new HTML elements in a framework-agnostic way. Since Web Components build on web standards, it...
Read more >
Use Dynamic Components to render HTML for 3rd party libraries
Dynamic components in Angular are very powerful and help you solve the trickiest problems. In this example we're going to learn how we...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found