@codethatstack/portals
TypeScript icon, indicating that this package has built-in type declarations

0.0.4 • Public • Published

Portals

Component Portals provides the ability to lazily load a NgModule and instantiate any component contained within any module without a direct reference to the component's underlying type.

See it in action here

Getting Started

Step 1: Install

npm install @codethatstack/portals --save

Step 2: Import CtsPortalModule

Import CtsPortalModule in the root application NgModule.

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, CtsPortalsModule],
  bootstrap: [AppComponent],
})
export class AppModule {}

Step 3: Register Portal Modules

Define a module identifier (moduleId) for each NgModule that will be lazy loaded. This uses same syntax as lazy loading child routes.

const PortalModules: ModuleLoaderDef[] = [
  {
    moduleId: 'Foo',
    load: () => import('./foo/foo.module').then(m => m.FooModule)
  },
  {
    moduleId: 'Bar',
    load: () => import('./bar/bar.module').then(m => m.BarModule)
  },
  {
    moduleId: 'Baz',
    load: () => import('./baz/baz.module').then(m => m.BazModule)
  }
];

Step 4: Register Portal Components

Define each component via a string value that will be lazy loaded, referencing the moduleId as above.

const PortalComponents: ComponentRegistryItem[] = [
  {
    componentId: 'FooComponent', // <- Component name as a string value
    moduleId: 'Foo' // <- References the Module declared in Step 3
  },
  {
    componentId: 'BarComponent',
    moduleId: 'Bar'
  },
  {
    componentId: 'BazComponent',
    moduleId: 'Baz'
  }
];

Step 5: Load Portal Configuration

Register both the Portal Modules and Portal Components using the Injection Tokens.

@NgModule({
  ...
  providers: [
    {
      provide: PORTAL_MODULE_TOKEN,
      useValue: PortalModules // <- Reference from Step 3
    },
    {
      provide: PORTAL_COMPONENTS_TOKEN,
      useValue: PortalComponents // <- Reference from Step 4
    }
  ],
  ...
})
export class AppModule {}

Step 6: Register ComponentType with Portal

Register the concrete Component Type to the Portal. The componentId needs to reference the string value provided when initializing Portals.

const BAR_COMPONENTS: ComponentRegistryItem[] = [
  {
    componentId: 'BarComponent', // <- Component identifier in Step 3
    componentType: BarComponent  // <- Concrete Component Type that will be instantiate given string value.
  }
];

@NgModule({
  imports: [],
  declarations: [BarComponent], // <- Concrete Component Type referenced above
  //entryComponents: [BarComponent] // <- required for pre-Ivy
})
export class BarModule {
  constructor(componentRegistry: ComponentPortalRegistry, moduleRef: NgModuleRef<BarModule>) {
    componentRegistry.register('BarModule', BAR_COMPONENTS, moduleRef); // <- The glue here
  }
}

Step 7: Use Component Portal

Create the component using the string value with direct reference to the Component Type.

  <ng-container
    [ctsComponentPortal]="'BarComponent'"
    (activated)="onActivated($event)"
    (deactivated)="onDeactivated($event)">
  </ng-container>

Can also pass in a context to the component, the same as passing context to Material Dialog.

  <ng-container
    [ctsComponentPortal]="'BarComponent'"
    [ctsComponentPortalContext]="{ myStringValue: 'Some value' }"
    (activated)="onActivated($event)"
    (deactivated)="onDeactivated($event)">
  </ng-container>

Reference the context using the injection token PORTAL_CONTEXT_DATA

export class BarComponent implements OnInit {

  constructor(
    @Optional() @Inject(PORTAL_CONTEXT_DATA) private context: any) { // <- ctsComponentPortalContext value

  }

Define a Portal Outlet

Portal Outlets provide the ability to attach a component portal anywhere in the DOM hierachy inside the application.

Register a portal outlet using the directive ctsPortalOutlet.

  <div ctsPortalOutlet="drawer"></div> <!-- Name the portal outlet ->

The Input property ctsComponentPortalAttachTo defines the portal the component will be attached to. By default the component will attached to the current ViewContainerRef.

  <ng-container
    [ctsComponentPortal]="'BarComponent'"
    [ctsComponentPortalContext]="{ myStringValue: 'Some value' }"
    [ctsComponentPortalAttachTo]="'drawer'"
    (activated)="onActivated($event)"
    (deactivated)="onDeactivated($event)">
  </ng-container>

Eager loading of NgModule

Eagerly load the NgModule on hover of some element to reduce lazy component loading time.

  <div #fooLazy="ctsPortalModule"
    [ctsPortalModule]="'Foo'"
    (loaded)="onModuleLoaded('Foo')"
    [class.loaded]="fooLazy.isLoaded">
    Hover over to Load FooModule
  </div>

Version 8 and prior support

If the version of Angular does not include the usage of the import('./bar/bar.module').then(m => m.BarModule) follow these steps:

Register Component:

const PortalComponents: ComponentRegistryItem[] = [
  {
    componentId: 'BarComponent',
    moduleId: {
      name: 'BarModule',              /** <-- Name of NgModule -> */
      path: 'src/app/bar/bar.module'  /** <-- Full path to NgModule */
    }
  },
  ...
];

Need to instruct Angular that there is a lazy module, open angular.json file and add the following:

Add the module to the list of lazyModules references.

{
  ...
  "projects": {
    "angular-dynamic-content": {
      ...
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            ...
            "lazyModules": ["src/app/bar/bar.module"] /** <-- Full path to NgModule */
          },
        }
      }
    }
  }
}

Readme

Keywords

Package Sidebar

Install

npm i @codethatstack/portals

Weekly Downloads

7

Version

0.0.4

License

MIT

Unpacked Size

380 kB

Total Files

89

Last publish

Collaborators

  • anthony_miller