Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/amark/gun/llms.txt

Use this file to discover all available pages before exploring further.

Angular Integration

GUN integrates well with Angular through services and can be combined with RxJS observables for reactive data patterns.

Installation

1

Install GUN

Install GUN in your Angular project:
npm install gun --save
2

Install type definitions

Install TypeScript definitions (optional but recommended):
npm install @types/gun --save-dev
3

Create GUN service

Generate a service to manage your GUN instance:
ng generate service gun

Creating a GUN Service

Create a service to provide a singleton GUN instance throughout your app:
// gun.service.ts
import { NgModule, Injectable } from '@angular/core';
import Gun from 'gun/gun';

@Injectable()
export class GunDb {
    readonly gun = Gun(location.origin + '/gun');
}
Register the service in your app module:
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { GunDb } from './gun.service';

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule],
  providers: [GunDb],
  bootstrap: [AppComponent]
})
export class AppModule { }

RxJS Observable Helpers

Create helper functions to convert GUN’s callbacks into RxJS observables:
// gun.helper.ts
import { Observable } from 'rxjs/Observable';
import Gun from 'gun/gun';
import { pick } from 'underscore';

export function on$(node, cleanup = true): Observable<any> {
    return Observable.fromEventPattern(
        h => {
            // there is no way to off() an on() until at least one value is triggered
            // so that we can access the event listener to off() it
            const signal = { stop: false };
            node.on((data, key, at, ev) => {
                if (signal.stop) {
                    ev.off();
                } else {
                    // modifying data directly does not seem to work...
                    h(cleanup ? pick(data, (v, k, o) => v !== null && k !== '_') : data);
                }
            });
            return signal;
        },
        (h, signal) => { signal.stop = true; }
    );
}

export function val$(node): Observable<any> {
    return new Observable(o => node.val(v => {
        o.next(v);
        o.complete();
    }));
}

Using GUN in Components

Here’s a complete example of a todo component using GUN:
// app.component.ts
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import Gun from 'gun/gun';

import { GunDb } from 'app/gun.service';
import { on$ } from 'app/gun.helper';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  newTodo = '';

  todos = this.db.gun.get('todos');
  todos$: Observable<string[]> = on$(this.todos);

  todosSub: Subscription;

  constructor(private db: GunDb) { }

  ngOnInit() { }

  add() {
    if (this.newTodo) {
      this.todos.get(Gun.text.random()).put(this.newTodo);
      this.newTodo = '';
    }
  }

  delete(key: string) {
    this.todos.get(key).put(null);
  }

  sub() {
    this.todosSub = this.todos$.subscribe(v => console.log(v));
  }

  unsub() {
    this.todosSub.unsubscribe();
  }
}

Component Template

<!-- app.component.html -->
<div class="todo-app">
  <h1>GUN Todo App</h1>
  
  <div class="add-todo">
    <input 
      type="text" 
      [(ngModel)]="newTodo" 
      (keyup.enter)="add()"
      placeholder="Add a todo..."
    >
    <button (click)="add()">Add</button>
  </div>

  <ul class="todo-list">
    <li *ngFor="let todo of todos$ | async | keyvalue" 
        (click)="delete(todo.key)">
      {{ todo.value }}
    </li>
  </ul>
</div>

Advanced RxJS Integration

Create a more sophisticated service with RxJS operators:
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { map, filter } from 'rxjs/operators';
import Gun from 'gun/gun';

@Injectable({
  providedIn: 'root'
})
export class GunService {
  private gun = Gun(location.origin + '/gun');

  // Convert GUN node to Observable
  observe(path: string): Observable<any> {
    return new Observable(observer => {
      this.gun.get(path).on((data, key) => {
        observer.next(data);
      });
    }).pipe(
      filter(data => data !== null && data !== undefined)
    );
  }

  // Get data once
  once(path: string): Observable<any> {
    return new Observable(observer => {
      this.gun.get(path).once((data, key) => {
        observer.next(data);
        observer.complete();
      });
    });
  }

  // Put data
  put(path: string, data: any): void {
    this.gun.get(path).put(data);
  }

  // Get gun instance for direct access
  getGun() {
    return this.gun;
  }
}
Usage in a component:
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { GunService } from './gun.service';

@Component({
  selector: 'app-data',
  template: `
    <div *ngIf="data$ | async as data">
      <h2>{{ data.title }}</h2>
      <p>{{ data.content }}</p>
    </div>
  `
})
export class DataComponent implements OnInit {
  data$: Observable<any>;

  constructor(private gunService: GunService) {}

  ngOnInit() {
    this.data$ = this.gunService.observe('myData');
  }

  updateData(title: string, content: string) {
    this.gunService.put('myData', { title, content });
  }
}

User Authentication

Implement user authentication with GUN’s SEA:
import { Injectable } from '@angular/core';
import Gun from 'gun/gun';
import 'gun/sea';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private gun = Gun();
  private user = this.gun.user();

  signup(username: string, password: string): Promise<any> {
    return new Promise((resolve, reject) => {
      this.user.create(username, password, (ack) => {
        if (ack.err) {
          reject(ack.err);
        } else {
          resolve(ack);
        }
      });
    });
  }

  login(username: string, password: string): Promise<any> {
    return new Promise((resolve, reject) => {
      this.user.auth(username, password, (ack) => {
        if (ack.err) {
          reject(ack.err);
        } else {
          resolve(ack);
        }
      });
    });
  }

  logout(): void {
    this.user.leave();
  }

  isAuthenticated(): boolean {
    return this.user.is !== undefined;
  }

  getUser() {
    return this.user;
  }
}

TypeScript Configuration

Ensure your tsconfig.json includes proper settings:
{
  "compilerOptions": {
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  }
}

Best Practices

  1. Use services: Encapsulate GUN logic in Angular services
  2. RxJS integration: Convert GUN callbacks to observables for Angular’s async pipe
  3. Dependency injection: Provide GUN services at the module or component level
  4. Type safety: Use TypeScript interfaces for your data structures
  5. Cleanup subscriptions: Always unsubscribe in ngOnDestroy()

Common Patterns

Cleanup on Destroy

export class MyComponent implements OnInit, OnDestroy {
  private subscription: Subscription;

  ngOnInit() {
    this.subscription = this.gunService.observe('data')
      .subscribe(data => console.log(data));
  }

  ngOnDestroy() {
    this.subscription.unsubscribe();
  }
}

Using Async Pipe

export class MyComponent {
  data$ = this.gunService.observe('myData');
  constructor(private gunService: GunService) {}
}
<div *ngIf="data$ | async as data">
  {{ data | json }}
</div>

Next Steps

Build docs developers (and LLMs) love