jwebmp-angular

star 5

Generates Angular 21 TypeScript projects from JWebMP annotations and serves SPAs via Vert.x with STOMP/WebSocket bridging. Provides @NgApp, @NgComponent, @NgRoutable, @NgDataService annotations, TypeScript code generation, reactive messaging, Angular control-flow components, and WebSocket group management. Use when working with JWebMP Angular integration, TypeScript generation, Angular components, STOMP/WebSocket communication, or building Angular 21 applications with JWebMP.

GuicedEE By GuicedEE schedule Updated 6/8/2026

name: jwebmp-angular description: Generates Angular 21 TypeScript projects from JWebMP annotations and serves SPAs via Vert.x with STOMP/WebSocket bridging. Provides @NgApp, @NgComponent, @NgRoutable, @NgDataService annotations, TypeScript code generation, reactive messaging, Angular control-flow components, and WebSocket group management. Use when working with JWebMP Angular integration, TypeScript generation, Angular components, STOMP/WebSocket communication, or building Angular 21 applications with JWebMP. metadata: short-description: Angular 21 integration and TypeScript generation

JWebMP Angular Plugin

Angular 21 TypeScript project generation and SPA hosting with STOMP/WebSocket bridge for JWebMP.

Core Features

  • TypeScript code generation from Java annotations
  • SPA hosting via Vert.x with static asset serving
  • STOMP/WebSocket bridge for real-time communication
  • Reactive message processing with event bus integration
  • Angular control-flow components (@if, @for, @let)
  • Routing module with @NgRoutable support
  • Environment module for configuration

Quick Start

1. Define an Angular App

@NgApp(value = "my-app", bootComponent = AppComponent.class)
public class MyApp extends NGApplication<MyApp> { }

2. Create Boot Component

@NgComponent("app-root")
public class AppComponent extends DivSimple<AppComponent>
        implements INgComponent<AppComponent> { }

3. Add Routable Pages

@NgRoutable(path = "dashboard", parent = {AppComponent.class})
@NgComponent("app-dashboard")
public class DashboardPage extends DivSimple<DashboardPage>
        implements INgComponent<DashboardPage> { }

4. Enable TypeScript Generation

export JWEBMP_PROCESS_ANGULAR_TS=true
# Or
mvn verify -Djwebmp.process.angular.ts=true

5. Start Application

IGuiceContext.instance();
// → TypeScript generated to ~/.jwebmp/<appName>/
// → Vert.x serves dist at configured routes
// → STOMP/WebSocket bridge live at /eventbus

TypeScript Compiler Pipeline

TypeScriptCompiler
 ├── AngularAppSetup        → Scaffolds angular.json, package.json, tsconfig
 ├── DependencyManager      → Resolves @TsDependency / @TsDevDependency
 ├── ComponentProcessor     → Processes INgComponent, INgDirective, INgDataService
 ├── AngularModuleProcessor → Generates boot module, routing module
 ├── TypeScriptCodeGenerator → Renders .ts files from annotations
 ├── AssetManager           → Copies @NgAsset, @NgScript, @NgStyleSheet
 └── TypeScriptCodeValidator → Validates generated output

Annotations

@NgApp

@NgApp(value = "my-app", bootComponent = AppComponent.class)
public class MyApp extends NGApplication<MyApp> { }

@NgComponent

@NgComponent("app-header")
public class HeaderComponent implements INgComponent<HeaderComponent> {
    @Override
    public String render() {
        return "<header><h1>My App</h1></header>";
    }
}

@NgRoutable

@NgRoutable(path = "users", parent = {AppComponent.class})
@NgComponent("app-users")
public class UsersPage implements INgComponent<UsersPage> { }

@NgDataService

@NgDataService
public class UserService implements INgDataService<UserService> {
    @Override
    public Object getData(AjaxCall<?> call, AjaxResponse<?> response) {
        return userRepository.findAll();
    }
}

@NgRestClient

Generate a fully typed, signal-based @Injectable Angular REST service from a single Java class — the REST counterpart to @NgGraphQL. Defined in the tsclient plugin (com.jwebmp.core.base.angular.client.annotations.angular); see the jwebmp-tsclient skill for the full attribute reference.

@NgRestClient(
    url = "/api/users",
    method = NgRestClient.HttpMethod.GET,
    responseType = User.class,
    responseArray = true,
    cachingEnabled = true,
    pollingEnabled = true)
@NgRestClientHeader(name = "Accept", value = "application/json")
@NgRestClientQueryParam(name = "active", value = "true")
public class UsersClient implements INgRestClient<UsersClient> {}

Generates an @Injectable HttpClient service exposing data/loading/error/ success/polling signals, execute() / executeWithBody(), plus polling, caching, deduplication, deep-merge, retry, and NONE/BEARER/BASIC/CUSTOM auth.

STOMP/WebSocket Communication

WebSocket Bridge Flow

Angular Client (STOMP)
 └─ ws://host/eventbus
     ├─ SEND /toBus/incoming    → Event bus consumer
     │   ├─ Deserialize WebSocketMessageReceiver
     │   ├─ Dispatch to IWebSocketMessageReceiver (ajax/data/dataSend)
     │   ├─ Run in CallScope (WebSocket)
     │   └─ Reply via event bus:
     │       ├─ dataReturns → publish to per-key addresses
     │       ├─ sessionStorage → publish to "SessionStorage"
     │       └─ localStorage → publish to "LocalStorage"
     └─ SUBSCRIBE /toStomp/*   ← Server pushes via StompEventBusPublisher

Built-in Message Receivers

Receiver Action Purpose
WebSocketAjaxCallReceiver ajax Deserializes AjaxCall, fires event, returns AjaxResponse
WebSocketDataRequestCallReceiver data Resolves INgDataService, calls getData()
WebSocketDataSendCallReceiver dataSend Resolves INgDataService, calls receiveData()
WSAddToGroupMessageReceiver AddToWebSocketGroup Adds session to WebSocket group
WSRemoveFromWebsocketGroupMessageReceiver RemoveFromWebSocketGroup Removes session from group

STOMP Configuration

Setting Value Notes
WebSocket path /eventbus STOMP over WebSocket endpoint
Server heartbeat 10000 ms Server → client
Client heartbeat 50000 ms Client → server (lenient for background tabs)
Sub-protocols v10.stomp, v11.stomp, v12.stomp Advertised on HTTP upgrade
Idle timeout 0 (disabled) Relies on STOMP heartbeats

Angular Control-Flow Components

NgIf / NgIfElse

NgIf<MyComponent> ifBlock = new NgIf<>(this)
    .setCondition("isLoggedIn")
    .add(new Paragraph<>().setText("Welcome!"));

NgIfElse<MyComponent> ifElse = new NgIfElse<>(this)
    .setCondition("hasData")
    .add(new Div<>().setText("Data loaded"))
    .setElseBlock(new NgElse<>()
        .add(new Div<>().setText("No data")));

Renders:

@if (isLoggedIn) {
  <p>Welcome!</p>
}

@if (hasData) {
  <div>Data loaded</div>
} @else {
  <div>No data</div>
}

NgFor

NgFor<MyComponent> forLoop = new NgFor<>(this)
    .setIterable("users")
    .setTrackBy("userId")
    .add(new Div<>().setText("{{ user.name }}"));

Renders:

@for (user of users; track userId) {
  <div>{{ user.name }}</div>
}

NgLet

NgLet<MyComponent> letVar = new NgLet<>(this)
    .setVariable("total")
    .setExpression("calculateTotal()");

Renders:

@let total = calculateTotal();

Routing

AngularRoutingModule

Scans @NgRoutable classes and generates route tree:

@NgRoutable(path = "dashboard", parent = {AppComponent.class})
public class DashboardPage { }

@NgRoutable(path = "users", parent = {DashboardPage.class})
public class UsersPage { }

@NgRoutable(path = "profile/:id", parent = {DashboardPage.class})
public class ProfilePage { }

Generates:

RouterModule.forRoot([
  {
    path: 'dashboard',
    component: DashboardComponent,
    children: [
      { path: 'users', component: UsersComponent },
      { path: 'profile/:id', component: ProfileComponent }
    ]
  }
])

RouterLink Component

RouterLink link = new RouterLink()
    .setRouterLink("/dashboard")
    .setText("Go to Dashboard");

RouterLink paramLink = new RouterLink()
    .setRouterLink("/profile/123")
    .setQueryParams(Map.of("tab", "settings"))
    .setText("View Profile");

Environment Module

EnvironmentModule env = new EnvironmentModule()
    .setOptions(new EnvironmentOptions()
        .setProduction(false)
        .setApiUrl("http://localhost:8080/api")
        .addCustomProperty("featureFlags", Map.of(
            "newUI", true,
            "betaFeatures", false
        )));

Generates:

export const environment = {
  production: false,
  apiUrl: 'http://localhost:8080/api',
  featureFlags: {
    newUI: true,
    betaFeatures: false
  }
};

Vert.x Router Wiring

Route Method Handler Purpose
/eventbus/* WebSocket STOMP server WebSocket → STOMP bridge
/assets/* GET StaticHandler Angular compiled assets (1-year cache)
/{file}.{ext} GET StaticHandler Root-level static files
/** (SPA fallback) GET sendFile(index.html) Angular Router routes

SPI Extension Points

SPI Purpose
AngularScanPackages Add packages to Angular classpath scan
RenderedAssets Provide additional build assets
NpmrcConfigurator Customize .npmrc file
WebSocketGroupAdd Custom logic for WebSocket group joins
TypescriptIndexPageConfigurator Customize generated index.html
IWebSocketAuthDataProvider Provide authentication data for WebSocket

Configuration

Environment Variable Default Purpose
JWEBMP_PROCESS_ANGULAR_TS false Enable/disable TypeScript generation
jwebmp.outputDirectory Override output directory
jwebmp ~ (user home) Base directory
ENVIRONMENT dev Runtime environment hint
PORT 8080 Server port

NPM Dependencies

The plugin manages Angular dependencies:

{
  "dependencies": {
    "@angular/core": "^20.0.0",
    "@angular/common": "^20.0.0",
    "@angular/router": "^20.0.0",
    "@angular/forms": "^20.0.0",
    "@stomp/ng2-stompjs": "^latest",
    "rxjs": "^7.8.0"
  }
}

Common Patterns

Data Service with WebSocket

@NgDataService
public class LiveDataService implements INgDataService<LiveDataService> {
    @Inject
    private DataRepository repository;

    @Override
    public Object getData(AjaxCall<?> call, AjaxResponse<?> response) {
        String entityId = call.getParameters().get("id");
        return repository.findById(entityId);
    }

    @Override
    public void receiveData(AjaxCall<?> call, AjaxResponse<?> response) {
        String data = call.getParameters().get("data");
        repository.save(data);

        // Push update to all clients in group
        StompEventBusPublisher.publish("/toStomp/updates", data);
    }
}

Event Handling

public class ButtonClickEvent extends OnClickAdapter {
    @Override
    public void onClick(AjaxCall<?> call, AjaxResponse<?> response) {
        // Process server-side
        String result = processData();

        // Update UI
        response.addComponent(new Div<>().setText("Result: " + result));

        // Publish to WebSocket group
        StompEventBusPublisher.publish("/toStomp/notifications", result);
    }
}

WebSocket Group Management

@NgComponent("chat-room")
public class ChatRoom implements INgComponent<ChatRoom> {
    @Override
    public void configure(IComponentHierarchyBase<?, ?> component) {
        // Component auto-joins WebSocket group
        component.setAttribute("websocketgroup", "chat-room-1");
    }
}

JPMS Module

module com.jwebmp.core.angular {
    requires transitive com.jwebmp.core.base.angular.client;
    requires transitive com.jwebmp.vertx;
    requires transitive io.vertx.eventbusbridge;
    requires transitive io.vertx.stomp;

    provides IGuicePreStartup with AngularPreStartup;
    provides IGuicePostStartup with AngularTSPostStartup;
    provides IGuiceModule with AngularTSSiteBinder;
    provides VertxRouterConfigurator with AngularTSSiteBinder;
    provides IWebSocketMessageReceiver with
        WebSocketAjaxCallReceiver,
        WebSocketDataRequestCallReceiver,
        WebSocketDataSendCallReceiver,
        WSAddToGroupMessageReceiver,
        WSRemoveFromWebsocketGroupMessageReceiver;
}

Key Classes

  • AngularTSSiteBinder — Core module + router configuration
  • TypeScriptCompiler — Orchestrates TS generation
  • NGApplication — Base class for @NgApp
  • AngularRoutingModule — Generates RouterModule.forRoot()
  • EnvironmentModule — Generates environment config
  • StompEventBusPublisher — Helper for STOMP publishing

Installation

<dependency>
  <groupId>com.jwebmp.plugins</groupId>
  <artifactId>angular</artifactId>
</dependency>

References

  • Module: com.jwebmp.core.angular
  • Java: 25+
  • Angular: 20
  • Dependencies: JWebMP Core, Vert.x, STOMP
  • License: Apache 2.0

Build Notes

Plugin generates TypeScript but does not run ng build. Build separately:

cd ~/.jwebmp/my-app
npm install
ng build

Dist output served automatically by Vert.x.

Install via CLI
npx skills add https://github.com/GuicedEE/ai-rules --skill jwebmp-angular
Repository Details
star Stars 5
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator