npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@23blocks/angular

v14.7.1

Published

Angular bindings for 23blocks SDK - Injectable services with typed delegation to block APIs

Readme

@23blocks/angular

Angular bindings for the 23blocks SDK - Injectable services with typed delegation to block APIs.

npm version License: MIT

v7.0.0 - Complete rewrite from Observable wrappers to typed getter delegation. See CHANGELOG.md for migration guide.

Installation

npm install @23blocks/angular

Overview

This package provides Angular-specific bindings for the 23blocks SDK:

  • Injectable Services - All 18 blocks exposed as Angular services
  • Typed Delegation - Services expose block sub-services via typed getters
  • Promise-based - All methods return Promises (use from() for Observables)
  • Token Management - Automatic token storage and refresh for auth flows
  • Dependency Injection - Full DI support with providers
  • Zero Maintenance - Getters auto-sync with block API types

Quick Start

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://auth.yourapp.com',
        // Add other service URLs as needed
        // products: 'https://products.yourapp.com',
        // crm: 'https://crm.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>
    </form>
  `,
})
export class LoginComponent {
  private auth = inject(AuthenticationService);

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

  login() {
    this.loading = true;
    // Auth-flow methods return Observables with automatic token management
    this.auth.signIn({ email: this.email, password: this.password })
      .subscribe({
        next: (response) => {
          console.log('Welcome', response.user.email);
        },
        error: (err) => {
          console.error('Login failed:', err.message);
        },
        complete: () => {
          this.loading = false;
        },
      });
  }
}

Service Architecture

Services expose block sub-services through typed getters. Each getter returns the block's native service, which provides Promise-based methods.

// All non-auth services use this pattern:
const products = inject(ProductsService);

// Access sub-services via getters
const list = await products.products.list({ page: 1, perPage: 20 });
const cart = await products.cart.get(cartId);
const categories = await products.categories.list();

// Or convert to Observables with from()
from(products.products.list()).subscribe(list => { ... });

// Full block access for advanced use cases
const block = products.productsBlock;

AuthenticationService (Hybrid)

AuthenticationService is special: auth-flow methods that manage tokens return Observables with automatic tap() for token storage. All other sub-services are delegated via getters (Promise-based).

const auth = inject(AuthenticationService);

// Observable methods (with token management):
auth.signIn({ email, password }).subscribe(...)
auth.signUp({ email, password, passwordConfirmation }).subscribe(...)
auth.signOut().subscribe(...)
auth.refreshToken({ refreshToken }).subscribe(...)
auth.facebookLogin({ accessToken }).subscribe(...)
auth.googleLogin({ accessToken }).subscribe(...)

// Promise-based sub-services (via getters):
const user = await auth.users.get(userId);
const roles = await auth.roles.list();
const keys = await auth.apiKeys.list();
from(auth.mfa.enable(userId)).subscribe(...)

// Token management:
auth.isAuthenticated()     // boolean | null
auth.getAccessToken()      // string | null
auth.clearTokens()         // void

Configuration Options

provideBlocks23 Options

provideBlocks23({
  // Required: Your API key
  apiKey: 'your-api-key',

  // Required: Service URLs (only configure what you need)
  urls: {
    authentication: 'https://auth.yourapp.com',
    products: 'https://products.yourapp.com',
    crm: 'https://crm.yourapp.com',
    // ... other services
  },

  // Optional: Tenant ID for multi-tenant setups
  tenantId: 'tenant-123',

  // Optional: Authentication mode (default: 'token')
  authMode: 'token', // 'token' | 'cookie'

  // Optional: Token storage (default: 'localStorage')
  storage: 'localStorage', // 'localStorage' | 'sessionStorage' | 'memory'

  // Optional: Enable debug logging
  debug: !environment.production,
})

Cookie Mode

provideBlocks23({
  apiKey: 'your-api-key',
  urls: { authentication: 'https://auth.yourapp.com' },
  authMode: 'cookie',
})

Multi-Tenant Setup

provideBlocks23({
  apiKey: 'your-api-key',
  urls: { authentication: 'https://auth.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://auth.yourapp.com' },
    }),
  ],
})
export class AppModule {}

Available Services

| Service | Sub-services | |---------|-------------| | AuthenticationService | auth, users, roles, permissions, apiKeys, mfa, oauth, avatars, tenants, apps, blocks, services, subscriptionModels, userSubscriptions, companySubscriptions, countries, states, counties, cities, currencies, guests, magicLinks, refreshTokens, userDevices, tenantUsers, mailTemplates, jwks, adminRsaKeys, oidc | | SearchService | search, history, favorites, entities, identities, jarvis | | ProductsService | products, cart, cartDetails, categories, brands, vendors, warehouses, channels, collections, productSets, shoppingLists, promotions, prices, filters, images, variations, reviews, variationReviews, stock, suggestions, addons, myCarts, remarketing, visitors, productVendors | | CrmService | accounts, contacts, contactEvents, leads, leadFollows, opportunities, meetings, meetingParticipants, meetingBillings, quotes, subscribers, referrals, touches, categories, calendarAccounts, busyBlocks, icsTokens, zoomMeetings, zoomHosts, mailTemplates, communications, users, billingReports, calendarSync | | ContentService | posts, postVersions, postTemplates, comments, categories, tags, users, moderation, activity, series | | GeolocationService | locations, addresses, areas, regions, routes, bookings, premises, premiseEvents, routeTracker, locationHours, locationImages, locationSlots, locationTaxes, locationGroups, identities, locationIdentities, geoCountries, geoStates, geoCities | | ConversationsService | messages, draftMessages, groups, groupInvites, notifications, conversations, websocketTokens, contexts, notificationSettings, availabilities, messageFiles, sources, users, meetings, webNotifications | | FilesService | storageFiles, entityFiles, fileSchemas, userFiles, fileCategories, fileTags, delegations, fileAccess, fileAccessRequests | | FormsService | forms, schemas, schemaVersions, instances, sets, landings, subscriptions, appointments, surveys, referrals, mailTemplates, applicationForms, crmSync | | AssetsService | assets, events, audits, categories, tags, vendors, warehouses, entities, operations, alerts, users, images | | CampaignsService | campaigns, campaignMedia, landingPages, audiences, landingTemplates, targets, results, markets, locations, templates, mediaResults, media | | CompanyService | companies, departments, teams, teamMembers, quarters, positions, employeeAssignments | | RewardsService | rewards, coupons, loyalty, badges, couponConfigurations, offerCodes, expirationRules, customers, badgeCategories, moneyRules, productRules, eventRules | | SalesService | orders, orderDetails, orderTaxes, payments, subscriptions, subscriptionModels, entities, users, customers, flexibleOrders, stripe, mercadopago, vendorPayments | | WalletService | wallets, transactions, authorizationCodes, webhooks | | JarvisService | agents, prompts, workflows, executions, conversations, aiModels, entities, clusters, users, workflowParticipants, workflowSteps, workflowInstances, agentRuntime, mailTemplates, marvinChat, promptComments, executionComments | | OnboardingService | onboardings, flows, userJourneys, userIdentities, onboard, mailTemplates, remarketing | | UniversityService | courses, lessons, enrollments, assignments, submissions, subjects, teachers, students, courseGroups, coachingSessions, tests, registrationTokens, placements, calendars, matches, attendance, notes |

Each service also exposes a {serviceName}Block getter for full block access.

Usage Examples

Authentication

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

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

  // Sign in (Observable with token management)
  signIn() {
    this.auth.signIn({ email: this.email, password: this.password }).subscribe({
      next: ({ user, accessToken }) => console.log('Welcome', user.email),
    });
  }

  // Sign up (Observable with token management)
  signUp() {
    this.auth.signUp({
      email: '[email protected]',
      password: 'password',
      passwordConfirmation: 'password',
    }).subscribe();
  }

  // Sign out (Observable with token management)
  signOut() {
    this.auth.signOut().subscribe();
  }

  // Check if authenticated
  isAuthenticated(): boolean | null {
    return this.auth.isAuthenticated();
  }

  // Access sub-services (Promise-based)
  async loadUser(id: string) {
    return await this.auth.users.get(id);
  }

  async listRoles() {
    return await this.auth.roles.list();
  }
}

Search

import { Component, inject } from '@angular/core';
import { SearchService } from '@23blocks/angular';
import { Subject, debounceTime, switchMap, from } 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 searchSvc = inject(SearchService);
  private searchSubject = new Subject<string>();
  results: any[] = [];

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

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

Products

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

@Component({ ... })
export class ProductsComponent {
  private products = inject(ProductsService);

  // Promise-based
  async loadProducts() {
    return await this.products.products.list({ page: 1, perPage: 20 });
  }

  // Observable-based
  products$ = from(this.products.products.list({ page: 1, perPage: 20 }));

  async addToCart(productId: string) {
    await this.products.cart.addItem({ productId, quantity: 1 });
  }
}

Content

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

@Component({ ... })
export class BlogComponent {
  private content = inject(ContentService);

  async loadPosts() {
    return await this.content.posts.list({ page: 1, perPage: 10 });
  }

  async createPost() {
    return await this.content.posts.create({
      title: 'My New Post',
      body: 'Post content here...',
      status: 'published',
    });
  }

  async loadSeries() {
    return await this.content.series.list({ page: 1, perPage: 10 });
  }
}

CRM

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

@Component({ ... })
export class CrmComponent {
  private crm = inject(CrmService);

  async loadContacts() {
    return await this.crm.contacts.list({ page: 1, perPage: 20 });
  }

  async createLead() {
    return await this.crm.leads.create({
      firstName: 'John',
      lastName: 'Doe',
      email: '[email protected]',
    });
  }
}

RxJS Patterns

Since sub-services return Promises, use from() to work with Observables:

import { from, forkJoin } from 'rxjs';

// Convert a single Promise
from(this.products.products.list()).subscribe(list => { ... });

// Combine multiple calls
forkJoin({
  products: from(this.products.products.list()),
  categories: from(this.products.categories.list()),
}).subscribe(({ products, categories }) => { ... });

// Use with RxJS operators
from(this.search.search.search({ query })).pipe(
  map(result => result.data),
  catchError(err => of([]))
).subscribe(results => { ... });

Caching with shareReplay

import { from, shareReplay } from 'rxjs';

// Cache categories
categories$ = from(this.products.categories.list()).pipe(
  shareReplay(1)
);

Error Handling

Every error includes a unique request ID for debugging:

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

this.auth.signIn({ email, password }).subscribe({
  error: (err) => {
    if (isBlockErrorException(err)) {
      console.log('Request ID:', err.requestId);  // "req_m5abc_xyz123"
      console.log('Duration:', err.duration);      // 145 (ms)

      switch (err.code) {
        case ErrorCodes.INVALID_CREDENTIALS:
          this.error = 'Invalid email or password';
          break;
        case ErrorCodes.UNAUTHORIZED:
          this.error = 'Session expired';
          break;
        default:
          this.error = err.message;
      }
    }
  },
});

Advanced Setup (Custom Transport)

For advanced use cases requiring custom transport configuration:

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

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

export const appConfig: ApplicationConfig = {
  providers: [
    provide23Blocks({
      transport,
      authentication: { apiKey: 'your-api-key' },
      search: { apiKey: 'your-api-key' },
      products: { apiKey: 'your-api-key' },
    }),
  ],
};

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 = {
    // Auth-flow methods return Observables
    signIn: jest.fn().mockReturnValue(of({
      user: { email: '[email protected]' },
      accessToken: 'token',
    })),
    // Sub-service getters return objects with Promise methods
    users: {
      get: jest.fn().mockResolvedValue({ email: '[email protected]' }),
      list: jest.fn().mockResolvedValue({ data: [] }),
    },
  };

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

TypeScript

All services are fully typed. Import types from block packages:

import type { User, SignInResponse, SignUpResponse } from '@23blocks/block-authentication';
import type { Product } from '@23blocks/block-products';
import type { Contact, Lead } from '@23blocks/block-crm';
import type { Post, Series } from '@23blocks/block-content';

Environment Variables

// environment.ts
export const environment = {
  production: false,
  apiKey: 'your-api-key',
  urls: {
    authentication: 'https://auth.yourapp.com',
    products: 'https://products.yourapp.com',
  },
};

// app.config.ts
import { environment } from './environments/environment';

export const appConfig: ApplicationConfig = {
  providers: [
    provideBlocks23({
      apiKey: environment.apiKey,
      urls: environment.urls,
    }),
  ],
};

Related Packages

License

MIT - Copyright (c) 2024 23blocks