@joist/di 4.0.0-next.2 → 4.0.0-next.21
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/README.md +37 -7
- 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 -3
- package/src/lib/injectable-el.test.ts +130 -0
- package/src/lib/injectable-el.ts +63 -0
- package/src/lib/injectable.test.ts +22 -114
- package/src/lib/injectable.ts +21 -61
- package/src/lib/injector.test.ts +132 -130
- package/src/lib/injector.ts +28 -14
- package/src/lib/lifecycle.test.ts +68 -64
- package/src/lib/lifecycle.ts +19 -4
- package/src/lib/metadata.ts +12 -0
- package/src/lib/provider.ts +4 -4
- package/src/lib.ts +1 -1
- 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.js.map +1 -1
- package/target/lib/inject.test.js +57 -89
- package/target/lib/inject.test.js.map +1 -1
- package/target/lib/injectable-el.d.ts +341 -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 +3 -6
- package/target/lib/injectable.js +14 -43
- package/target/lib/injectable.js.map +1 -1
- package/target/lib/injectable.test.js +55 -233
- package/target/lib/injectable.test.js.map +1 -1
- package/target/lib/injector.d.ts +1 -1
- package/target/lib/injector.js +18 -11
- 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 +2 -4
- package/target/lib/lifecycle.js +15 -4
- package/target/lib/lifecycle.js.map +1 -1
- package/target/lib/lifecycle.test.js +142 -123
- package/target/lib/lifecycle.test.js.map +1 -1
- package/target/lib/metadata.d.ts +6 -0
- package/target/lib/metadata.js +5 -0
- package/target/lib/metadata.js.map +1 -0
- package/target/lib/provider.d.ts +4 -4
- package/target/lib/provider.js.map +1 -1
- package/target/lib.d.ts +1 -1
- package/target/lib.js +1 -1
- package/target/lib.js.map +1 -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/README.md
CHANGED
|
@@ -16,10 +16,10 @@ Allows you to inject services into other class instances (including custom eleme
|
|
|
16
16
|
- [Hierarchical Injectors](#hierarchical-injectors)
|
|
17
17
|
- [Custom Elements](#custom-elements)
|
|
18
18
|
|
|
19
|
-
## Installation
|
|
19
|
+
## Installation
|
|
20
20
|
|
|
21
21
|
```BASH
|
|
22
|
-
npm i @joist/di
|
|
22
|
+
npm i @joist/di@next
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Injectors
|
|
@@ -228,17 +228,43 @@ const app = new Injector();
|
|
|
228
228
|
const url: string = await app.inject(URL_TOKEN);
|
|
229
229
|
```
|
|
230
230
|
|
|
231
|
+
This allows you to dynamically import services
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
const HTTP_SERVICE = new StaticToken('HTTP_SERVICE', () => {
|
|
235
|
+
return import('./http.service.js').then((m) => new m.HttpService());
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
class HackerNewsService {
|
|
239
|
+
#http = inject(HTTP_SERVICE);
|
|
240
|
+
|
|
241
|
+
async getData() {
|
|
242
|
+
const http = await this.#http();
|
|
243
|
+
|
|
244
|
+
const url = new URL('https://hacker-news.firebaseio.com/v0/beststories.json');
|
|
245
|
+
url.searchParams.set('limitToFirst', count.toString());
|
|
246
|
+
url.searchParams.set('orderBy', '"$key"');
|
|
247
|
+
|
|
248
|
+
return http.fetchJson<string[]>(url);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const url: string = await app.inject(URL_TOKEN);
|
|
253
|
+
```
|
|
254
|
+
|
|
231
255
|
## LifeCycle
|
|
232
256
|
|
|
233
257
|
To help provide more information to services that are being created, joist will call several life cycle hooks as services are created. These hooks are defined using the provided symbols so there is no risk of naming colisions.
|
|
234
258
|
|
|
235
259
|
```ts
|
|
236
260
|
class MyService {
|
|
237
|
-
|
|
261
|
+
@created()
|
|
262
|
+
onCreated() {
|
|
238
263
|
// called the first time a service is created. (not pulled from cache)
|
|
239
264
|
}
|
|
240
265
|
|
|
241
|
-
|
|
266
|
+
@injected()
|
|
267
|
+
onInjected() {
|
|
242
268
|
// called every time a service is returned, whether it is from cache or not
|
|
243
269
|
}
|
|
244
270
|
}
|
|
@@ -246,12 +272,16 @@ class MyService {
|
|
|
246
272
|
|
|
247
273
|
## Hierarchical Injectors
|
|
248
274
|
|
|
249
|
-
Injectors can be defined with a parent
|
|
275
|
+
Injectors can be defined with a parent. The top most parent will (by default) be where services are constructed and cached. Only if manually defined providers are found earlier in the chain will services be constructed lower. The injector resolution algorithm behaves as following.
|
|
250
276
|
|
|
251
277
|
1. Do I have a cached instance locally?
|
|
252
278
|
2. Do I have a local provider definition for the token?
|
|
253
|
-
3. Do I have a parent?
|
|
254
|
-
4.
|
|
279
|
+
3. Do I have a parent?
|
|
280
|
+
4. Does parent have a local instance or provider definition?
|
|
281
|
+
5. If parent exists but no instance found, create instance in parent.
|
|
282
|
+
6. If not parent, All clear, go ahead and construct and cache the requested service.
|
|
283
|
+
|
|
284
|
+
Having injectors resolve this way means that all children have access to services created by their parents.
|
|
255
285
|
|
|
256
286
|
```mermaid
|
|
257
287
|
graph TD
|
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.21",
|
|
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,11 @@
|
|
|
1
1
|
import { InjectionToken } from './provider.js';
|
|
2
|
-
|
|
3
|
-
import { Injectables } from './injector.js';
|
|
2
|
+
import { injectables } from './injector.js';
|
|
4
3
|
|
|
5
4
|
export type Injected<T> = () => T;
|
|
6
5
|
|
|
7
6
|
export function inject<This extends object, T>(token: InjectionToken<T>): Injected<T> {
|
|
8
7
|
return function (this: This) {
|
|
9
|
-
const injector =
|
|
8
|
+
const injector = injectables.get(this);
|
|
10
9
|
|
|
11
10
|
if (injector === undefined) {
|
|
12
11
|
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 InjectablElement 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,34 @@
|
|
|
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 {
|
|
27
|
+
constructor(public arg = 'a') {}
|
|
28
|
+
}
|
|
121
29
|
|
|
122
|
-
|
|
30
|
+
const instance = new MyService('b');
|
|
123
31
|
|
|
124
|
-
|
|
125
|
-
|
|
32
|
+
assert.ok(injectables.has(instance));
|
|
33
|
+
assert.ok(instance.arg === 'b');
|
|
126
34
|
});
|