@joist/di 4.0.0-next.42 → 4.0.0-next.45
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 +1 -1
- package/src/lib/context/injector.ts +4 -3
- package/src/lib/context/protocol.ts +17 -5
- package/src/lib/dom-injector.test.ts +30 -14
- package/src/lib/dom-injector.ts +23 -8
- package/src/lib/inject.test.ts +21 -21
- package/src/lib/inject.ts +6 -6
- package/src/lib/injectable-el.test.ts +28 -28
- package/src/lib/injectable-el.ts +22 -18
- package/src/lib/injectable.test.ts +17 -17
- package/src/lib/injectable.ts +19 -10
- package/src/lib/injector.test.ts +45 -45
- package/src/lib/injector.ts +25 -17
- package/src/lib/lifecycle.test.ts +16 -16
- package/src/lib/lifecycle.ts +11 -7
- package/src/lib/metadata.ts +5 -3
- package/src/lib/provider.ts +2 -2
- package/src/lib.ts +11 -6
- package/target/lib/context/injector.d.ts +3 -3
- package/target/lib/context/injector.js +2 -2
- package/target/lib/context/injector.js.map +1 -1
- package/target/lib/context/protocol.d.ts +1 -1
- package/target/lib/context/protocol.js +1 -1
- package/target/lib/context/protocol.js.map +1 -1
- package/target/lib/dom-injector.d.ts +2 -1
- package/target/lib/dom-injector.js +13 -6
- package/target/lib/dom-injector.js.map +1 -1
- package/target/lib/dom-injector.test.js +19 -11
- package/target/lib/dom-injector.test.js.map +1 -1
- package/target/lib/inject.d.ts +1 -1
- package/target/lib/inject.js +2 -3
- package/target/lib/inject.js.map +1 -1
- package/target/lib/inject.test.js +21 -21
- package/target/lib/injectable-el.d.ts +1 -1
- package/target/lib/injectable-el.js +16 -14
- package/target/lib/injectable-el.js.map +1 -1
- package/target/lib/injectable-el.test.js +28 -28
- package/target/lib/injectable-el.test.js.map +1 -1
- package/target/lib/injectable.d.ts +1 -1
- package/target/lib/injectable.js +8 -8
- package/target/lib/injectable.js.map +1 -1
- package/target/lib/injectable.test.js +17 -17
- package/target/lib/injectable.test.js.map +1 -1
- package/target/lib/injector.d.ts +1 -2
- package/target/lib/injector.js +8 -13
- package/target/lib/injector.js.map +1 -1
- package/target/lib/injector.test.js +44 -44
- package/target/lib/injector.test.js.map +1 -1
- package/target/lib/lifecycle.d.ts +2 -2
- package/target/lib/lifecycle.js +2 -2
- package/target/lib/lifecycle.js.map +1 -1
- package/target/lib/lifecycle.test.js +16 -16
- package/target/lib/lifecycle.test.js.map +1 -1
- package/target/lib/metadata.d.ts +2 -2
- package/target/lib/metadata.js.map +1 -1
- package/target/lib/provider.d.ts +2 -2
- package/target/lib/provider.js +1 -1
- package/target/lib/provider.js.map +1 -1
- package/target/lib.d.ts +6 -6
- package/target/lib.js +6 -6
- package/target/lib.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Injector } from
|
|
2
|
-
import { Context, createContext } from
|
|
1
|
+
import type { Injector } from "../injector.js";
|
|
2
|
+
import { type Context, createContext } from "./protocol.js";
|
|
3
3
|
|
|
4
|
-
export const INJECTOR_CTX: Context<
|
|
4
|
+
export const INJECTOR_CTX: Context<"injector", Injector> =
|
|
5
|
+
createContext("injector");
|
|
@@ -15,7 +15,12 @@ export type UnknownContext = Context<unknown, unknown>;
|
|
|
15
15
|
/**
|
|
16
16
|
* A helper type which can extract a Context value type from a Context type
|
|
17
17
|
*/
|
|
18
|
-
export type ContextType<T extends UnknownContext> = T extends Context<
|
|
18
|
+
export type ContextType<T extends UnknownContext> = T extends Context<
|
|
19
|
+
infer _,
|
|
20
|
+
infer V
|
|
21
|
+
>
|
|
22
|
+
? V
|
|
23
|
+
: never;
|
|
19
24
|
|
|
20
25
|
/**
|
|
21
26
|
* A function which creates a Context value object
|
|
@@ -29,7 +34,10 @@ export function createContext<KeyType, ValueType>(key: KeyType) {
|
|
|
29
34
|
* A callback which is provided by a context requester and is called with the value satisfying the request.
|
|
30
35
|
* This callback can be called multiple times by context providers as the requested value is changed.
|
|
31
36
|
*/
|
|
32
|
-
export type ContextCallback<ValueType> = (
|
|
37
|
+
export type ContextCallback<ValueType> = (
|
|
38
|
+
value: ValueType,
|
|
39
|
+
unsubscribe?: () => void,
|
|
40
|
+
) => void;
|
|
33
41
|
|
|
34
42
|
/**
|
|
35
43
|
* An event fired by a context requester to signal it desires a named context.
|
|
@@ -46,8 +54,12 @@ export class ContextRequestEvent<T extends UnknownContext> extends Event {
|
|
|
46
54
|
callback: ContextCallback<ContextType<T>>;
|
|
47
55
|
subscribe?: boolean;
|
|
48
56
|
|
|
49
|
-
public constructor(
|
|
50
|
-
|
|
57
|
+
public constructor(
|
|
58
|
+
context: T,
|
|
59
|
+
callback: ContextCallback<ContextType<T>>,
|
|
60
|
+
subscribe?: boolean,
|
|
61
|
+
) {
|
|
62
|
+
super("context-request", { bubbles: true, composed: true });
|
|
51
63
|
|
|
52
64
|
this.context = context;
|
|
53
65
|
this.callback = callback;
|
|
@@ -61,6 +73,6 @@ declare global {
|
|
|
61
73
|
* A 'context-request' event can be emitted by any element which desires
|
|
62
74
|
* a context value to be injected by an external provider.
|
|
63
75
|
*/
|
|
64
|
-
|
|
76
|
+
"context-request": ContextRequestEvent<Context<unknown, unknown>>;
|
|
65
77
|
}
|
|
66
78
|
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { assert } from
|
|
2
|
-
|
|
3
|
-
import { INJECTOR_CTX } from
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
|
|
3
|
+
import { INJECTOR_CTX } from "./context/injector.js";
|
|
4
|
+
import {
|
|
5
|
+
ContextRequestEvent,
|
|
6
|
+
type UnknownContext,
|
|
7
|
+
} from "./context/protocol.js";
|
|
8
|
+
import { DOMInjector } from "./dom-injector.js";
|
|
9
|
+
import { Injector } from "./injector.js";
|
|
10
|
+
|
|
11
|
+
describe("DOMInjector", () => {
|
|
12
|
+
it("should respond to elements looking for an injector", () => {
|
|
9
13
|
const injector = new DOMInjector();
|
|
10
14
|
injector.attach(document.body);
|
|
11
15
|
|
|
12
|
-
const host = document.createElement(
|
|
16
|
+
const host = document.createElement("div");
|
|
13
17
|
document.body.append(host);
|
|
14
18
|
|
|
15
19
|
let parent: Injector | null = null;
|
|
@@ -17,7 +21,7 @@ describe('DOMInjector', () => {
|
|
|
17
21
|
host.dispatchEvent(
|
|
18
22
|
new ContextRequestEvent(INJECTOR_CTX, (i) => {
|
|
19
23
|
parent = i;
|
|
20
|
-
})
|
|
24
|
+
}),
|
|
21
25
|
);
|
|
22
26
|
|
|
23
27
|
assert.equal(parent, injector);
|
|
@@ -26,23 +30,35 @@ describe('DOMInjector', () => {
|
|
|
26
30
|
host.remove();
|
|
27
31
|
});
|
|
28
32
|
|
|
29
|
-
it(
|
|
33
|
+
it("should send request looking for other injector contexts", () => {
|
|
30
34
|
const parent = new Injector();
|
|
31
35
|
const injector = new DOMInjector();
|
|
32
36
|
|
|
33
|
-
const cb = (e:
|
|
37
|
+
const cb = (e: ContextRequestEvent<UnknownContext>) => {
|
|
34
38
|
if (e.context === INJECTOR_CTX) {
|
|
35
39
|
e.callback(parent);
|
|
36
40
|
}
|
|
37
41
|
};
|
|
38
42
|
|
|
39
|
-
document.body.addEventListener(
|
|
43
|
+
document.body.addEventListener("context-request", cb);
|
|
40
44
|
|
|
41
45
|
injector.attach(document.body);
|
|
42
46
|
|
|
43
47
|
assert.equal(injector.parent, parent);
|
|
44
48
|
|
|
45
49
|
injector.detach();
|
|
46
|
-
document.body.removeEventListener(
|
|
50
|
+
document.body.removeEventListener("context-request", cb);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should throw an error if attempting to attach an already attached DOMInjector", () => {
|
|
54
|
+
const injector = new DOMInjector();
|
|
55
|
+
|
|
56
|
+
const el = document.createElement("div");
|
|
57
|
+
|
|
58
|
+
injector.attach(el);
|
|
59
|
+
|
|
60
|
+
assert.throw(() => {
|
|
61
|
+
injector.attach(el);
|
|
62
|
+
});
|
|
47
63
|
});
|
|
48
64
|
});
|
package/src/lib/dom-injector.ts
CHANGED
|
@@ -1,33 +1,46 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { INJECTOR_CTX } from "./context/injector.js";
|
|
2
|
+
import {
|
|
3
|
+
ContextRequestEvent,
|
|
4
|
+
type UnknownContext,
|
|
5
|
+
} from "./context/protocol.js";
|
|
6
|
+
import { Injector } from "./injector.js";
|
|
4
7
|
|
|
5
8
|
export class DOMInjector extends Injector {
|
|
6
9
|
#element: HTMLElement | null = null;
|
|
7
10
|
#controller: AbortController | null = null;
|
|
8
11
|
|
|
12
|
+
get isAttached(): boolean {
|
|
13
|
+
return this.#element !== null && this.#controller !== null;
|
|
14
|
+
}
|
|
15
|
+
|
|
9
16
|
attach(element: HTMLElement): void {
|
|
17
|
+
if (this.isAttached) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`This DOMInjector is already attached to ${this.#element}. Detach first before attaching again`,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
this.#element = element;
|
|
11
24
|
this.#controller = new AbortController();
|
|
12
25
|
|
|
13
26
|
this.#element.addEventListener(
|
|
14
|
-
|
|
27
|
+
"context-request",
|
|
15
28
|
(e: ContextRequestEvent<UnknownContext>) => {
|
|
16
29
|
if (e.context === INJECTOR_CTX) {
|
|
17
|
-
if (e.target !==
|
|
30
|
+
if (e.target !== element) {
|
|
18
31
|
e.stopPropagation();
|
|
19
32
|
|
|
20
33
|
e.callback(this);
|
|
21
34
|
}
|
|
22
35
|
}
|
|
23
36
|
},
|
|
24
|
-
{ signal: this.#controller.signal }
|
|
37
|
+
{ signal: this.#controller.signal },
|
|
25
38
|
);
|
|
26
39
|
|
|
27
40
|
this.#element.dispatchEvent(
|
|
28
41
|
new ContextRequestEvent(INJECTOR_CTX, (parent) => {
|
|
29
|
-
this.
|
|
30
|
-
})
|
|
42
|
+
this.parent = parent;
|
|
43
|
+
}),
|
|
31
44
|
);
|
|
32
45
|
}
|
|
33
46
|
|
|
@@ -35,5 +48,7 @@ export class DOMInjector extends Injector {
|
|
|
35
48
|
if (this.#controller) {
|
|
36
49
|
this.#controller.abort();
|
|
37
50
|
}
|
|
51
|
+
|
|
52
|
+
this.#element = null;
|
|
38
53
|
}
|
|
39
54
|
}
|
package/src/lib/inject.test.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { assert } from
|
|
1
|
+
import { assert } from "chai";
|
|
2
2
|
|
|
3
|
-
import { inject } from
|
|
4
|
-
import { injectable } from
|
|
5
|
-
import { Injector } from
|
|
6
|
-
import { StaticToken } from
|
|
3
|
+
import { inject } from "./inject.js";
|
|
4
|
+
import { injectable } from "./injectable.js";
|
|
5
|
+
import { Injector } from "./injector.js";
|
|
6
|
+
import { StaticToken } from "./provider.js";
|
|
7
7
|
|
|
8
|
-
it(
|
|
8
|
+
it("should throw error if called in constructor", () => {
|
|
9
9
|
assert.throws(() => {
|
|
10
10
|
class FooService {
|
|
11
|
-
value =
|
|
11
|
+
value = "1";
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
@injectable()
|
|
@@ -23,12 +23,12 @@ it('should throw error if called in constructor', () => {
|
|
|
23
23
|
const parent = new Injector();
|
|
24
24
|
|
|
25
25
|
parent.inject(BarService);
|
|
26
|
-
},
|
|
26
|
+
}, "BarService is either not injectable or a service is being called in the constructor.");
|
|
27
27
|
});
|
|
28
28
|
|
|
29
|
-
it(
|
|
29
|
+
it("should throw error if static token is unavailable", () => {
|
|
30
30
|
assert.throws(() => {
|
|
31
|
-
const TOKEN = new StaticToken(
|
|
31
|
+
const TOKEN = new StaticToken("test");
|
|
32
32
|
|
|
33
33
|
const parent = new Injector();
|
|
34
34
|
|
|
@@ -36,9 +36,9 @@ it('should throw error if static token is unavailable', () => {
|
|
|
36
36
|
}, 'Provider not found for "test"');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
-
it(
|
|
39
|
+
it("should use the calling injector as parent", () => {
|
|
40
40
|
class FooService {
|
|
41
|
-
value =
|
|
41
|
+
value = "1";
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
@injectable()
|
|
@@ -52,23 +52,23 @@ it('should use the calling injector as parent', () => {
|
|
|
52
52
|
FooService,
|
|
53
53
|
{
|
|
54
54
|
use: class extends FooService {
|
|
55
|
-
value =
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
]
|
|
59
|
-
]
|
|
55
|
+
value = "100";
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
],
|
|
60
60
|
});
|
|
61
61
|
|
|
62
|
-
assert.strictEqual(parent.inject(BarService).foo().value,
|
|
62
|
+
assert.strictEqual(parent.inject(BarService).foo().value, "100");
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
it(
|
|
66
|
-
const TOKEN = new StaticToken(
|
|
65
|
+
it("should inject a static token", () => {
|
|
66
|
+
const TOKEN = new StaticToken("test", () => "Hello World");
|
|
67
67
|
|
|
68
68
|
@injectable()
|
|
69
69
|
class HelloWorld {
|
|
70
70
|
hello = inject(TOKEN);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
assert.strictEqual(new HelloWorld().hello(),
|
|
73
|
+
assert.strictEqual(new HelloWorld().hello(), "Hello World");
|
|
74
74
|
});
|
package/src/lib/inject.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { injectables } from "./injector.js";
|
|
2
|
+
import type { InjectionToken } from "./provider.js";
|
|
3
3
|
|
|
4
4
|
export type Injected<T> = () => T;
|
|
5
5
|
|
|
6
|
-
export function inject<This extends object, T>(
|
|
6
|
+
export function inject<This extends object, T>(
|
|
7
|
+
token: InjectionToken<T>,
|
|
8
|
+
): Injected<T> {
|
|
7
9
|
return function (this: This) {
|
|
8
10
|
const injector = injectables.get(this);
|
|
9
11
|
|
|
10
12
|
if (injector === undefined) {
|
|
11
|
-
const name = this.constructor.name;
|
|
12
|
-
|
|
13
13
|
throw new Error(
|
|
14
|
-
`${name} is either not injectable or a service is being called in the constructor. \n Either add the @injectable() to your class or use the @injected callback method
|
|
14
|
+
`${this.constructor.name} is either not injectable or a service is being called in the constructor. \n Either add the @injectable() to your class or use the @injected callback method.`,
|
|
15
15
|
);
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { assert } from
|
|
1
|
+
import { assert } from "chai";
|
|
2
2
|
|
|
3
|
-
import { inject } from
|
|
4
|
-
import { injectable } from
|
|
3
|
+
import { inject } from "./inject.js";
|
|
4
|
+
import { injectable } from "./injectable.js";
|
|
5
5
|
|
|
6
|
-
it(
|
|
6
|
+
it("should allow services to be injected into custom element", () => {
|
|
7
7
|
class Foo {}
|
|
8
8
|
|
|
9
9
|
@injectable()
|
|
@@ -11,14 +11,14 @@ it('should allow services to be injected into custom element', () => {
|
|
|
11
11
|
foo = inject(Foo);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
customElements.define(
|
|
14
|
+
customElements.define("injectable-1", MyElement);
|
|
15
15
|
|
|
16
16
|
const el = new MyElement();
|
|
17
17
|
|
|
18
18
|
assert.instanceOf(el.foo(), Foo);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
it(
|
|
21
|
+
it("should allow services to be injected into custom elements that has been extended", () => {
|
|
22
22
|
class Foo {}
|
|
23
23
|
|
|
24
24
|
class MyBaseElement extends HTMLElement {}
|
|
@@ -28,14 +28,14 @@ it('should allow services to be injected into custom elements that has been exte
|
|
|
28
28
|
foo = inject(Foo);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
customElements.define(
|
|
31
|
+
customElements.define("injectable-2", MyElement);
|
|
32
32
|
|
|
33
33
|
const el = new MyElement();
|
|
34
34
|
|
|
35
35
|
assert.instanceOf(el.foo(), Foo);
|
|
36
36
|
});
|
|
37
37
|
|
|
38
|
-
it(
|
|
38
|
+
it("should handle parent HTML Injectors", async () => {
|
|
39
39
|
@injectable()
|
|
40
40
|
class A {}
|
|
41
41
|
|
|
@@ -49,8 +49,8 @@ it('should handle parent HTML Injectors', async () => {
|
|
|
49
49
|
@injectable({
|
|
50
50
|
providers: [
|
|
51
51
|
[B, { use: B }],
|
|
52
|
-
[A, { use: AltA }]
|
|
53
|
-
]
|
|
52
|
+
[A, { use: AltA }],
|
|
53
|
+
],
|
|
54
54
|
})
|
|
55
55
|
class Parent extends HTMLElement {}
|
|
56
56
|
|
|
@@ -59,10 +59,10 @@ it('should handle parent HTML Injectors', async () => {
|
|
|
59
59
|
b = inject(B);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
customElements.define(
|
|
63
|
-
customElements.define(
|
|
62
|
+
customElements.define("injectable-parent-1", Parent);
|
|
63
|
+
customElements.define("injectable-child-1", Child);
|
|
64
64
|
|
|
65
|
-
const el = document.createElement(
|
|
65
|
+
const el = document.createElement("div");
|
|
66
66
|
el.innerHTML = /*html*/ `
|
|
67
67
|
<injectable-parent-1>
|
|
68
68
|
<injectable-child-1></injectable-child-1>
|
|
@@ -71,24 +71,24 @@ it('should handle parent HTML Injectors', async () => {
|
|
|
71
71
|
|
|
72
72
|
document.body.append(el);
|
|
73
73
|
|
|
74
|
-
const child = el.querySelector<Child>(
|
|
74
|
+
const child = el.querySelector<Child>("injectable-child-1");
|
|
75
75
|
|
|
76
|
-
assert.instanceOf(child
|
|
76
|
+
assert.instanceOf(child?.b().a(), AltA);
|
|
77
77
|
|
|
78
78
|
el.remove();
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
it(
|
|
81
|
+
it("should handle changing contexts", async () => {
|
|
82
82
|
class A {}
|
|
83
83
|
class AltA implements A {}
|
|
84
84
|
|
|
85
85
|
@injectable({
|
|
86
|
-
providers: [[A, { use: A }]]
|
|
86
|
+
providers: [[A, { use: A }]],
|
|
87
87
|
})
|
|
88
88
|
class Ctx1 extends HTMLElement {}
|
|
89
89
|
|
|
90
90
|
@injectable({
|
|
91
|
-
providers: [[A, { use: AltA }]]
|
|
91
|
+
providers: [[A, { use: AltA }]],
|
|
92
92
|
})
|
|
93
93
|
class Ctx2 extends HTMLElement {}
|
|
94
94
|
|
|
@@ -97,11 +97,11 @@ it('should handle changing contexts', async () => {
|
|
|
97
97
|
a = inject(A);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
customElements.define(
|
|
101
|
-
customElements.define(
|
|
102
|
-
customElements.define(
|
|
100
|
+
customElements.define("ctx-1", Ctx1);
|
|
101
|
+
customElements.define("ctx-2", Ctx2);
|
|
102
|
+
customElements.define("ctx-child", Child);
|
|
103
103
|
|
|
104
|
-
const el = document.createElement(
|
|
104
|
+
const el = document.createElement("div");
|
|
105
105
|
el.innerHTML = /*html*/ `
|
|
106
106
|
<div>
|
|
107
107
|
<ctx-1>
|
|
@@ -114,17 +114,17 @@ it('should handle changing contexts', async () => {
|
|
|
114
114
|
|
|
115
115
|
document.body.append(el);
|
|
116
116
|
|
|
117
|
-
const ctx2 = el.querySelector(
|
|
117
|
+
const ctx2 = el.querySelector("ctx-2");
|
|
118
118
|
|
|
119
|
-
let child = el.querySelector<Child>(
|
|
119
|
+
let child = el.querySelector<Child>("ctx-child");
|
|
120
120
|
|
|
121
|
-
assert.instanceOf(child
|
|
121
|
+
assert.instanceOf(child?.a(), A);
|
|
122
122
|
|
|
123
123
|
child.remove();
|
|
124
124
|
|
|
125
|
-
ctx2
|
|
125
|
+
ctx2?.append(child);
|
|
126
126
|
|
|
127
|
-
child = el.querySelector<Child>(
|
|
127
|
+
child = el.querySelector<Child>("ctx-child");
|
|
128
128
|
|
|
129
|
-
assert.instanceOf(child
|
|
129
|
+
assert.instanceOf(child?.a(), AltA);
|
|
130
130
|
});
|
package/src/lib/injectable-el.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
(Symbol as any).metadata ??= Symbol("Symbol.metadata");
|
|
2
|
+
|
|
3
|
+
import { INJECTOR_CTX } from "./context/injector.js";
|
|
4
|
+
import { ContextRequestEvent } from "./context/protocol.js";
|
|
5
|
+
import { injectables } from "./injector.js";
|
|
6
|
+
import { callLifecycle } from "./lifecycle.js";
|
|
7
|
+
import type { InjectableMetadata } from "./metadata.js";
|
|
8
|
+
import type { ConstructableToken } from "./provider.js";
|
|
7
9
|
|
|
8
10
|
export function injectableEl<T extends ConstructableToken<HTMLElement>>(
|
|
9
11
|
Base: T,
|
|
10
|
-
ctx: ClassDecoratorContext
|
|
12
|
+
ctx: ClassDecoratorContext,
|
|
11
13
|
): T {
|
|
12
14
|
const metadata: InjectableMetadata = ctx.metadata;
|
|
13
15
|
|
|
@@ -18,17 +20,15 @@ export function injectableEl<T extends ConstructableToken<HTMLElement>>(
|
|
|
18
20
|
|
|
19
21
|
const injector = injectables.get(this);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
e.
|
|
23
|
+
if (injector) {
|
|
24
|
+
this.addEventListener("context-request", (e) => {
|
|
25
|
+
if (e.target !== this && e.context === INJECTOR_CTX) {
|
|
26
|
+
e.stopPropagation();
|
|
24
27
|
|
|
25
|
-
if (injector) {
|
|
26
28
|
e.callback(injector);
|
|
27
29
|
}
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
+
});
|
|
30
31
|
|
|
31
|
-
if (injector) {
|
|
32
32
|
callLifecycle(this, injector, metadata?.onCreated);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -39,8 +39,8 @@ export function injectableEl<T extends ConstructableToken<HTMLElement>>(
|
|
|
39
39
|
if (injector) {
|
|
40
40
|
this.dispatchEvent(
|
|
41
41
|
new ContextRequestEvent(INJECTOR_CTX, (ctx) => {
|
|
42
|
-
injector.
|
|
43
|
-
})
|
|
42
|
+
injector.parent = ctx;
|
|
43
|
+
}),
|
|
44
44
|
);
|
|
45
45
|
|
|
46
46
|
callLifecycle(this, injector, metadata?.onInjected);
|
|
@@ -52,13 +52,17 @@ export function injectableEl<T extends ConstructableToken<HTMLElement>>(
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
disconnectedCallback() {
|
|
55
|
-
injectables.get(this)
|
|
55
|
+
const injector = injectables.get(this);
|
|
56
|
+
|
|
57
|
+
if (injector) {
|
|
58
|
+
injector.parent = undefined;
|
|
59
|
+
}
|
|
56
60
|
|
|
57
61
|
if (super.disconnectedCallback) {
|
|
58
62
|
super.disconnectedCallback();
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
|
-
}
|
|
65
|
+
},
|
|
62
66
|
};
|
|
63
67
|
|
|
64
68
|
return def[Base.name];
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { assert } from
|
|
1
|
+
import { assert } from "chai";
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { StaticToken } from
|
|
3
|
+
import { inject } from "./inject.js";
|
|
4
|
+
import { injectable } from "./injectable.js";
|
|
5
|
+
import { Injector, injectables } from "./injector.js";
|
|
6
|
+
import { StaticToken } from "./provider.js";
|
|
7
7
|
|
|
8
|
-
it(
|
|
8
|
+
it("should locally override a provider", () => {
|
|
9
9
|
class Foo {}
|
|
10
10
|
|
|
11
11
|
class Bar extends Foo {}
|
|
12
12
|
|
|
13
13
|
@injectable({
|
|
14
|
-
providers: [[Foo, { use: Bar }]]
|
|
14
|
+
providers: [[Foo, { use: Bar }]],
|
|
15
15
|
})
|
|
16
16
|
class MyService {
|
|
17
17
|
foo = inject(Foo);
|
|
@@ -22,19 +22,19 @@ it('should locally override a provider', () => {
|
|
|
22
22
|
assert.instanceOf(el.foo(), Bar);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
it(
|
|
25
|
+
it("should define an injector for a service instance", () => {
|
|
26
26
|
@injectable()
|
|
27
27
|
class MyService {
|
|
28
|
-
constructor(public arg =
|
|
28
|
+
constructor(public arg = "a") {}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const instance = new MyService(
|
|
31
|
+
const instance = new MyService("b");
|
|
32
32
|
|
|
33
33
|
assert.ok(injectables.has(instance));
|
|
34
|
-
assert.ok(instance.arg ===
|
|
34
|
+
assert.ok(instance.arg === "b");
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
it(
|
|
37
|
+
it("should inject the current service injectable instance", () => {
|
|
38
38
|
@injectable()
|
|
39
39
|
class MyService {
|
|
40
40
|
injector = inject(Injector);
|
|
@@ -46,18 +46,18 @@ it('should inject the current service injectable instance', () => {
|
|
|
46
46
|
assert.equal(service.injector(), injectables.get(service));
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
-
it(
|
|
49
|
+
it("should not override the name of the original class", () => {
|
|
50
50
|
@injectable()
|
|
51
51
|
class MyService {}
|
|
52
52
|
|
|
53
|
-
assert.equal(MyService.name,
|
|
53
|
+
assert.equal(MyService.name, "MyService");
|
|
54
54
|
});
|
|
55
55
|
|
|
56
|
-
it(
|
|
57
|
-
const TOKEN = new StaticToken(
|
|
56
|
+
it("should provide itself for spefified tokens", () => {
|
|
57
|
+
const TOKEN = new StaticToken("MY_TOKEN");
|
|
58
58
|
|
|
59
59
|
@injectable({
|
|
60
|
-
provideSelfAs: [TOKEN]
|
|
60
|
+
provideSelfAs: [TOKEN],
|
|
61
61
|
})
|
|
62
62
|
class MyService {
|
|
63
63
|
value = inject(TOKEN);
|
package/src/lib/injectable.ts
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
(Symbol as any).metadata ??= Symbol(
|
|
1
|
+
(Symbol as any).metadata ??= Symbol("Symbol.metadata");
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { injectableEl } from "./injectable-el.js";
|
|
4
|
+
import { Injector, injectables } from "./injector.js";
|
|
5
|
+
import type {
|
|
6
|
+
ConstructableToken,
|
|
7
|
+
InjectionToken,
|
|
8
|
+
Provider,
|
|
9
|
+
} from "./provider.js";
|
|
6
10
|
|
|
7
11
|
export interface InjectableOpts {
|
|
8
12
|
name?: string;
|
|
@@ -13,7 +17,7 @@ export interface InjectableOpts {
|
|
|
13
17
|
export function injectable(opts?: InjectableOpts) {
|
|
14
18
|
return function injectableDecorator<T extends ConstructableToken<any>>(
|
|
15
19
|
Base: T,
|
|
16
|
-
ctx: ClassDecoratorContext
|
|
20
|
+
ctx: ClassDecoratorContext,
|
|
17
21
|
): T {
|
|
18
22
|
const def = {
|
|
19
23
|
[Base.name]: class extends Base {
|
|
@@ -23,25 +27,30 @@ export function injectable(opts?: InjectableOpts) {
|
|
|
23
27
|
const injector = new Injector(opts);
|
|
24
28
|
|
|
25
29
|
injector.providers.set(Injector, {
|
|
26
|
-
factory: () => injector
|
|
30
|
+
factory: () => injector,
|
|
27
31
|
});
|
|
28
32
|
|
|
29
33
|
if (opts?.provideSelfAs) {
|
|
30
34
|
for (const token of opts.provideSelfAs) {
|
|
31
35
|
injector.providers.set(token, {
|
|
32
|
-
factory: () => this
|
|
36
|
+
factory: () => this,
|
|
33
37
|
});
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
injectables.set(this, injector);
|
|
38
42
|
}
|
|
39
|
-
}
|
|
43
|
+
},
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
// Only apply custom element bootstrap logic if the decorated class is an HTMLElement
|
|
43
|
-
if (
|
|
44
|
-
if (
|
|
47
|
+
if ("HTMLElement" in globalThis) {
|
|
48
|
+
if (
|
|
49
|
+
Object.prototype.isPrototypeOf.call(
|
|
50
|
+
HTMLElement.prototype,
|
|
51
|
+
Base.prototype,
|
|
52
|
+
)
|
|
53
|
+
) {
|
|
45
54
|
return injectableEl(def[Base.name], ctx);
|
|
46
55
|
}
|
|
47
56
|
}
|