diff --git a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts
index 696b425bbc..87520a37ba 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/src/template_symbol_builder.ts
@@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
-import {AST, ASTWithSource, BindingPipe, MethodCall, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
+import {AST, ASTWithSource, BindingPipe, MethodCall, ParseSourceSpan, PropertyRead, PropertyWrite, SafeMethodCall, SafePropertyRead, TmplAstBoundAttribute, TmplAstBoundEvent, TmplAstElement, TmplAstNode, TmplAstReference, TmplAstTemplate, TmplAstTextAttribute, TmplAstVariable} from '@angular/compiler';
import * as ts from 'typescript';
import {AbsoluteFsPath} from '../../file_system';
@@ -150,7 +150,24 @@ export class SymbolBuilder {
private getDirectiveMeta(
host: TmplAstTemplate|TmplAstElement,
directiveDeclaration: ts.Declaration): TypeCheckableDirectiveMeta|null {
- const directives = this.templateData.boundTarget.getDirectivesOfNode(host);
+ let directives = this.templateData.boundTarget.getDirectivesOfNode(host);
+
+ // `getDirectivesOfNode` will not return the directives intended for an element
+ // on a microsyntax template, for example `
`,
+ // the `dir` will be skipped, but it's needed in language service.
+ const firstChild = host.children[0];
+ if (firstChild instanceof TmplAstElement) {
+ const isMicrosyntaxTemplate = host instanceof TmplAstTemplate &&
+ sourceSpanEqual(firstChild.sourceSpan, host.sourceSpan);
+ if (isMicrosyntaxTemplate) {
+ const firstChildDirectives = this.templateData.boundTarget.getDirectivesOfNode(firstChild);
+ if (firstChildDirectives !== null && directives !== null) {
+ directives = directives.concat(firstChildDirectives);
+ } else {
+ directives = directives ?? firstChildDirectives;
+ }
+ }
+ }
if (directives === null) {
return null;
}
@@ -577,3 +594,7 @@ export class SymbolBuilder {
function anyNodeFilter(n: ts.Node): n is ts.Node {
return true;
}
+
+function sourceSpanEqual(a: ParseSourceSpan, b: ParseSourceSpan) {
+ return a.start.offset === b.start.offset && a.end.offset === b.end.offset;
+}
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts
index 8c133e94d1..d510ecf5b0 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_checker__get_symbol_of_template_node_spec.ts
@@ -223,8 +223,9 @@ runInEachFileSystem(() => {
beforeEach(() => {
const fileName = absoluteFrom('/main.ts');
+ const dirFile = absoluteFrom('/dir.ts');
const templateString = `
-
+
{{user.name}} {{user.streetNumber}}
`;
@@ -239,9 +240,23 @@ runInEachFileSystem(() => {
}
export class Cmp { users: User[]; }
`,
- declarations: [ngForDeclaration()],
+ declarations: [
+ ngForDeclaration(),
+ {
+ name: 'TestDir',
+ selector: '[dir]',
+ file: dirFile,
+ type: 'directive',
+ inputs: {name: 'name'}
+ },
+ ],
},
ngForTypeCheckTarget(),
+ {
+ fileName: dirFile,
+ source: `export class TestDir {name:string}`,
+ templates: {},
+ },
]);
templateTypeChecker = testValues.templateTypeChecker;
program = testValues.program;
@@ -250,6 +265,13 @@ runInEachFileSystem(() => {
templateNode = getAstTemplates(templateTypeChecker, cmp)[0];
});
+ it('should retrieve a symbol for a directive on a microsyntax template', () => {
+ const symbol = templateTypeChecker.getSymbolOfNode(templateNode, cmp);
+ const testDir = symbol?.directives.find(dir => dir.selector === '[dir]');
+ expect(testDir).toBeDefined();
+ expect(program.getTypeChecker().symbolToString(testDir!.tsSymbol)).toEqual('TestDir');
+ });
+
it('should retrieve a symbol for an expression inside structural binding', () => {
const ngForOfBinding =
templateNode.templateAttrs.find(a => a.name === 'ngForOf')! as TmplAstBoundAttribute;