@sebgroup/green-core-ng
v2.35.0
Published
Angular wrappers for @sebgroup/green-core web components.
Readme
@sebgroup/green-core-ng
Angular wrappers for @sebgroup/green-core web components.
Status: Beta
Overview
@sebgroup/green-core-ng provides type-safe Angular component wrappers for all Green Design System web components. These wrappers are automatically generated from the Custom Elements Manifest, ensuring they stay in sync with the underlying web components while providing a native Angular development experience.
The wrappers eliminates the need to use CUSTOM_ELEMENTS_SCHEMA.
Versioning
@sebgroup/green-core-ng is versioned and released in tandem with @sebgroup/green-core, and includes @sebgroup/green-core as a dependency. Angular consumers should generally only need to specify @sebgroup/green-core-ng as a direct dependency in their package.json, and the correct version of the underlying @sebgroup/green-core should be automatically installed.
Key Features
- Fully type-safe - Complete TypeScript definitions for all components, properties, and events
- Automatic property syncing - Angular
@Inputproperties automatically sync to web component properties - Native Angular events - Web component custom events are exposed as Angular
@OutputEventEmitters - Form integration - Form controls implement
ControlValueAccessorand work seamlessly with Angular forms - Router integration - Link components work natively with Angular's
RouterLinkdirective - Standalone components - All components are standalone
- Auto-generated - Components are generated from the web component manifest, ensuring consistency
Installation
npm install @sebgroup/green-core-ngQuick Start
Import components directly in your standalone components or NgModules:
import { Component } from '@angular/core';
import { GdsButtonComponent, GdsInputComponent } from '@sebgroup/green-core-ng';
@Component({
selector: 'app-example',
standalone: true,
imports: [GdsButtonComponent, GdsInputComponent],
template: `
<gds-input
label="Email"
type="email"
[(ngModel)]="email">
</gds-input>
<gds-button (click)="submit()">
Submit
</gds-button>
`
})
export class ExampleComponent {
email = '';
submit() {
console.log('Email:', this.email);
}
}How It Works
Code Generation Pipeline
Pipeline Steps:
- Source: Web components in
@sebgroup/green-coreare built with Lit - Manifest: The build process for Core generates a Custom Elements Manifest (CEM) describing all components. The information is then by the
component-metamodule i Core, and accessed by this libarary from there. - Analysis: The generator (
generate-wrappers.ts) fetches the component-meta data to determine:- General component metadata (tag names, descriptions)
- Properties (inputs with types)
- Events (outputs with payload types)
- Slots (content projection)
- Methods (public API)
- Special characteristics (form controls, linkable components)
- Generation: For each component, TypeScript files are generated:
- Angular component wrapper (
.component.ts) - Unit test file (
.component.spec.ts) - Barrel export (
index.ts)
- Angular component wrapper (
- Build: The generated code is compiled and packaged using
ng-packagr
graph TB
subgraph "1. @sebgroup/green-core"
A[Web Components TypeScript Source Files<br/>libs/core/src/components/]
A --> B[CEM Analyzer<br/>npx cem analyze --litelement]
B --> C[custom-elements.json<br/>Custom Elements Manifest]
C --> E[component-meta<br/>CemParser.parseAllComponents]
E --> F[Extract Properties]
E --> G[Extract Events]
E --> H[Extract Slots]
E --> I[Extract Methods]
E --> J[Detect Special Types<br/>Form Controls, Links]
F & G & H & I & J --> K[ComponentData Objects]
end
subgraph "2. @sebgroup/green-core-ng"
K --> L[AngularGenerator]
L --> M[Generate Imports<br/>Angular decorators, base classes]
L --> N[Generate Component Class<br/>@Component decorator + metadata]
L --> O[Generate Properties<br/>@Input with transformations]
L --> P[Generate Events<br/>@Output EventEmitters]
L --> Q[Generate Lifecycle Hooks<br/>OnInit, OnChanges, AfterViewInit]
M & N & O & P & Q --> R[Component Files]
K --> S[TestGenerator]
S --> T[Test Files<br/>.component.spec.ts]
R --Output--> V["src/generated/*"]
V --> W["ng-packagr<br/>nx/angular:package"]
subgraph "Build & package"
W --> X["dist/libs/core-ng/<br/>Published NPM Package"]
end
end
class B,D,L,S,W processRuntime Behavior
Property Synchronization (@ProxyInputs decorator):
- Angular
@Inputproperties are defined with the same names and types as web component properties - The
@ProxyInputsdecorator interceptsngOnChangesandngAfterViewInitlifecycle hooks - Property changes are synchronized to the underlying web component DOM element outside Angular's zone for optimal performance
Event Handling:
- Web component custom events are captured during
ngOnInit - Event listeners are set up for custom events in order to rename them from
gds-event-nametogdsEventName(kebab case to camel case) - When events fire, they're emitted through Angular
@OutputEventEmitters - This allows standard Angular event binding syntax:
(gdsClose)="handler()"
Change Detection:
- Components use
ChangeDetectionStrategy.OnPushfor performance - The change detector is detached in the constructor for non-form-control components
- Form control components use the base class's change detection strategy
Project Structure
libs/core-ng/
├── src/
│ ├── generated/ # Auto-generated component wrappers. This directory is gitignored
│ ├── form-control-base.ts # Base class for form control components
│ ├── proxy-inputs.decorator.ts # Decorator for property syncing
│ └── index.ts # Main entry point
│
├── build-scripts/
│ ├── generate-wrappers.ts # Orchestrates the generation process
│ ├── generator.ts # Core generator logic. Component code generation templates are in here
│ └── test-generator.ts # Test generator
│
├── documentation/ # Implementation docsSpecial Features
Form Controls
Components that extend GdsFormControlElement in Green Core automatically implement ControlValueAccessor. This means they work out-of-the-box with:
- Reactive Forms:
formControl,formControlName,formGroup - Template-driven Forms:
ngModel,ngModelGroup - Form validation: Validation states sync to the web component's
invalidproperty
Router Link Integration
Components with an href property (like gds-link, gds-button, gds-menu-button) automatically integrate with Angular's RouterLink. No additional directives needed - the integration is built in!
Development Workflow
Generating Wrappers
Wrappers are automatically regenerated during the build process, but you can trigger generation manually:
# Only generate component wrapper sources, withoput build or test
nx run core-ng:generate-wrappers
# Generates wrappers and runs the generated test suite
nx run core-ng:test
# Gnereates the wrappers and builds with @nx/angular:package
nx run core-ng:buildGenerated Files
⚠️ Do not manually edit files in src/generated/ - they will be overwritten on the next generation.
Instead:
- Modify the generator logic in
build-scripts/ - Update base classes (
form-control-base.ts,proxy-inputs.decorator.ts) - Adjust component detection logic in
libs/core/src/utils/helpers/component-meta.ts
Implementation Details
Essentially, the wrappers are dummy classes decorated with Angulars @Component, @Input and @Output decorators.
By using the web components element name as the selector for the decorated class, Angular is "tricked" into creating an instance of the web component at run-time.
@Component({
selector: 'gds-button', // <-- Angular will create a <gds-button> element in DOM
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
template: `<ng-content></ng-content>` // <-- Wrapper itself contains no template or logic, other than <ng-content>
})Property Type Mapping
The generator extracts component structure info from the Custom Elements Manifest and maps it to Angular component properties:
// In web component (Green Core):
@property()
variant: 'primary' | 'secondary' = 'primary'
// Generated Angular wrapper:
@Input()
variant?: GdsButton['variant'] // Type: 'primary' | 'secondary' | undefinedThis approach ensures:
- Type safety: Angular's compiler knows the exact types
- Single source of truth: Types come from the web component definitions
- Automatic updates: If web components change, wrappers reflect those changes after regeneration
Kebab-case properties are converted to camelCase to align more with Angular conventions.
Event Type Mapping
@Output decorators are added for all events, and any kebab-cased events (like gds-close) will be automatically mapped to a camelCased output (gdsClose) for better alignment with Angular conventions.
Boolean Input Transformation
Boolean inputs automatically use Angular's booleanAttribute transform:
@Input({ transform: booleanAttribute })
disabled?: GdsButton['disabled']This allows both attribute and property binding:
<!-- Both work -->
<gds-button disabled></gds-button>
<gds-button [disabled]="true"></gds-button>