{"id":31193306,"url":"https://github.com/MillerSvt/ng-elementum","last_synced_at":"2025-09-20T00:07:28.534Z","repository":{"id":311156766,"uuid":"1041331006","full_name":"MillerSvt/ng-elementum","owner":"MillerSvt","description":null,"archived":false,"fork":false,"pushed_at":"2025-09-19T11:15:43.000Z","size":246,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-19T13:24:42.696Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MillerSvt.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-20T10:28:36.000Z","updated_at":"2025-09-19T11:15:46.000Z","dependencies_parsed_at":"2025-08-22T14:19:24.623Z","dependency_job_id":"42a60e6d-4c26-4613-b799-fed38dff54a9","html_url":"https://github.com/MillerSvt/ng-elementum","commit_stats":null,"previous_names":["millersvt/ng-elementum"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/MillerSvt/ng-elementum","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MillerSvt%2Fng-elementum","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MillerSvt%2Fng-elementum/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MillerSvt%2Fng-elementum/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MillerSvt%2Fng-elementum/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MillerSvt","download_url":"https://codeload.github.com/MillerSvt/ng-elementum/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MillerSvt%2Fng-elementum/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":276020068,"owners_count":25571454,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-19T02:00:09.700Z","response_time":108,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-20T00:03:28.566Z","updated_at":"2025-09-20T00:07:28.526Z","avatar_url":"https://github.com/MillerSvt.png","language":"TypeScript","funding_links":[],"categories":["Framework Interoperability"],"sub_categories":["Wrappers"],"readme":"# ng-elementum\n\n\u003cimg src=\"images/logo.svg\" width=\"40%\" alt=\"logo\"\u003e\n\n`ng-elementum` is a modern fork of `@angular/elements` that enhances the integration of Angular components with the Web\nComponents standard. It preserves the simplicity of Angular Elements while adding new powerful features for exposing\ncomponent APIs, improving flexibility, and ensuring better developer experience.\n\n\u003e **Important:** `ng-elementum` works **only in zoneless mode** (no `zone.js`). See the **Zoneless requirement** section\n\u003e below.\n\n---\n\n## Overview\n\n**ng-elementum** packages Angular components\nas [Custom Elements](https://developer.mozilla.org/docs/Web/Web_Components/Using_custom_elements), also known as Web\nComponents. These are framework-agnostic HTML elements defined by JavaScript classes and registered with the browser's\n`CustomElementRegistry`.\n\nBy transforming Angular components into custom elements, you can:\n\n- Use Angular components outside of Angular applications\n- Distribute reusable UI components without requiring Angular knowledge\n- Leverage native browser APIs for interoperability\n\n---\n\n## Installation\n\n```bash\nnpm install ng-elementum --save\n```\n\n---\n\n## Zoneless requirement\n\n`ng-elementum` relies on Angular's **zoneless change detection** and must run **without `zone.js`**.\n\n- `createCustomElement()` **automatically provides** `provideZonelessChangeDetection()` for the element's internal app\n  config.\n- Do not load `zone.js` on the page where your element runs.\n- If you are composing multiple Angular apps on the same page, ensure they are all zoneless to avoid mixed modes.\n\n---\n\n## How it Works\n\nThe `createCustomElement()` function converts an Angular component into a class that can be registered as a custom\nelement.\n\n```typescript\nimport { createCustomElement } from 'ng-elementum';\nimport { MyComponent } from './my.component';\n\nconst MyElement = createCustomElement(MyComponent, {\n  applicationConfig: { providers: [] },\n});\n\ncustomElements.define('my-element', MyElement);\n```\n\nOnce registered, the element can be used like any other HTML tag:\n\n```html\n\u003cmy-element message=\"Hello from ng-elementum!\"\u003e\u003c/my-element\u003e\n```\n\n---\n\n## Key Features\n\n### Inputs and Outputs\n\n- Component **inputs** become element \u003cu\u003edash-cased\u003c/u\u003e attributes.\n- Component **outputs** are dispatched as \u003cu\u003edash-cased\u003c/u\u003e [CustomEvents](https://developer.mozilla.org/docs/Web/API/CustomEvent). The\n  event name matches the output name (or alias) and the payload is placed on `event.detail`.\n\n```typescript\nimport { Component, input, output } from '@angular/core';\n\n@Component({ standalone: true, template: `{{ message() }}` })\nexport class MyComponent {\n  message = input\u003cstring\u003e('');\n\n  closed = output\u003cvoid\u003e();\n}\n```\n\n```html\n\u003cmy-element message=\"Hello\"\u003e\u003c/my-element\u003e\n```\n\n```typescript\nconst el = document.querySelector('my-element')!;\nel.addEventListener('closed', () =\u003e console.log('Element closed'));\n```\n\n---\n\n### Automatic exposure of signal inputs\n\nIn Angular, `@Input()` properties (and later `input()` signals) were proxied onto the custom element instance at runtime, but **TypeScript typings did not reflect them**.\n\nWith `ng-elementum`, all **signal inputs** defined with `input()` are **both**:\n\n- Automatically proxied as runtime properties on the custom element instance\n- Automatically reflected in the generated element type, with the correct TypeScript type\n\nThis removes the need for manual duplication between runtime behavior and typings.\n\n- Each signal input maps to a writable property on the element\n- The property has the correct TypeScript type inferred from the `input\u003cT\u003e()` definition\n- Assigning to that property updates the underlying signal and triggers change detection\n\n```ts\n@Component({ standalone: true, template: `{{ count() }}` })\nexport class CounterComponent {\n  count = input\u003cnumber\u003e(0);\n}\n\nconst CounterElement = createCustomElement(CounterComponent, {\n  applicationConfig: { providers: [] },\n});\n\ncustomElements.define('counter-element', CounterElement);\n\n// Usage with correct typings\nconst el = document.querySelector('counter-element');\nel.count = 42; // ✅ typed as number\nel.count = 'hi'; // ❌ compile error\n```\n\n### Exposing Component Methods\n\n`ng-elementum` lets you expose component methods directly on the custom element instance:\n\n```typescript\nconst MyElement = createCustomElement(MyComponent, {\n  applicationConfig: { providers: [] },\n  exposedMethods: ['open', 'close'],\n});\n\ncustomElements.define('my-element', MyElement);\n\nconst el = document.querySelector('my-element');\nawait el.open(); // Calls MyComponent.open()\nawait el.close(); // Calls MyComponent.close()\n```\n\n✅ Preserves method context  \n✅ Works across Angular and non-Angular hosts  \n✅ Allows explicit public API definition\n\n---\n\n## Independent routing inside an element\n\nTo keep routing **scoped to the element** and avoid interfering with the host page or other Angular apps, you can provide the router for the element using `provideWebComponentRouter`, that use an in-memory location implementation so it does not bind to `window.location`.\n\n```ts\nimport { createCustomElement } from 'ng-elementum';\nimport { provideWebComponentRouter } from 'ng-elementum/router';\nimport { Routes, RouterLink } from '@angular/router';\nimport { Component } from '@angular/core';\n\n@Component({\n  template: `\n    \u003cnav\u003e\n      \u003ca routerLink=\"/home\"\u003eHome\u003c/a\u003e\n      \u003ca routerLink=\"/about\"\u003eAbout\u003c/a\u003e\n    \u003c/nav\u003e\n    \u003crouter-outlet\u003e\u003c/router-outlet\u003e\n  `,\n  imports: [RouterLink],\n})\nexport class ShellComponent {}\n\n@Component({ template: `Home works!` })\nexport class HomeCmp {}\n\n@Component({ template: `About works!` })\nexport class AboutCmp {}\n\nconst routes: Routes = [\n  { path: 'home', component: HomeCmp },\n  { path: 'about', component: AboutCmp },\n  { path: '', pathMatch: 'full', redirectTo: 'home' },\n];\n\nconst RouterElement = createCustomElement(ShellComponent, {\n  applicationConfig: {\n    providers: [provideWebComponentRouter(routes)],\n  },\n});\n\ncustomElements.define('router-element', RouterElement);\n```\n\nThis element owns its router and URL state, does not conflict with any other Angular app on the page, and supports programmatic navigation through its internal Router.\n\n---\n\n## Using HttpClient in `platform` level\n\nBy default, Angular does not allow `HttpClient` to be provided at the **platform** level.\nThe `provideHttpClient()` API can only be used in the application root (via `bootstrapApplication`), not in `platformBrowser` `StaticProvider`s.\n\nTo work around this limitation, `ng-elementum/http` exposes a helper function: `createHttpClient()`.\nIt allows you to instantiate a standalone `HttpClient` and register it at the platform level:\n\n```ts\nimport { createHttpClient } from 'ng-elementum/http';\nimport { HttpClient } from '@angular/common/http';\n\nconst platform = platformBrowser([\n  {\n    provide: HttpClient,\n    useFactory: () =\u003e createHttpClient(),\n  },\n]);\n```\n\n---\n\n## TypeScript Support\n\nDeclare typings to unlock IntelliSense and type safety:\n\n```typescript\nimport { createCustomElement } from 'ng-elementum';\n\nconst MyElement = createCustomElement(/*...*/);\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'my-element': InstanceType\u003ctypeof MyElement\u003e;\n  }\n}\n```\n\nNow TypeScript can infer correct types:\n\n```typescript\nconst el = document.createElement('my-element');\nawait el.open(); // ✅ Type-safe\n```\n\n---\n\n## Dependency Injection Architecture\n\n`ng-elementum` introduces a clear **two-level DI architecture** for custom elements:\n\n### Platform scope (providedIn: platform)\n\n- The **platform injector** acts as the **global scope**.\n- Dependencies registered here are shared across **all web elements** created on the same page.\n- Typical use cases: core Angular providers, common services (e.g. global configuration, theming, analytics).\n- There is only **one platform per browser page context**.\n\n### Root scope (providedIn: root)\n\n- Each element instance has its own **root injector**.\n- Providers registered in `applicationConfig` when calling `createCustomElement()` live in this root scope.\n- Dependencies in this scope are **isolated to the element instance**.\n- Typical use cases: services that should not leak across elements, component-local state, per-element routing.\n\n### Resolution order\n\n1. Injector first looks in the element’s **root scope**.\n2. If not found, it falls back to the **platform scope**.\n3. If still not found, Angular throws an error.\n\n### Example\n\n```ts\nconst platform = platformBrowser([\n  {\n    provide: AuthService,\n  },\n]);\n\nconst MyElement = createCustomElement(MyComponent, {\n  applicationConfig: {\n    providers: [UserService],\n  },\n});\n\ncustomElements.define('my-element', MyElement);\n```\n\n---\n\n## Limitations\n\n- Destroying and re-attaching custom elements may cause issues with lifecycle\n  callbacks ([see issue](https://github.com/angular/angular/issues/38778)).\n- Exposed methods must exist on the component class.\n- Elements must be attached to the DOM before calling methods.\n- Requires **zoneless mode** (no `zone.js`).\n\n---\n\n## Authors\n\n| \u003ca href=\"https://github.com/MillerSvt\"\u003e\u003cimg src=\"https://github.com/MillerSvt.png?size=100\" alt=\"Svyatoslav Zaytsev\" width=\"100\" style=\"border-radius:20%;\" /\u003e\u003c/a\u003e |\n| :----------------------------------------------------------------------------------------------------------------------------------------------------------------: |\n|                                                       [**Svyatoslav Zaytsev**](https://github.com/MillerSvt)                                                       |\n|                                                          💻 [sviatsv@yandex.ru](mailto:sviatsv@yandex.ru)                                                          |\n\n---\n\n## License\n\nMIT © 2025\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMillerSvt%2Fng-elementum","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMillerSvt%2Fng-elementum","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMillerSvt%2Fng-elementum/lists"}