Add custom elements and attributes to compiler schema
Asked Answered
R

3

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.

Reverse answered 22/3, 2017 at 13:30 Comment(0)
Y
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

Yoghurt answered 25/3, 2017 at 5:30 Comment(2)
Thanks, good one! I was pretty sure that DomElementSchemaRegistry class is internal. Btw, it has to be !!(elementProperties && elementProperties[propName]).Reverse
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: stackblitz.com/edit/…Skimmia
Z
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
Zamora answered 13/4, 2017 at 15:39 Comment(4)
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.Reverse
{{bar}} is the dynamic value for attribute data-bar in @Yoghurt example. my question is to have that attribute rendered on the DOM with its value set, and not just have <div> </div> renderedZamora
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_SCHEMAZamora
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)Tartarus
G
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

Gilbertine answered 7/2, 2020 at 12:47 Comment(2)
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.Reverse
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.Gilbertine

© 2022 - 2024 — McMap. All rights reserved.