fix(ivy): allow abstract directives to have an invalid constructor (#32987)
For abstract directives, i.e. directives without a selector, it may happen that their constructor is called explicitly from a subclass, hence its parameters are not required to be valid for Angular's DI purposes. Prior to this commit however, having an abstract directive with a constructor that has parameters that are not eligible for Angular's DI would produce a compilation error. A similar scenario may occur for `@Injectable`s, where an explicit `use*` definition allows for the constructor to be irrelevant. For example, the situation where `useFactory` is specified allows for the constructor to be called explicitly with any value, so its constructor parameters are not required to be valid. For `@Injectable`s this is handled by generating a DI factory function that throws. This commit implements the same solution for abstract directives, such that a compilation error is avoided while still producing an error at runtime if the type is instantiated implicitly by Angular's DI mechanism. Fixes #32981 PR Close #32987
This commit is contained in:
@ -1336,7 +1336,7 @@ runInEachFileSystem(os => {
|
||||
env.write('test.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
@ -1364,7 +1364,75 @@ runInEachFileSystem(os => {
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toMatch(/function Test_Factory\(t\) { throw new Error\(/ms);
|
||||
expect(jsContents)
|
||||
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
|
||||
});
|
||||
|
||||
it('should not give a compile-time error if an invalid @Injectable is used with useFactory',
|
||||
() => {
|
||||
env.tsconfig({strictInjectionParameters: true});
|
||||
env.write('test.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
useFactory: () => '42',
|
||||
})
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
|
||||
});
|
||||
|
||||
it('should not give a compile-time error if an invalid @Injectable is used with useExisting',
|
||||
() => {
|
||||
env.tsconfig({strictInjectionParameters: true});
|
||||
env.write('test.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
export class MyService {}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
useExisting: MyService,
|
||||
})
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
|
||||
});
|
||||
|
||||
it('should not give a compile-time error if an invalid @Injectable is used with useClass',
|
||||
() => {
|
||||
env.tsconfig({strictInjectionParameters: true});
|
||||
env.write('test.ts', `
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
export class MyService {}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
useClass: MyService,
|
||||
})
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toMatch(/function Test_Factory\(t\) { i0\.ɵɵinvalidFactory\(\)/ms);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1382,7 +1450,8 @@ runInEachFileSystem(os => {
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).toContain('Test.ɵfac = function Test_Factory(t) { throw new Error(');
|
||||
expect(jsContents)
|
||||
.toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()');
|
||||
});
|
||||
|
||||
it('should compile an @Injectable provided in the root on a class with a non-injectable constructor',
|
||||
@ -1399,12 +1468,69 @@ runInEachFileSystem(os => {
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toContain('Test.ɵfac = function Test_Factory(t) { throw new Error(');
|
||||
.toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('compiling invalid @Directives', () => {
|
||||
describe('directives with a selector', () => {
|
||||
it('should give a compile-time error if an invalid constructor is used', () => {
|
||||
env.tsconfig({strictInjectionParameters: true});
|
||||
env.write('test.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive({selector: 'app-test'})
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
`);
|
||||
|
||||
const errors = env.driveDiagnostics();
|
||||
expect(errors.length).toBe(1);
|
||||
expect(errors[0].messageText).toContain('No suitable injection token for parameter');
|
||||
});
|
||||
});
|
||||
|
||||
describe('abstract directives', () => {
|
||||
it('should generate a factory function that throws', () => {
|
||||
env.tsconfig({strictInjectionParameters: false});
|
||||
env.write('test.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()');
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate a factory function that throws, even under strictInjectionParameters',
|
||||
() => {
|
||||
env.tsconfig({strictInjectionParameters: true});
|
||||
env.write('test.ts', `
|
||||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class Test {
|
||||
constructor(private notInjectable: string) {}
|
||||
}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents)
|
||||
.toContain('Test.ɵfac = function Test_Factory(t) { i0.ɵɵinvalidFactory()');
|
||||
});
|
||||
});
|
||||
|
||||
describe('templateUrl and styleUrls processing', () => {
|
||||
const testsForResource = (resource: string) => [
|
||||
// [component location, resource location, resource reference]
|
||||
|
Reference in New Issue
Block a user