When dealing with loading components dynamically, it is essential to register these components within the entryComponents
of your modal component decorator. In addition, since these components are classes in TypeScript, you must import them from the relevant component where your modal is called. To streamline this process, it's recommended to gather all dynamic components in a single file and export them as an array.
For example, create a file named dynamic-components.ts
to store all potential dynamic components:
import { FooComponent } from './path/to/foo';
import { BarComponent } from './path/to/bar';
import { BazComponent } from './path/to/baz';
// Export all dynamic components
export const dynamicComponents = [
FooComponent, BarComponent, BazComponent
]
In your modal component, simply spread these components into the entryComponents
property:
import { dynamicComponents } from './path/to/dynamic-components';
@Component({
//...
entryComponents: [ ...dynamicComponents ]
})
export class ModalComponent {}
Now, within your modal component, you can create a method that renders a component based on the provided component name and metadata to handle properties dynamically. This approach enhances the flexibility of your modal by enabling the rendering of components with varying inputs and outputs.
Instead of hardcoding the component in the method, extract it from the dynamicComponents
array. You can do this by utilizing the name property of JavaScript classes, which matches the parameter passed to your function with the component names in dynamicComponents
.
export class ModalComponent {
//...
createComponent(name: string, metadata: any) {
const viewContainerRef = this.entryContainer.viewContainerRef;
const cmpClass = dynamicComponents.find(cmp => cmp.name === name);
const cmpToCreate = new DynamicComponent(cmpClass, metadata);
const componentFactory = this.cmpFactoryResolver.resolveComponentFactory(cmpToCreate.component)
viewContainerRef.clear();
const cmpRef = viewContainerRef.createComponent(componentFactory);
// Patch input values ...
for (let input in metadata.inputs) {
if (metadata.inputs.hasOwnProperty(input)) {
cmpRef.instance[input] = metadata.inputs[input];
}
}
// Subscribe to outputs
for (let output in metadata.outputs) {
if (metadata.outputs.hasOwnProperty(output)) {
console.log('hasOutput', metadata.outputs[output]);
cmpRef.instance[output].subscribe(metadata.outputs[output]);
}
}
}
}
To ensure proper type handling, define the DynamicComponent
class as follows:
export class DynamicComponent {
constructor(public component: Type<any>, data: any) {}
}
The rest of the function remains similar to what you already have, with the additional use of the metadata parameter. This object contains inputs and outputs for the instantiated components. When calling the method, provide the component name and respective metadata:
export class SomeComponentThatRendersTheModal() {
renderFooComponent() {
// Assume there's a modal service to open the modal
this.modalService.openModal();
this.modalService.createComponent(
'FooComponent', {
inputs: { fooInputTest: 'kitten' },
outputs: { fooOutputTest: handleOutput }
}
);
}
// Handle the output event
handleOutput(emittedEvent) {
// Implement functionality here
}
}
Finally, adjust the metadata
structure or how you manage input values and output handlers according to your requirements. This setup provides a foundational guide on creating components dynamically.
For a visual demonstration, check out the associated demo. I hope this explanation clarifies the process for you.
10/22/21 Update:
Note that directly accessing the name
property of a Function
object may cause issues once the code gets minified for production. To address this concern, refer to the documented workaround provided in the angular repo.