Skip to main content

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