Angular Adapter
Angular 17+ with Ivy compiler, Signals, standalone components, lazy-loaded routes, and RxJS — with SWC accelerating TypeScript compilation for faster builds.
Angular 17+ · Ivy · Signals
Ivy compiler
Full Angular Ivy compilation: ɵɵdefineComponent, ɵɵtemplate — producing optimal tree-shaken bundles.
Signals
Angular 17 Signals API (signal, computed, effect) for fine-grained reactivity without Zone.js overhead.
Standalone components
No NgModule required. Standalone components, directives, and pipes with direct imports.
RxJS compatible
Full RxJS 7+ support. Observable-based data fetching, event streams, and state management.
SWC TypeScript
SWC replaces tsc for transpilation — 10× faster type stripping. tsc is still used for type checking.
Lazy routes
loadComponent() and loadChildren() produce separate async chunks for route-level code splitting.
Setup
bash
npm create lunx-dev@latest my-angular-app -- --framework angular --ts cd my-angular-app && npm install && npm run dev
typescriptlunx.config.ts
import { defineConfig } from 'lunx' export default defineConfig({ framework: 'angular', // Angular-specific options angular: { // Enable Angular Signals support signals: true, // Use esbuild-based browser builder (default for Angular 17+) builder: 'browser-esbuild', }, })
Standalone component with Signals
typescriptsrc/app/components/counter.component.ts
import { Component, signal, computed, effect } from '@angular/core' import { CommonModule } from '@angular/common' @Component({ selector: 'app-counter', standalone: true, imports: [CommonModule], template: ` <div class="counter"> <h2>Count: {{ count() }}</h2> <p>Doubled: {{ doubled() }}</p> <div class="controls"> <button (click)="decrement()">−</button> <button (click)="increment()">+</button> <button (click)="reset()" [disabled]="count() === 0">Reset</button> </div> </div> `, styles: [` .counter { display: flex; flex-direction: column; gap: 1rem; padding: 1.5rem; } .controls { display: flex; gap: 0.5rem; } button { padding: 0.5rem 1rem; border-radius: 8px; cursor: pointer; } button:disabled { opacity: 0.4; cursor: not-allowed; } `], }) export class CounterComponent { count = signal(0) doubled = computed(() => this.count() * 2) constructor() { // Runs whenever count changes — like useEffect effect(() => { console.log(`Count changed to ${this.count()}`) }) } increment() { this.count.update(n => n + 1) } decrement() { this.count.update(n => n - 1) } reset() { this.count.set(0) } }
Standalone app bootstrap
typescriptsrc/main.ts
import { bootstrapApplication } from '@angular/platform-browser' import { provideRouter } from '@angular/router' import { provideHttpClient } from '@angular/common/http' import { AppComponent } from './app/app.component' import { routes } from './app/app.routes' bootstrapApplication(AppComponent, { providers: [ provideRouter(routes), provideHttpClient(), ], }).catch(console.error)
Lazy-loaded routes
typescriptsrc/app/app.routes.ts
import { Routes } from '@angular/router' export const routes: Routes = [ { path: '', loadComponent: () => import('./pages/home.component').then(m => m.HomeComponent), }, { path: 'dashboard', loadComponent: () => import('./pages/dashboard.component').then(m => m.DashboardComponent), canActivate: [() => inject(AuthService).isLoggedIn()], }, { path: 'admin', loadChildren: () => import('./admin/admin.routes').then(m => m.adminRoutes), }, { path: '**', loadComponent: () => import('./pages/not-found.component').then(m => m.NotFoundComponent), }, ]
HTTP data fetching with rxjs
typescriptsrc/app/services/users.service.ts
import { Injectable, inject } from '@angular/core' import { HttpClient } from '@angular/common/http' import { Observable } from 'rxjs' import { catchError, map } from 'rxjs/operators' export interface User { id: number name: string email: string } @Injectable({ providedIn: 'root' }) export class UsersService { private http = inject(HttpClient) private base = import.meta.env.LUNX_PUBLIC_API_URL getUsers(): Observable<User[]> { return this.http.get<User[]>(`${this.base}/users`).pipe( map(users => users.sort((a, b) => a.name.localeCompare(b.name))), catchError(err => { console.error(err); throw err }) ) } getUserById(id: number): Observable<User> { return this.http.get<User>(`${this.base}/users/${id}`) } }
Zone.js and Signals
Angular 17 supports running without Zone.js when using Signals exclusively for change detection. In
lunx.config.ts, set angular.zoneless: true to enable the provideExperimentalZonelessChangeDetection() provider automatically.