0.3.7 • Published 6 months ago

@trellisorg/rx-dynamic-component v0.3.7

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

@trellisorg/rx-dynamic-component

A 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.

Adding to your application:

Define your modules and components to be lazy loaded

With a Module and DYNAMIC_COMPONENT token

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. This is required otherwise rx-dynamic-component is not able to resolve and know what to render

With a standalone component

@Component({
    standalone: true,
    //...
})
export class StandaloneComponent {}

The above code was generated using yarn nx g c query-param1 --standalone.

Define your dynamic component manifests

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,
    },
];

Provide into your root AppModule

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 register manifests in other places as long as provideRxDynamicComponent() has been called.

Set up an observable to trigger the creation of a ComponentFactory

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#getComponentFactory must equal one of the componentIds from the manifest you provided.

Import RxDynamicDirective into the component you will be dynamically loading into

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.

@NgModule({
    declarations: [AppComponent],
    imports: [
        // other imports
        RxDynamicDirective,
    ],
    providers: [],
    bootstrap: [AppComponent],
})
export class AppModule {}
<!--Will load the outlet as soon as the observable emits-->
<div rxDynamic [load]='queryParamComponent$ | async'></div>

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.

<div rxDynamic
     load='query1'>
</div>

Support for Inputs and Outputs

This library provides two decorators:

@DynamicInput() - Will pass inputs down into the dynamically rendered component @DynamicOutput() - Will emit outputs up from the dynamically rendered component

This requires a bit of boilerplate and an adapter directive to support.

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<string>();
}

Usage

<div rxDynamic
     (myOutput)='myOutputCalled()'
     inputOutputAdapter
     myInput='Hello World'
     load='query1'>
</div>

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.

(Optional) Advanced manifest configuration

rx-dynamic-component provides the ability to preload manifests.

Manifest Configuration:

export interface DynamicComponentManifest<T = string> {
    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 not

priority - 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: true

timeout - 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'

(Optional) Manually preload manifests

The RxDynamicComponentService exposes a method that can be called to force a preload for a manifest.

loadManifest(manifest: DynamicComponentManifest)

0.3.7

6 months ago

0.3.6

1 year ago

0.3.5

2 years ago

0.3.4

2 years ago

0.0.0-watch

2 years ago

0.3.0

2 years ago

0.2.7

2 years ago

0.2.6

2 years ago

0.2.9

2 years ago

0.2.8

2 years ago

0.1.9

2 years ago

0.3.2

2 years ago

0.2.3

2 years ago

0.3.1

2 years ago

0.2.2

2 years ago

0.2.5

2 years ago

0.3.3

2 years ago

0.2.4

2 years ago

0.2.0-rc.2

2 years ago

0.2.0-rc.1

2 years ago

0.2.1

2 years ago

0.2.0

2 years ago

0.1.8

2 years ago

0.1.7

3 years ago

0.1.4

3 years ago

0.1.6

3 years ago

0.1.5

3 years ago

0.1.3

3 years ago

0.1.0

3 years ago

0.1.2

3 years ago

0.1.1

3 years ago

0.0.10

3 years ago

0.0.11

3 years ago

0.0.112

3 years ago

0.0.9

3 years ago

0.0.8

3 years ago

0.0.7

3 years ago

0.0.5

3 years ago

0.0.4

3 years ago

0.0.3

3 years ago

0.0.2

3 years ago

0.0.1

3 years ago