This commit elaborates diagnostics produced for invalid template contexts by including the name of the embedded template type using the template context, and in the common case that the implicity property is being referenced (e.g. in a `for .. of ..` expression), suggesting to refine the type of the context. This suggestion is provided because users will sometimes use a base class as the type of the context in the embedded view, and a more specific context later on (e.g. in an `ngOnChanges` method). Closes https://github.com/angular/vscode-ng-language-service/issues/251 PR Close #34751
167 lines
3.8 KiB
TypeScript
167 lines
3.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {Component, Directive, EventEmitter, Input, OnChanges, Output, SimpleChanges, TemplateRef, ViewContainerRef} from '@angular/core';
|
|
|
|
import {Hero} from './app.component';
|
|
|
|
@Component({
|
|
template: `
|
|
<h1>
|
|
Some <~{incomplete-open-lt}a~{incomplete-open-a} ~{incomplete-open-attr} text
|
|
</h1>`,
|
|
})
|
|
export class CaseIncompleteOpen {
|
|
}
|
|
|
|
@Component({
|
|
template: '<h1>Some <a> ~{missing-closing} text</h1>',
|
|
})
|
|
export class CaseMissingClosing {
|
|
}
|
|
|
|
@Component({
|
|
template: '<h1>Some <unknown ~{unknown-element}> text</h1>',
|
|
})
|
|
export class CaseUnknown {
|
|
}
|
|
|
|
@Component({
|
|
template: '<h1>{{data | ~{before-pipe}lowe~{in-pipe}rcase~{after-pipe} }}',
|
|
})
|
|
export class Pipes {
|
|
data = 'Some string';
|
|
}
|
|
|
|
@Component({
|
|
template: '<h1 h~{no-value-attribute}></h1>',
|
|
})
|
|
export class NoValueAttribute {
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[string-model]',
|
|
})
|
|
export class StringModel {
|
|
@Input() model: string = 'model';
|
|
@Output() modelChange: EventEmitter<string> = new EventEmitter();
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[number-model]',
|
|
})
|
|
export class NumberModel {
|
|
@Input('inputAlias') model: number = 0;
|
|
@Output('outputAlias') modelChange: EventEmitter<number> = new EventEmitter();
|
|
}
|
|
|
|
@Directive({
|
|
selector: '[hint-model]',
|
|
inputs: ['hint'],
|
|
outputs: ['hintChange'],
|
|
})
|
|
export class HintModel {
|
|
hint: string = 'hint';
|
|
hintChange: EventEmitter<string> = new EventEmitter();
|
|
}
|
|
|
|
interface Person {
|
|
name: string;
|
|
age: number;
|
|
street: string;
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div *ngFor="let person of people | async">
|
|
{{person.~{async-person-name}name}}
|
|
</div>
|
|
<div *ngIf="promisedPerson | async as person">
|
|
{{person.~{promised-person-name}name}}
|
|
</div>
|
|
`,
|
|
})
|
|
export class AsyncForUsingComponent {
|
|
people: Promise<Person[]> = Promise.resolve([]);
|
|
promisedPerson: Promise<Person> = Promise.resolve({
|
|
name: 'John Doe',
|
|
age: 42,
|
|
street: '123 Angular Ln',
|
|
});
|
|
}
|
|
|
|
@Component({
|
|
template: `
|
|
<div #div>
|
|
<test-comp #test1>
|
|
{{~{test-comp-content}}}
|
|
{{test1.~{test-comp-after-test}name}}
|
|
{{div.~{test-comp-after-div}.innerText}}
|
|
</test-comp>
|
|
</div>
|
|
<test-comp #test2></test-comp>`,
|
|
})
|
|
export class References {
|
|
}
|
|
|
|
class CounterDirectiveContext<T> {
|
|
constructor(public $implicit: T) {}
|
|
}
|
|
|
|
@Directive({selector: '[counterOf]'})
|
|
export class CounterDirective implements OnChanges {
|
|
// Object does not have an "$implicit" property.
|
|
constructor(private container: ViewContainerRef, private template: TemplateRef<Object>) {}
|
|
|
|
@Input('counterOf') counter: number = 0;
|
|
ngOnChanges(_changes: SimpleChanges) {
|
|
this.container.clear();
|
|
for (let i = 0; i < this.counter; ++i) {
|
|
this.container.createEmbeddedView(this.template, new CounterDirectiveContext<number>(i + 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This Component provides the `test-comp` selector.
|
|
*/
|
|
/*BeginTestComponent*/ @Component({
|
|
selector: 'test-comp',
|
|
template: '<div>Testing: {{name}}</div>',
|
|
})
|
|
export class TestComponent {
|
|
@Input('tcName') name = 'test';
|
|
@Output('test') testEvent = new EventEmitter();
|
|
} /*EndTestComponent*/
|
|
|
|
@Component({
|
|
templateUrl: 'test.ng',
|
|
})
|
|
export class TemplateReference {
|
|
/**
|
|
* This is the title of the `TemplateReference` Component.
|
|
*/
|
|
title = 'Some title';
|
|
hero: Hero = {id: 1, name: 'Windstorm'};
|
|
heroes: Hero[] = [this.hero];
|
|
tupleArray: [string, Hero] = ['test', this.hero];
|
|
league: Hero[][] = [this.heroes];
|
|
heroesByName: {[name: string]: Hero} = {};
|
|
primitiveIndexType: {[name: string]: string} = {};
|
|
anyValue: any;
|
|
myClick(event: any) {}
|
|
}
|
|
|
|
@Component({
|
|
template: '{{~{empty-interpolation}}}',
|
|
})
|
|
export class EmptyInterpolation {
|
|
title = 'Some title';
|
|
subTitle = 'Some sub title';
|
|
}
|