Skip to content

Commit 0414bfd

Browse files
authored
Add Lazy loading components RFC
re orchestratora/orchestrator#75
1 parent bbc6ab7 commit 0414bfd

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed
+96
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# Lazy loading components
2+
3+
## Summary
4+
5+
Allow to lazy load components in runtime and render them as usual dynamic components.
6+
7+
## Motivation
8+
9+
Currently Orchestrator supports components to be provided via DI which limits it to build-time mostly.
10+
However it is benefitial to be able to load components on demand whenewer they are required to be rendered in the UI to reduce initial bundle size of the application.
11+
12+
## Detailed Explanation
13+
14+
To support lazy loading usecase the following changes have to be done:
15+
16+
1. Allow to register dynamic components by providing a function that returns Promise of an actual component
17+
2. Adjust `orc-render-item` component to await for a lazy component to be loaded before rendering
18+
3. Allow to add component registries at runtime via `ComponentLocatorService` service so that custom lazy loading techniques may be implemented
19+
20+
## Rationale and Alternatives
21+
22+
The most simplest version for lazy loading would be to just implement item #3 from the section above and let devs to implement custom lazy loading components, however this will make a simple usecase harder to implment and different projects may have different implementations of the same problem - making them less interoperable between each other which is not ideal.
23+
24+
## Implementation
25+
26+
1. Update type definitions of `ComponentMap` and `ComponentRegistry` to support lazy loading by adding function signature that returns the same type it currently holds:
27+
28+
```ts
29+
export type LazyComponentLoader<T extends Type<any> = DefaultDynamicComponent> =
30+
() => Promise<T>;
31+
32+
export type ComponentOrLoader<T extends Type<any> = DefaultDynamicComponent> =
33+
| T
34+
| LazyComponentLoader<T>;
35+
36+
export interface ComponentMap<T extends Type<any> = DefaultDynamicComponent> {
37+
[k: string]: ComponentOrLoader<T>;
38+
}
39+
40+
export type ComponentRegistry<T extends Type<any> = DefaultDynamicComponent> =
41+
| ComponentOrLoader<T>[]
42+
| ComponentMap<T>;
43+
```
44+
45+
Update `ComponentLocatorService` to handle lazy component signatures:
46+
47+
```ts
48+
class ComponentLocatorService {
49+
resolve<T, C = GetOrchestratorDynamicComponentConfig<T>>(
50+
component: string | OrchestratorDynamicComponentType<C>,
51+
): Promise<OrchestratorDynamicComponentType<C>> | undefined;
52+
}
53+
```
54+
55+
Method `resolve` MUST perform the following actions:
56+
57+
- Check if the component map has entry for requested component
58+
- If NOT found - eeturn `undefined`
59+
- Check if the component map has lazy loader of the component
60+
- If true - execute lazy loader and return it's promise
61+
- Once resolved - replace lazy loader in the component map with a loaded component
62+
- If rejected - throw an error with details about which component failed to load and original error reason using `ErrorStrategy.handle()` API
63+
- Othrwise return component from the component map as-is
64+
65+
2. Update `orc-render-item`:
66+
67+
1. Wait for a component to be resolved from `ComponentLocatorService`
68+
2. Resolve config for a loaded component
69+
3. Render loaded component
70+
71+
3. Update `ComponentLocatorService` to allow registering new components and expose all loaded/registered components:
72+
73+
```ts
74+
class ComponentLocatorService {
75+
+registerComponent(component: ComponentOrLoader): void;
76+
+getComponentNames(): Observable<string[]>;
77+
+getLoadedComponents(): Observable<OrchestratorDynamicComponentType[]>;
78+
}
79+
```
80+
81+
Method `registerComponent` MUST perform the following actions:
82+
83+
- Extend currently registered component map
84+
85+
We specifically have 2 separate methods for getting components which are Observables so they can update whenever a new component is registered/loaded:
86+
87+
- component names that are registered/loaded `getComponentNames`
88+
- already loaded components `getLoadedComponents`
89+
90+
## Unresolved Questions and Bikeshedding
91+
92+
Open questions:
93+
94+
- What should be/not be rendered while a component is being loaded?
95+
For example we can have optional InjectionToken that can provide a component for rendering.
96+
- Should we provide a way for custom loader strategy ie. custom service?

0 commit comments

Comments
 (0)