• Stars
    star
    557
  • Rank 79,968 (Top 2 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created almost 8 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Dynamic components with full life-cycle support for inputs and outputs for Angular

ng-dynamic-component

Dynamic components with full life-cycle support for inputs and outputs

Release Workflow Test Workflow Appveyor Coverage Maintainability Npm Npm Downloads Licence semantic-release

Hey! There is a proposal for new API!

So if you are using this library please give your vote/feedback.

Compatibility with Angular
Angular ng-dynamic-component NPM package
>=14.1.3 10.3.1 ng-dynamic-component@^10.3.1
>=14.x.x 10.2.x ng-dynamic-component@^10.2.0
13.x.x 10.1.x ng-dynamic-component@~10.1.0
12.x.x 9.x.x ng-dynamic-component@^9.0.0
11.x.x 8.x.x ng-dynamic-component@^8.0.0
10.x.x 7.x.x ng-dynamic-component@^7.0.0
9.x.x 6.x.x ng-dynamic-component@^6.0.0
8.x.x 5.x.x ng-dynamic-component@^5.0.0
7.x.x 4.x.x ng-dynamic-component@^4.0.0
6.x.x 3.x.x ng-dynamic-component@^3.0.0
5.x.x 2.x.x ng-dynamic-component@^2.0.0
4.x.x 1.x.x ng-dynamic-component@^1.0.0
2.x.x 0.x.x ng-dynamic-component@^0.0.0

Installation

$ npm install ng-dynamic-component --save

Usage

DynamicComponent

Import DynamicModule where you need to render dynamic components:

import { DynamicModule } from 'ng-dynamic-component';

@NgModule({
  imports: [DynamicModule],
})
export class MyModule {}

Then in your component's template include <ndc-dynamic> where you want to render component and bind from your component class type of component to render:

@Component({
  selector: 'my-component',
  template: ` <ndc-dynamic [ndcDynamicComponent]="component"></ndc-dynamic> `,
})
class MyComponent {
  component = Math.random() > 0.5 ? MyDynamicComponent1 : MyDynamicComponent2;
}

Standalone API

Since v10.7.0

You may use <ndc-dynamic> as a standalone component:

import { DynamicComponent } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: ` <ndc-dynamic [ndcDynamicComponent]="component"></ndc-dynamic> `,
  imports: [DynamicComponent],
  standalone: true,
})
class MyComponent {
  component = Math.random() > 0.5 ? MyDynamicComponent1 : MyDynamicComponent2;
}

NOTE: Hovewer you should be aware that this will only import <ndc-dynamic> into your component and nothing else so things like dynamic inputs/outputs will not work and you will have to import them separately (see their respective sections).

If you still need to use both <ndc-dynamic> and dynamic inputs/outputs it is recommended to keep using DynamicModule API.

NgComponentOutlet

You can also use NgComponentOutlet directive from @angular/common instead of <ndc-dynamic>.

Import DynamicIoModule where you need to render dynamic inputs:

import { DynamicIoModule } from 'ng-dynamic-component';

@NgModule({
  imports: [DynamicIoModule],
})
export class MyModule {}

Now apply ndcDynamicInputs and ndcDynamicOutputs to ngComponentOutlet:

@Component({
  selector: 'my-component',
  template: `<ng-template [ngComponentOutlet]="component"
                           [ndcDynamicInputs]="inputs"
                           [ndcDynamicOutputs]="outputs"
                           ></ng-template>`
})
class MyComponent {
  component = MyDynamicComponent1;
  inputs = {...};
  outputs = {...};
}

Also you can use ngComponentOutlet with * syntax:

@Component({
  selector: 'my-component',
  template: `<ng-container *ngComponentOutlet="component;
                            ndcDynamicInputs: inputs;
                            ndcDynamicOutputs: outputs"
                            ></ng-container>`
})
class MyComponent {
  component = MyDynamicComponent1;
  inputs = {...};
  outputs = {...};
}

Standalone API

Since v10.7.0

You may use dynamic inputs/outputs with *ngComponentOutlet as a standalone API:

import { ComponentOutletInjectorModule } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `<ng-container *ngComponentOutlet="component;
                            ndcDynamicInputs: inputs;
                            ndcDynamicOutputs: outputs"
                            ></ng-container>`
  imports: [ComponentOutletInjectorModule],
  standalone: true,
})
class MyComponent {
  component = MyDynamicComponent1;
  inputs = {...};
  outputs = {...};
}

If you want to use standard dynamic inputs/outputs with ngComponentOutlet as a standalone API you need to add the DynamicIoDirective to your imports:

import { DynamicIoDirective, ComponentOutletInjectorModule } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `<ng-container *ngComponentOutlet="component;
                            ndcDynamicInputs: inputs;
                            ndcDynamicOutputs: outputs"
                            ></ng-container>`
  imports: [DynamicIoDirective, ComponentOutletInjectorModule],
  standalone: true,
})
class MyComponent {
  component = MyDynamicComponent1;
  inputs = {...};
  outputs = {...};
}

Inputs and Outputs

You can pass inputs and outputs to your dynamic components:

Import module DynamicIoModule and then in template:

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      [ndcDynamicInputs]="inputs"
      [ndcDynamicOutputs]="outputs"
    ></ndc-dynamic>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  inputs = {
    hello: 'world',
    something: () => 'can be really complex',
  };
  outputs = {
    onSomething: (type) => alert(type),
  };
}

@Component({
  selector: 'my-dynamic-component1',
  template: 'Dynamic Component 1',
})
class MyDynamicComponent1 {
  @Input()
  hello: string;
  @Input()
  something: Function;
  @Output()
  onSomething = new EventEmitter<string>();
}

Here you can update your inputs (ex. inputs.hello = 'WORLD') and they will trigger standard Angular's life-cycle hooks (of course you should consider which change detection strategy you are using).

Standalone API

Since v10.7.0

You can use standalone API to pass dynamic inputs/outputs using DynamicIoDirective with DynamicComponent or ngComponentOutlet:

import { DynamicIoDirective, DynamicComponent } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      [ndcDynamicInputs]="inputs"
      [ndcDynamicOutputs]="outputs"
    ></ndc-dynamic>
  `,
  imports: [DynamicIoDirective, DynamicComponent]
})
class MyComponent {
  component = MyDynamicComponent1;
  inputs = {...};
  outputs = {...};
}

Output template variables

Since v6.1.0

When you want to provide some values to your output handlers from template - you can do so by supplying a special object to your output that has shape {handler: fn, args: []}:

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      [ndcDynamicOutputs]="{
        onSomething: { handler: doSomething, args: ['$event', tplVar] }
      }"
    ></ndc-dynamic>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  tplVar = 'some value';
  doSomething(event, tplValue) {}
}

Here you can specify at which argument event value should arrive via '$event' literal.

HINT: You can override event literal by providing IoEventArgumentToken in DI.

Output Handler Context

Since v10.4.0

You can specify the context (this) that will be used when calling the output handlers by providing either:

  • IoEventContextToken - which will be; injected and used directly as a context value
  • IoEventContextProviderToken - which will be provided and instantiated within the IoService and used as a context value.
    This useful if you have some generic way of retrieving a context for every dynamic component so you may encapsulate it in an Angular DI provider that will be instantiated within every component's injector;

Example using your component as an output context:

import { IoEventContextToken } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      [ndcDynamicOutputs]="{
        onSomething: doSomething
      }"
    ></ndc-dynamic>
  `,
  providers: [
    {
      provide: IoEventContextToken,
      useExisting: MyComponent,
    },
  ],
})
class MyComponent {
  component = MyDynamicComponent1;
  doSomething(event) {
    // Here `this` will be an instance of `MyComponent`
  }
}

Component Creation Events

You can subscribe to component creation events, being passed a reference to the ComponentRef:

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      (ndcDynamicCreated)="componentCreated($event)"
    ></ndc-dynamic>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  componentCreated(compRef: ComponentRef<any>) {
    // utilize compRef in some way ...
  }
}

Attributes

Since v2.2.0 you can now declaratively set attributes, as you would inputs, via ndcDynamicAttributes.

Import module DynamicAttributesModule and then in template:

import { AttributesMap } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      [ndcDynamicAttributes]="attrs"
    ></ndc-dynamic>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  attrs: AttributesMap = {
    'my-attribute': 'attribute-value',
    class: 'some classes',
  };
}

Remember that attributes values are always strings (while inputs can be any value). So to have better type safety you can use AttributesMap interface for your attributes maps.

Also you can use ngComponentOutlet and ndcDynamicAttributes with * syntax:

import { AttributesMap } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ng-container
      *ngComponentOutlet="component; ndcDynamicAttributes: attrs"
    ></ng-container>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  attrs: AttributesMap = {
    'my-attribute': 'attribute-value',
    class: 'some classes',
  };
}

Standalone API

Since v10.7.0

You can use standalone API to pass dynamic inputs/outputs using DynamicAttributesDirective with DynamicComponent or ngComponentOutlet:

import { DynamicAttributesDirective, DynamicComponent } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ndc-dynamic
      [ndcDynamicComponent]="component"
      [ndcDynamicAttributes]="attrs"
    ></ndc-dynamic>
  `,
  imports: [DynamicAttributesDirective, DynamicComponent]
})
class MyComponent {
  component = MyDynamicComponent1;
  attrs: AttributesMap = {...};
}

Directives (experimental)

Since v3.1.0 you can now declaratively set directives, via ndcDynamicDirectives.

NOTE: There is a known issue with OnChanges hook not beign triggered on dynamic directives since this part of functionality has been removed from the core as Angular now supports this out of the box for dynamic components.

In dynamic directives queries like @ContentChild and host decorators like @HostBinding will not work due to involved complexity required to implement it (but PRs are welcome!).

Import module DynamicDirectivesModule and then in template:

import { dynamicDirectiveDef } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ng-container
      [ngComponentOutlet]="component"
      [ndcDynamicDirectives]="dirs"
    ></ng-container>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  dirs = [dynamicDirectiveDef(MyDirective)];
}

It's also possible to bind inputs and outputs to every dynamic directive:

import { dynamicDirectiveDef } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ng-container
      [ngComponentOutlet]="component"
      [ndcDynamicDirectives]="dirs"
    ></ng-container>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  directiveInputs = { prop1: 'value' };
  directiveOutputs = { output1: (evt) => this.doSomeStuff(evt) };
  dirs = [
    dynamicDirectiveDef(
      MyDirective,
      this.directiveInputs,
      this.directiveOutputs,
    ),
  ];
}

To change inputs, just update the object:

class MyComponent {
  updateDirectiveInput() {
    this.directiveInputs.prop1 = 'new value';
  }
}

You can have multiple directives applied to same dynamic component (only one directive by same type):

import { dynamicDirectiveDef } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ng-container
      [ngComponentOutlet]="component"
      [ndcDynamicDirectives]="dirs"
    ></ng-container>
  `,
})
class MyComponent {
  component = MyDynamicComponent1;
  dirs = [
    dynamicDirectiveDef(MyDirective1),
    dynamicDirectiveDef(MyDirective2),
    dynamicDirectiveDef(MyDirective3),
    dynamicDirectiveDef(MyDirective1), // This will be ignored because MyDirective1 already applied above
  ];
}

Standalone API

Since v10.7.0

You can use standalone API to pass dynamic inputs/outputs using DynamicDirectivesDirective with DynamicComponent or ngComponentOutlet:

import { DynamicDirectivesDirective, DynamicComponent } from 'ng-dynamic-component';

@Component({
  selector: 'my-component',
  template: `
    <ng-container
      [ngComponentOutlet]="component"
      [ndcDynamicDirectives]="dirs"
    ></ng-container>
  `,
  imports: [DynamicDirectivesDirective, DynamicComponent]
})
class MyComponent {
  component = MyDynamicComponent1;
  dirs = [...];
}

Extra

You can have more advanced stuff over your dynamically rendered components like setting custom injector ([ndcDynamicInjector]) or providing additional/overriding providers ([ndcDynamicProviders]) or both simultaneously or projecting nodes ([ndcDynamicContent]).

Since v10.6.0: You can provide custom NgModuleRef ([ndcDynamicNgModuleRef]) or EnvironmentInjector ([ndcDynamicEnvironmentInjector]) for your dynamic component.


NOTE: In practice functionality of this library is split in two pieces:

  • one - component (ndc-dynamic) that is responsible for instantiating and rendering of dynamic components;
  • two - directive (ndcDynamic also bound to ndc-dynamic) that is responsible for carrying inputs/outputs to/from dynamic component by the help of so called DynamicComponentInjector.

Thanks to this separation you are able to connect inputs/outputs and life-cycle hooks to different mechanisms of injecting dynamic components by implementing DynamicComponentInjector and providing it via DynamicComponentInjectorToken in DI.

It was done to be able to reuse NgComponentOutlet added in Angular 4-beta.3.

To see example of how to implement custom component injector - see ComponentOutletInjectorDirective that is used to integrate NgComponentOutlet directive with inputs/outputs.

Contributing

You are welcome to contribute to this project. Simply follow the contribution guide.

License

MIT © Alex Malkevich

More Repositories

1

eslint-plugin-deprecation

ESLint rule that reports usage of deprecated code
TypeScript
317
star
2

ng-http-interceptor

Http Interceptor library for Angular
TypeScript
104
star
3

ngx-router-meta

Configure HTML meta tags like title and description in route configuration of Angular for SSR
TypeScript
4
star
4

time-slots

Small library to generate slots for a date range between a time range with optional intervals
TypeScript
4
star
5

ngx-element-boundary

Strategy for @angular/elements that allows to inherit Injectors between any Angular Custom Elements just like default Angular Components do
TypeScript
3
star
6

ng-annotations

Extract metadata of Angular components, directives, service etc.
JavaScript
3
star
7

ngx-strength-bar

[WIP] Extendable strength bar for Angular
TypeScript
2
star
8

ng-s-triangle-demo

Sierpinski Triangle demo built with Angular
JavaScript
2
star
9

mosaic-canva

This is challenge task for Canva
JavaScript
2
star
10

rollup-globals-regex

Configurable globals with RegExp for Rollup
TypeScript
2
star
11

node-mongo-graphql

Example project built with NodeJS, MongoDB, Mongoose, GraphQL and Graffiti
TypeScript
2
star
12

pooley

Generic pool implementation that works with Webworkers, Promises, etc.
TypeScript
2
star
13

rx-marbles

WIP Interactive RX marbles diagrams
TypeScript
2
star
14

auction

Auction
JavaScript
1
star
15

angular-dynamic-component-test

A POC of dynamic component with full support of lifecycles
TypeScript
1
star
16

ng-router-resolver

Resolve routes from Angular Module
TypeScript
1
star
17

game-companion

Board Games Companion repo
TypeScript
1
star
18

ngx-ag-grid

Useful things for better integration between AgGrid and Angular
TypeScript
1
star
19

ngx-renderer

Advanced abstract renderer for Angular
TypeScript
1
star
20

crossover-project

A test project for CrossOver application process
CSS
1
star
21

selectable

Library that provides selection capabilities to JS ecosystem
TypeScript
1
star