Skip to content

Commit 96ac044

Browse files
committed
fix: #1608 support factory form in provide* helpers
provideTranslateLoader, provideTranslateCompiler, provideTranslateParser, and provideMissingTranslationHandler now accept a zero-arg factory in addition to a class. Factories run inside an Angular injection context, so inject() is available for pulling dependencies into the constructed instance. The existing class-form signature is unchanged and still produces a ClassProvider. Fixes #1608
1 parent d7b2207 commit 96ac044

2 files changed

Lines changed: 233 additions & 9 deletions

File tree

projects/ngx-translate/src/lib/translate.providers.ts

Lines changed: 40 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ClassProvider, Provider, Type } from "@angular/core";
1+
import { ClassProvider, FactoryProvider, Provider, ProviderToken, Type } from "@angular/core";
22
import {
33
DefaultMissingTranslationHandler,
44
MissingTranslationHandler,
@@ -30,22 +30,53 @@ export interface RootTranslateServiceConfig extends ChildTranslateServiceConfig
3030

3131
}
3232

33-
export function provideTranslateLoader(loader: Type<TranslateLoader>): ClassProvider {
34-
return { provide: TranslateLoader, useClass: loader };
33+
function isClass<T>(fn: Type<T> | (() => T)): fn is Type<T> {
34+
return /^class\s/.test(Function.prototype.toString.call(fn));
3535
}
3636

37-
export function provideTranslateCompiler(compiler: Type<TranslateCompiler>): ClassProvider {
38-
return { provide: TranslateCompiler, useClass: compiler };
37+
function toProvider<T>(
38+
token: ProviderToken<T>,
39+
value: Type<T> | (() => T),
40+
): ClassProvider | FactoryProvider {
41+
return isClass(value)
42+
? { provide: token, useClass: value }
43+
: { provide: token, useFactory: value };
3944
}
4045

41-
export function provideTranslateParser(parser: Type<TranslateParser>): ClassProvider {
42-
return { provide: TranslateParser, useClass: parser };
46+
export function provideTranslateLoader(loader: Type<TranslateLoader>): ClassProvider;
47+
export function provideTranslateLoader(factory: () => TranslateLoader): FactoryProvider;
48+
export function provideTranslateLoader(
49+
loaderOrFactory: Type<TranslateLoader> | (() => TranslateLoader),
50+
): ClassProvider | FactoryProvider {
51+
return toProvider(TranslateLoader, loaderOrFactory);
52+
}
53+
54+
export function provideTranslateCompiler(compiler: Type<TranslateCompiler>): ClassProvider;
55+
export function provideTranslateCompiler(factory: () => TranslateCompiler): FactoryProvider;
56+
export function provideTranslateCompiler(
57+
compilerOrFactory: Type<TranslateCompiler> | (() => TranslateCompiler),
58+
): ClassProvider | FactoryProvider {
59+
return toProvider(TranslateCompiler, compilerOrFactory);
60+
}
61+
62+
export function provideTranslateParser(parser: Type<TranslateParser>): ClassProvider;
63+
export function provideTranslateParser(factory: () => TranslateParser): FactoryProvider;
64+
export function provideTranslateParser(
65+
parserOrFactory: Type<TranslateParser> | (() => TranslateParser),
66+
): ClassProvider | FactoryProvider {
67+
return toProvider(TranslateParser, parserOrFactory);
4368
}
4469

4570
export function provideMissingTranslationHandler(
4671
handler: Type<MissingTranslationHandler>,
47-
): ClassProvider {
48-
return { provide: MissingTranslationHandler, useClass: handler };
72+
): ClassProvider;
73+
export function provideMissingTranslationHandler(
74+
factory: () => MissingTranslationHandler,
75+
): FactoryProvider;
76+
export function provideMissingTranslationHandler(
77+
handlerOrFactory: Type<MissingTranslationHandler> | (() => MissingTranslationHandler),
78+
): ClassProvider | FactoryProvider {
79+
return toProvider(MissingTranslationHandler, handlerOrFactory);
4980
}
5081

5182
export function provideTranslateService(config: RootTranslateServiceConfig = {}): Provider[] {

projects/ngx-translate/src/tests/translate.providers.spec.ts

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { FactoryProvider, InjectionToken, inject } from "@angular/core";
12
import { TestBed } from "@angular/core/testing";
23
import {
34
provideTranslateService,
@@ -65,13 +66,95 @@ class TestMissingTranslationHandler extends MissingTranslationHandler {
6566
}
6667
}
6768

69+
const TEST_PREFIX_TOKEN = new InjectionToken<string>("TEST_PREFIX_TOKEN");
70+
71+
class FactoryTestLoader extends TranslateLoader {
72+
constructor(public readonly prefix: string) {
73+
super();
74+
}
75+
getTranslation(lang: string): Observable<TranslationObject> {
76+
return of({ [lang]: `${this.prefix}-${lang}` });
77+
}
78+
}
79+
80+
class FactoryTestCompiler extends TranslateCompiler {
81+
constructor(public readonly prefix: string) {
82+
super();
83+
}
84+
compile(value: string): string {
85+
return `${this.prefix}:${value}`;
86+
}
87+
compileTranslations(
88+
translations: TranslationObject,
89+
lang: string,
90+
): InterpolatableTranslationObject {
91+
void lang;
92+
return translations as InterpolatableTranslationObject;
93+
}
94+
}
95+
96+
class FactoryTestParser extends TranslateParser {
97+
constructor(public readonly prefix: string) {
98+
super();
99+
}
100+
interpolate(expr: InterpolateFunction | string, params?: InterpolationParameters): string {
101+
void params;
102+
return `${this.prefix}:${expr}`;
103+
}
104+
}
105+
106+
class FactoryTestMissingHandler extends MissingTranslationHandler {
107+
constructor(public readonly prefix: string) {
108+
super();
109+
}
110+
handle(params: MissingTranslationHandlerParams): string {
111+
return `${this.prefix}:${params.key}`;
112+
}
113+
}
114+
68115
describe("Translate Providers", () => {
69116
describe("provideTranslateLoader", () => {
70117
it("should provide TranslateLoader with specified class", () => {
71118
const provider = provideTranslateLoader(TestTranslateLoader);
72119
expect(provider.provide).toBe(TranslateLoader);
73120
expect(provider.useClass).toBe(TestTranslateLoader);
74121
});
122+
123+
it("should produce a FactoryProvider when given a zero-arg factory", () => {
124+
const factory = () => new FactoryTestLoader("static");
125+
const provider = provideTranslateLoader(factory) as FactoryProvider;
126+
expect(provider.provide).toBe(TranslateLoader);
127+
expect(provider.useFactory).toBe(factory);
128+
});
129+
130+
it("factory form produces a working loader when injected via TestBed", () => {
131+
TestBed.configureTestingModule({
132+
providers: [
133+
provideTranslateService({
134+
loader: provideTranslateLoader(() => new FactoryTestLoader("static")),
135+
}),
136+
],
137+
});
138+
const loader = TestBed.inject(TranslateLoader);
139+
expect(loader).toBeInstanceOf(FactoryTestLoader);
140+
expect((loader as FactoryTestLoader).prefix).toBe("static");
141+
});
142+
143+
it("factory can use inject() to pull a DI dependency", () => {
144+
TestBed.configureTestingModule({
145+
providers: [
146+
{ provide: TEST_PREFIX_TOKEN, useValue: "from-di" },
147+
provideTranslateService({
148+
loader: provideTranslateLoader(
149+
() => new FactoryTestLoader(inject(TEST_PREFIX_TOKEN)),
150+
),
151+
}),
152+
],
153+
});
154+
const loader = TestBed.inject(TranslateLoader);
155+
expect(loader).toBeInstanceOf(FactoryTestLoader);
156+
expect((loader as FactoryTestLoader).prefix).toBe("from-di");
157+
});
75158
});
76159

77160
describe("provideTranslateCompiler", () => {
@@ -80,6 +163,42 @@ describe("Translate Providers", () => {
80163
expect(provider.provide).toBe(TranslateCompiler);
81164
expect(provider.useClass).toBe(TestTranslateCompiler);
82165
});
166+
167+
it("should produce a FactoryProvider when given a zero-arg factory", () => {
168+
const factory = () => new FactoryTestCompiler("static");
169+
const provider = provideTranslateCompiler(factory) as FactoryProvider;
170+
expect(provider.provide).toBe(TranslateCompiler);
171+
expect(provider.useFactory).toBe(factory);
172+
});
173+
174+
it("factory form produces a working compiler when injected via TestBed", () => {
175+
TestBed.configureTestingModule({
176+
providers: [
177+
provideTranslateService({
178+
compiler: provideTranslateCompiler(() => new FactoryTestCompiler("static")),
179+
}),
180+
],
181+
});
182+
const compiler = TestBed.inject(TranslateCompiler);
183+
expect(compiler).toBeInstanceOf(FactoryTestCompiler);
184+
expect((compiler as FactoryTestCompiler).prefix).toBe("static");
185+
});
186+
187+
it("factory can use inject() to pull a DI dependency", () => {
188+
TestBed.configureTestingModule({
189+
providers: [
190+
{ provide: TEST_PREFIX_TOKEN, useValue: "from-di" },
191+
provideTranslateService({
192+
compiler: provideTranslateCompiler(
193+
() => new FactoryTestCompiler(inject(TEST_PREFIX_TOKEN)),
194+
),
195+
}),
196+
],
197+
});
198+
const compiler = TestBed.inject(TranslateCompiler);
199+
expect(compiler).toBeInstanceOf(FactoryTestCompiler);
200+
expect((compiler as FactoryTestCompiler).prefix).toBe("from-di");
201+
});
83202
});
84203

85204
describe("provideTranslateParser", () => {
@@ -88,6 +207,42 @@ describe("Translate Providers", () => {
88207
expect(provider.provide).toBe(TranslateParser);
89208
expect(provider.useClass).toBe(TestTranslateParser);
90209
});
210+
211+
it("should produce a FactoryProvider when given a zero-arg factory", () => {
212+
const factory = () => new FactoryTestParser("static");
213+
const provider = provideTranslateParser(factory) as FactoryProvider;
214+
expect(provider.provide).toBe(TranslateParser);
215+
expect(provider.useFactory).toBe(factory);
216+
});
217+
218+
it("factory form produces a working parser when injected via TestBed", () => {
219+
TestBed.configureTestingModule({
220+
providers: [
221+
provideTranslateService({
222+
parser: provideTranslateParser(() => new FactoryTestParser("static")),
223+
}),
224+
],
225+
});
226+
const parser = TestBed.inject(TranslateParser);
227+
expect(parser).toBeInstanceOf(FactoryTestParser);
228+
expect((parser as FactoryTestParser).prefix).toBe("static");
229+
});
230+
231+
it("factory can use inject() to pull a DI dependency", () => {
232+
TestBed.configureTestingModule({
233+
providers: [
234+
{ provide: TEST_PREFIX_TOKEN, useValue: "from-di" },
235+
provideTranslateService({
236+
parser: provideTranslateParser(
237+
() => new FactoryTestParser(inject(TEST_PREFIX_TOKEN)),
238+
),
239+
}),
240+
],
241+
});
242+
const parser = TestBed.inject(TranslateParser);
243+
expect(parser).toBeInstanceOf(FactoryTestParser);
244+
expect((parser as FactoryTestParser).prefix).toBe("from-di");
245+
});
91246
});
92247

93248
describe("provideMissingTranslationHandler", () => {
@@ -96,6 +251,44 @@ describe("Translate Providers", () => {
96251
expect(provider.provide).toBe(MissingTranslationHandler);
97252
expect(provider.useClass).toBe(TestMissingTranslationHandler);
98253
});
254+
255+
it("should produce a FactoryProvider when given a zero-arg factory", () => {
256+
const factory = () => new FactoryTestMissingHandler("static");
257+
const provider = provideMissingTranslationHandler(factory) as FactoryProvider;
258+
expect(provider.provide).toBe(MissingTranslationHandler);
259+
expect(provider.useFactory).toBe(factory);
260+
});
261+
262+
it("factory form produces a working handler when injected via TestBed", () => {
263+
TestBed.configureTestingModule({
264+
providers: [
265+
provideTranslateService({
266+
missingTranslationHandler: provideMissingTranslationHandler(
267+
() => new FactoryTestMissingHandler("static"),
268+
),
269+
}),
270+
],
271+
});
272+
const handler = TestBed.inject(MissingTranslationHandler);
273+
expect(handler).toBeInstanceOf(FactoryTestMissingHandler);
274+
expect((handler as FactoryTestMissingHandler).prefix).toBe("static");
275+
});
276+
277+
it("factory can use inject() to pull a DI dependency", () => {
278+
TestBed.configureTestingModule({
279+
providers: [
280+
{ provide: TEST_PREFIX_TOKEN, useValue: "from-di" },
281+
provideTranslateService({
282+
missingTranslationHandler: provideMissingTranslationHandler(
283+
() => new FactoryTestMissingHandler(inject(TEST_PREFIX_TOKEN)),
284+
),
285+
}),
286+
],
287+
});
288+
const handler = TestBed.inject(MissingTranslationHandler);
289+
expect(handler).toBeInstanceOf(FactoryTestMissingHandler);
290+
expect((handler as FactoryTestMissingHandler).prefix).toBe("from-di");
291+
});
99292
});
100293

101294
describe("provideChildTranslateService", () => {

0 commit comments

Comments
 (0)