Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC]elements schema: add support for custom schemas #12045

Open
vicb opened this issue Oct 3, 2016 · 45 comments
Open

[RFC]elements schema: add support for custom schemas #12045

vicb opened this issue Oct 3, 2016 · 45 comments
Assignees
Labels
area: core Issues related to the framework runtime area: security Issues related to built-in security features, such as HTML sanitation core: directive matching cross-cutting: custom elements feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature freq2: medium P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Milestone

Comments

@vicb
Copy link
Contributor

vicb commented Oct 3, 2016

in @Compoent, schemas can currently only use the following values:

  • CUSTOM_ELEMENTS_SCHEMA -> allow any custom-tag with any property,
  • NO_ERRORS_SCHEMA -> allow any tag and any property

We need to implement a way to defined custom schemas, ie defining all polymer elements and their properties (and events).

@mprobst when would you be available so that we can discuss how to integrate with the security schema ?

@vicb vicb added feature Issue that requests a new feature area: core Issues related to the framework runtime area: security Issues related to built-in security features, such as HTML sanitation labels Oct 3, 2016
@mprobst
Copy link
Contributor

mprobst commented Oct 3, 2016

I'll be around tomorrow.

Victor Berchet notifications@github.com schrieb am Mo., 3. Okt. 2016,
10:13:

in @NgCompoent, schemas can currently only use the following values:

  • CUSTOM_ELEMENTS_SCHEMA -> allow any custom-tag with any property,
  • NO_ERRORS_SCHEMA -> allow any tag and any property

We need to implement a way to defined custom schemas, ie defining all
polymer elements and their properties (and events).

@mprobst https://github.com/mprobst when would you be available so that
we can discuss how to integrate with the security schema ?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#12045, or mute the thread
https://github.com/notifications/unsubscribe-auth/AAAcKMPT2b2IueuXEW86LpfPFCkwDWjDks5qwTexgaJpZM4KM0J4
.

@tbosch
Copy link
Contributor

tbosch commented Oct 6, 2016

If we do this right, our current DomSchema should not be special cased any more, right?

@vicb
Copy link
Contributor Author

vicb commented Oct 26, 2016

Roberto Fischer would be interested in this feature

@lacolaco
Copy link
Contributor

I think #12840 is a similar request in terms of the customizable sanitization.

@vicb
Copy link
Contributor Author

vicb commented Apr 25, 2017

/ref #16306

@BaerMitUmlaut
Copy link

BaerMitUmlaut commented Apr 11, 2019

I accidentally created a duplicate of this, here is my suggestion to resolve it (copy & pasted from the other issue):


I'd expand the SchemaMetadata interface like this:

export interface SchemaMetadata {
  name: string;
  tokenValid?: (token: string) => boolean;
}

The functions would check if the given token (element or attribute name) is valid, it's optional for full backwards compatibility (though that's probably not even necessary). Alternatively this could be split into two functions (attributeValid & elementValid).

Then there could be a class or helper function that generates an object which simply checks if the given token is within a set of previously defined tokens.

This would also clean up existing code because you don't have to hardcode the logic of the schema everywhere it's used.

If you approve, I can implement this and PR, should be pretty simple.

@trotyl
Copy link
Contributor

trotyl commented Apr 13, 2019

If you approve, I can implement this and PR, should be pretty simple.

@BaerMitUmlaut How do you make the compiler to execute a specific function in metadata, without running other code? The main problem is this isn't happening at runtime.

@BaerMitUmlaut
Copy link

I'm not an Angular pro, but these are the two spots where I could find logic associated with CUSTOM_ELEMENTS_SCHEMA (it's less often used overall, thus it was easier to find, I'd guess these are also all the places where NO_ERRORS_SCHEMA gets handled).

if (schema === NO_ERRORS_SCHEMA ||
schema === CUSTOM_ELEMENTS_SCHEMA && tagName && tagName.indexOf('-') > -1) {

if (schemaMetas.some((schema) => schema.name === CUSTOM_ELEMENTS_SCHEMA.name)) {

They would have to be changed to call the tokenValid method instead. Unless there is some other magic happening somewhere else which I'm missing, that should be it, right?

@trotyl
Copy link
Contributor

trotyl commented May 8, 2019

@BaerMitUmlaut The ivy one is not feature complete and currently WIP at #30301

Also, the schemas must be understood by language-service, thus cannot be runtime-only.

@Jack-Works
Copy link

Jack-Works commented May 13, 2019

app.module.ts

import { MyComponent } from './my-comp'
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent],
  customElements: [MyCompoent]
})
export class AppModule {}

my-comp.ts

export interface MyComponent {
    customData: [string, number]
    onChange: (data: string) => void
}
export const MyComponent = { tag: 'my-comp' } as const
class MyComp extends HTMLElement {
    // .......
}

app.component.html

<!-- The exported value ensure angular recognize it's tag name -->
<!-- The exported interface ensure angular compiler check all properties of `my-comp` component -->
<!-- They shared the same name "MyCompoent" so angular compiler can combine them together. -->
<!-- Now customData get the type [string, number], $event in "onChange" is type "string" -->
<my-comp [customData]="data" (onChange)="onChange($event)"></my-comp>

app.component.ts

@Component({
  templateUrl: "./app.component.html"
})
export class AppComponent {
  data = ['a', 0];
  onChange(newVal: string) {
    this.count[0] = newVal;
  };
}

@RobM-ADP
Copy link

I don't think this should be considered low priority or frequency. We are probably all starting to see the shifting of design system component libraries being built using web components and this makes it very difficult for devs to work with them inside angular apps. Right now to use custom elements you must essentially turn off all schema validation. An ideal solution (for us anyway) would be to be able to build and import a custom schema that was generated by the Stencil compiler.

@DmitryEfimenko
Copy link

I totally agree with @RobM-ADP. Just recently we had to do quite an ugly workaround to avoid the CUSTOM_ELEMENTS_SCHEMA in our app where we'd introduce a million of directives that would target custom elements that come from a 3rd party lib just so that Angular "knows" about them. In a lot of cases we had to add @inputs() to these directives so that Angular does not complain about these. The thing is, when doing that, in some cases it changes the behavior of the custom component, which is not desired at all. To summarize, it's a super ugly workaround, but it's better than using CUSTOM_ELEMENTS_SCHEMA. It'd be nice if there was an official way to handle this scenario.

@RobM-ADP
Copy link

Another thing worth noting is that creating a lot of directives or wrapper components as @DmitryEfimenko describes just to make the compiler happy introduces a lot of extra code in the final bundles that doesn't need to be there. One of the big advantages of building a design system as webcomponents is that code defining the components doesn't need to exist in the Angular app's code. So neither CUSTOM_ELEMENTS_SCHEMA nor custom wrappers/directives are a good solution to a problem that is going to happen more frequently over time.

@sijakret
Copy link

is there any reason to not just provide a way to customize the following checks?

override hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean {

override hasElement(tagName: string, schemaMetas: SchemaMetadata[]): boolean {

if these were pluggable users could already significantly refine which tags to bail out on.
this would already be much better than just dropping everything when CUSTOM_ELEMENTS_SCHEMA is used

if i patch my local compiler with 3 lines i can get immediately it to do what i need!

@waterplea
Copy link
Contributor

This sounds reasonable. If it was to be the choice, default checks must be exposed too so we can do something like:

override hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean { 
  return propName.includes('MY_CUSTOM_PREFIX') || DEFAULT_CHECK(tagName, propName, schemaMetas);
}

@sijakret
Copy link

Why not even allow plugging in a full hook?

override hasProperty(tagName: string, propName: string, schemaMetas: SchemaMetadata[]): boolean { 
  return config.hasPropertyOverride ?  config.hasPropertyOverride(tagName, propName, schemaMetas) : DEFAULT_CHECK(tagName, propName, schemaMetas);
}

@wSedlacek
Copy link

Why not even allow plugging in a full hook?

As far as I have looked into, the issue is how to expose it this configuration.
DomElementSchemaRegistry is used in the AOT compiler as far as I understand so standard dependency injection doesn't seem like it would cover us there.

@sijakret
Copy link

Why not even allow plugging in a full hook?

As far as I have looked into, the issue is how to expose it this configuration.
DomElementSchemaRegistry is used in the AOT compiler as far as I understand so standard dependency injection doesn't seem like it would cover us there.

Ok. Would some sort of pattern or similar for the config work?

@sijakret
Copy link

sijakret commented Sep 28, 2021

Hi,

I cooked up the following proposal:

sijakret@a5cc45e?branch=a5cc45e121d5ba0437461cab84b452a04ebcfd82&diff=unified

it adds a new angular compiler option angularCompilerOptions.webComponentPattern

{
  ...
  "angularCompilerOptions": {
    ...
    "webComponentPattern": "my-prefix-" // RegEx
  }
}

if it is not used there should be no change in behavior.
if set + CUSTOM_ELEMENTS_SCHEMA is used then

  • only tags that match the pattern will be considered as web components.
  • all tags that don't match the pattern are checked in the default way (html5 tag or angular component)

Example (config option set like so: "webComponentPattern": "my-prefix-")

@Component({
  selector: 'component-exists',
  template: `<div style="padding: 20px">{{prop}}</div>`,
})
export class ComponentExists {
  @Input() prop:string = 'default'
}

@NgModule({
  declarations: [
    // ...
    ComponentExists
  ],
  //...
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }

template

<!-- A
regular angular component with a prop
=> works
-->
<component-exists [prop]="'prop was set'"></component-exists>

<!-- B
same component, but trying to set prop with wrong type
=> error even though CUSTOM_ELEMENTS_SCHEMA is configured
-->
<component-exists [prop]="1"></component-exists>


<!-- C
same component, but trying to set non-existing prop
=> error even though CUSTOM_ELEMENTS_SCHEMA is configured
-->
<component-exists [propOoops]="'prop was set'"></component-exists>

<!-- D
trying to use an angular component that does not exist
=> error,.. 
-->
<component-does-not-exist></component-does-not-exist>

<!-- E
matches pattern /my-prefix-/i
=> no checks at all
-->
<my-prefix-component></my-prefix-component>

This behavior would be a significant upgrade to the current one since we can have E but still get validation feedback for B, C and D. Works well in a test project with both ng serve and ng build.

Looking for feedback from the team here - do you see issues with this approach?
In case this path can prepare a PR according to the guidelines with tests etc.

To reiterate, the use case:
My goal (and the goal of any enterprise that has a web component library) is to simply be able to use web components in an angular app without having to completely turn off validation in templates.
Typically these libraries have some sort of prefix in their custom element tags (like <abc-slider >, <abc-button >) so a some sort of RegEx (/^abc-/) is a pretty concise way of targeting them specifically.

I am aware this is not the most comprehensive or elegant solution but it would be very low-risk/high-impact and get the job done
A more better solution that would probably also allow integration with the language service and actually
enumerating all custom elements could be based on the schema VSCode already supports: https://github.com/microsoft/vscode-custom-data

@wSedlacek @CSchulz @waterplea

@MichaelBrimer
Copy link

Thanks, - this would be a great improvement !!

@waterplea
Copy link
Contributor

@sijakret it wouldn't help with HostBinding, because they belong to the parent view, so you will not be able to guarantee that CUSTOM_ELEMENTS_SCHEMA is present there. It would also not help in my case, where I want to be able to store some arbitrary values as properties on elements. Zone.js does that with __zone_symbol_xxx why can't we? There's a standard valid way to store string values as attributes with data- but sometimes I need to store other types.

@sijakret
Copy link

@waterplea can you provide an example?

@waterplea
Copy link
Contributor

waterplea commented Sep 28, 2021

If you had some directive that processes value somehow and do this, it would cause an error, even if you add CUSTOM_ELEMENTS_SCHEMA to the directive module:

@Directive({ selector: 'clamp' })
export class ClampDirective {
  @Input() min = 0;
  @Input() max = Infinite;
  @Input() value = 0;

  @HostBinding('prop')
  get prop(): number {
    return Math.min(Math.max(this.value, this.min), this.max);
  }
}
<!-- Web component with 'prop' input -->
<component-exists clamp [value]="value"></component-exists>

It's a fairly crazy example, I know, but I just wanted to highlight that there's also @HostBinding which participates in bindings and it's a special case because even if you define it in one component of one module, it is actually executed in another view in another module and follows that module rules. Besides, you need to consider libraries. How would compiler parameters for the library be respected in app that uses this library? I believe there are runtime checks which would through error currently.

@sijakret
Copy link

I cannot get your example to do what i think it is inteded to do regardless of any schema-related issues, here it is alive: https://stackblitz.com/edit/angular-ivy-8mzrpk?file=src%2Fapp%2Fapp.module.ts

Funny thing is, it works for the web component (CUSTOM_ELEMENT_SCHEMA and WC are commented out in the stackblitz since the problem with the directiv seem unrelated to me)

<!-- works, clamps and sets clamped prop -->
<my-prefix-custom-element clamp name="with clamp directive" [value]="10"></my-prefix-custom-element>
<!-- breaks, value should be number -> with the update i am suggeting it will even typecheck the directive  -->
<my-prefix-custom-element clamp name="with clamp directive" [value]="'10'"></my-prefix-custom-element>

Regarding libraries: I am not sure about the way angular handles this, but the compiler probably compiles these things AOT and hence use the compiler settings at compile time of the lib for its code (at least regarding templates). Don't know this though.

@alxhub looking forward to your thoughts on my proposal #12045 (comment)

@wSedlacek
Copy link

Having just a prefix won't solve the Schema for NativeScript.
Really for that, we need the ability to inject a new schema for that platform.

@sijakret
Copy link

Instead of a RegExp pattern the config load external schemas, for instance as described here: https://github.com/microsoft/vscode-custom-data (example: https://github.com/microsoft/vscode-custom-data/blob/main/samples/helloworld/html.html-data.json) and check against them.
These are already supported by vscode - gives you autocomplete etc. also in conjunction with angular tooling.

This change would be a bit more involved but in essence a regex vs. a dictionary pretty much won't change much about the way it hooks into the compiler. At this point i am looking for feedback whether i am on the right track in the first place ;)

@sijakret
Copy link

sijakret commented Oct 8, 2021

I updated the proposal: sijakret@2339fe2

Now you can actually extend the dom schema so it is even less intrusive and reuses the exsting paths there.

you can specify specific tags (same way as it is done in dom_element_schema_registry.ts already)

{
  "angularCompilerOptions": {
    "customSchema": [
      "my-prefix-custom-element^[HTMLElement]|string-prop,#num-prop",
      "my-other-element^[HTMLElement]|myprop"
    ]
  }
}

I also did a quick tryout with the the angular language service extension for vs code, that also works.
Should probably be extended to load external definition files, but I have no idea how the whole config infrastructure works.

What do you think about this approach?

@wSedlacek @MichaelBrimer

@lovi88
Copy link

lovi88 commented Oct 14, 2021

It would be a really nice addendum to be able to provide custom schema templates. Those could be pre-compiled so they do not have the runtime and maintenance overhead as the wrappers, and this would provide code completion for 3rd party component libraries provided as HTML5 components.

@sijakret
Copy link

sijakret commented Nov 9, 2021

This issue has been around since 2016, is not hard to fix and would help EVERY angular project that uses web components.
To me it feels like it is strategically ignored to keep up lock in.

@Abadii
Copy link

Abadii commented Jul 29, 2022

Are there any updates regarding this issue?

@Kim-Andersen
Copy link

I hate to agree that this issue increasingly smells like a lock-in strategy.
Custom Elements is a standard, just like DIV and DIALOG elements, and is here to stay, which makes it weird that Angular is doing nothing is this regard. It feels like a punishment when "opting out" with the CUSTOM_ELEMENTS_SCHEMA.

Angular team; please at least share a reason, roadmap, thoughts, anything.

@danwulff
Copy link

danwulff commented Dec 13, 2022

Can confirm this is affecting our organization. We're migrating our Web design system components from Angular to https://lit.dev/ in order to support framework-less frontends. It feels like we're being punished for using Custom Elements even though that's a web standard. We wouldn't mind doing a little extra work in order for Angular to understand the specific elements and their attributes/properties.

Edit: it's also interesting to note that back in the JIT compiler days there were a few different blog posts out there about overriding DomElementSchemaRegistry in order to define custom elements instead of relying on CUSTOM_ELEMENTS_SCHEMA

Additional note: if we get a proposal in-place it would be great to be able to define attributes and properties for each element separately. Currently in our Lit Custom Elements have defined "camelCase" properties that directly correlate to "barrel-case" attributes. A dev can choose to use either, but they are basically using the same "types" underneath (attributes of course are just strings but Lit has a getter/setter layer that allows us to automatically convert those to Numbers, Dates, other custom objects etc)

@CSchulz
Copy link

CSchulz commented Dec 13, 2022

We have used the old way to patch the JIT compiler. Because this isn't functional we have switched to patching the angular core, but that is not straight forward.
I think it could be a better option to do such things on a predefined way with extended diagnostics or similar. For this I have opened a feature request #48117.

@elgreco247
Copy link

If you had some directive that processes value somehow and do this, it would cause an error, even if you add CUSTOM_ELEMENTS_SCHEMA to the directive module:

@waterplea Isn't it the case, that you always should add the CUSTOM_ELEMENTS_SCHEMA to the module declaring the component which uses the custom element? And in the case of standalone components, to the component itself?

@waterplea
Copy link
Contributor

If you had some directive that processes value somehow and do this, it would cause an error, even if you add CUSTOM_ELEMENTS_SCHEMA to the directive module:

@waterplea Isn't it the case, that you always should add the CUSTOM_ELEMENTS_SCHEMA to the module declaring the component which uses the custom element? And in the case of standalone components, to the component itself?

I guess, but that means if you distribute reusable components you cannot bundle required config with it and people will have to do it themselves, which would then affect their code, which it really shouldn't.

@break-stuff
Copy link

break-stuff commented Jan 5, 2024

Is there any update with this? We currently use these plugins to generate VS Code and JetBrains autocomplete and docs, but it would be nice if we could also support type safety for our components.

@olaf-k
Copy link

olaf-k commented Mar 1, 2024

hey @pkozlowski-opensource! would you mind weighing in? it'd be nice to get the team's position on this.
can we hold our breath for a future solution to feed the compiler custom elements schemas? is there any alternative to creating wrappers/directive?

@eiswind
Copy link

eiswind commented Mar 24, 2024

Sad to see that there is no progress on this. I`d also like to throw in web-types, which could be a way to even validate custom elements. I am used to this from vaadin components, but I have seen that it's already in use be some other component libraries.
It's simply a json schema to describe your component api.

https://github.com/JetBrains/web-types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: core Issues related to the framework runtime area: security Issues related to built-in security features, such as HTML sanitation core: directive matching cross-cutting: custom elements feature: under consideration Feature request for which voting has completed and the request is now under consideration feature Issue that requests a new feature freq2: medium P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
Feature Requests
Needs Project Proposal
Development

No branches or pull requests