Framework guides

Angular

@scribe-atp/angular ships two APIs. Choose based on your component style:

APIStyleReturns
ScribeServiceRxJS / async pipeCold Observables — fetch starts on subscribe, cancels on unsubscribe
injectSite / injectArticleAngular SignalsReadonly signals — fetch starts immediately, cancels on component destroy

Install

npm install @scribe-atp/angular

Requires Angular 16 or later.

Observable API — ScribeService

ScribeService is provided in the root injector. Use it with the async pipe for the most concise result:

import { Component, inject } from '@angular/core';
import { AsyncPipe, NgIf, NgFor } from '@angular/common';
import { ScribeService } from '@scribe-atp/angular';

@Component({
  standalone: true,
  imports: [AsyncPipe, NgIf, NgFor],
  template: `
    <ng-container *ngIf="site$ | async as site">
      <h1>{{ site.title }}</h1>
      <section *ngFor="let group of site.groups">
        <h2>{{ group.title }}</h2>
        <ul>
          <li *ngFor="let article of group.articles">{{ article.title }}</li>
        </ul>
      </section>
    </ng-container>
  `,
})
export class BlogComponent {
  site$ = inject(ScribeService).getSite('alice.bsky.social', 'https://alice.bsky.social');
}

The fetch is cancelled automatically when you unsubscribe or when the async pipe's host component is destroyed.

Signals API — injectSite / injectArticle

Injection functions return { site, loading, error } as readonly signals. The fetch is aborted automatically when the host component is destroyed.

import { Component } from '@angular/core';
import { NgIf, NgFor } from '@angular/common';
import { injectSite } from '@scribe-atp/angular';

@Component({
  standalone: true,
  imports: [NgIf, NgFor],
  template: `
    <p *ngIf="vm.loading()">Loading…</p>
    <p *ngIf="vm.error()">{{ vm.error()!.message }}</p>
    <ng-container *ngIf="vm.site() as site">
      <h1>{{ site.title }}</h1>
      <section *ngFor="let group of site.groups">
        <h2>{{ group.title }}</h2>
      </section>
    </ng-container>
  `,
})
export class BlogComponent {
  vm = injectSite('alice.bsky.social', 'https://alice.bsky.social');
}

Note

Injection functions must be called in an injection context — inside a constructor or class field initialiser. They cannot be called inside lifecycle hooks or event handlers.

Just the AT URI — injectPublicationUri / injectDocumentUri

@scribe-atp/social's LikeButton, SubscribeButton, and ShareButton are React components, so they aren't directly usable from an Angular template — but if you're building your own like/subscribe UI against the AT Protocol yourself (or linking out to a Bluesky embed), injectPublicationUri and injectDocumentUri resolve just the AT URI without fetching (or re-rendering on) the full site/article object:

import { Component } from '@angular/core';
import { NgIf } from '@angular/common';
import { injectArticle, injectDocumentUri } from '@scribe-atp/angular';

@Component({
  standalone: true,
  imports: [NgIf],
  template: `
    <article *ngIf="article.article() as a">
      <h1>{{ a.title }}</h1>
      <div [innerHTML]="a.content"></div>
    </article>
    <p *ngIf="uri.uri() as documentUri">AT URI: {{ documentUri }}</p>
  `,
})
export class ArticleComponent {
  article = injectArticle('alice.bsky.social', 'my-first-post');
  uri = injectDocumentUri('alice.bsky.social', 'https://alice.bsky.social', 'my-first-post');
}

TypeScript types

All types from @scribe-atp/core are re-exported from @scribe-atp/angular:

import type { Site, Article, ArticleRef, SiteGroup } from '@scribe-atp/angular';

Settings

Appearance