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:
- eager loading + standard Angular template binding
<some-element [prop]="value">
- 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
andDefaultDomRenderer2
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:
- Created 4 years ago
- Reactions:7
- Comments:17 (13 by maintainers)
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.
For reference, here is the current impl which allows dynamically override rendered tag with IVY