Apollo GraphQL client generation for JWebMP. Annotate a Java class with @NgGraphQL, declare the GraphQL operation, and the compiler emits a fully typed, signal-based @Injectable Angular service backed by apollo-angular β no handwritten TypeScript. One annotated class per operation (query, mutation, or subscription), mirroring the @NgRestClient pattern.
Built on apollo-angular 11 Β· Angular 21 Β· JWebMP Core Β· JPMS module com.jwebmp.angular.graphql Β· Java 25+
<dependency>
<groupId>com.jwebmp.plugins</groupId>
<artifactId>angular-graphql</artifactId>
</dependency>Version is managed by the
com.jwebmp:jwebmp-bomimported in the parent POM.
The plugin declares the Apollo dependencies automatically via @TsDependency:
{
"dependencies": {
"apollo-angular": "^11.0.0",
"@apollo/client": "^4.0.0",
"graphql": "^16.9.0"
}
}@NgGraphQLannotation β One annotation per GraphQL operation generates a complete@InjectableAngular service at build time- Query / Mutation / Subscription β
operationTypeselects the trigger method:execute(),mutate(), orsubscribe() - Signal-based state β Every service exposes
data(),loading(),error(), andsuccess()as AngularWritableSignals gqldocument embedding β The GraphQL operation text is embedded into agqltemplate literal in the generated service- Default variables β
@NgGraphQLVariabledeclares default operation variables merged with call-time variables - Polling & refetch β Queries support
startPolling()/stopPolling()andrefetch()via Apollo'swatchQuery - Fetch & error policies β Configure Apollo
FetchPolicyandErrorPolicyper operation - Typed responses β Bind a
responseType(INgDataType) andresponseArrayfor typed signal results - Automatic cleanup β Generated
ngOnDestroyandDestroyRefregistration unsubscribe on teardown - JPMS modular β Registers itself for GuicedEE scanning; new client classes need no extra registration
Implement INgGraphQLClient<Self> and annotate the class with @NgGraphQL. The class body is empty β the
TypeScript is generated entirely from the annotation.
@NgGraphQL(
operation = """
query Users($active: Boolean) {
users(active: $active) { id name email }
}
""",
operationType = NgGraphQL.OperationType.QUERY,
fetchOnCreate = true,
pollingEnabled = true,
pollingIntervalMs = 10_000)
@NgGraphQLVariable(name = "active", value = "true")
public class UsersQuery implements INgGraphQLClient<UsersQuery> { }@NgGraphQL(
operation = """
mutation CreateOrder($input: OrderInput!) {
createOrder(input: $input) { id status }
}
""",
operationType = NgGraphQL.OperationType.MUTATION,
errorPolicy = NgGraphQL.ErrorPolicy.ALL)
public class CreateOrderMutation implements INgGraphQLClient<CreateOrderMutation> { }@NgGraphQL(
operation = """
subscription OnNotification {
notificationAdded { id message }
}
""",
operationType = NgGraphQL.OperationType.SUBSCRIPTION,
responseArray = true,
fetchOnCreate = true)
public class NotificationSubscription implements INgGraphQLClient<NotificationSubscription> { }@NgComponent("app-users")
public class UsersComponent extends DivSimple<UsersComponent>
implements INgComponent<UsersComponent> {
// inject UsersQuery in the generated component and read usersQuery.data() / loading() / error()
}For the UsersQuery example above, the plugin produces an Angular service with this layout:
import { Injectable } from '@angular/core';
import { inject } from '@angular/core';
import { signal, WritableSignal } from '@angular/core';
import { DestroyRef } from '@angular/core';
import { OnDestroy } from '@angular/core';
import { Apollo, gql } from 'apollo-angular';
import { Subscription } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UsersQuery implements OnDestroy
{
private readonly apollo = inject(Apollo);
private readonly destroyRef = inject(DestroyRef);
readonly data: WritableSignal<any | undefined> = signal<any | undefined>(undefined);
readonly loading: WritableSignal<boolean> = signal<boolean>(false);
readonly error: WritableSignal<any> = signal<any>(undefined);
readonly success: WritableSignal<boolean> = signal<boolean>(false);
private readonly document = gql`
query Users($active: Boolean) {
users(active: $active) { id name email }
}
`;
private operationSubscription?: Subscription;
private queryRef?: any;
private pollingIntervalMs = 10000;
readonly polling: WritableSignal<boolean> = signal<boolean>(false);
constructor()
{
this.destroyRef.onDestroy(() => {
this.operationSubscription?.unsubscribe();
});
this.execute();
}
private mergeVariables(variables?: Record<string, any>): Record<string, any> {
const defaults: Record<string, any> = {
'active': true,
};
return { ...defaults, ...(variables ?? {}) };
}
execute(variables?: Record<string, any>): void {
this.loading.set(true);
this.error.set(undefined);
this.success.set(false);
this.operationSubscription?.unsubscribe();
this.queryRef = this.apollo.watchQuery<any | undefined>({
query: this.document,
variables: this.mergeVariables(variables),
fetchPolicy: 'cache-first',
errorPolicy: 'none',
pollInterval: this.pollingIntervalMs,
});
this.operationSubscription = this.queryRef.valueChanges.subscribe({
next: (result: any) => this.handleResult(result),
error: (err: any) => { /* ... */ }
});
this.polling.set(true);
}
refetch(variables?: Record<string, any>): void { /* ... */ }
startPolling(intervalMs?: number): void { /* ... */ }
stopPolling(): void { /* ... */ }
private handleResult(result: any): void { /* sets data/loading/error/success */ }
reset(): void { /* resets signals + queryRef */ }
ngOnDestroy(): void { this.operationSubscription?.unsubscribe(); }
}The generated service is written under the Angular app's src/app/<package>/<ClassName>/ directory by the
Angular Maven plugin, alongside other generated components and services.
| Attribute | Default | Purpose |
|---|---|---|
operation |
(required) | Raw GraphQL document; embedded into a gql template literal |
operationType |
QUERY |
QUERY, MUTATION, or SUBSCRIPTION |
value |
"" |
Optional friendly service name |
responseType |
INgDataType.class (β any) |
Typed result class; imported automatically |
responseArray |
false |
Result is an array of responseType |
singleton |
true |
providedIn: 'root' vs 'any' |
fetchOnCreate |
false |
Auto-run on inject (QUERY β execute(), SUBSCRIPTION β subscribe(); never mutations) |
pollingEnabled |
false |
Query polling via Apollo pollInterval |
pollingIntervalMs |
30000 |
Polling interval in ms |
fetchPolicy |
CACHE_FIRST |
CACHE_FIRST, CACHE_AND_NETWORK, NETWORK_ONLY, CACHE_ONLY, NO_CACHE, STANDBY |
errorPolicy |
NONE |
NONE, IGNORE, ALL |
@NgGraphQLVariable(name, value) is repeatable. The value is emitted as a raw TypeScript expression
(e.g. "true", "42", "'active'"). Defaults merge with call-time variables (call-time wins).
All operation types expose the data(), loading(), error(), success() signals plus reset() and
ngOnDestroy. The trigger method depends on operationType:
| Operation | Trigger | Extra methods | Apollo call |
|---|---|---|---|
QUERY |
execute(variables?) |
refetch(), startPolling(), stopPolling(), polling signal |
apollo.watchQuery |
MUTATION |
mutate(variables?) |
β (never auto-executes) | apollo.mutate |
SUBSCRIPTION |
subscribe(variables?) |
β | apollo.subscribe |
module com.jwebmp.angular.graphql {
requires transitive com.jwebmp.core.angular;
requires transitive com.jwebmp.core;
requires transitive com.guicedee.client;
exports com.jwebmp.angular.graphql;
exports com.jwebmp.angular.graphql.annotations;
provides IGuiceScanModuleInclusions with AngularGraphQLScanModule;
}
The plugin registers com.jwebmp.angular.graphql for GuicedEE classpath scanning via
AngularGraphQLScanModule. Client classes only need to live in a scanned module β no per-client registration.
| Class | Role |
|---|---|
NgGraphQL |
Annotation declaring the operation, type, response, policies, polling |
NgGraphQLVariable / NgGraphQLVariables |
Repeatable default operation variables |
INgGraphQLClient<J> |
Generator interface β renders the apollo-angular service from the annotation |
AngularGraphQLScanModule |
IGuiceScanModuleInclusions registering the module for scanning |
com.jwebmp.angular.graphql
βββ com.jwebmp.core.angular (Angular integration + TypeScript compiler)
βββ com.jwebmp.core.base.angular.client (TypeScript client β annotations, IComponent, INgDataType)
βββ com.jwebmp.core (JWebMP Core)
βββ com.jwebmp.vertx (JWebMP Vert.x bridge)
βββ com.guicedee.client (Guice DI + classpath scanning)
- Java: 25 LTS
- Maven: inherits
com.jwebmp:parent:2.0.3-SNAPSHOT - JPMS: module descriptor at
src/main/java/module-info.java
mvn clean installRun tests:
mvn testJWebMP Angular Apollo GraphQL β typed, signal-based Apollo services for Java web applications, generated from a single annotation.