7

Let me preface this by saying that I've never really used Angular before. I'm trying to debug a problem one of the users of my web component library is facing.

I have a custom element, using lit-element, that has a boolean property. That boolean property has a custom attribute converter that makes it treat the string "false" as falsy (as opposed to native html element boolean attributes where the presence of the attribute makes it true). The converter looks like this:

{
  fromAttribute(val) {
    if (val === 'false') {
      return false;
    }
    if (val === '') {
      return true;
    }
    return Boolean(val);
  },
  toAttribute(val) {
    if (!val) {
      return null;
    }
    return '';
  }
}

As far as I can tell, Angular has 2 ways of binding a value to an html element in the template:

  1. By attribute, enabled="{{ enabled }}"
  2. By property, [enabled]="enabled"

For native elements like <img>, the attribute binding works as expected - I can see the attribute when I inspect the html.

For custom elements, attributes don't appear to be set at all - Angular seems to be setting a property no matter what. But, it is stringifying the value.

Here's a stackblitz that tries to demonstrate this: https://stackblitz.com/edit/angular-lit-element-1vz1nf?file=src%2Fapp%2Fapp.component.ts

I have a custom <hello-world> component that accepts a name, and enabled. If you inspect the html, name is not set as an attribute at all. enabled is set, but not to the value I'd expect.

There's also an <img> tag, where src is being set correctly.

Does angular special case custom elements and only ever set properties? Is this documented somewhere? To me it feels like a bug, and breaks some people's code who are using my custom elements in Angular.

Brian Schlenker
  • 4,966
  • 6
  • 31
  • 44

2 Answers2

15

To bind to attributes, you need to use the [attr.*] binding, otherwise you are binding to properties of the element, or @Input() of the custom element/component. In your example this would look like:

<hello-world name="{{ name }}" [attr.enabled]="enabled"></hello-world> 

stack

You also have the @Attribute() decorator. I've added an example of it to the stack. I don't know how this would look like in a custom element. I do know that you can't do dynamic binding to it. The value needs to be fixed, and needs to be string

Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
  • Wow, thanks for answering so quick! That makes it work. So then, what does the double curly syntax do? Is it documented somewhere that it sets a property? – Brian Schlenker Feb 06 '20 at 14:17
  • The double curly syntax is a bit of another notation, and uses string interpolation. The docs of angular explain it pretty [well](https://angular.io/guide/template-syntax#interpolation-) – Poul Kruijt Feb 06 '20 at 14:20
0

In Angular, when binding properties to web components, the property names should be in kebab case. This means that if a web component has a property named myProperty, you would bind to it in Angular as my-property. For example, if you have a web component named my-custom-element with a property named myProperty, you would bind to it in Angular like this:

<my-custom-element [my-property]="someValueProperty"></my-custom-element> 
<my-custom-element my-property="someValue"></my-custom-element> 

If you try to bind to the property using camel case, like this:

<my-custom-element [myProperty]="someValue"></my-custom-element> 

It will not work.

Shashi Kiran
  • 367
  • 3
  • 9