recreate providers

This commit is contained in:
Michał Sieciechowicz 2026-01-17 00:05:18 +01:00
parent 91f8dd5d82
commit be5961dde9
6 changed files with 68 additions and 108 deletions

View File

@ -1,7 +1,7 @@
// Core types and classes // Core types and classes
export { Core } from "./core"; export { Core } from "./core";
export type { Type, ComponentType, DirectiveType } from "./module/type"; export type { Type, ComponentType, DirectiveType } from "./module/type";
export { Injector, LocalProvider } from "./module/injector"; export { Injector } from "./module/injector";
// Component system // Component system
export { IComponent, ViewEncapsulation } from "./module/component"; export { IComponent, ViewEncapsulation } from "./module/component";
@ -29,6 +29,6 @@ export { signal, computed, effect } from "./angular/signals";
export type { Signal, WritableSignal, EffectRef, CreateSignalOptions, CreateEffectOptions } from "./angular/signals"; export type { Signal, WritableSignal, EffectRef, CreateSignalOptions, CreateEffectOptions } from "./angular/signals";
// types // types
export type { ApplicationConfig, EnvironmentProviders, PluginConfig, PluginRoutingMode } from "./angular/app-config"; export type { ApplicationConfig, EnvironmentProviders, PluginConfig, PluginRoutingMode, Provider } from "./angular/app-config";
export { ComponentUtils } from "./utils/component-utils"; export { ComponentUtils } from "./utils/component-utils";
export { TemplateFragment } from "./module/template-renderer"; export { TemplateFragment } from "./module/template-renderer";

View File

@ -2,12 +2,12 @@ import {
DirectiveType, DirectiveType,
DirectiveRegistry, DirectiveRegistry,
Injector, Injector,
LocalProvider,
IDirective, IDirective,
effect, effect,
EffectRef, EffectRef,
WritableSignal, WritableSignal,
} from '../index'; } from '../index';
import { Provider } from '../angular/app-config';
import { ActivatedRoute } from '../../router/angular/types'; import { ActivatedRoute } from '../../router/angular/types';
import { WebComponent } from './web-component'; import { WebComponent } from './web-component';
@ -60,7 +60,7 @@ export class DirectiveRunner {
element: HTMLElement, element: HTMLElement,
): DirectiveInstance | null { ): DirectiveInstance | null {
const injector = Injector.get(); const injector = Injector.get();
const localProviders: LocalProvider[] = [ const localProviders: Provider[] = [
{ provide: HTMLElement, useValue: element }, { provide: HTMLElement, useValue: element },
]; ];

View File

@ -1,9 +1,6 @@
import { Type } from "../index"; import { Type } from "../index";
import { Provider } from "../angular/app-config";
export interface LocalProvider {
provide: Type<any> | any;
useValue: any;
}
export class Injector { export class Injector {
private static instance: Injector; private static instance: Injector;
@ -28,76 +25,56 @@ export class Injector {
} }
public createInstance<T>(classType: Type<T>): T { public createInstance<T>(classType: Type<T>): T {
return this.createInstanceWithProviders(classType, {}); return this.createInstanceWithProviders(classType, []);
} }
public createInstanceWithProvidersOld<T>(classType: Type<T>, localProviders: Record<string, any>): T { private findProvider(token: any, providers: Provider[]): Provider | undefined {
if (!classType) { const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name;
throw new Error(`[DI] createInstance called with undefined classType`);
}
const key = (classType as any).__quarc_original_name__ || classType.name; return providers.find(p => {
// Prevent instantiation of built-in classes const providerName = typeof p.provide === 'string'
if (key === "HTMLElement") { ? p.provide
throw new Error(`[DI] Cannot create instance of HTMLElement`); : (p.provide as any).__quarc_original_name__ || p.provide.name;
} return providerName === tokenName;
});
// First check local cache
if (this.instanceCache[key]) {
return this.instanceCache[key];
}
// Then check shared instances (cross-build sharing)
if (this.sharedInstances[key]) {
const sharedInstance = this.sharedInstances[key];
return sharedInstance;
}
try {
const dependencies = this.resolveDependencies(classType);
const instance = new classType(...dependencies);
this.instanceCache[key] = instance;
this.sharedInstances[key] = instance;
return instance;
} catch (error) {
const className = this.getReadableClassName(classType);
const dependencyInfo = this.getDependencyInfo(classType);
throw new Error(`[DI] Failed to create instance of "${className}": ${(error as Error).message}\nDependencies: ${dependencyInfo}`);
}
} }
private convertLocalProvidersToRecord(localProviders: LocalProvider[]): Record<string, any> { private resolveProviderValue(provider: Provider, providers: Provider[]): any {
const record: Record<string, any> = {}; if ('useValue' in provider) {
return provider.useValue;
for (const provider of localProviders) { } else if ('useFactory' in provider && provider.useFactory) {
const key = typeof provider.provide === 'string' return provider.useFactory();
? provider.provide } else if ('useExisting' in provider && provider.useExisting) {
: (provider.provide as any).__quarc_original_name__ || provider.provide.name; const existingToken = provider.useExisting;
const existingProvider = this.findProvider(existingToken, providers);
record[key] = provider.useValue; if (existingProvider) {
return this.resolveProviderValue(existingProvider, providers);
}
const existingKey = typeof existingToken === 'string'
? existingToken
: (existingToken as any).__quarc_original_name__ || existingToken.name;
return this.sharedInstances[existingKey] || this.instanceCache[existingKey];
} else if ('useClass' in provider && provider.useClass) {
return this.createInstanceWithProviders(provider.useClass, providers);
} }
return undefined;
return record;
} }
public createInstanceWithProviders<T>(classType: Type<T>, localProviders: Record<string, any>): T; public createInstanceWithProviders<T>(classType: Type<T>, providers: Provider[]): T {
public createInstanceWithProviders<T>(classType: Type<T>, localProviders: LocalProvider[]): T;
public createInstanceWithProviders<T>(classType: Type<T>, localProviders: Record<string, any> | LocalProvider[]): T {
if (!classType) { if (!classType) {
throw new Error(`[DI] createInstanceWithProviders called with undefined classType`); throw new Error(`[DI] createInstanceWithProviders called with undefined classType`);
} }
// Convert LocalProvider[] to Record<string, any> if needed
const providersRecord = Array.isArray(localProviders)
? this.convertLocalProvidersToRecord(localProviders)
: localProviders;
try { try {
const dependencies = this.resolveDependenciesWithProviders(classType, providersRecord); console.log({
className: (classType as any).__quarc_original_name__ || classType.name,
classType,
});
const dependencies = this.resolveDependenciesWithProviders(classType, providers);
/** / /** /
console.log({ console.log({
className: (classType as any).__quarc_original_name__ || classType.name, className: (classType as any).__quarc_original_name__ || classType.name,
localProviders: providersRecord, providers,
dependencies, dependencies,
classType, classType,
}); });
@ -189,50 +166,31 @@ export class Injector {
}); });
} }
private resolveDependenciesWithProviders(classType: Type<any>, localProviders: Record<string, any>): any[] { private resolveDependenciesWithProviders(classType: Type<any>, providers: Provider[]): any[] {
const tokens = this.getConstructorParameterTypes(classType); const tokens = this.getConstructorParameterTypes(classType);
const contextProviders: Record<string, any> = {
...this.sharedInstances,
...this.instanceCache,
...localProviders,
};
return tokens.map(token => { return tokens.map(token => {
const dep = this.resolveDependency(token, contextProviders, localProviders); return this.resolveDependency(token, providers);
const depName = dep.__quarc_original_name__ || dep.name;
return dep;
}); });
} }
private resolveDependency(token: any, contextProviders: Record<string, any>, localProviders: Record<string, any>): any { private resolveDependency(token: any, providers: Provider[]): any {
const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name; const tokenName = typeof token === 'string' ? token : (token as any).__quarc_original_name__ || token.name;
// First check local providers (they have highest priority) const provider = this.findProvider(token, providers);
if (localProviders[tokenName]) { if (provider) {
const providerValue = localProviders[tokenName]; return this.resolveProviderValue(provider, providers);
}
// If the provider value is a constructor (type), create a new instance if (this.sharedInstances[tokenName]) {
if (typeof providerValue === 'function' && providerValue.prototype && providerValue.prototype.constructor === providerValue) { return this.sharedInstances[tokenName];
return this.createInstanceWithProviders(providerValue, localProviders); }
}
return providerValue; if (this.instanceCache[tokenName]) {
} return this.instanceCache[tokenName];
}
// Then check other context providers return this.createInstanceWithProviders(token, providers);
if (contextProviders[tokenName]) {
const providerValue = contextProviders[tokenName];
// If the provider value is a constructor (type), create a new instance
if (typeof providerValue === 'function' && providerValue.prototype && providerValue.prototype.constructor === providerValue) {
return this.createInstanceWithProviders(providerValue, localProviders);
}
return providerValue;
}
return this.createInstanceWithProviders(token, localProviders);
} }
private getConstructorParameterTypes(classType: Type<any>): any[] { private getConstructorParameterTypes(classType: Type<any>): any[] {

View File

@ -1,4 +1,5 @@
import { IComponent, WebComponent, Injector, LocalProvider, ComponentType, ComponentUtils, ChangeDetectorRef } from '../index'; import { IComponent, WebComponent, Injector, ComponentType, ComponentUtils, ChangeDetectorRef } from '../index';
import { Provider } from '../angular/app-config';
import { ActivatedRoute } from '../../router'; import { ActivatedRoute } from '../../router';
import '../global'; import '../global';
@ -90,23 +91,24 @@ export class WebComponentFactory {
this.getWebComponentInstances().set(webComponentId, webComponent); this.getWebComponentInstances().set(webComponentId, webComponent);
//const changeDetectorRef = new ChangeDetectorRef(webComponentId); //const changeDetectorRef = new ChangeDetectorRef(webComponentId);
const localProviders: Record<string, any> = { const localProviders: Provider[] = [
HTMLElement: element, { provide: HTMLElement, useValue: element },
//ChangeDetectorRef: changeDetectorRef, { provide: ActivatedRoute, useValue: this.findActivatedRouteFromElement(element) },
ActivatedRoute: this.findActivatedRouteFromElement(element), ];
};
const componentMeta = componentType._quarcComponent?.[0]; const componentMeta = componentType._quarcComponent?.[0];
if (componentMeta?.providers) { if (componentMeta?.providers) {
for (const providerType of componentMeta.providers) { for (const providerType of componentMeta.providers) {
if (typeof providerType === 'function' && !localProviders[providerType]) { if (typeof providerType === 'function') {
const providerInstance = injector.createInstanceWithProviders(providerType, localProviders); const alreadyProvided = localProviders.some(p => p.provide === providerType);
const provider = providerType.__quarc_original_name__ || providerType.name || providerType.constructor?.name || providerType; if (!alreadyProvided) {
localProviders[provider] = providerInstance; localProviders.push({ provide: providerType, useClass: providerType });
}
} }
} }
} }
console.log({localProviders});
return injector.createInstanceWithProviders<IComponent>(componentType, localProviders); return injector.createInstanceWithProviders<IComponent>(componentType, localProviders);
} }

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

View File

@ -1,4 +1,4 @@
import { Directive, IDirective, input, IComponent, InputSignal } from "../../core"; import { Directive, IDirective, input } from "../../core";
import { Router } from "../angular/router"; import { Router } from "../angular/router";
import { ActivatedRoute } from "../angular/types"; import { ActivatedRoute } from "../angular/types";
@ -6,7 +6,7 @@ import { ActivatedRoute } from "../angular/types";
selector: '[routerLink]', selector: '[routerLink]',
}) })
export class RouterLink implements IDirective { export class RouterLink implements IDirective {
static __quarc_original_name__ = "RouterLink"; //static __quarc_original_name__ = "RouterLink";
public routerLink = input<string | string[]>(); public routerLink = input<string | string[]>();