A library for dynamically loading angular modules and components anywhere in the DOM with support for Inputs and Outputs.
npm install @trellisorg/rx-dynamic-componentA library for dynamically loading angular modules and components anywhere in the DOM with support for Inputs and
Outputs.
Working example: check apps/rx-dynamic-host
to run demo: yarn nx serve rx-dynamic-host and navigate to http://localhost:4200
> Note: If you are using Angular <14 you will need to install v0.1.9, if using Angular ^14 any version will work.
#### With a Module and DYNAMIC_COMPONENT token
``typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { QueryParam1Component } from './query-param1.component';
import { DYNAMIC_COMPONENT } from '@trellisorg/rx-dynamic-component';
@NgModule({
declarations: [QueryParam1Component],
exports: [QueryParam1Component],
imports: [CommonModule],
providers: [
{
provide: DYNAMIC_COMPONENT,
useValue: QueryParam1Component,
},
],
})
export class QueryParam1Module {}
`
The above code was generated using yarn nx g (m|c) query-param1.
Note the DYNAMIC_COMPONENT injection token that provides the Component that will be dynamically loaded into the DOM.rx-dynamic-component
This is required otherwise
is not able to resolve and know what to render
#### With a standalone component
`typescript`
@Component({
standalone: true,
//...
})
export class StandaloneComponent {}
The above code was generated using yarn nx g c query-param1 --standalone.
`typescript
import { DynamicComponentManifest } from './rx-dynamic-component.manifest';
const manifests: DynamicComponentManifest[] = [
// Using dynamic import and module + token
{
componentId: 'query1',
loadChildren: () => import('./query-param1/query-param1.module').then((m) => m.QueryParam1Module),
},
// Using dynamic import and standalone component
{
componentId: 'standalone',
loadComponent: () => import('./standalone/standalone.component').then((m) => m.StandaloneComponent),
},
// Using direct module reference
{
componentId: 'query1',
loadChildren: (m) => m.QueryParam1Module,
},
];
`
`typescript
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { RxDynamicComponentModule } from '@trellisorg/rx-dynamic-component';
import { RouterModule } from '@angular/router';
import { provideRxDynamicComponent } from './rx-dynamic-component.providers';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, RouterModule.forRoot([])],
providers: [
// Note: This used to be RxDynamicComponentModule.forRoot() imported into the imports array`
provideRxDynamicComponent({
manifests: [
// manifests
],
}),
],
bootstrap: [AppComponent],
})
export class AppModule {}
You can enable devMode to have console.warn's show up in the console of your application. By default, it is false.
There is also a provideRxDynamicComponentManifests() function that can be used in feature modules to registerprovideRxDynamicComponent()
manifests in other places as long as has been called.
`typescript
import { Component, ComponentFactory } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { RxDynamicComponentService } from '@trellisorg/rx-dynamic-component';
import { filter, switchMap } from 'rxjs/operators';
@Component({
selector: 'trellisorg-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent {
queryParamComponent$ = this._route.queryParams.pipe(
filter((params) => !!params['query']),
switchMap((params) => this.rxDynamicComponentService.getComponent(params['query']))
);
constructor(private _route: ActivatedRoute, private rxDynamicComponentService: RxDynamicComponentService) {}
}
`
This will set up an observable that listens on query params and loads the correct component factory. In our demo case it
is either query1 or query2. The value you pass into RxDynamicComponentService#getComponentFactorycomponentId
must equal one of the s from the manifest you provided.
rx-dynamic-directive provides a directive that can be used that is set up for ease of use, but if you know what you
are doing you can implement whatever sort of outlet you want to render the component.
`typescript`
@NgModule({
declarations: [AppComponent],
imports: [
// other imports
RxDynamicDirective,
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
`angular2html`
When the query params query property is equal to one of the manifest entries the corresponding Angular
component will be loaded into the DOM.
You can also load using the manifestId directly instead of a loaded component from a manifest from an observable.
`angular2html
$3
This library provides two decorators:
@DynamicInput() - Will pass inputs down into the dynamically rendered component
@DynamicOutput() - Will emit outputs up from the dynamically rendered componentThis requires a bit of boilerplate and an adapter directive to support.
`typescript
import { Directive, EventEmitter, Input, Output } from '@angular/core';
import { DynamicInput, DynamicOutput } from '@trellisorg/rx-dynamic-component';@Directive({
standalone: true,
selector: '[inputOutputAdapter]',
})
export class StandaloneAdapterDirective implements OnDestroy {
@DynamicInput() @Input() myInput: string | null = '';
@DynamicOutput() @Output() myOutput = new EventEmitter();
}
`#### Usage
`angular2html (myOutput)='myOutputCalled()'
inputOutputAdapter
myInput='Hello World'
load='query1'>
This will allow you to pass inputs in and listen on outputs for your dynamically rendered component, if your component
changes (the
load input changes) then the outputs will be unsubscribed from and will resubscribe to the new outputs on
the newly rendered dynamic component. Inputs will also be set if that input exists on the rendered component.$3
rx-dynamic-component provides the ability to preload manifests.Manifest Configuration:
`typescript
export interface DynamicComponentManifest {
preload?: boolean;
priority?: DynamicManifestPreloadPriority;
timeout?: number;
cacheFactories?: boolean;
componentId: T;
// Required if loadComponent is omitted
loadChildren: LoadChildrenCallback;
// Required if loadChildren is omitted
loadComponent: LoadComponentCallback;
}
`We have already seen
componentId and loadChildren above in the first couple of steps.Each of the following properties can be configured globally (in
forRoot()) or at the manifest level. Global values are
used if the manifest does not have a value set for that property, and the manifest level properties override global
properties.#### Preloading
We can configure these manifests to be preloaded so that when we go to use them in our application the asset bundles
have already been downloaded to the browser.
preload - Whether this manifest should be preloaded or notpriority - Manifests can either be preloaded immediately or when the browser is idling as to not block the main
thread. Values will either be 'idle' or 'immediate'. priority will only be used if preload: truetimeout - The timeout to configure for window.requestIdleCallback that will preload the manifest in the background
when using DynamicManifestPreloadPriority.IDLE
Reference: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback, timeout is ignored
if preload: false or priority: 'immediate'$3
The
RxDynamicComponentService exposes a method that can be called to force a preload for a manifest.loadManifest(manifest: DynamicComponentManifest)`