GitHub

Using Ecology from Angular

Most ESA apps are Angular. The interactive half of this kit is standards-based Web Components, which Angular can consume directly — so component pages show the Angular binding beside the HTML one. This page is the part that does not fit on a component page: which components you can actually use, and what does not work yet.

Only half the kit is an element you can use

This is the first thing to get straight, because the names run against intuition. Of the 66 components, 35 are Lit web components that register a real custom element, and 31 are .astro — compiled to HTML at build time, with no runtime element behind them at all. Writing <esa-badge> in an Angular template does not render a badge. It renders nothing, and reports no error.

The presentational ones are the ones you would reach for first: esa-button, esa-badge, esa-icon, esa-form-field, esa-card are all in that bucket.

So the Angular tab appears only on pages for components that really are elements. Its absence is information: that component is a markup-and-tokens contract to reproduce, not a package to import. The rest of this kit — the tokens — is plain CSS and works in any stack today.

Binding: attributes, properties, events

Add CUSTOM_ELEMENTS_SCHEMA to the component that hosts the element, and import the element once for its side effect — each one self-registers, so there is no module to configure and no customElements.define to call.

import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import '@esa/ecology/esa-select';

@Component({
  selector: 'app-region-picker',
  standalone: true,
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
  template: `
    <esa-select label="Region" [options]="regions" (change)="onChange($event)"></esa-select>
  `,
})
export class RegionPicker {
  regions = [{ value: 'n', label: 'North' }];
  onChange(event: Event) {
    const { value } = (event as CustomEvent).detail;
  }
}

Three rules cover almost everything, and each one fails silently when broken:

RuleWhy it bites
Read the payload from $event.detail These are CustomEvents, including the ones named change. $event.target.value is empty — no error, just nothing.
Write event names exactly as documented They are lowercase, even when multi-word: pagechange, tabchange, featureselect. Angular passes the name straight to addEventListener, so (pageChange) binds a listener that never fires.
Arrays and objects need [property] binding [options], [items], [data]. Each component page's API table names the property and says when it has no attribute at all.

One caution about CUSTOM_ELEMENTS_SCHEMA: it silences unknown-element errors and unknown-attribute errors. A typo in an attribute name compiles clean and does nothing, so check names against the API table rather than the compiler.

What does not work yet

You cannot npm install this into a stock Angular CLI build today. The snippets on the component pages are the interop contract — element name, attribute versus property, event name and detail shape — and they are generated from the live specimen beside them, so they cannot drift from what the component really does. They are not yet a copy-paste install. Three things are in the way:

  • @esa/ecology publishes raw TypeScript from src/ with no build output and no tsconfig, and the Angular compiler does not compile TypeScript inside node_modules.
  • Its relative imports carry .js extensions — correct ESM, and awkward for an unbuilt package.
  • astro is a hard peer dependency of the package, even for the Lit half that has nothing to do with Astro.

None of this is Angular-specific and none of it is in the components themselves — the elements are standards-only and self-registering. It is a packaging job on the hub: a real build step and a loosened peer dependency. Until then, treat the Angular tab as the specification you are implementing against.

Migrating an existing Angular app

Adopting the design system across an app that already exists is a different job from using one element, and it has its own tooling: the migrate-kit plugin (plugins/migrate-kit/) — an audit, a per-component playbook, a per-screen playbook, and a verify stage, driven by the app's design spoke.

It takes the opposite approach to this page on purpose. Rather than importing the Lit elements, it has the app build its own permanent Angular component library against the same token and structure contract. That suits a large app with an established component layer; importing the elements suits a new surface or a gradual adoption. Both target the same tokens, so they converge.