Skip to content
Permalink
Browse files

fix(ivy): prevent unknown element check for AOT-compiled components (#…

…34024)

Prior to this commit, the unknown element can happen twice for AOT-compiled components: once during compilation and once again at runtime. Due to the fact that `schemas` information is not present on Component and NgModule defs after AOT compilation, the second check (at runtime) may fail, even though the same check was successful at compile time. This commit updates the code to avoid the second check for AOT-compiled components by checking whether `schemas` information is present in a logic that executes the unknown element check.

PR Close #34024
  • Loading branch information
AndrewKushnir authored and mhevery committed Nov 24, 2019
1 parent e059df3 commit 955a3129f0f8851304578d6e730df91e5ba0ce04
@@ -3013,6 +3013,44 @@ runInEachFileSystem(os => {
`/*@__PURE__*/ (function () { i0.ɵsetClassMetadata(Service, [{ type: Injectable, args: [{ providedIn: 'root' }] }], null, null); })();`);
});

it('should not include `schemas` in component and module defs', () => {
env.write('test.ts', `
import {Component, NgModule, NO_ERRORS_SCHEMA} from '@angular/core';
@Component({
selector: 'comp',
template: '<custom-el></custom-el>',
schemas: [NO_ERRORS_SCHEMA],
})
class MyComp {}
@NgModule({
declarations: [MyComp],
schemas: [NO_ERRORS_SCHEMA],
})
class MyModule {}
`);

env.driveMain();
const jsContents = trim(env.getContents('test.js'));
expect(jsContents).toContain(trim(`
MyComp.ɵcmp = i0.ɵɵdefineComponent({
type: MyComp,
selectors: [["comp"]],
decls: 1,
vars: 0,
template: function MyComp_Template(rf, ctx) {
if (rf & 1) {
i0.ɵɵelement(0, "custom-el");
}
},
encapsulation: 2
});
`));
expect(jsContents)
.toContain(trim('MyModule.ɵmod = i0.ɵɵdefineNgModule({ type: MyModule });'));
});

it('should emit setClassMetadata calls for all types', () => {
env.write('test.ts', `
import {Component, Directive, Injectable, NgModule, Pipe} from '@angular/core';
@@ -255,6 +255,14 @@ function setDirectiveStylingInput(

function validateElement(
hostView: LView, element: RElement, tNode: TNode, hasDirectives: boolean): void {
const schemas = hostView[TVIEW].schemas;

// If `schemas` is set to `null`, that's an indication that this Component was compiled in AOT
// mode where this check happens at compile time. In JIT mode, `schemas` is always present and
// defined as an array (as an empty array in case `schemas` field is not defined) and we should
// execute the check below.
if (schemas === null) return;

const tagName = tNode.tagName;

// If the element matches any directive, it's considered as valid.
@@ -128,6 +128,13 @@ export function compileNgModuleDefs(
schemas: ngModule.schemas ? flatten(ngModule.schemas) : null,
id: ngModule.id || null,
});
// Set `schemas` on ngModuleDef to an empty array in JIT mode to indicate that runtime
// should verify that there are no unknown elements in a template. In AOT mode, that check
// happens at compile time and `schemas` information is not present on Component and Module
// defs after compilation (so the check doesn't happen the second time at runtime).
if (!ngModuleDef.schemas) {
ngModuleDef.schemas = [];
}
}
return ngModuleDef;
}
@@ -7,7 +7,8 @@
*/

import {CommonModule} from '@angular/common';
import {CUSTOM_ELEMENTS_SCHEMA, Component, NO_ERRORS_SCHEMA, NgModule} from '@angular/core';
import {CUSTOM_ELEMENTS_SCHEMA, Component, NO_ERRORS_SCHEMA, NgModule, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';

import {TestBed} from '@angular/core/testing';
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';

@@ -193,6 +194,71 @@ describe('NgModule', () => {
}).toThrowError(/'custom' is not a known element/);
});

onlyInIvy('test relies on Ivy-specific AOT format')
.it('should not throw unknown element error for AOT-compiled components', () => {
/*
* @Component({
* selector: 'comp',
* template: '<custom-el></custom-el>',
* })
* class MyComp {}
*/
class MyComp {
static ɵfac = () => new MyComp();
static ɵcmp = defineComponent({
type: MyComp,
selectors: [['comp']],
decls: 1,
vars: 0,
template: function MyComp_Template(rf, ctx) {
if (rf & 1) {
element(0, 'custom-el');
}
},
encapsulation: 2
});
}
setClassMetadata(
MyComp, [{
type: Component,
args: [{
selector: 'comp',
template: '<custom-el></custom-el>',
}]
}],
null, null);

/*
* @NgModule({
* declarations: [MyComp],
* schemas: [NO_ERRORS_SCHEMA],
* })
* class MyModule {}
*/
class MyModule {
static ɵmod = defineNgModule({type: MyModule});
static ɵinj = defineInjector({factory: () => new MyModule()});
}
setClassMetadata(
MyModule, [{
type: NgModule,
args: [{
declarations: [MyComp],
schemas: [NO_ERRORS_SCHEMA],
}]
}],
null, null);

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

expect(() => {
const fixture = TestBed.createComponent(MyComp);
fixture.detectChanges();
}).not.toThrow();
});

it('should not throw unknown element error with CUSTOM_ELEMENTS_SCHEMA', () => {
@Component({template: `<custom-el></custom-el>`})
class MyComp {

0 comments on commit 955a312

Please sign in to comment.
You can’t perform that action at this time.