Angular Guide

February 17, 2026 ยท View on GitHub

Setup and usage guide for Angular applications using @23blocks/angular.

Installation

npm install @23blocks/angular

The simplest way to use the SDK with automatic token management:

1. Configure providers

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provideBlocks23 } from '@23blocks/angular';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBlocks23({
      apiKey: 'your-api-key',
      urls: {
        authentication: 'https://api.yourapp.com',
      },
    }),
  ],
};

2. Bootstrap with the config

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig);

3. Use the services

import { Component, inject } from '@angular/core';
import { AuthenticationService } from '@23blocks/angular';

@Component({
  selector: 'app-login',
  template: `
    <form (ngSubmit)="login()">
      <input [(ngModel)]="email" placeholder="Email" />
      <input [(ngModel)]="password" type="password" placeholder="Password" />
      <button type="submit" [disabled]="loading">
        {{ loading ? 'Signing in...' : 'Sign In' }}
      </button>
      <p *ngIf="error" class="error">{{ error }}</p>
    </form>
  `,
})
export class LoginComponent {
  private auth = inject(AuthenticationService);

  email = '';
  password = '';
  loading = false;
  error = '';

  login() {
    this.loading = true;
    this.error = '';

    // Tokens are stored automatically!
    this.auth.signIn({ email: this.email, password: this.password })
      .subscribe({
        next: (response) => {
          console.log('Welcome', response.user.email);
        },
        error: (err) => {
          this.error = err.message;
          this.loading = false;
        },
        complete: () => {
          this.loading = false;
        },
      });
  }

  logout() {
    // Tokens are cleared automatically!
    this.auth.signOut().subscribe();
  }

  checkAuth() {
    // Check if user is authenticated (token mode only)
    return this.auth.isAuthenticated();
  }
}

Configuration Options

Token Mode (Default)

provideBlocks23({
  apiKey: 'your-api-key',
  urls: { authentication: 'https://api.yourapp.com' },
  // authMode: 'token', // default
  // storage: 'localStorage', // 'sessionStorage' | 'memory'
})
provideBlocks23({
  apiKey: 'your-api-key',
  urls: { authentication: 'https://api.yourapp.com' },
  authMode: 'cookie',
})

Multi-Tenant Setup

provideBlocks23({
  apiKey: 'your-api-key',
  urls: { authentication: 'https://api.yourapp.com' },
  tenantId: 'tenant-123',
})

NgModule-based Applications

// app.module.ts
import { NgModule } from '@angular/core';
import { getBlocks23Providers } from '@23blocks/angular';

@NgModule({
  providers: [
    ...getBlocks23Providers({
      apiKey: 'your-api-key',
      urls: { authentication: 'https://api.yourapp.com' },
    }),
  ],
})
export class AppModule {}

Advanced Setup (Custom Transport)

For advanced use cases requiring custom transport configuration:

npm install @23blocks/transport-http @23blocks/angular @23blocks/block-authentication

1. Configure providers

// app.config.ts
import { ApplicationConfig } from '@angular/core';
import { provide23Blocks } from '@23blocks/angular';
import { createHttpTransport } from '@23blocks/transport-http';

const transport = createHttpTransport({
  baseUrl: 'https://api.yourapp.com',
  headers: () => {
    const token = localStorage.getItem('access_token');
    return token ? { Authorization: `Bearer ${token}` } : {};
  },
});

export const appConfig: ApplicationConfig = {
  providers: [
    provide23Blocks({
      transport,
      authentication: { apiKey: 'your-api-key' },
      search: { apiKey: 'your-api-key' },
      // Add more blocks as needed
    }),
  ],
};

2. Bootstrap with the config

// main.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';

bootstrapApplication(AppComponent, appConfig);

Using Services (Advanced API)

AuthenticationService

import { Component, inject } from '@angular/core';
import { AuthenticationService } from '@23blocks/angular';

@Component({
  selector: 'app-login',
  template: `
    <form (ngSubmit)="login()">
      <input [(ngModel)]="email" placeholder="Email" />
      <input [(ngModel)]="password" type="password" placeholder="Password" />
      <button type="submit" [disabled]="loading">
        {{ loading ? 'Signing in...' : 'Sign In' }}
      </button>
      <p *ngIf="error" class="error">{{ error }}</p>
    </form>
  `,
})
export class LoginComponent {
  private auth = inject(AuthenticationService);

  email = '';
  password = '';
  loading = false;
  error = '';

  login() {
    this.loading = true;
    this.error = '';

    this.auth.signIn({ email: this.email, password: this.password })
      .subscribe({
        next: (response) => {
          localStorage.setItem('access_token', response.accessToken);
          console.log('Welcome', response.user.email);
        },
        error: (err) => {
          this.error = err.message;
          this.loading = false;
        },
        complete: () => {
          this.loading = false;
        },
      });
  }
}

SearchService

import { Component, inject } from '@angular/core';
import { SearchService } from '@23blocks/angular';
import { Subject, debounceTime, switchMap } from 'rxjs';

@Component({
  selector: 'app-search',
  template: `
    <input (input)="onSearch($event)" placeholder="Search..." />
    <ul>
      <li *ngFor="let result of results">{{ result.title }}</li>
    </ul>
  `,
})
export class SearchComponent {
  private search = inject(SearchService);
  private searchSubject = new Subject<string>();

  results: any[] = [];

  constructor() {
    this.searchSubject.pipe(
      debounceTime(300),
      switchMap((query) => this.search.search({ query, limit: 10 }))
    ).subscribe({
      next: (response) => {
        this.results = response.data;
      },
    });
  }

  onSearch(event: Event) {
    const query = (event.target as HTMLInputElement).value;
    this.searchSubject.next(query);
  }
}

ProductsService

import { Component, inject, OnInit } from '@angular/core';
import { ProductsService } from '@23blocks/angular';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-products',
  template: `
    <div *ngFor="let product of products$ | async">
      <h3>{{ product.name }}</h3>
      <p>{{ product.price | currency }}</p>
    </div>
  `,
})
export class ProductsComponent implements OnInit {
  private products = inject(ProductsService);
  products$!: Observable<any[]>;

  ngOnInit() {
    this.products$ = this.products.listProducts({ limit: 20 }).pipe(
      map(response => response.data)
    );
  }
}

Available Services

ServiceBlockDescription
AuthenticationServiceauthenticationSign in, sign up, password reset, MFA
SearchServicesearchFull-text search, favorites
ProductsServiceproductsProducts, categories, variants
CrmServicecrmContacts, organizations, deals
ContentServicecontentCMS content, pages
GeolocationServicegeolocationAddresses, places, geocoding
ConversationsServiceconversationsMessages, threads
FilesServicefilesFile uploads
FormsServiceformsForm builder, submissions
AssetsServiceassetsAsset management
CampaignsServicecampaignsMarketing campaigns
CompanyServicecompanyCompany settings
RewardsServicerewardsLoyalty programs
SalesServicesalesOrders, invoices
WalletServicewalletDigital wallet
JarvisServicejarvisAI assistant
OnboardingServiceonboardingUser onboarding
UniversityServiceuniversityLearning management

Health Check

Every block exposes a health() method to verify service connectivity:

import { AuthenticationService } from '@23blocks/angular';

@Component({ ... })
export class HealthComponent {
  private auth = inject(AuthenticationService);

  async checkHealth() {
    const status = await this.auth.health();
    console.log(status.service, status.status, status.version);
  }
}

Error Handling

All services return RxJS Observables. Errors are instances of BlockErrorException:

import { BlockErrorException, isBlockErrorException } from '@23blocks/contracts';

this.auth.signIn({ email, password }).subscribe({
  error: (err) => {
    if (isBlockErrorException(err)) {
      // Typed error with code, message, details
      console.log(err.code);    // e.g., 'INVALID_CREDENTIALS'
      console.log(err.message); // e.g., 'Invalid email or password'
    }
  },
});

RxJS Patterns

Combining Multiple Blocks

import { forkJoin } from 'rxjs';

// Fetch user and their favorites in parallel
forkJoin({
  user: this.auth.getCurrentUser(),
  favorites: this.search.listFavorites({ limit: 10 }),
}).subscribe({
  next: ({ user, favorites }) => {
    console.log(user, favorites);
  },
});

Caching with shareReplay

import { shareReplay } from 'rxjs';

// Cache the current user
currentUser$ = this.auth.getCurrentUser().pipe(
  shareReplay(1)
);

Lazy Loading Modules

For lazy-loaded modules, you can provide block configs at the module level:

// feature.module.ts
import { NgModule } from '@angular/core';
import { provide23Blocks } from '@23blocks/angular';

@NgModule({
  providers: [
    provide23Blocks({
      transport: existingTransport,
      products: { apiKey: 'feature-specific-api-key' },
    }),
  ],
})
export class FeatureModule {}

Testing

Mock services in your tests:

import { TestBed } from '@angular/core/testing';
import { AuthenticationService } from '@23blocks/angular';
import { of } from 'rxjs';

describe('LoginComponent', () => {
  const mockAuth = {
    signIn: jest.fn().mockReturnValue(of({ user: { email: 'test@test.com' }, accessToken: 'token' })),
  };

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        { provide: AuthenticationService, useValue: mockAuth },
      ],
    });
  });
});