@joist/di 4.0.0-next.3 → 4.0.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -14
- package/src/lib/dom-injector.test.ts +16 -18
- package/src/lib/dom-injector.ts +3 -3
- package/src/lib/inject.test.ts +32 -55
- package/src/lib/inject.ts +2 -2
- package/src/lib/injectable-el.test.ts +130 -0
- package/src/lib/injectable-el.ts +63 -0
- package/src/lib/injectable.test.ts +19 -114
- package/src/lib/injectable.ts +16 -58
- package/src/lib/injector.test.ts +132 -130
- package/src/lib/injector.ts +10 -9
- package/src/lib/lifecycle.test.ts +56 -58
- package/src/lib/lifecycle.ts +13 -2
- package/src/lib/provider.ts +0 -2
- package/target/lib/dom-injector.js +3 -3
- package/target/lib/dom-injector.test.js +16 -18
- package/target/lib/dom-injector.test.js.map +1 -1
- package/target/lib/inject.js +2 -2
- package/target/lib/inject.test.js +57 -89
- package/target/lib/inject.test.js.map +1 -1
- package/target/lib/injectable-el.d.ts +334 -0
- package/target/lib/injectable-el.js +40 -0
- package/target/lib/injectable-el.js.map +1 -0
- package/target/lib/injectable-el.test.js +238 -0
- package/target/lib/injectable-el.test.js.map +1 -0
- package/target/lib/injectable.d.ts +2 -5
- package/target/lib/injectable.js +11 -41
- package/target/lib/injectable.js.map +1 -1
- package/target/lib/injectable.test.js +50 -233
- package/target/lib/injectable.test.js.map +1 -1
- package/target/lib/injector.d.ts +1 -1
- package/target/lib/injector.js +5 -7
- package/target/lib/injector.js.map +1 -1
- package/target/lib/injector.test.js +215 -216
- package/target/lib/injector.test.js.map +1 -1
- package/target/lib/lifecycle.d.ts +11 -2
- package/target/lib/lifecycle.js +4 -2
- package/target/lib/lifecycle.js.map +1 -1
- package/target/lib/lifecycle.test.js +120 -122
- package/target/lib/lifecycle.test.js.map +1 -1
- package/target/lib/provider.d.ts +0 -1
- package/src/lib/injector.test-node.ts +0 -187
- package/target/lib/injector.test-node.js +0 -231
- package/target/lib/injector.test-node.js.map +0 -1
- /package/target/lib/{injector.test-node.d.ts → injectable-el.test.d.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joist/di",
|
|
3
|
-
"version": "4.0.0-next.
|
|
3
|
+
"version": "4.0.0-next.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./target/lib.js",
|
|
6
6
|
"module": "./target/lib.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"description": "Dependency Injection for Vanilla JS classes",
|
|
21
21
|
"repository": {
|
|
22
22
|
"type": "git",
|
|
23
|
-
"url": "git+https://github.com/
|
|
23
|
+
"url": "git+https://github.com/joist-framework/joist.git"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"TypeScript",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"author": "deebloo",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"bugs": {
|
|
34
|
-
"url": "https://github.com/
|
|
34
|
+
"url": "https://github.com/joist-framework/joist/issues"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
@@ -57,17 +57,7 @@
|
|
|
57
57
|
"test": {
|
|
58
58
|
"command": "wtr --config wtr.config.mjs",
|
|
59
59
|
"files": [
|
|
60
|
-
"
|
|
61
|
-
],
|
|
62
|
-
"output": [],
|
|
63
|
-
"dependencies": [
|
|
64
|
-
"build",
|
|
65
|
-
"test:node"
|
|
66
|
-
]
|
|
67
|
-
},
|
|
68
|
-
"test:node": {
|
|
69
|
-
"command": "node --test target/**/*.test-node.js",
|
|
70
|
-
"files": [
|
|
60
|
+
"wtr.config.mjs",
|
|
71
61
|
"target/**"
|
|
72
62
|
],
|
|
73
63
|
"output": [],
|
|
@@ -1,30 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assert } from 'chai';
|
|
2
2
|
|
|
3
3
|
import { DOMInjector } from './dom-injector.js';
|
|
4
|
-
import {
|
|
4
|
+
import { injectables } from './injector.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const app = new DOMInjector();
|
|
6
|
+
it('should attach an injector to a dom element', () => {
|
|
7
|
+
const root = document.createElement('div');
|
|
8
|
+
const app = new DOMInjector();
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
app.attach(root);
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
const injector = injectables.get(root);
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
assert.strictEqual(injector, app);
|
|
15
|
+
});
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
it('should remove an injector associated with a dom element', () => {
|
|
18
|
+
const root = document.createElement('div');
|
|
19
|
+
const app = new DOMInjector();
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
app.attach(root);
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
assert.strictEqual(injectables.get(root), app);
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
app.detach(root);
|
|
27
26
|
|
|
28
|
-
|
|
29
|
-
});
|
|
27
|
+
assert.strictEqual(injectables.get(root), undefined);
|
|
30
28
|
});
|
package/src/lib/dom-injector.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { injectables, Injector } from './injector.js';
|
|
2
2
|
|
|
3
3
|
export class DOMInjector extends Injector {
|
|
4
4
|
attach(root: HTMLElement) {
|
|
5
|
-
|
|
5
|
+
injectables.set(root, this);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
detach(root: HTMLElement) {
|
|
9
|
-
|
|
9
|
+
injectables.delete(root);
|
|
10
10
|
}
|
|
11
11
|
}
|
package/src/lib/inject.test.ts
CHANGED
|
@@ -1,25 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assert } from 'chai';
|
|
2
2
|
|
|
3
3
|
import { inject } from './inject.js';
|
|
4
4
|
import { injectable } from './injectable.js';
|
|
5
5
|
import { Injector } from './injector.js';
|
|
6
6
|
import { StaticToken } from './provider.js';
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class HelloService {}
|
|
11
|
-
|
|
12
|
-
@injectable()
|
|
13
|
-
class HelloWorld extends HTMLElement {
|
|
14
|
-
hello = inject(HelloService);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
customElements.define('inject-1', HelloWorld);
|
|
18
|
-
|
|
19
|
-
expect(new HelloWorld().hello()).to.be.instanceOf(HelloService);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should throw error if called in constructor', () => {
|
|
8
|
+
it('should throw error if called in constructor', () => {
|
|
9
|
+
assert.throws(() => {
|
|
23
10
|
class FooService {
|
|
24
11
|
value = '1';
|
|
25
12
|
}
|
|
@@ -35,49 +22,39 @@ describe('inject', () => {
|
|
|
35
22
|
|
|
36
23
|
const parent = new Injector();
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
throw new Error('Should not succeed');
|
|
42
|
-
} catch (err) {
|
|
43
|
-
const error = err as Error;
|
|
44
|
-
|
|
45
|
-
expect(error.message).to.equal(
|
|
46
|
-
`BarService is either not injectable or a service is being called in the constructor. \n Either add the @injectable() to your class or use the [LifeCycle.onInject] callback method.`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should use the calling injector as parent', () => {
|
|
52
|
-
class FooService {
|
|
53
|
-
value = '1';
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@injectable()
|
|
57
|
-
class BarService {
|
|
58
|
-
foo = inject(FooService);
|
|
59
|
-
}
|
|
25
|
+
parent.inject(BarService);
|
|
26
|
+
}, 'BarService is either not injectable or a service is being called in the constructor.');
|
|
27
|
+
});
|
|
60
28
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
29
|
+
it('should use the calling injector as parent', () => {
|
|
30
|
+
class FooService {
|
|
31
|
+
value = '1';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@injectable()
|
|
35
|
+
class BarService {
|
|
36
|
+
foo = inject(FooService);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const parent = new Injector([
|
|
40
|
+
{
|
|
41
|
+
provide: FooService,
|
|
42
|
+
use: class extends FooService {
|
|
43
|
+
value = '100';
|
|
67
44
|
}
|
|
68
|
-
|
|
45
|
+
}
|
|
46
|
+
]);
|
|
69
47
|
|
|
70
|
-
|
|
71
|
-
|
|
48
|
+
assert.strictEqual(parent.inject(BarService).foo().value, '100');
|
|
49
|
+
});
|
|
72
50
|
|
|
73
|
-
|
|
74
|
-
|
|
51
|
+
it('should inject a static token', () => {
|
|
52
|
+
const TOKEN = new StaticToken('test', () => 'Hello World');
|
|
75
53
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
54
|
+
@injectable()
|
|
55
|
+
class HelloWorld {
|
|
56
|
+
hello = inject(TOKEN);
|
|
57
|
+
}
|
|
80
58
|
|
|
81
|
-
|
|
82
|
-
});
|
|
59
|
+
assert.strictEqual(new HelloWorld().hello(), 'Hello World');
|
|
83
60
|
});
|
package/src/lib/inject.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { InjectionToken } from './provider.js';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { injectables } from './injector.js';
|
|
4
4
|
|
|
5
5
|
export type Injected<T> = () => T;
|
|
6
6
|
|
|
7
7
|
export function inject<This extends object, T>(token: InjectionToken<T>): Injected<T> {
|
|
8
8
|
return function (this: This) {
|
|
9
|
-
const injector =
|
|
9
|
+
const injector = injectables.get(this);
|
|
10
10
|
|
|
11
11
|
if (injector === undefined) {
|
|
12
12
|
const name = Object.getPrototypeOf(this.constructor).name;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { assert } from 'chai';
|
|
2
|
+
|
|
3
|
+
import { inject } from './inject.js';
|
|
4
|
+
import { injectable } from './injectable.js';
|
|
5
|
+
|
|
6
|
+
it('should allow services to be injected into custom element', () => {
|
|
7
|
+
class Foo {}
|
|
8
|
+
|
|
9
|
+
@injectable()
|
|
10
|
+
class MyElement extends HTMLElement {
|
|
11
|
+
foo = inject(Foo);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
customElements.define('injectable-1', MyElement);
|
|
15
|
+
|
|
16
|
+
const el = new MyElement();
|
|
17
|
+
|
|
18
|
+
assert.instanceOf(el.foo(), Foo);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should allow services to be injected into custom elements that has been extended', () => {
|
|
22
|
+
class Foo {}
|
|
23
|
+
|
|
24
|
+
class MyBaseElement extends HTMLElement {}
|
|
25
|
+
|
|
26
|
+
@injectable()
|
|
27
|
+
class MyElement extends MyBaseElement {
|
|
28
|
+
foo = inject(Foo);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
customElements.define('injectable-2', MyElement);
|
|
32
|
+
|
|
33
|
+
const el = new MyElement();
|
|
34
|
+
|
|
35
|
+
assert.instanceOf(el.foo(), Foo);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should handle parent HTML Injectors', async () => {
|
|
39
|
+
@injectable()
|
|
40
|
+
class A {}
|
|
41
|
+
|
|
42
|
+
@injectable()
|
|
43
|
+
class B {
|
|
44
|
+
a = inject(A);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class AltA implements A {}
|
|
48
|
+
|
|
49
|
+
@injectable({
|
|
50
|
+
providers: [
|
|
51
|
+
{ provide: B, use: B },
|
|
52
|
+
{ provide: A, use: AltA }
|
|
53
|
+
]
|
|
54
|
+
})
|
|
55
|
+
class Parent extends HTMLElement {}
|
|
56
|
+
|
|
57
|
+
@injectable()
|
|
58
|
+
class Child extends HTMLElement {
|
|
59
|
+
b = inject(B);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
customElements.define('injectable-parent-1', Parent);
|
|
63
|
+
customElements.define('injectable-child-1', Child);
|
|
64
|
+
|
|
65
|
+
const el = document.createElement('div');
|
|
66
|
+
el.innerHTML = /*html*/ `
|
|
67
|
+
<injectable-parent-1>
|
|
68
|
+
<injectable-child-1></injectable-child-1>
|
|
69
|
+
</injectable-parent-1>
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
document.body.append(el);
|
|
73
|
+
|
|
74
|
+
const child = el.querySelector<Child>('injectable-child-1')!;
|
|
75
|
+
|
|
76
|
+
assert.instanceOf(child.b().a(), AltA);
|
|
77
|
+
|
|
78
|
+
el.remove();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should handle changing contexts', async () => {
|
|
82
|
+
class A {}
|
|
83
|
+
class AltA implements A {}
|
|
84
|
+
|
|
85
|
+
@injectable({
|
|
86
|
+
providers: [{ provide: A, use: A }]
|
|
87
|
+
})
|
|
88
|
+
class Ctx1 extends HTMLElement {}
|
|
89
|
+
|
|
90
|
+
@injectable({
|
|
91
|
+
providers: [{ provide: A, use: AltA }]
|
|
92
|
+
})
|
|
93
|
+
class Ctx2 extends HTMLElement {}
|
|
94
|
+
|
|
95
|
+
@injectable()
|
|
96
|
+
class Child extends HTMLElement {
|
|
97
|
+
a = inject(A);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
customElements.define('ctx-1', Ctx1);
|
|
101
|
+
customElements.define('ctx-2', Ctx2);
|
|
102
|
+
customElements.define('ctx-child', Child);
|
|
103
|
+
|
|
104
|
+
const el = document.createElement('div');
|
|
105
|
+
el.innerHTML = /*html*/ `
|
|
106
|
+
<div>
|
|
107
|
+
<ctx-1>
|
|
108
|
+
<ctx-child></ctx-child>
|
|
109
|
+
</ctx-1>
|
|
110
|
+
|
|
111
|
+
<ctx-2></ctx-2>
|
|
112
|
+
</div>
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
document.body.append(el);
|
|
116
|
+
|
|
117
|
+
const ctx2 = el.querySelector('ctx-2')!;
|
|
118
|
+
|
|
119
|
+
let child = el.querySelector<Child>('ctx-child')!;
|
|
120
|
+
|
|
121
|
+
assert.instanceOf(child.a(), A);
|
|
122
|
+
|
|
123
|
+
child.remove();
|
|
124
|
+
|
|
125
|
+
ctx2.append(child);
|
|
126
|
+
|
|
127
|
+
child = el.querySelector<Child>('ctx-child')!;
|
|
128
|
+
|
|
129
|
+
assert.instanceOf(child.a(), AltA);
|
|
130
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { injectables, Injector } from './injector.js';
|
|
2
|
+
import { ConstructableToken } from './provider.js';
|
|
3
|
+
|
|
4
|
+
export function injectableEl<T extends ConstructableToken<HTMLElement>>(
|
|
5
|
+
Base: T,
|
|
6
|
+
_ctx: ClassDecoratorContext
|
|
7
|
+
) {
|
|
8
|
+
return class InjectablElementeNode extends Base {
|
|
9
|
+
constructor(..._: any[]) {
|
|
10
|
+
super();
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Listen for the finddiroot event.
|
|
14
|
+
* This is event is triggered when the element is connected to the dom
|
|
15
|
+
* This event will bubble up until it finds a parent injector which is then attached
|
|
16
|
+
* This will also work through shadow roots (that are not "closed")
|
|
17
|
+
*/
|
|
18
|
+
this.addEventListener('finddiroot', (e) => {
|
|
19
|
+
const parentInjector = findInjectorRoot(e);
|
|
20
|
+
|
|
21
|
+
if (parentInjector) {
|
|
22
|
+
injectables.get(this)?.setParent(parentInjector);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
connectedCallback() {
|
|
28
|
+
if (this.isConnected) {
|
|
29
|
+
this.dispatchEvent(new Event('finddiroot', { bubbles: true }));
|
|
30
|
+
|
|
31
|
+
if (super.connectedCallback) {
|
|
32
|
+
super.connectedCallback();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
disconnectedCallback() {
|
|
38
|
+
injectables.get(this)?.setParent(undefined);
|
|
39
|
+
|
|
40
|
+
if (super.disconnectedCallback) {
|
|
41
|
+
super.disconnectedCallback();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function findInjectorRoot(e: Event): Injector | null {
|
|
48
|
+
const path = e.composedPath();
|
|
49
|
+
|
|
50
|
+
// find firt parent
|
|
51
|
+
// skips the first item which is the target
|
|
52
|
+
for (let i = 1; i < path.length; i++) {
|
|
53
|
+
const part = path[i];
|
|
54
|
+
|
|
55
|
+
const injector = injectables.get(part);
|
|
56
|
+
|
|
57
|
+
if (injector) {
|
|
58
|
+
return injector;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
@@ -1,126 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assert } from 'chai';
|
|
2
2
|
|
|
3
3
|
import { injectable } from './injectable.js';
|
|
4
4
|
import { inject } from './inject.js';
|
|
5
|
+
import { injectables } from './injector.js';
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class Foo {}
|
|
9
|
-
class Bar {}
|
|
7
|
+
it('should locally override a provider', () => {
|
|
8
|
+
class Foo {}
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
class MyElement extends HTMLElement {
|
|
13
|
-
foo = inject(Foo);
|
|
14
|
-
bar = inject(Bar);
|
|
15
|
-
}
|
|
10
|
+
class Bar extends Foo {}
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
@injectable({
|
|
13
|
+
providers: [{ provide: Foo, use: Bar }]
|
|
14
|
+
})
|
|
15
|
+
class MyService {
|
|
16
|
+
foo = inject(Foo);
|
|
17
|
+
}
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
const el = new MyService();
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
it('should locally override a provider', () => {
|
|
25
|
-
class Foo {}
|
|
26
|
-
|
|
27
|
-
class Bar extends Foo {}
|
|
28
|
-
|
|
29
|
-
@injectable({
|
|
30
|
-
providers: [{ provide: Foo, use: Bar }]
|
|
31
|
-
})
|
|
32
|
-
class MyElement {
|
|
33
|
-
foo = inject(Foo);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const el = new MyElement();
|
|
37
|
-
|
|
38
|
-
expect(el.foo()).to.be.instanceOf(Bar);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it('should handle parent HTML Injectors', async () => {
|
|
42
|
-
@injectable()
|
|
43
|
-
class A {}
|
|
44
|
-
|
|
45
|
-
@injectable()
|
|
46
|
-
class B {
|
|
47
|
-
a = inject(A);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
class AltA implements A {}
|
|
51
|
-
|
|
52
|
-
@injectable({
|
|
53
|
-
providers: [
|
|
54
|
-
{ provide: B, use: B },
|
|
55
|
-
{ provide: A, use: AltA }
|
|
56
|
-
]
|
|
57
|
-
})
|
|
58
|
-
class Parent extends HTMLElement {}
|
|
59
|
-
|
|
60
|
-
@injectable()
|
|
61
|
-
class Child extends HTMLElement {
|
|
62
|
-
b = inject(B);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
customElements.define('injectable-parent-1', Parent);
|
|
66
|
-
customElements.define('injectable-child-1', Child);
|
|
67
|
-
|
|
68
|
-
const el = await fixture(html`
|
|
69
|
-
<injectable-parent-1>
|
|
70
|
-
<injectable-child-1></injectable-child-1>
|
|
71
|
-
</injectable-parent-1>
|
|
72
|
-
`);
|
|
73
|
-
|
|
74
|
-
const child = el.querySelector<Child>('injectable-child-1')!;
|
|
75
|
-
|
|
76
|
-
expect(child.b().a()).to.be.instanceOf(AltA);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should handle changing contexts', async () => {
|
|
80
|
-
class A {}
|
|
81
|
-
class AltA implements A {}
|
|
82
|
-
|
|
83
|
-
@injectable({
|
|
84
|
-
providers: [{ provide: A, use: A }]
|
|
85
|
-
})
|
|
86
|
-
class Ctx1 extends HTMLElement {}
|
|
87
|
-
|
|
88
|
-
@injectable({
|
|
89
|
-
providers: [{ provide: A, use: AltA }]
|
|
90
|
-
})
|
|
91
|
-
class Ctx2 extends HTMLElement {}
|
|
92
|
-
|
|
93
|
-
@injectable()
|
|
94
|
-
class Child extends HTMLElement {
|
|
95
|
-
a = inject(A);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
customElements.define('ctx-1', Ctx1);
|
|
99
|
-
customElements.define('ctx-2', Ctx2);
|
|
100
|
-
customElements.define('ctx-child', Child);
|
|
101
|
-
|
|
102
|
-
const el = await fixture(html`
|
|
103
|
-
<div>
|
|
104
|
-
<ctx-1>
|
|
105
|
-
<ctx-child></ctx-child>
|
|
106
|
-
</ctx-1>
|
|
107
|
-
|
|
108
|
-
<ctx-2></ctx-2>
|
|
109
|
-
</div>
|
|
110
|
-
`);
|
|
111
|
-
|
|
112
|
-
const ctx2 = el.querySelector('ctx-2')!;
|
|
113
|
-
|
|
114
|
-
let child = el.querySelector<Child>('ctx-child')!;
|
|
115
|
-
|
|
116
|
-
expect(child.a()).to.be.instanceOf(A);
|
|
117
|
-
|
|
118
|
-
child.remove();
|
|
21
|
+
assert.instanceOf(el.foo(), Bar);
|
|
22
|
+
});
|
|
119
23
|
|
|
120
|
-
|
|
24
|
+
it('should define an injector for a service instance', () => {
|
|
25
|
+
@injectable()
|
|
26
|
+
class MyService {}
|
|
121
27
|
|
|
122
|
-
|
|
28
|
+
const instance = new MyService();
|
|
123
29
|
|
|
124
|
-
|
|
125
|
-
});
|
|
30
|
+
assert.ok(injectables.has(instance));
|
|
126
31
|
});
|
package/src/lib/injectable.ts
CHANGED
|
@@ -1,73 +1,31 @@
|
|
|
1
1
|
import { ConstructableToken, Provider } from './provider.js';
|
|
2
|
-
import {
|
|
2
|
+
import { injectables, Injector } from './injector.js';
|
|
3
|
+
import { injectableEl } from './injectable-el.js';
|
|
3
4
|
|
|
4
5
|
export interface InjectableOpts {
|
|
5
|
-
providers
|
|
6
|
+
providers?: Provider<unknown>[];
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
export function injectable(opts?: InjectableOpts) {
|
|
9
|
-
return function injectableDecorator<T extends ConstructableToken<any>>(
|
|
10
|
-
|
|
10
|
+
return function injectableDecorator<T extends ConstructableToken<any>>(
|
|
11
|
+
Base: T,
|
|
12
|
+
ctx: ClassDecoratorContext
|
|
13
|
+
) {
|
|
14
|
+
class InjectableNode extends Base {
|
|
11
15
|
constructor(..._: any[]) {
|
|
12
16
|
super();
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
const injector = new Injector(opts?.providers);
|
|
16
|
-
|
|
17
|
-
Injectables.set(this, injector);
|
|
18
|
-
|
|
19
|
-
// If the current injectable instance is a HTMLElement preform additional startup logic
|
|
20
|
-
// this will find and attach parent injectors
|
|
21
|
-
if ('HTMLElement' in globalThis && this instanceof HTMLElement) {
|
|
22
|
-
this.addEventListener('finddiroot', (e) => {
|
|
23
|
-
const parentInjector = findInjectorRoot(e);
|
|
24
|
-
|
|
25
|
-
if (parentInjector !== null) {
|
|
26
|
-
injector.setParent(parentInjector);
|
|
27
|
-
}
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
connectedCallback() {
|
|
33
|
-
if ('HTMLElement' in globalThis && this instanceof HTMLElement) {
|
|
34
|
-
this.dispatchEvent(new Event('finddiroot'));
|
|
35
|
-
|
|
36
|
-
if (super.connectedCallback) {
|
|
37
|
-
super.connectedCallback();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
18
|
+
injectables.set(this, new Injector(opts?.providers));
|
|
40
19
|
}
|
|
20
|
+
}
|
|
41
21
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
injector.setParent(undefined);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (super.disconnectedCallback) {
|
|
50
|
-
super.disconnectedCallback();
|
|
51
|
-
}
|
|
22
|
+
// Only apply custom element bootstrap logic if the decorated class is an HTMLElement
|
|
23
|
+
if ('HTMLElement' in globalThis) {
|
|
24
|
+
if (HTMLElement.prototype.isPrototypeOf(Base.prototype)) {
|
|
25
|
+
return injectableEl(InjectableNode, ctx);
|
|
52
26
|
}
|
|
53
|
-
};
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function findInjectorRoot(e: Event): Injector | null {
|
|
58
|
-
const path = e.composedPath();
|
|
59
|
-
|
|
60
|
-
// find firt parent
|
|
61
|
-
// skips the first item which is the target
|
|
62
|
-
for (let i = 1; i < path.length; i++) {
|
|
63
|
-
const part = path[i];
|
|
64
|
-
|
|
65
|
-
const injector = Injectables.get(part);
|
|
66
|
-
|
|
67
|
-
if (injector) {
|
|
68
|
-
return injector;
|
|
69
27
|
}
|
|
70
|
-
}
|
|
71
28
|
|
|
72
|
-
|
|
29
|
+
return InjectableNode;
|
|
30
|
+
};
|
|
73
31
|
}
|