Dynamic component loader
@flyze/lib-core-angular 1.0.0-alpha.34· latest[loadComponent] renders a component chosen at runtime into the host’s view container, hands it a
configuration object, and swaps it when the configuration names a different component. It is what
lets the flyze platform build a view out of stored data: a dashboard is a list of
{ component, config } pairs, and neither the container nor the loaded components know each other
at compile time.
Exported by DynamicComponentModule
(dynamic-component.module.ts),
which also provides DynamicComponentResolverService.
The contract
Section titled “The contract”A loadable component implements nothing mandatory. DynamicComponent<K> declares an optional
config property
(dynamic-component.model.ts:1)
and the directive discovers the rest by inspecting the instance:
| The directive looks for | And then |
|---|---|
setConfig(config) | calls it |
config | assigns to it, when setConfig does not exist |
environment (a method) | calls it with the environment |
environment (a field) | assigns to it |
Config is only applied when the loader config object actually has a config property
(dynamic-component-loader.directive.ts:148);
{ component: X } alone leaves the component’s own default untouched.
Wiring by convention rather than @Input() is deliberate — createComponent() produces a component
that has no template binding to receive inputs through, and requiring a shared base class would
couple every consumer to this library. Rationale:
ADR 0005.
The property the environment is written to is configurable via environmentKey (default
'environment'), and empty or non-string values are rejected so the default survives
(dynamic-component-loader.directive.ts:82).
setEnvironment only writes when the key already exists on the instance
(dynamic-component-loader.directive.ts:168)
— a component that does not declare the property is skipped silently, so the environment cannot be
used to bolt a property onto a component that never asked for it. setConfig has no such guard: it
assigns config whether or not the component declares it.
Lifecycle
Section titled “Lifecycle”The directive tracks whether ngOnInit has run and refuses to create anything before it
(dynamic-component-loader.directive.ts:118).
Input setters fire before ngOnInit, and in any order, so a create in the config setter could run
before environment was ever set. The first create therefore always happens in ngOnInit, with
every input in place.
Afterwards, the config setter decides between two paths by comparing the component identity
(dynamic-component-loader.directive.ts:50):
- Different component →
initialize(): destroy the previousComponentRef, create the new one, then apply config and environment. - Same component →
setConfig()only. The instance is kept and its config updated, so component state survives a config change.
initialize() destroys the previous ref explicitly
(dynamic-component-loader.directive.ts:122)
— ViewContainerRef.createComponent() appends rather than replaces, so without the destroy the old
component would stay in the DOM alongside the new one.
A falsy config, or one without component, is reported with console.error and otherwise ignored
(dynamic-component-loader.directive.ts:38)
— the previously loaded component stays.
componentLoaded emits the new ComponentRef from the private setter, so it fires on every create
before config and environment are applied
(dynamic-component-loader.directive.ts:27).
A subscriber reading ref.instance.config in that handler sees the previous value or undefined.
The string registry
Section titled “The string registry”config.component may also be a string. DynamicComponentResolverService looks it up in
DynamicComponentLookupRegistry before creating
(dynamic-component-resolver.service.ts:14)
— the path used when the component name comes out of stored data.
Components register themselves with @DynamicComponentDecorator('MyComponent'). The key must be
passed explicitly. Deriving it from the class name looks obvious and does not work: minification
rewrites class names, so a production build would register keys nobody can look up
(dynamic-component.decorator.ts:14).
The registry is a module-level Map, so a component only registers once its file has been evaluated
— a component in a lazy chunk is unknown until that chunk loads. An unknown key logs
[loadComponent] Component not found and then calls createComponent(undefined), which throws.
Note — sample code in the published bundle
Section titled “Note — sample code in the published bundle”dynamic-component.model.ts ends with a // #region EXAMPLES block
(dynamic-component.model.ts:26–:70)
declaring FooComponent, BarComponent and a fooBarConfig value that demonstrate the generic
types. None of it is exported through public-api.ts, but it is real code in a file that is: it
ships in the bundle. Useful as documentation of the type gymnastics, not something to imitate or
extend.