-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Lifetime flexibility #2
Comments
Lifetime of services is managed using the Do you have another use-case that isn't covered by these options? And if so, how would you like it to work? |
Thank you @jahudka, this I have a web components based app. All the components and services are expected to be registered via dicc. Here is a hack for web components (as they require parameterless constructors). And here I use dicc injected dependency to instantiate a web component. This time a singleton is fine because I actually inject a factory, not a dependency, as I need multiple |
Well it isn't too hard to add an option to the DICC config file which would allow you to override the default service scope and I don't see a reason not to do it, so I'll just do it within, say, a day or two. Looking at your example code, I see the problem.. you want constructor injection in web components, which don't allow constructor arguments. Except... they kinda do, you just can't create them using The point is that you can just write your components as regular classes without the As a side note, if all your web components extend from, say, import { ServiceDecorator } from 'dicc';
// just export this from one of your resource files:
export const makeWebComponentsPrivate = {
scope: 'private',
} satisfies ServiceDecorator<HTMLElement>; And you can also use an auto-implemented factory in order to be able to pass some arguments to the component's constructor at the call site, on top of injecting services, like this: export class Counter {
constructor(state: CounterState, color: string) {
// ...
}
}
export interface CounterFactory {
create(color: string): Counter;
} And then inject |
Thanks for your effort to give a suggestion. I am not just "someone who literally never used web components", I am not even a frontend guy 😄. The thing is that I want to keep a possibility to use markup, see https://github.com/skyscrapercity-ru/skyscrapers/blob/main/src/index.html#L10. So, I now have all the components registered in dicc from one hand, and a possibility to use them in markup from the other. Btw, |
Okay, so I have a potential solution: let's say you start out with a service class like this: export abstract class CounterComponent extends HTMLElement {
protected constructor(
private readonly state: CounterState,
private readonly anotherDep: AnotherDep,
) {
super();
}
} Then DICC could compile a service factory like this: // in the generated factory map:
'#CounterComponent0.0': {
factory: (di) => class extends CounterComponent { // notice no "new" keyword
constructor() {
super(di.get('#CounterState0.0'), di.get('#AnotherDep0.0')); // inject dependencies via super() call
}
},
onCreate: (service) => {
// when the service is first created, register the custom element:
window.customElements.define('counter-component', service);
},
}, An entrypoint service of your application could then depend on export class CounterList {
constructor(private readonly Counter: CounterComponent) {}
// later when you need it:
render() {
const counter = new this.Counter(); // no args; even document.createElement('...') would work
}
} How does that sound? You cold code this manually for each component using factory functions, but that'd be cumbersome.. so if this looks like it would work, I'll look into having a way for DICC to do that, possibly via an extension. |
The feature looks very useful for my scenario but not really sure if this is useful enough for the community. I am now forced to do similar manually in the plugin, you can find it here. And yes, this looks quite crazy 😄. As for P.S. Ability to change settings defaults looks useful regardless of what could be done to help with my case. I'd say that having |
uh... yeah, that does look wild 😂 plus I don't think it's gonna work now, the source code and structure changed quite dramatically in the new version.. anyway, basically, DICC already does most of the heavy lifting, you just need to persuade it a little bit to get it to do what you need.. services don't necessarily have to be class instances - they can be almost anything that you can give a sufficiently unique type to, and then you can just use a service factory instead of a class constructor to create an instance of the service (which is not necessarily the same thing as an instance of a class) so you can have a factory function which returns a class, e.g. like this: // components/counter.ts
export class Counter extends HTMLElement {
constructor(private readonly counterState: CounterState) {}
}
// components/types.ts
export interface ComponentClass<C extends HTMLElement> {
new (): C;
}
// definitions.ts
// factory function for an injected Counter component *class*:
export function createCounterClass(
getCounterState: () => CounterState, // define dependencies using accessors
): ComponentClass<Counter> { // return a *class*, not an instance
return class extends Counter {
constructor() {
super(getCounterState());
}
};
} Then put And you don't have to register an But of course the fact remains that you'd still have to write all the factory functions manually.. which is what I was proposing to implement as an extension to DICC, ie. a plugin / addon / optional thing that people either use or don't use according to their use case. |
Ofc if your component services are classes and not instances of classes, then you don't need to make them private / transient (and in fact you probably shouldn't, otherwise things like instanceof might stop working in some cases); you'd have to create the instances yourself as needed. But it would work with both Btw I just found out that you can do some neat tricks with mapped tuple types - e.g. you can easily convert export const createCounterClass = createComponentClassFactory(Counter, 'counter-component'); The export type Accessors<Params extends any[]> = {
[Idx in keyof Params]: () => Params[Idx];
};
export function createComponentClassFactory<C extends new(...args: any[]) => HTMLElement>(
componentClass: C,
componentName: string,
): ComponentClass<C> {
// this is what the DICC compiler would see - and thanks to the Accessors type,
// it would be able to inject the correct values for '...args':
return function(...args: Accessors<ConstructorParameters<C>>) {
const injectedComponentClass =
// @ts-expect-error: according to TS, this is a mixin, and mixin
// constructors must have a single ...args: any[] argument..
class
extends componentClass {
constructor() {
super(...args.map((arg) => arg()) as ConstructorParameters<C>);
}
};
window.customElements.define(componentName, injectedComponentClass);
return injectedComponentClass;
};
} To get this to work with DICC, only two relatively minor things need to happen:
I'm not entirely sure if injecting such component services into other services would work as expected because it's using generics, and those don't really play all that nice with DICC at the moment, but I think that if it was typed using the |
Hi, this is now implemented as of |
I've played around with this on your code while debugging the 1. Add this to
|
The current public behaviour is singletons only. It doesn't work for quite some scenarios. There is a workaround like this:
but then you always have transient life scope. If you want to have singleton lifetime for some services, you have to continue modifying this 'get' overloading instead of modifying the place of the service registration (which is the service itself in our case). I'd suggest doing something like the following:
The text was updated successfully, but these errors were encountered: