Skip to the solution
MediumFrontend9 min read

What is the customElements schema in Angular and when do you need to use it?

222 views

The problem

Question presented to candidate: "We want to use a design-system button built as a Web Component, <ds-button>, inside our Angular templates, and the build fails with 'ds-button is not a known element'. What is CUSTOM_ELEMENTS_SCHEMA, what exactly does it switch off, and what are the risks of adding it?"

What a strong answer should cover:

  • Angular's compiler checks every element and property binding in a template against known components and the DOM schema; unknown ones fail the build with NG8001 (element) and NG8002 (property).
  • schemas: [CUSTOM_ELEMENTS_SCHEMA] on a component (or NgModule) tells it to accept any element with a dash in its name and any property on such elements, which is what Web Components look like.
  • Bindings then go straight to the element: [value] sets a DOM property, (rate) listens to a DOM event, plain attributes stay attributes; the browser upgrades the element when customElements.define runs.
  • Risk: it also silences typos and missing imports for dashed tags; scope it to the components that host Web Components, never app-wide. NO_ERRORS_SCHEMA is broader still (any element, any property) and mostly a test shortcut.
  • Elements without a dash still error; the schema does not register or load anything.
  • The reverse direction, Angular component as a Web Component, is @angular/elements with createCustomElement, which needs no schema in the Angular app that defines it.

Clarifying questions expected:

  • "Is the Web Component library loaded and defined before our templates render?"
  • "Are we consuming Web Components, or exposing Angular components to other frameworks?"

Code / implementation expected: Yes β€” a template using a custom element with property and event bindings under the schema.

  • web-components
  • schemas

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

9 min read Β· 10 sections

Read the code

AngularUsing a plain Web Component in an Angular template with CUSTOM_ELEMENTS_SCHEMA
import { CUSTOM_ELEMENTS_SCHEMA, Component, signal } from "@angular/core";

// A plain Web Component, written without Angular (it could come from any library).
class RatingStars extends HTMLElement {
  #value = 0;
  set value(v: number) { this.#value = v; this.render(); } // a DOM *property*
  get value() { return this.#value; }
  connectedCallback() { this.render(); }
  render() {
    const max = Number(this.getAttribute("max") ?? 5);         // a DOM *attribute*
    this.innerHTML = "";
    for (let i = 1; i <= max; i++) {
      const star = document.createElement("button");
      star.textContent = i <= this.#value ? "β˜…" : "β˜†";
      star.onclick = () => this.dispatchEvent(new CustomEvent("rate", { detail: i }));
      this.append(star);
    }
  }
}
if (!window.customElements.get("rating-stars")) window.customElements.define("rating-stars", RatingStars);

@Component({
  selector: "app-root",
  // Without this line, ng build fails: NG8001 'rating-stars' is not a known element.
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <rating-stars max="5" [value]="score()" (rate)="score.set($any($event).detail)"></rating-stars>
    <p>Angular sees score = {{ score() }}</p>
  `,
})
export class AppComponent {
  score = signal(2);
}
AngularThe reverse direction: publishing an Angular component as a custom element with @angular/elements
import { Component, Injector, inject, input, output } from "@angular/core";
import { createCustomElement } from "@angular/elements";

// An ordinary Angular component...
@Component({
  selector: "greeting-badge-cmp",
  template: `<button (click)="greet.emit(name())">Hello, {{ name() }}</button>`,
})
export class GreetingBadge {
  name = input("world");
  greet = output<string>();
}

// ...registered as a browser custom element, usable from plain HTML, React, Vue or another Angular app.
@Component({ selector: "app-root", template: `<p>elements registered</p>` })
export class AppComponent {
  constructor() {
    const el = createCustomElement(GreetingBadge, { injector: inject(Injector) });
    if (!window.customElements.get("greeting-badge")) window.customElements.define("greeting-badge", el);
  }
}

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 73 of 121 in Angular

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Angular questions
Up nextMedium

Why does @for require track and how does @empty improve UX?

Continue