2

I’m trying to pass an object as an input to an Angular custom element created with @angular/elements, not via HTML attributes (since setAttribute only supports strings).

Here’s a minimal example:

import {
  Component,
  Input,
  Injector,
  inject,
  NO_ERRORS_SCHEMA,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { CommonModule } from '@angular/common';

@Component({
  imports: [CommonModule],
  selector: 'foo',
  template: `foo works! {{ data.test() | json }}`,
})
export class Foo {
  @Input() name = '';
  @Input() data: any;
}

@Component({
  selector: 'app-root',
  template: `
    <h1>Hello from {{ name }}!</h1>
    <a target="_blank" href="https://angular.dev/overview">
      Learn more about Angular
    </a>
    <app-foo></app-foo>
  `,
  schemas: [NO_ERRORS_SCHEMA],
})
export class App {
  name = 'Angular';
  injector = inject(Injector);

  constructor() {
    const el = createCustomElement(Foo, { injector: this.injector });
    customElements.define('app-foo', el);

    const e = document.querySelector('app-foo') as any;
    queueMicrotask(() => {
      e.data = {
        test: () => {
          console.log('in test');
          return 'test';
        },
      };
    });
  }
}

bootstrapApplication(App);

I expect the <app-foo> element to print "foo works! test" in the template and log "in test" to the console. However, the template doesn’t update — it looks like the @Input() isn’t detecting the object assignment.

Question: How can I pass a complex object (with functions or nested data) programmatically to an Angular Element so that the component receives it and the template updates?

Notes:

  • Using setAttribute() only allows passing strings, not objects.
  • I’m using Angular 20.0.0.
  • Tested on StackBlitz.

Stackblitz Demo

1 Answer 1

2

Your code, just needs to have the e property moved inside the queueMicrotask callback, because only then, will the latest value be set, when the callback is called.

In your code, the e is evaluated before queueMicrotask hence you are facing this issue.

constructor() {
  const el = createCustomElement(Foo, { injector: this.injector });
  customElements.define('app-foo', el);
  queueMicrotask(() => {
    const e = document.querySelector('app-foo') as any;
    e.data = {
      test: () => {
        console.log('in test');
        return 'test';
      },
    };
  });
}

Full Code:

import {
  Component,
  Input,
  Injector,
  inject,
  NO_ERRORS_SCHEMA,
} from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { CommonModule } from '@angular/common';

@Component({
  imports: [CommonModule],
  selector: 'foo',
  template: ` foo works! {{ data.test() | json }}`,
})
export class Foo {
  @Input() name = '';
  @Input() data: any;
}

@Component({
  selector: 'app-root',
  template: `
    <h1>Hello from {{ name }}!</h1>
    <a target="_blank" href="https://angular.dev/overview">
      Learn more about Angular
    </a>
    <app-foo />
  `,
  schemas: [NO_ERRORS_SCHEMA],
})
export class App {
  name = 'Angular';
  injector = inject(Injector);

  constructor() {
    const el = createCustomElement(Foo, { injector: this.injector });
    customElements.define('app-foo', el);
    queueMicrotask(() => {
      const e = document.querySelector('app-foo') as any;
      e.data = {
        test: () => {
          console.log('in test');
          return 'test';
        },
      };
    });
  }
}

bootstrapApplication(App);

Stackblitz Demo

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.