fix(core): prevent unknown property check for AOT-compiled components (#36072)
Prior to this commit, the unknown property check was unnecessarily invoked for AOT-compiled components (for these components, the check happens at compile time). This commit updates the code to avoid unknown property verification for AOT-compiled components by checking whether schemas information is present (as a way to detect whether this is JIT or AOT compiled component). Resolves #35945. PR Close #36072
This commit is contained in:
		
							parent
							
								
									88b0985bad
								
							
						
					
					
						commit
						4a9f0bebc3
					
				@ -1038,6 +1038,12 @@ export function setNgReflectProperties(
 | 
			
		||||
function validateProperty(
 | 
			
		||||
    tView: TView, lView: LView, element: RElement|RComment, propName: string,
 | 
			
		||||
    tNode: TNode): boolean {
 | 
			
		||||
  // 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 (tView.schemas === null) return true;
 | 
			
		||||
 | 
			
		||||
  // The property is considered valid if the element matches the schema, it exists on the element
 | 
			
		||||
  // or it is synthetic, and we are in a browser context (web worker nodes should be skipped).
 | 
			
		||||
  if (matchingSchemas(tView, lView, tNode.tagName) || propName in element ||
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,7 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import {CommonModule} from '@angular/common';
 | 
			
		||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, Injectable, InjectionToken, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element} from '@angular/core';
 | 
			
		||||
import {Component, CUSTOM_ELEMENTS_SCHEMA, Injectable, InjectionToken, NgModule, NgModuleRef, NO_ERRORS_SCHEMA, ɵsetClassMetadata as setClassMetadata, ɵɵdefineComponent as defineComponent, ɵɵdefineInjector as defineInjector, ɵɵdefineNgModule as defineNgModule, ɵɵelement as element, ɵɵproperty as property} from '@angular/core';
 | 
			
		||||
import {TestBed} from '@angular/core/testing';
 | 
			
		||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
 | 
			
		||||
import {modifiedInIvy, onlyInIvy} from '@angular/private/testing';
 | 
			
		||||
@ -247,8 +247,50 @@ describe('NgModule', () => {
 | 
			
		||||
              }).toThrowError(/'custom' is not a known element/);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
    onlyInIvy('test relies on Ivy-specific AOT format')
 | 
			
		||||
        .it('should not log unknown element warning for AOT-compiled components', () => {
 | 
			
		||||
    onlyInIvy('test relies on Ivy-specific AOT format').describe('AOT-compiled components', () => {
 | 
			
		||||
      function createComponent(
 | 
			
		||||
          template: (rf: any) => void, vars: number, consts?: (number|string)[][]) {
 | 
			
		||||
        class Comp {
 | 
			
		||||
          static ɵfac = () => new Comp();
 | 
			
		||||
          static ɵcmp = defineComponent({
 | 
			
		||||
            type: Comp,
 | 
			
		||||
            selectors: [['comp']],
 | 
			
		||||
            decls: 1,
 | 
			
		||||
            vars,
 | 
			
		||||
            consts,
 | 
			
		||||
            template,
 | 
			
		||||
            encapsulation: 2
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
        setClassMetadata(
 | 
			
		||||
            Comp, [{
 | 
			
		||||
              type: Component,
 | 
			
		||||
              args: [
 | 
			
		||||
                {selector: 'comp', template: '...'},
 | 
			
		||||
              ]
 | 
			
		||||
            }],
 | 
			
		||||
            null, null);
 | 
			
		||||
        return Comp;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      function createNgModule(Comp: any) {
 | 
			
		||||
        class Module {
 | 
			
		||||
          static ɵmod = defineNgModule({type: Module});
 | 
			
		||||
          static ɵinj = defineInjector({factory: () => new Module()});
 | 
			
		||||
        }
 | 
			
		||||
        setClassMetadata(
 | 
			
		||||
            Module, [{
 | 
			
		||||
              type: NgModule,
 | 
			
		||||
              args: [{
 | 
			
		||||
                declarations: [Comp],
 | 
			
		||||
                schemas: [NO_ERRORS_SCHEMA],
 | 
			
		||||
              }]
 | 
			
		||||
            }],
 | 
			
		||||
            null, null);
 | 
			
		||||
        return Module;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      it('should not log unknown element warning for AOT-compiled components', () => {
 | 
			
		||||
        const spy = spyOn(console, 'warn');
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
@ -258,31 +300,11 @@ describe('NgModule', () => {
 | 
			
		||||
         *  })
 | 
			
		||||
         *  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) {
 | 
			
		||||
        const MyComp = createComponent((rf: any) => {
 | 
			
		||||
          if (rf & 1) {
 | 
			
		||||
            element(0, 'custom-el');
 | 
			
		||||
          }
 | 
			
		||||
                  },
 | 
			
		||||
              encapsulation: 2
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
          setClassMetadata(
 | 
			
		||||
              MyComp, [{
 | 
			
		||||
                type: Component,
 | 
			
		||||
                args: [{
 | 
			
		||||
                  selector: 'comp',
 | 
			
		||||
                  template: '<custom-el></custom-el>',
 | 
			
		||||
                }]
 | 
			
		||||
              }],
 | 
			
		||||
              null, null);
 | 
			
		||||
        }, 0);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         *  @NgModule({
 | 
			
		||||
@ -291,19 +313,7 @@ describe('NgModule', () => {
 | 
			
		||||
         *  })
 | 
			
		||||
         *  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);
 | 
			
		||||
        const MyModule = createNgModule(MyComp);
 | 
			
		||||
 | 
			
		||||
        TestBed.configureTestingModule({
 | 
			
		||||
          imports: [MyModule],
 | 
			
		||||
@ -314,6 +324,45 @@ describe('NgModule', () => {
 | 
			
		||||
        expect(spy).not.toHaveBeenCalled();
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      it('should not log unknown property warning for AOT-compiled components', () => {
 | 
			
		||||
        const spy = spyOn(console, 'warn');
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         *  @Component({
 | 
			
		||||
         *    selector: 'comp',
 | 
			
		||||
         *    template: '<div [foo]="1"></div>',
 | 
			
		||||
         *  })
 | 
			
		||||
         *  class MyComp {}
 | 
			
		||||
         */
 | 
			
		||||
        const MyComp = createComponent((rf: any) => {
 | 
			
		||||
          if (rf & 1) {
 | 
			
		||||
            element(0, 'div', 0);
 | 
			
		||||
          }
 | 
			
		||||
          if (rf & 2) {
 | 
			
		||||
            property('foo', true);
 | 
			
		||||
          }
 | 
			
		||||
        }, 1, [[3, 'foo']]);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
         *  @NgModule({
 | 
			
		||||
         *    declarations: [MyComp],
 | 
			
		||||
         *    schemas: [NO_ERRORS_SCHEMA],
 | 
			
		||||
         *  })
 | 
			
		||||
         *  class MyModule {}
 | 
			
		||||
         */
 | 
			
		||||
        const MyModule = createNgModule(MyComp);
 | 
			
		||||
 | 
			
		||||
        TestBed.configureTestingModule({
 | 
			
		||||
          imports: [MyModule],
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const fixture = TestBed.createComponent(MyComp);
 | 
			
		||||
        fixture.detectChanges();
 | 
			
		||||
 | 
			
		||||
        expect(spy).not.toHaveBeenCalled();
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    onlyInIvy('unknown element check logs a warning rather than throwing')
 | 
			
		||||
        .it('should not warn about unknown elements with CUSTOM_ELEMENTS_SCHEMA', () => {
 | 
			
		||||
          @Component({template: `<custom-el></custom-el>`})
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user