Skip to content

Commit b78dd7e

Browse files
chore: basic docs added
1 parent 85506f3 commit b78dd7e

File tree

3 files changed

+986
-6
lines changed

3 files changed

+986
-6
lines changed

packages/angular/README.md

Lines changed: 330 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,330 @@
1+
<div align="center">
2+
3+
![Storyblok Angular](https://raw.githubusercontent.com/storyblok/.github/refs/heads/main/profile/public/github-banner.png)
4+
5+
<h1 align="center">@storyblok/angular</h1>
6+
<p align="center">
7+
The Angular SDK to interact with <a href="http://www.storyblok.com?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-angular" target="_blank">Storyblok API</a> and enable the <a href="https://www.storyblok.com/docs/guide/essentials/visual-editor?utm_source=github.com&utm_medium=readme&utm_campaign=storyblok-angular" target="_blank">Real-time Visual Editing Experience</a>.
8+
</p>
9+
<br />
10+
</div>
11+
12+
<p align="center">
13+
<a href="https://npmjs.com/package/@storyblok/angular">
14+
<img src="https://img.shields.io/npm/v/@storyblok/angular/latest.svg?style=flat-square&color=8d60ff" alt="Storyblok Angular SDK" />
15+
</a>
16+
<a href="https://npmjs.com/package/@storyblok/angular" rel="nofollow">
17+
<img src="https://img.shields.io/npm/dt/@storyblok/angular.svg?style=flat-square&color=8d60ff" alt="npm">
18+
</a>
19+
<a href="https://storyblok.com/join-discord">
20+
<img src="https://img.shields.io/discord/700316478792138842?label=Join%20Our%20Discord%20Community&style=appveyor&logo=discord&color=8d60ff">
21+
</a>
22+
<a href="https://twitter.com/intent/follow?screen_name=storyblok">
23+
<img src="https://img.shields.io/badge/Follow-%40storyblok-8d60ff?style=appveyor&logo=twitter" alt="Follow @Storyblok" />
24+
</a><br/>
25+
<a href="https://app.storyblok.com/#!/signup?utm_source=github.com&utm_medium=readme&utm_campaign=@storyblok/angular">
26+
<img src="https://img.shields.io/badge/Try%20Storyblok-Free-8d60ff?style=appveyor&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAHqADAAQAAAABAAAAHgAAAADpiRU/AAACRElEQVRIDWNgGGmAEd3D3Js3LPrP8D8WXZwSPiMjw6qvPoHhyGYwIXNAbGpbCjbzP0MYuj0YFqMroBV/wCxmIeSju64eDNzMBJUxvP/9i2Hnq5cM1devMnz984eQsQwETeRhYWHgIcJiXqC6VHlFBjUeXgav40cIWkz1oLYXFmGwFBImaDFBHyObcOzdW4aSq5eRhRiE2dgYlpuYoYSKJi8vw3GgWnyAJIs/AuPu4scPGObd/fqVQZ+PHy7+6udPOBsXgySLDfn5GRYYmaKYJcXBgWLpsx8/GPa8foWiBhuHJIsl2DkYQqWksZkDFgP5PObcKYYff//iVAOTIDlx/QPqRMb/YSYBaWlOToZIaVkGZmAZSQiQ5OPtwHwacuo4iplMQEu6tXUZMhSUGDiYmBjylFQYvv/7x9B04xqKOnQOyT5GN+Df//8M59ASXKyMHLoyDD5JPtbj42OYrm+EYgg70JfuYuIoYmLs7AwMjIzA+uY/zjAnyWJpDk6GOFnCvrn86SOwmsNtKciVFAc1ileBHFDC67lzG10Yg0+SjzF0ownsf/OaofvOLYaDQJoQIGix94ljv1gIZI8Pv38zPvj2lQWYf3HGKbpDCFp85v07NnRN1OBTPY6JdRSGxcCw2k6sZuLVMZ5AV4s1TozPnGGFKbz+/PE7IJsHmC//MDMyhXBw8e6FyRFLv3Z0/IKuFqvFyIqAzd1PwBzJw8jAGPfVx38JshwlbIygxmYY43/GQmpais0ODDHuzevLMARHBcgIAQAbOJHZW0/EyQAAAABJRU5ErkJggg==" alt="Follow @Storyblok" />
27+
</a>
28+
</p>
29+
30+
## Features
31+
32+
- Fetch content from the Content Delivery API using `StoryblokService`
33+
- Dynamic component rendering with `SbBlokDirective`
34+
- Real-time Visual Editing with `LivePreviewService`
35+
- Rich text rendering with `SbRichTextComponent`
36+
- Full SSR support with Angular Universal
37+
- Tree-shakeable features for optimal bundle size
38+
- Lazy loading support for Storyblok components
39+
40+
## Installation
41+
42+
Install `@storyblok/angular`:
43+
44+
```bash
45+
npm install @storyblok/angular
46+
# or
47+
pnpm add @storyblok/angular
48+
# or
49+
yarn add @storyblok/angular
50+
```
51+
52+
## Setup
53+
54+
### 1. Configure the SDK
55+
56+
Add the Storyblok provider to your application configuration:
57+
58+
```typescript
59+
// app.config.ts
60+
import { ApplicationConfig } from '@angular/core';
61+
import {
62+
provideStoryblok,
63+
withStoryblokComponents,
64+
withLivePreview,
65+
} from '@storyblok/angular';
66+
67+
export const appConfig: ApplicationConfig = {
68+
providers: [
69+
provideStoryblok(
70+
{
71+
accessToken: 'YOUR_ACCESS_TOKEN',
72+
region: 'eu', // 'eu', 'us', 'ap', 'ca', or 'cn'
73+
},
74+
withStoryblokComponents({
75+
page: () => import('./components/page').then((m) => m.PageComponent),
76+
teaser: () => import('./components/teaser').then((m) => m.TeaserComponent),
77+
}),
78+
withLivePreview()
79+
),
80+
],
81+
};
82+
```
83+
84+
### 2. Create Storyblok components
85+
86+
Create Angular components that match your Storyblok component names:
87+
88+
```typescript
89+
// components/teaser.ts
90+
import { Component, input } from '@angular/core';
91+
import { type SbBlokData } from '@storyblok/angular';
92+
93+
interface TeaserBlok extends SbBlokData {
94+
headline?: string;
95+
}
96+
97+
@Component({
98+
selector: 'app-teaser',
99+
template: `<h2>{{ blok().headline }}</h2>`,
100+
})
101+
export class TeaserComponent {
102+
readonly blok = input.required<TeaserBlok>();
103+
}
104+
```
105+
106+
### 3. Fetch and render content
107+
108+
Use `StoryblokService` to fetch content and `SbBlokDirective` to render it:
109+
110+
```typescript
111+
// routes/home.component.ts
112+
import { Component, inject, signal, OnInit } from '@angular/core';
113+
import { StoryblokService, SbBlokDirective, type SbBlokData } from '@storyblok/angular';
114+
115+
@Component({
116+
selector: 'app-home',
117+
imports: [SbBlokDirective],
118+
template: `<ng-container [sbBlok]="content()" />`,
119+
})
120+
export class HomeComponent implements OnInit {
121+
private readonly storyblok = inject(StoryblokService);
122+
readonly content = signal<SbBlokData | null>(null);
123+
124+
async ngOnInit() {
125+
const client = this.storyblok.getClient();
126+
const { data } = await client.stories.get('home', {
127+
query: { version: 'draft' },
128+
});
129+
this.content.set(data?.story?.content);
130+
}
131+
}
132+
```
133+
134+
## Region parameter
135+
136+
Set the region based on where your Storyblok space was created:
137+
138+
| Region | Value |
139+
|--------|-------|
140+
| Europe | `eu` (default) |
141+
| United States | `us` |
142+
| Australia | `ap` |
143+
| Canada | `ca` |
144+
| China | `cn` |
145+
146+
```typescript
147+
provideStoryblok({
148+
accessToken: 'YOUR_ACCESS_TOKEN',
149+
region: 'us',
150+
})
151+
```
152+
153+
> **Note:** For spaces created in the United States or China, the `region` parameter **must** be specified.
154+
155+
## Live Preview
156+
157+
Enable real-time visual editing in the Storyblok Visual Editor:
158+
159+
```typescript
160+
// app.config.ts
161+
provideStoryblok(
162+
{ accessToken: 'YOUR_ACCESS_TOKEN' },
163+
withLivePreview({
164+
resolveRelations: ['article.author'],
165+
})
166+
)
167+
```
168+
169+
In your route component, listen for live updates:
170+
171+
```typescript
172+
import { Component, inject, linkedSignal, input, OnInit } from '@angular/core';
173+
import { LivePreviewService, SbBlokDirective, type ISbStoryData } from '@storyblok/angular';
174+
175+
@Component({
176+
imports: [SbBlokDirective],
177+
template: `<ng-container [sbBlok]="story()?.content" />`,
178+
})
179+
export class CatchAllComponent implements OnInit {
180+
private readonly livePreview = inject(LivePreviewService);
181+
182+
readonly storyInput = input<ISbStoryData | null>(null, { alias: 'story' });
183+
readonly story = linkedSignal(() => this.storyInput());
184+
185+
ngOnInit() {
186+
this.livePreview.listen((updatedStory) => {
187+
this.story.set(updatedStory);
188+
});
189+
}
190+
}
191+
```
192+
193+
## Render rich text
194+
195+
Use the `SbRichTextComponent` to render rich text fields:
196+
197+
```typescript
198+
import { Component, input } from '@angular/core';
199+
import { SbRichTextComponent, type StoryblokRichTextNode } from '@storyblok/angular';
200+
201+
@Component({
202+
imports: [SbRichTextComponent],
203+
template: `<sb-rich-text [doc]="content()" />`,
204+
})
205+
export class ArticleComponent {
206+
readonly content = input<StoryblokRichTextNode>();
207+
}
208+
```
209+
210+
### Custom rich text components
211+
212+
Override default elements with custom Angular components:
213+
214+
```typescript
215+
// app.config.ts
216+
import { withStoryblokRichtextComponents } from '@storyblok/angular';
217+
218+
provideStoryblok(
219+
{ accessToken: 'YOUR_ACCESS_TOKEN' },
220+
withStoryblokRichtextComponents({
221+
link: CustomLinkComponent,
222+
image: OptimizedImageComponent,
223+
})
224+
)
225+
```
226+
227+
Create a custom component using the `props` input pattern:
228+
229+
```typescript
230+
import { Component, input, computed } from '@angular/core';
231+
import { SbRichTextNodeComponent, type AngularRenderNode } from '@storyblok/angular';
232+
233+
interface LinkProps {
234+
href?: string;
235+
target?: string;
236+
children?: AngularRenderNode[];
237+
}
238+
239+
@Component({
240+
imports: [SbRichTextNodeComponent],
241+
template: `
242+
<a [href]="href()" [target]="target()" class="custom-link">
243+
<sb-rich-text-node [nodes]="children()" />
244+
</a>
245+
`,
246+
})
247+
export class CustomLinkComponent {
248+
readonly props = input<LinkProps>({});
249+
readonly href = computed(() => this.props().href ?? '');
250+
readonly target = computed(() => this.props().target ?? '_self');
251+
readonly children = computed(() => this.props().children ?? []);
252+
}
253+
```
254+
255+
## Lazy loading components
256+
257+
Register components with lazy loading for optimal bundle size:
258+
259+
```typescript
260+
const storyblokComponents: StoryblokComponentsMap = {
261+
page: () => import('./components/page').then((m) => m.PageComponent),
262+
teaser: () => import('./components/teaser').then((m) => m.TeaserComponent),
263+
grid: () => import('./components/grid').then((m) => m.GridComponent),
264+
};
265+
266+
provideStoryblok(
267+
{ accessToken: 'YOUR_ACCESS_TOKEN' },
268+
withStoryblokComponents(storyblokComponents)
269+
)
270+
```
271+
272+
## API reference
273+
274+
### Providers
275+
276+
| Provider | Description |
277+
|----------|-------------|
278+
| `provideStoryblok(config, ...features)` | Configure the SDK with access token and optional features |
279+
| `withStoryblokComponents(map)` | Register Storyblok components (eager or lazy) |
280+
| `withLivePreview(options?)` | Enable real-time visual editing |
281+
| `withStoryblokRichtextComponents(map)` | Register custom rich text components |
282+
283+
### Services
284+
285+
| Service | Description |
286+
|---------|-------------|
287+
| `StoryblokService` | Access the Storyblok API client |
288+
| `LivePreviewService` | Listen for live preview updates |
289+
290+
### Components and directives
291+
292+
| Component/Directive | Description |
293+
|---------------------|-------------|
294+
| `SbBlokDirective` | Dynamically render Storyblok components |
295+
| `SbRichTextComponent` | Render rich text content |
296+
| `SbRichTextNodeComponent` | Render rich text nodes (for custom components) |
297+
298+
### Types
299+
300+
| Type | Description |
301+
|------|-------------|
302+
| `SbBlokData` | Base type for Storyblok block data |
303+
| `ISbStoryData` | Story data from the API |
304+
| `StoryblokRichTextNode` | Rich text document node |
305+
| `AngularRenderNode` | Rendered node in rich text AST |
306+
307+
## Documentation
308+
309+
For complete documentation, visit the [Storyblok Angular SDK documentation](https://www.storyblok.com/docs/packages/storyblok-angular).
310+
311+
## Contributing
312+
313+
If you'd like to contribute, refer to the [contributing guidelines](../../CONTRIBUTING.md).
314+
315+
## Community
316+
317+
For help, discussion about best practices, or any other conversation:
318+
319+
- [Discuss Storyblok on GitHub Discussions](https://github.com/storyblok/monoblok/discussions)
320+
- [Join our Discord Community](https://storyblok.com/join-discord)
321+
322+
## Support
323+
324+
For bugs or feature requests, [submit an issue](https://github.com/storyblok/monoblok/issues/new/choose).
325+
326+
> **Important:** Search existing issues before submitting a new one. Issues without a minimal reproducible example will be closed. [Why reproductions are required](https://antfu.me/posts/why-reproductions-are-required).
327+
328+
## License
329+
330+
[MIT License](../../LICENSE)

0 commit comments

Comments
 (0)