Vscode-ng-language-service: The template context does not define a member called 'item'

Created on 23 Jan 2020  ·  17Comments  ·  Source: angular/vscode-ng-language-service

Describe the bug

Not sure if this issue is related to this extension, but the code does compile and work even if the language service gives me an error in the console.

In a component.html file I have this:

<ng-template ng-label-tmp ng-option-tmp let-item="item">
    {{ item.name }} -- {{ item.description }} -- {{ item.price | currency }}
</ng-template>

the let-item="item" piece is red highlighted and this is the description:

The template context does not define a member called 'item'

To Reproduce
Use any *-tmp directives along with let-* ones.

Expected behavior
No Errors should be highlighted for the let-item="item" directive.

Logs

  1. Console output
[Info  - 17:40:22] Angular language server process ID: 21305
[Info  - 17:40:22] Using typescript v3.7.4 from /home/user/.vscode/extensions/angular.ng-template-0.900.5/node_modules/typescript/lib/tsserverlibrary.js
[Info  - 17:40:24] Using @angular/language-service v9.0.0-rc.9 from /home/user/.vscode/extensions/angular.ng-template-0.900.5/server/node_modules/@angular/language-service/bundles/language-service.umd.js
[Info  - 17:40:25] Log file: /home/user/.config/Code/logs/20200123T153034/exthost9/Angular.ng-template/nglangsvc.log
[Error - 17:40:39] No config file for /home/user/git/projects/my-app/src/app/ui-widgets/app-card/add-item-card/add-item-card.component.html
  1. Log file
    nglangsvc.log

Additional context
If I remove both ng-label-tmp and ng-option-tmp directives the error disappears.

UPDATE
Downgrading the extension to v0.900.4 the message error disappears.
The v0.900.5 starts to show this error.

bug

Most helpful comment

@fdinardo okay, the PR above (https://github.com/angular/angular/pull/35036) will make this a warning 🙂

All 17 comments

Can you show the definition of the ng-label-tmp or ng-option-tmp directives? I think this may be a case of https://github.com/angular/vscode-ng-language-service/issues/251.

Hi thanks for the answer.
I looked at #251, I don't think it's the same case, because #251 has been opened on 18 May 2018.
Until 2 days ago this error didn't show up. It started with ng-language-service v0.900.5 and above.

The definitions of those directives are in a third party package.

For now, I just downgraded the extension to v0.900.4 which is the latest that was working without that error.

When diagnostics are performed in the language service, the context for the embedded view is assumed to be the context of the structural directive that is applied to ng-template.
This assumption is incorrect, according to the docs in angular.io:

Angular automatically expands the shorthand syntax as it compiles the template.
The context for each embedded view is logically merged to the current component
context according to its lexical position.

Is there anything we can do to help fix this? There's been a couple of patches since 900.5, and this is still an issue.

Here is a directive that reproduces the issue for me:

````

import { combineLatest, isObservable, Observable, Subscription } from 'rxjs';
import { map, scan, startWith } from 'rxjs/operators';

import {
ChangeDetectorRef, Directive, Input, OnDestroy, OnInit, TemplateRef, ViewContainerRef
} from '@angular/core';

interface Streams {
}

export class SubscribeContext {
public $implicit: any = null;
public ngSubscribe: any = null;
}

@Directive({
selector: '[ngSubscribe]',
exportAs: 'ngSubscribe',
})
export class NgSubscribeDirective implements OnInit, OnDestroy {
private context: SubscribeContext = new SubscribeContext();
private current$: Observable;
private subscription = new Subscription();

@Input()
set ngSubscribe(input: Observable | Streams) {
const current$ = isObservable(input) ? input : joinAll(input as Streams);

if (compare(this.current$, current$)) {
  return;
}

this.ngOnDestroy();

this.current$ = current$;

this.subscription = this.current$.subscribe(value => {
  this.context.ngSubscribe = value;

  if (value && !isObservable(value)) {
    Object.keys(value).forEach(key => this.context[key] = value[key]);
  }

  this._cdr.markForCheck();
});

}

constructor(
private _cdr: ChangeDetectorRef,
private _view: ViewContainerRef,
private _template: TemplateRef,
) { }

ngOnInit() {
this._view.createEmbeddedView(this._template, this.context);
}

ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}

function joinAll(streams: Streams): Observable {
return combineLatest(
Object.keys(streams).map(
key => streams[key].pipe(
startWith(null),
scan((acc, val) => ({ ...acc, [key]: val }), {}),
),
),
).pipe(
map(value => Object.assign({}, ...value)),
);
}

function compare(value1, value2) {
if (value1 === value2) {
return true;
}
/* eslint-disable no-self-compare */
// if both values are NaNs return true
if (value1 !== value1 && value2 !== value2) {
return true;
}
if ({}.toString.call(value1) !== {}.toString.call(value2)) {
return false;
}
if (value1 !== Object(value1)) {
// non equal primitives
return false;
}
if (!value1) {
return false;
}
if (Array.isArray(value1)) {
return compareArrays(value1, value2);
}
if ({}.toString.call(value1) === '[object Object]') {
return compareObjects(value1, value2);
} else {
return compareNativeSubtypes(value1, value2);
}
}

function compareNativeSubtypes(value1, value2) {
// e.g. Function, RegExp, Date
return value1.toString() === value2.toString();
}

function compareArrays(value1, value2) {
const len = value1.length;
if (len !== value2.length) {
return false;
}
let alike = true;
for (let i = 0; i < len; i++) {
if (!compare(value1[i], value2[i])) {
alike = false;
break;
}
}
return alike;
}

function compareObjects(value1, value2) {
const keys1 = Object.keys(value1).sort();
const keys2 = Object.keys(value2).sort();
const len = keys1.length;
if (len !== keys2.length) {
return false;
}
for (let i = 0; i < len; i++) {
const key1 = keys1[i];
const key2 = keys2[i];
if (!(key1 === key2 && compare(value1[key1], value2[key2]))) {
return false;
}
}
return true;
}
````

@lincolnthree your example is a case of #251. If you refine the type of _template in NgSubscribeDirective's constructor to be TemplateRef<SubscribeContext>, the diagnostic message will resolve.

@fdinardo Are you able to provide the definitions of the directives you are using that are causing this issue? Without that information, I have been unable to reproduce the error message aside as it appears in cases like #251.

For what it is worth, the code generating the diagnostic message is https://github.com/angular/angular/blob/d15d6b7a6346e76cd4a4fbf74525c66706ab6f5a/packages/language-service/src/expression_diagnostics.ts#L290-L307. The language service looks for the template context of applied directive and checks if that context contains a variable declared in the template.

@ayazhafiz Thank you, that did indeed resolve the issue! Updating the rest now :)

I have the error too. And it only recently appeared in our Kendo Grids. Does that mean it's a problem on their side?

I’m not familiar with what Kendo Grids are. As mentioned previously, without seeing the definition of the directive it is difficult to say. If the template context is typed relatively loosely, for example as any or object, then there is not much the language service can do but provide a suggestion to more strongly type the context (#251).


От: Jeroen Rombouts notifications@github.com
Отправлено: вторник, января 28, 2020 2:41 PM
Кому: angular/vscode-ng-language-service
Копия: hafiz; Mention
Тема: Re: [angular/vscode-ng-language-service] The template context does not define a member called 'item' (#572)

I have the error too. And it only recently appears in our Kendo Grids. Does that mean it's a problem on their side?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/angular/vscode-ng-language-service/issues/572?email_source=notifications&email_token=AE6GL6TNIKZWPBPRVKXQU7DRACYA3A5CNFSM4KK2RS6KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEKFHQDY#issuecomment-579500047, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AE6GL6RE7L7ZXPMBMHVQ4XLRACYA3ANCNFSM4KK2RS6A.

This seems to be a problem with the language service. See @kyliau comment above.

@ayazhafiz The definition of those directives comes from a third party package.
Here is the definition for ng-option-tmp:

@Directive({ selector: '[ng-option-tmp]' })
export class NgOptionTemplateDirective {
    constructor(public template: TemplateRef<any>) { }
}

So, this package uses TemplateRef<any> which regards the case #251 .

I just don't get why this diagnostic message doesn't come up with v9.000.4 and previous versions!
This is actually a problem, in this case I can make PR to that package to fix this, but sometime we could use package we can't change, and the error keep show up, leading us to lost focus on the work and figure out if it's a "real" error or this (which actually compiles correctly).

For more info about that package you can check this:
https://github.com/ng-select/ng-select/blob/master/src/ng-select/lib/ng-templates.directive.ts

I just don't get why this diagnostic message doesn't come up with v9.000.4 and previous versions!

I agree, this is a strange. Recently there has been a lot of development on the language service, especially with respect to template bindings and variables. I think this is just one case we didn't catch correctly before.

leading us to lost focus on the work and figure out if it's a "real" error or this (which actually compiles correctly).

As far as I'm aware, the Angular compiler does not perform a check on the members made available by the template context; for me, AOT compilation with TemplateRef<null> seemed to work. At least for now, this diagnostic message is caused by the language service's attempt to provide some additional type checking.

With that, I think there are at least four ways to proceed:

  1. Keep diagnostic as it is, with the "better" message mentioned in #251 and added in https://github.com/angular/angular/pull/34751

  2. Reduce the severity of the diagnostic from an error to a warning or suggestion

  3. Make a special case for allowing any members when the context is any or object. I don't like this option, as it is very brittle and and would not fix the cases for other "base" contexts (like base classes)

  4. Remove the diagnostic entirely

Curious to hear others' thoughts.

this diagnostic message is caused by the language service's attempt to provide some additional type checking.

What I meant with my last comment is that the diagnostic message comes up in the console -> problems, and colors red the file in the structure view. When I look at it I think:
"Hey what is wrong with this file?" and I have to figure out that this is not a "real" error.

I agree, this is a strange

Another strange behavior is that the diagnostic error (with the file highlighted in red) is not recognized until I open the file...

Curious to hear others' thoughts.

IMHO, I think that solution n.2 does fit the current state of the art. I am not into angular language service development, but, as a user, I would appreciate a warning or a suggestion since this "error" doesn't prevent the compilation.

@fdinardo okay, the PR above (https://github.com/angular/angular/pull/35036) will make this a warning 🙂

I saw the PR right now!
Your solution is perfect, it's a warning but not a big one! it tells us that something is potentially wrong, but could not be blocking!

Thanks... I am Very happy with this PR :slightly_smiling_face:

I think we can close this.

The fix will be included in v0.900.8 scheduled to be published on Thursday Jan 30 (PST)

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings