Skip to content

JWebMP/AngularApollo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

JWebMP Angular Apollo GraphQL

Maven Central License

Java 25+ Modular Angular TypeScript

Apollo GraphQL JWebMP

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+

πŸ“¦ Installation

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

Version is managed by the com.jwebmp:jwebmp-bom imported in the parent POM.

NPM Dependencies

The plugin declares the Apollo dependencies automatically via @TsDependency:

{
  "dependencies": {
    "apollo-angular": "^11.0.0",
    "@apollo/client": "^4.0.0",
    "graphql": "^16.9.0"
  }
}

✨ Features

  • @NgGraphQL annotation β€” One annotation per GraphQL operation generates a complete @Injectable Angular service at build time
  • Query / Mutation / Subscription β€” operationType selects the trigger method: execute(), mutate(), or subscribe()
  • Signal-based state β€” Every service exposes data(), loading(), error(), and success() as Angular WritableSignals
  • gql document embedding β€” The GraphQL operation text is embedded into a gql template literal in the generated service
  • Default variables β€” @NgGraphQLVariable declares default operation variables merged with call-time variables
  • Polling & refetch β€” Queries support startPolling() / stopPolling() and refetch() via Apollo's watchQuery
  • Fetch & error policies β€” Configure Apollo FetchPolicy and ErrorPolicy per operation
  • Typed responses β€” Bind a responseType (INgDataType) and responseArray for typed signal results
  • Automatic cleanup β€” Generated ngOnDestroy and DestroyRef registration unsubscribe on teardown
  • JPMS modular β€” Registers itself for GuicedEE scanning; new client classes need no extra registration

πŸš€ Quick Start

Implement INgGraphQLClient<Self> and annotate the class with @NgGraphQL. The class body is empty β€” the TypeScript is generated entirely from the annotation.

Query

@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> { }

Mutation

@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> { }

Subscription

@NgGraphQL(
    operation = """
        subscription OnNotification {
          notificationAdded { id message }
        }
        """,
    operationType = NgGraphQL.OperationType.SUBSCRIPTION,
    responseArray = true,
    fetchOnCreate = true)
public class NotificationSubscription implements INgGraphQLClient<NotificationSubscription> { }

Consume the generated service

@NgComponent("app-users")
public class UsersComponent extends DivSimple<UsersComponent>
        implements INgComponent<UsersComponent> {
    // inject UsersQuery in the generated component and read usersQuery.data() / loading() / error()
}

🧩 Generated Output

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.

βš™οΈ @NgGraphQL Attributes

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).

πŸ§ͺ Generated Service API

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

πŸ—ΊοΈ JPMS Module

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.

Key Classes

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

πŸ”— Dependencies

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)

πŸ› οΈ Build

  • Java: 25 LTS
  • Maven: inherits com.jwebmp:parent:2.0.3-SNAPSHOT
  • JPMS: module descriptor at src/main/java/module-info.java
mvn clean install

Run tests:

mvn test

πŸ“„ License

Apache 2.0


JWebMP Angular Apollo GraphQL β€” typed, signal-based Apollo services for Java web applications, generated from a single annotation.

About

A GraphQL library for Angular which exposes @NgGraphQL to create the typescript to map queries and results

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages