Skip to content

Commit

Permalink
fix(core): produce proper error message for unknown props on `<ng-tem…
Browse files Browse the repository at this point in the history
…plate>`s

Currently for cases when an unknown structural directive is applied to `<ng-template>`s, an error message thrown by the framework doesn't contain a tag name, for example:

```
NG0303: Can't bind to 'unknownDir' since it isn't a known property of 'null'.
```

The underlying reason is that the tag name for the `<ng-template>` is not produced (`null` is useed as a value) by the compiler in case of inline templates and runtime logic relies on this effect.

This commit handles this situation when an error message is thrown, as the fastest way to improve the error message. More refactoring would be needed to avoid relying on the mentioned effect at runtime.
  • Loading branch information
AndrewKushnir committed May 20, 2022
1 parent 0e14df6 commit 85c576d
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 3 deletions.
17 changes: 14 additions & 3 deletions packages/core/src/render3/instructions/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1034,7 +1034,7 @@ export function elementPropertyInternal<T>(
validateAgainstEventProperties(propName);
if (!validateProperty(element, tNode.value, propName, tView.schemas)) {
// Return here since we only log warnings for unknown properties.
handleUnknownPropertyError(propName, tNode.value);
handleUnknownPropertyError(propName, tNode);
return;
}
ngDevMode.rendererSetProperty++;
Expand All @@ -1053,7 +1053,7 @@ export function elementPropertyInternal<T>(
// If the node is a container and the property didn't
// match any of the inputs or schemas we should throw.
if (ngDevMode && !matchingSchemas(tView.schemas, tNode.value)) {
handleUnknownPropertyError(propName, tNode.value);
handleUnknownPropertyError(propName, tNode);
}
}
}
Expand Down Expand Up @@ -1170,7 +1170,18 @@ export function matchingSchemas(schemas: SchemaMetadata[]|null, tagName: string|
* @param propName Name of the invalid property.
* @param tagName Name of the node on which we encountered the property.
*/
function handleUnknownPropertyError(propName: string, tagName: string): void {
function handleUnknownPropertyError(propName: string, tNode: TNode): void {
let tagName = tNode.value;

// Special-case a situation when a structural directive is applied to
// an `<ng-template>` element, for example: `<ng-template *ngIf="true">`.
// In this case the compiler generates the `ɵɵtemplate` instruction with
// the `null` as the tagName. The directive matching logic at runtime relies
// on this effect (see `isInlineTemplate`), thus using the 'ng-template' as
// a default value of the `tNode.value` is not feasible without extra refactoring.
if (!tagName && tNode.type === TNodeType.Container) {
tagName = 'ng-template';
}
const message = `Can't bind to '${propName}' since it isn't a known property of '${tagName}'.`;
if (shouldThrowErrorOnUnknownProperty) {
throw new RuntimeError(RuntimeErrorCode.UNKNOWN_BINDING, message);
Expand Down
56 changes: 56 additions & 0 deletions packages/core/test/acceptance/ng_module_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,62 @@ describe('NgModule', () => {
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'unknown-prop' since it isn't a known property of 'div'/);
});

it('should log an error on unknown props of `ng-template` if NO_ERRORS_SCHEMA is absent',
() => {
@Component({
selector: 'my-comp',
template: `
<ng-template *ngIf="condition"></ng-template>
`,
})
class MyComp {
condition = true;
}

@NgModule({
declarations: [MyComp],
})
class MyModule {
}

TestBed.configureTestingModule({imports: [MyModule]});

const spy = spyOn(console, 'error');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'ngIf' since it isn't a known property of 'ng-template'/);
});

it('should log an error on unknown props of `ng-container` if NO_ERRORS_SCHEMA is absent',
() => {
@Component({
selector: 'my-comp',
template: `
<ng-container *ngIf="condition"></ng-container>
`,
})
class MyComp {
condition = true;
}

@NgModule({
declarations: [MyComp],
})
class MyModule {
}

TestBed.configureTestingModule({imports: [MyModule]});

const spy = spyOn(console, 'error');
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
debugger;
expect(spy.calls.mostRecent().args[0])
.toMatch(/Can't bind to 'ngIf' since it isn't a known property of 'ng-container'/);
});

it('should throw an error with errorOnUnknownProperties on unknown props if NO_ERRORS_SCHEMA is absent',
() => {
@Component({
Expand Down

0 comments on commit 85c576d

Please sign in to comment.