Skip to content

Commit 9e8feea

Browse files
Raymond Fengraymondfeng
authored andcommitted
feat(core): allow a component to register nesting components
In a large project, a component can have dependencies on other components. This feature makes it easier to mount required components to the application. Signed-off-by: Raymond Feng <enjoyjava@gmail.com>
1 parent 92865e6 commit 9e8feea

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

packages/core/src/__tests__/acceptance/application.acceptance.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import {Constructor, inject, Provider} from '@loopback/context';
77
import {expect} from '@loopback/testlab';
8-
import {Application, ControllerClass} from '../..';
8+
import {Application, Component, ControllerClass} from '../..';
99

1010
describe('Bootstrapping the application', () => {
1111
context('with user-defined components', () => {
@@ -20,6 +20,29 @@ describe('Bootstrapping the application', () => {
2020
expect(componentInstance).to.be.instanceOf(AuditComponent);
2121
});
2222

23+
it('register all child components from a component', () => {
24+
let componentACreated = 0;
25+
class ComponentA implements Component {
26+
constructor() {
27+
componentACreated++;
28+
}
29+
}
30+
class ComponentB implements Component {}
31+
class ParentComponent implements Component {
32+
components = [ComponentA, ComponentB];
33+
}
34+
const app = new Application();
35+
app.component(ParentComponent);
36+
const componentKeys = app.find('components.*').map(b => b.key);
37+
expect(componentKeys).to.containEql('components.ComponentA');
38+
expect(componentKeys).to.containEql('components.ComponentB');
39+
expect(componentKeys).to.containEql('components.ParentComponent');
40+
41+
// Re-registration of ComponentA does not have side effects
42+
app.component(ComponentA);
43+
expect(componentACreated).to.eql(1);
44+
});
45+
2346
it('registers all providers from components', () => {
2447
class FooProvider {
2548
value() {

packages/core/src/application.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,11 @@ export class Application extends Context implements LifeCycleObserver {
470470
defaultScope: BindingScope.SINGLETON,
471471
...toOptions(nameOrOptions),
472472
});
473+
// Check if the component is already bound
474+
const found = this.registry.get(binding.key);
475+
if (found?.valueConstructor === binding.valueConstructor) {
476+
return binding;
477+
}
473478
if (isLifeCycleObserverClass(componentCtor)) {
474479
binding.apply(asLifeCycleObserver);
475480
}

packages/core/src/component.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ export interface Component {
9191
*/
9292
bindings?: Binding[];
9393

94+
/**
95+
* An array of component classes
96+
*/
97+
components?: Constructor<Component>[];
98+
9499
/**
95100
* Other properties
96101
*/
@@ -152,4 +157,11 @@ export function mountComponent(app: Application, component: Component) {
152157
app.service(service);
153158
}
154159
}
160+
161+
if (component.components) {
162+
for (const c of component.components) {
163+
if (c === component) continue;
164+
app.component(c);
165+
}
166+
}
155167
}

0 commit comments

Comments
 (0)