Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions packages/core/schematics/ng-generate/inject-migration/migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ function createInjectReplacementCall(
const moduleName = '@angular/core';
const sourceFile = param.getSourceFile();
const decorators = getAngularDecorators(localTypeChecker, ts.getDecorators(param) || []);
const literalProps: ts.ObjectLiteralElementLike[] = [];
const literalProps = new Set<string>();
const type = param.type;
let injectedType = '';
let typeArguments = type && hasGenerics(type) ? [type] : undefined;
Expand Down Expand Up @@ -451,24 +451,27 @@ function createInjectReplacementCall(
const expression = ts.factory.createNewExpression(constructorRef, undefined, [firstArg]);
injectedType = printer.printNode(ts.EmitHint.Unspecified, expression, sourceFile);
typeArguments = undefined;
// @Attribute is implicitly optional.
hasOptionalDecorator = true;
literalProps.add('optional');
}
break;

case 'Optional':
hasOptionalDecorator = true;
literalProps.push(ts.factory.createPropertyAssignment('optional', ts.factory.createTrue()));
literalProps.add('optional');
break;

case 'SkipSelf':
literalProps.push(ts.factory.createPropertyAssignment('skipSelf', ts.factory.createTrue()));
literalProps.add('skipSelf');
break;

case 'Self':
literalProps.push(ts.factory.createPropertyAssignment('self', ts.factory.createTrue()));
literalProps.add('self');
break;

case 'Host':
literalProps.push(ts.factory.createPropertyAssignment('host', ts.factory.createTrue()));
literalProps.add('host');
break;
}
}
Expand All @@ -479,8 +482,14 @@ function createInjectReplacementCall(
const injectRef = tracker.addImport(param.getSourceFile(), 'inject', moduleName);
const args: ts.Expression[] = [ts.factory.createIdentifier(PLACEHOLDER)];

if (literalProps.length > 0) {
args.push(ts.factory.createObjectLiteralExpression(literalProps));
if (literalProps.size > 0) {
args.push(
ts.factory.createObjectLiteralExpression(
Array.from(literalProps, (prop) =>
ts.factory.createPropertyAssignment(prop, ts.factory.createTrue()),
),
),
);
}

let expression: ts.Expression = ts.factory.createCallExpression(injectRef, typeArguments, args);
Expand Down
27 changes: 26 additions & 1 deletion packages/core/schematics/test/inject_migration_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ describe('inject migration', () => {
``,
`@Directive()`,
`class MyDir {`,
` private foo = inject(new HostAttributeToken('foo'));`,
` private foo = inject(new HostAttributeToken('foo'), { optional: true });`,
`}`,
]);
});
Expand Down Expand Up @@ -1256,6 +1256,31 @@ describe('inject migration', () => {
]);
});

it('should add non-null assertion for @Attribute injections when enabled', async () => {
writeFile(
'/dir.ts',
[
`import { Attribute, Directive } from '@angular/core';`,
``,
`@Directive()`,
`class MyDir {`,
` constructor(@Attribute('tabindex') private foo: string) {}`,
`}`,
].join('\n'),
);

await runMigration({nonNullableOptional: true});

expect(tree.readContent('/dir.ts').split('\n')).toEqual([
`import { Directive, HostAttributeToken, inject } from '@angular/core';`,
``,
`@Directive()`,
`class MyDir {`,
` private foo = inject(new HostAttributeToken('tabindex'), { optional: true })!;`,
`}`,
]);
});

it('should pick up the first non-literal type if a parameter has a union type', async () => {
writeFile(
'/dir.ts',
Expand Down