6

There are some custom elements and attributes in component template (in this example they are used by third-party non-Angular code):

<foo></foo>
<div data-bar="{{ bar }}"></div>

They cause a compiler error:

Template parse errors:
'foo' is not a known element:
1. If 'foo' is an Angular component, then verify that it is part of this module.
2. If 'foo' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '@NgModule.schemas' of this component to suppress this message. ("
    [ERROR ->]<foo></foo>
    <div data-bar="{{ bar }}"></div>
  "): App@1:4
Can't bind to 'bar' since it isn't a known property of 'div'. ("
    <foo></foo>
    <div [ERROR ->]data-bar="{{ bar }}"></div>
  ")
...

How can foo element and data-bar attribute be added to compiler schema?

NO_ERRORS_SCHEMA is not an option because it is not desirable for other unknown elements and attributes to be whitelisted.

Estus Flask
  • 206,104
  • 70
  • 425
  • 565

3 Answers3

8

You can try to override DomElementSchemaRegistry like this:

import { DomElementSchemaRegistry, ElementSchemaRegistry } from '@angular/compiler'
import { SchemaMetadata } from '@angular/core';

const MY_DOM_ELEMENT_SCHEMA = [
  'foo'
];

const MY_CUSTOM_PROPERTIES_SCHEMA = {
  'div': {
    'bar': 'string'
  }
};

export class CustomDomElementSchemaRegistry extends DomElementSchemaRegistry {
  constructor() {
    super();
  }

  hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean {
    return MY_DOM_ELEMENT_SCHEMA.indexOf(tagName) > -1 || 
         super.hasElement(tagName, schemaMetas);
  }

  hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean {
    const elementProperties = MY_CUSTOM_PROPERTIES_SCHEMA[tagName.toLowerCase()];
    return (elementProperties && elementProperties[propName]) || 
        super.hasProperty(tagName, propName, schemaMetas);
  }
}

platformBrowserDynamic().bootstrapModule(AppModule, {
  providers: [{ provide: ElementSchemaRegistry, useClass: CustomDomElementSchemaRegistry }]
});

Plunker Example

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • Thanks, good one! I was pretty sure that DomElementSchemaRegistry class is internal. Btw, it has to be `!!(elementProperties && elementProperties[propName])`. – Estus Flask Mar 25 '17 at 10:24
  • 1
    Perhaps something has changed in later Angular versions, but I am not apparently able to bootstrap the module in this way. TBH, I've rarely-if-ever done anything fancy with the bootstrapping so it could just be that I don't understand. In trying to follow this answer, I'm receiving an error because, apparently, the argument passed into bootstrapModule does not match its expected type. I've attempt to Stackblitz it at: https://stackblitz.com/edit/add-custom-elements-and-attributes-to-compiler-schema?file=src/main.ts – Dan Overlander Jun 19 '18 at 15:49
1

Looking good, but notice in you Plunker Example, the div in the markup is as below:

data-bar="{{ bar }}"

bar being = "test" is outputting on the dom just as:

<div></div>

and not

<div data-bar="test"></div>

If you want the attribute rendered - say for polymer components you can use the following.

[attr.data-bar]="bar" in the markup
peterh
  • 11,875
  • 18
  • 85
  • 108
  • I'm not sure what you mean but if you're suggesting that `data-bar="{{ bar }}"` could be replaced with `data-bar="test"`, then no, it's not possible - the whole point of the question is how custom attribute can be dynamic. `bar` doesn't change in the example, but this was made for simplicity. – Estus Flask Apr 13 '17 at 16:14
  • {{bar}} is the dynamic value for attribute data-bar in @yurzui example. my question is to have that attribute rendered on the DOM with its value set, and not just have
    rendered
    – Conor Tierney Apr 14 '17 at 07:24
  • Sorry nevermind - got it - [attr.data-bar]="bar" and angular will be able to set the attr dynamically to render, above example useful if you dont want to enable all elements as you say using CUSTOM_ELEMENTS_SCHEMA – Conor Tierney Apr 14 '17 at 08:22
  • Conor is right. Even if we add data-bar attr as exclusion for element schema - Angular removes attribute at final render (so div has no bar attr at all) – Oleksandr Poshtaruk Feb 06 '20 at 11:11
0

Injecting CustomDomElementSchemaRegistry works now-a-days a little-bit differently:

JIT compiler

The injector has changed since the previous answer, so the type signature changed a very little. From version 5 StaticInjector needs the deps array with useClass (Angular 8.2)

platformBrowserDynamic().bootstrapModule(AppModule, 
 {
   providers: [
    { 
       provide: ElementSchemaRegistry, 
       useClass: CustomDomElementSchemaRegistry, 
       deps: [] 
    }
   ]
 }
);

AOT compiler

We can NOT use the injector for ahead of time compilation, but there is a possiblity to "monkey-patch" the class DomElementSchemaRegistry before it is instantiated by the compiler, for a detailed description check it here:

https://medium.com/angular-in-depth/angular-elementschemaregistry-for-dummies-83d54cd31478

kisp
  • 6,402
  • 3
  • 21
  • 19
  • Thanks. Can you please explain further what this answer adds to the code from the accepted one? It seems the only thing that is different is `deps: []`, which isn't used for class providers. It's true that there's static injector in A5, but legacy reflective injector wasn't used in previous solution, so I'd expect it to be still relevant. – Estus Flask Feb 07 '20 at 13:32
  • Sorry, it is not complete yet. The compilerOptions has a type signature which compiles with angular 8.2, but the custom class may not called. I'll mark it on the answer itself. Its an answer to Dan Overlander, where I could put in code example. – kisp Feb 07 '20 at 21:36