Skip to content

Don't convert attribute values to strings for a-frame interop #20452

@urish

Description

@urish

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[X] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

When binding to an attribute, Angular automatically converts the value to a string before calling element.setAttribute(). Per MDN, setAttribute indeed expects the value to be a string, and any non-string value specified is converted automatically into a string.

A-Frame components provide an extended version of setAttribute, which can also receive object values. For example, given the following A-Frame scene:

<a-scene>
  <a-sphere position="0 1.5 -5" color="red" id="sphere1"></a-sphere>
</a-scene>

You can call setAttribute to update the position of the sphere in the frame:

  document.getElementById('sphere1').setAttribute("position", {x: 1, y: 1.5, z: -5});

It is also possible to achieve the same result by passing a string, but this can negatively affect the performance, as well as result in less readable code (when you need to parse/construct the string manually):

  document.getElementById('sphere1').setAttribute("position", '1 1.5 -5');

I created a small plnkr example where this can be seen in action: https://plnkr.co/edit/rIG4qcjpoPwZ13EiPSao?p=preview

Expected behavior

There should be a way to tell angular to pass objects directly to setAttribute(), without calling toString() first. A possible solution would be to remove the toString() call altogether, as setAttribute should convert the given argument to string anyway for native elements. I can send a PR for this, if you are interested.

Workaround

Currently, I use a pipe as a workaround in my code. The pipe basically defeats the toString() behavior of Angular, by overriding the toString() method of the given object:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'aframe' })
export class AframePipe implements PipeTransform {
  transform(value: any, args?: any): any {
    if (value && typeof value === 'object') {
      return { toString: () => value };
    }
    return value;
  }
}

Then, I use this pipe whenever binding an object to an A-Frame attribute, e.g.:

<a-sphere [attr.position]="{x: calculateX(), y: 1.5, z: -5}|aframe"></a-sphere>

Minimal reproduction of the problem with instructions

Here is a minimal Angular app with the A-Frame scene described above:

http://plnkr.co/edit/qx0pHThNsB7KXmnHCSim?p=preview

Note that this example is working, since I have added my aframe pipe workaround. If you remove the "aframe" pipe from the attribute binding, you will see that the code stops working, as A-Frame gets '[object Object]' instead of the actual object.

What is the motivation / use case for changing the behavior?

Better interoperability with A-Frame

Environment

Angular version: 5.0.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: coreIssues related to the framework runtimecore: DOM renderingcore: binding & interpolationIssue related to property/attribute binding or text interpolationfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions