npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@trellisorg/rx-dynamic-component

v0.3.7

Published

A library for dynamically loading angular modules and components anywhere in the DOM with support for Inputs and Outputs.

Downloads

82

Readme

@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)