{"id":28437780,"url":"https://github.com/chihab/elemnt","last_synced_at":"2026-04-16T14:03:17.474Z","repository":{"id":192186852,"uuid":"572256489","full_name":"chihab/elemnt","owner":"chihab","description":"Custom Elements with strict typing","archived":false,"fork":false,"pushed_at":"2023-03-24T08:27:24.000Z","size":110,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-07-13T12:31:23.040Z","etag":null,"topics":["angular","custom-elements","web-components","web-development-tools"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chihab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-11-29T22:02:43.000Z","updated_at":"2025-06-19T07:03:28.000Z","dependencies_parsed_at":"2023-09-03T07:58:40.166Z","dependency_job_id":null,"html_url":"https://github.com/chihab/elemnt","commit_stats":null,"previous_names":["chihab/elemnt"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/chihab/elemnt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chihab%2Felemnt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chihab%2Felemnt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chihab%2Felemnt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chihab%2Felemnt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chihab","download_url":"https://codeload.github.com/chihab/elemnt/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chihab%2Felemnt/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273529427,"owners_count":25121823,"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-03T02:00:09.631Z","response_time":76,"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":["angular","custom-elements","web-components","web-development-tools"],"created_at":"2025-06-06T00:08:35.249Z","updated_at":"2026-04-16T14:03:12.428Z","avatar_url":"https://github.com/chihab.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @elemnt/angular (WIP)\n\nCustom Elements with strict typing.\n\n✅ No need for CUSTOM_ELEMENTS_SCHEMA\n\u003cbr /\u003e\n✅ Works with any Custom Element\n\u003cbr /\u003e\n✅ Strictly Typed properties\n\u003cbr /\u003e\n✅ Good experience with Angular Language Service\n\u003cbr /\u003e\n✅ Easy way to create Value Accessors [(ngModel)]\n\u003cbr /\u003e\n✅ Small runtime overhead (~1KB)\n\n## In a nutshell\n\nGiven the Custom Element below\n\n```ts\nimport { html, LitElement } from \"lit\";\nimport { customElement, property } from \"lit/decorators.js\";\n\n@customElement(\"ui-button\")\nexport class Button extends LitElement {\n  @property({ type: String })\n  text: string = \"\";\n\n  protected render() {\n    return html`\u003cbutton\u003e${this.text}\u003c/button\u003e`;\n  }\n}\n```\n\nIn order to have strict type checking we need to create an Angular Component wrapping it.\n\n`@elemnt/angular` makes it simple.\n\n```ts\nimport { Component, ElementRef, inject, Input, NgZone } from \"@angular/core\";\nimport { Element } from \"@elemnt/angular\";\n\nimport type { UiRange } from \"ui-range\";\nimport \"ui-range/ui-range.js\";\n\n@Element()\n@Component({\n  selector: \"ui-button\",\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  template: \"\u003cng-content\u003e\u003c/ng-content\u003e\",\n  standalone: true,\n})\nexport class ButtonComponent {\n  e = inject(ElementRef);\n  z = inject(NgZone);\n  @Input() text!: Button[\"text\"];\n}\n\n@Component({\n  selector: \"app-root\",\n  template: `\n    \u003c!-- Wrong properties are not accepted --\u003e ✅\n    \u003cui-range\n      [range]=\"'This is not a number'\" // Wrong Type is not accepted ✅\n      [unitx]=\"'kg'\" // Typo in property is not accepted ✅\n    \u003e\u003c/ui-range\u003e\n    \n    \u003c!-- 2. Wrong Selector is not accepted --\u003e  ✅\n    \u003cui-rangerrr [range]=\"75\" [unit]=\"'❤'\"\u003e\u003c/ui-rangerrr\u003e\n  `,\n  imports: [UiRangeComponent], // Import the Component Wrapper\n  standalone: true,\n})\nexport class AppComponent {\n  range = 90;\n}\n```\n\n## The issue with `CUSTOM_ELEMENTS_SCHEMA`\n\nIn order to use a custom element in our template we usually add `CUSTOM_ELEMENTS_SCHEMA` to the schemas of the `NgModule` or Standalone Component.\n\n`CUSTOM_ELEMENTS_SCHEMA` allows any custom-tag with any property without type checking.\n\n```html\n\u003c!-- CUSTOM_ELEMENTS_SCHEMA is dangerous!--\u003e ❌\n\n\u003c!-- Wrong properties --\u003e\n\u003cui-range\n  [range]=\"'This is not a number'\" // Wrong Type is accepted ❌\n  [unitx]=\"'kg'\" // Typo in property is ignored ❌\n\u003e\u003c/ui-range\u003e\n\n\u003c!-- Wrong selector is accepted --\u003e\n\u003cui-rangerrr [range]=\"75\" [unit]=\"'❤'\"\u003e\u003c/ui-rangerrr\u003e ❌\n```\n\n```ts\nimport \"ui-range/ui-range.js\";\n\n@Component({\n  selector: \"app-root\",\n  template: \"./app.component.html\",\n  schemas: [CUSTOM_ELEMENTS_SCHEMA], // This is dangerous for your templates ❌\n  standalone: true,\n})\nexport class AppComponent {}\n```\n\n### **@elemnt/angular**\n\n@elemnt/angular helps creating tiny component wrappers.\n\n```sh\nnpm add @elemnt/angular\n```\n\n```ts\nimport { Element, ElementProvider } from \"@elemnt/angular\";\n\n// 1. Import Custom Element\nimport \"ui-range/ui-range.js\";\nimport type { UiRange } from \"ui-range\";\n\n// 2. Decorate with @Element\n@Element()\n@Component({\n  // 3. Use the name of your custom element\n  selector: \"ui-range\",\n  template: \"\u003cng-content\u003e\u003c/ng-content\u003e\",\n  standalone: true,\n})\nexport class UiRangeComponent {\n  // 4. Inject ElementREf and NgZone, this are used by the Element decorator\n  e = inject(ElementRef);\n  z = inject(NgZone);\n  // 5. Add the Inputs you need\n  @Input() value!: UiRange[\"value\"];\n  @Input() unit!: UiRange[\"unit\"];\n  @Input() interval!: UiRange[\"interval\"];\n}\n```\n\n```ts\n@Component({\n  selector: \"app-root\",\n  template: \"./app.component.html\",\n  imports: [UiRangeComponent], // 0. Import the Component Wrapper\n  standalone: true,\n})\nexport class AppComponent {\n  range = 90;\n}\n```\n\n```html\n\u003c!-- wrong properties are not accepted --\u003e ✅\n\u003cui-range\n  [range]=\"'This is not a number'\" // Wrong Type is not accepted\n  [unitx]=\"'kg'\" // Typo in property is not accepted\n\u003e\u003c/ui-range\u003e\n\n\u003c!-- wrong selector is not accepted --\u003e  ✅\n\u003cui-rangerrr [range]=\"75\" [unit]=\"'❤'\"\u003e\u003c/ui-rangerrr\u003e\n```\n\n## Custom Form Components\n\nIn order to attach the default value accessor to a custom element we can add `ngDefaultControl` attribute\nbut it does not work with Custom Events\n\n```html\n\u003cui-range\n  ngDefaultControl // Does not match our Custom Element 'range' event ❌\n  [(ngModel)]=\"range\" ❌\n  [unit]=\"{'wrong': 'type'}\"  ❌\n  [unknown]=\"2\"  ❌\n\u003e\u003c/ui-range\u003e\n\u003cp\u003eRange: {{ range }}\u003c/p\u003e\n```\n\n```ts\n@Component({\n  selector: \"app-root\",\n  template: \"./app.component.html\",\n  schemas: [CUSTOM_ELEMENTS_SCHEMA], // This is dangerous for your templates ❌\n  standalone: true,\n})\nexport class AppComponent {\n  range = 90;\n}\n```\n\n### @elemnt/angular\n\n```ts\n@Component({\n  selector: \"app-root\",\n  template: ` \u003cui-range [(ngModel)]=\"range\" unit=\"kg\"\u003e\u003c/ui-range\u003e `,\n  imports: [UiRangeComponent],\n  standalone: true,\n})\nexport class AppComponent {\n  range = 90;\n}\n```\n\n```ts\nimport { Component, forwardRef, Input } from \"@angular/core\";\nimport { NG_VALUE_ACCESSOR } from \"@angular/forms\";\nimport { Element, ElementValueAccessor } from \"@elemnt/angular\";\n\nimport type { UiRange } from \"ui-range\";\nimport \"ui-range/ui-range.js\";\n\n// 1. Add binding configuration\n//    Default is { prop: \"value\", event: \"input\" }\n@Element({ accessor: { prop: \"value\", event: \"range\" } })\n@Component({\n  selector: \"ui-range\",\n  template: \"\u003cng-content\u003e\u003c/ng-content\u003e\",\n  standalone: true,\n\n  // 2. Provide the component to NG_VALUE_ACCESSOR\n  providers: [\n    {\n      provide: NG_VALUE_ACCESSOR,\n      useExisting: forwardRef(() =\u003e UiRangeComponent),\n      multi: true,\n    },\n  ],\n})\nexport class UiRangeComponent extends ElementValueAccessor {\n  // 3. Extend ElementValueAccessor\n\n  @Input() value!: UiRange[\"value\"];\n  @Input() unit!: UiRange[\"unit\"];\n  @Input() interval!: UiRange[\"interval\"];\n}\n```\n\n## Component overhead\n\nThe `@Element()` Component wrapper is lightweight, here is a comparaison of the usage a basic component\nused in the template with `CUSTOM_ELEMENTS_SCHEMA` and with `@elemnt/angular`\n\n**CUSTOM_ELEMENTS_SCHEMA**\n\n```\nInitial Chunk Files       | Names  |  Raw Size | Estimated Transfer Size\nmain.8ab91566fd99e31c.js  | main   | 101.30 kB |                30.97 kB\n```\n\n**@elemnt/angular**\n\n```\nInitial Chunk Files       | Names  |  Raw Size | Estimated Transfer Size\nmain.756c16a61c1d76e0.js  | main   | 104.81 kB |                31.89 kB\n```\n\nRuntime overhead\n\n- **\u003c 1kb** for a single component.\n- `@Element` bundled code is shared/not duplicated if used in multiple component wrappers\n\n\n\n# @elemnt/cem-plugin-angular (WIP)\n\n[@custom-elements-manifest/analyzer](https://github.com/open-wc/custom-elements-manifest) plugin to automatically create Angular wrappers for your custom elements based on your custom elements manifest.\n\n## Usage\n\n### Install:\n\n```bash\nnpm i -D @elemnt/cem-plugin-angular\n```\n\n### Import\n\n`custom-elements-manifest.config.js`:\n```js\nimport angular  from 'cem-plugin-angular';\n\nexport default {\n  plugins: [\n    angular()\n  ]\n}\n```\n\n### Configuration\n\n`custom-elements-manifest.config.js`:\n```js\nimport angular from 'cem-plugin-angular';\n\nexport default {\n  plugins: [\n    angular({\n        /** Directory to write the Angular wrappers */\n        outDir: './angular',\n        /** Array of component class names to exclude */\n        exclude: ['MyElement']\n        /** Whether to generate a Standalone component for each component, defaults to `true` */\n        standalone: true,\n        /** Whether to generate a NgModule for each component, defaults to `false` */\n        ngModule: false,\n        /** Whether to register the custom element, defaults to `false` */\n        register: true,        \n        /** which components should be integrated with ngModel. \n         * It lets you set what the target prop is (i.e. value), which event will cause the target prop to change */\n        accessors: [{\n            event: \"input\",\n            prop: \"value\",\n            elements: [\"ui-text\"],\n        }],\n        /** A mapper to get the custom element import */\n        packageMapper: (packageJson, component) =\u003e {\n            return packageJson.name + `/${component.name.toLowerCase()}.js`\n        }\n    });\n  ]\n}\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchihab%2Felemnt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchihab%2Felemnt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchihab%2Felemnt/lists"}