DEV Community

Aliaksandr Zasim
Aliaksandr Zasim

Posted on

I Built a Tiny Library to Make Apollo Work Natively with Angular Signals

Angular has evolved significantly with the introduction of signals in version 16, bringing a more reactive and performant approach to state management. However, integrating Apollo GraphQL with Angular’s new signal-based paradigm has remained a challenge — until now.


The Problem

Apollo Angular works beautifully with RxJS observables, but modern Angular applications are increasingly adopting signals for their reactivity model. This creates friction when you need to use GraphQL data in signal-based components, forcing developers to write boilerplate code to bridge the gap between observables and signals.


Introducing apollo-angular-signal

apollo-angular-signal is a lightweight library that seamlessly converts Apollo GraphQL queries and subscriptions into Angular signals.

With just one function and a declarative component, you can transform your Apollo queries into native Angular signals and handle loading/error states elegantly.


Installation

npm install apollo-angular-signal
Enter fullscreen mode Exit fullscreen mode

Key Features

1. Simple API

The library exposes a single function, gqlQuery(), that handles all the complexity.

import { gqlQuery } from 'apollo-angular-signal';

users = gqlQuery(
  this.apollo.watchQuery({ query: GET_USERS }).valueChanges
);
Enter fullscreen mode Exit fullscreen mode

2. Built-in State Management

No need to manually track loading states and errors.

The signal returns a structured result.

interface GqlSignalResult<T> {
  data?: T;
  loading: boolean;
  hasError: boolean;
  error?: unknown;
}
Enter fullscreen mode Exit fullscreen mode

3. Declarative State Handling with GqlSignalStatus

The GqlSignalStatus component eliminates repetitive @if blocks for loading and error states.

It has display: contents in its styling, meaning it does not render any wrapper element in the DOM — the component itself has no visual impact. Inside, it only contains conditional logic to render the loading, error, or success templates based on the signal's value.

@Component({
  imports: [GqlSignalStatus],
  template: `
    <gql-signal-status [gql]="users()">
      <div gqlLoading>Loading users...</div>
      <div gqlError>Failed to load users</div>

      <ul>
        @for (user of users().data?.users; track user.id) {
          <li>{{ user.name }} - {{ user.email }}</li>
        }
      </ul>
    </gql-signal-status>
  `
})
export class UsersComponent {
  private apollo = inject(Apollo);

  users = gqlQuery<{ users: User[] }>(
    this.apollo.watchQuery({ query: GET_USERS }).valueChanges
  );
}
Enter fullscreen mode Exit fullscreen mode

Benefits of GqlSignalStatus

  • Declarative — No more nested @if conditions
  • Reusable — Define loading/error templates once, use everywhere
  • Flexible — Customize per component or set global defaults
  • Clean — Separates concerns between data and presentation

4. Global Configuration

Set default templates for all your queries at once.

import { provideGqlSignalConfig } from 'apollo-angular-signal';
import { LoadingSpinnerComponent } from './loading-spinner.component';
import { ErrorAlertComponent } from './error-alert.component';

export const appConfig: ApplicationConfig = {
  providers: [
    provideGqlSignalConfig({
      loadingDefaultTemplate: LoadingSpinnerComponent,
      errorDefaultTemplate: ErrorAlertComponent
    })
  ]
};
Enter fullscreen mode Exit fullscreen mode

Now every GqlSignalStatus component automatically uses your custom loading spinner and error alert — no need to repeat them in every template.


5. Reactive Queries

The library supports reactive queries that automatically re-execute when dependencies change.

userId = signal('1');

user = gqlQuery(() => {
  const id = this.userId();
  if (!id) return null;

  return this.apollo.watchQuery({
    query: GET_USER,
    variables: { id }
  }).valueChanges;
});
Enter fullscreen mode Exit fullscreen mode

When userId changes, the query automatically re-executes — no manual subscription management needed.


6. Subscription Support

GraphQL subscriptions work just as seamlessly.

messages = gqlQuery(
  this.apollo.subscribe({
    query: MESSAGE_SUBSCRIPTION
  })
);
Enter fullscreen mode Exit fullscreen mode

How It Works

Under the hood, the library leverages Angular’s effect system to:

  • Subscribe to Apollo observables
  • Update a writable signal with query results
  • Automatically clean up subscriptions using takeUntilDestroyed()
  • Handle reactive re-execution when dependencies change

For reactive queries, it uses Angular’s computed() to track signal dependencies and automatically manage subscription lifecycles.

The GqlSignalStatus component uses Angular's content projection to conditionally render loading, error, or success states based on the signal's current value, with support for both inline templates and global configuration.


Conclusion

  • Type-Safe — Full TypeScript support with generics
  • Zero Boilerplate — No manual subscription management
  • Automatic Cleanup — Leverages Angular’s lifecycle management
  • Reactive by Design — Works naturally with signal-based architectures
  • Declarative UI — Handle loading/error states elegantly with GqlSignalStatus
  • Flexible Configuration — Per-component customization or global defaults
  • Lightweight — Minimal bundle size impact

Links

npm install apollo-angular-signal
Enter fullscreen mode Exit fullscreen mode

Top comments (0)