---
title: Content Modeling in Angular
description: Discover Storyblok's documentation with comprehensive developer guides, user manuals, API references, and examples to help you get the most out of the headless CMS platform.
url: https://storyblok.com/docs/guides/angular/content-modeling
---

# Content Modeling in Angular

Learn how to handle different nestable and content type blocks, render rich text, and use story references to manage content globally.

## Setup

In the existing space, create the following blocks:

-   An `article` content type block with the following fields:
    -   `title`: Text
    -   `content`: Rich text
-   An `article-overview` content type block with the following field:
    -   `title`: Text
-   A `featured-articles` nestable block with the following field:
    -   `articles`: References

  

> [!NOTE]
> Learn more about fields in the [concept](/docs/concepts/fields).

Next, create an `Articles` folder, open it, and create the following stories:

-   A few stories that use the `article` content type.
-   An article overview story with a `article-overview` content type. Select the option **Define as root for the folder**.

Finally, add the `featured-articles` block to the `body` field of the **Home** story, and select articles to feature.

## Fetch and list all articles

Create a new `src/app/services/article.service.ts` file to fetch articles from the Storyblok API client.

src/app/services/article.service.ts

```typescript
import { inject, Injectable } from '@angular/core';
import { StoryblokService, type Story } from '@storyblok/angular';

@Injectable({ providedIn: 'root' })
export class ArticleService {
  private readonly storyblok = inject(StoryblokService);

  async getArticles(): Promise<Story[]> {
    const client = this.storyblok.getClient();
    const { data } = await client.stories.list({
      query: { version: 'draft', starts_with: 'articles/', content_type: 'article' },
    });
    return (data?.stories as Story[]) ?? [];
  }
}
```

The `client.stories.list()` fetches multiple stories. The `starts_with` parameter fetches only stories from the “Articles” folder. The `content_type` parameter restricts the results to stories with the `article` content type.

> [!TIP]
> Learn more about parameters and filter queries in the [Content Delivery API documentation](https://www.storyblok.com/docs/api/content-delivery/v2).

> [!NOTE]
> The `ArticleService` fetches data on the client. With Angular SSR, the same request runs on the server and again on the client, which leads to duplicate requests. Use Angular’s [TransferState](https://angular.dev/api/core/TransferState) API to reuse server-fetched data on the client.

Create a new `src/app/components/article-overview/article-overview.component.ts` component.

src/app/components/article-overview/article-overview.component.ts

```typescript
import { Component, ChangeDetectionStrategy, inject, OnInit, signal, input } from '@angular/core';
import { RouterLink } from '@angular/router';
import { type SbBlokData, type Story } from '@storyblok/angular';
import { ArticleService } from '../../services/article.service';

export interface ArticleOverviewComponentBlok extends SbBlokData {
  title?: string;
}

@Component({
  selector: 'app-article-overview',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [RouterLink],
  template: `
    <div>
      <h1>{{ blok().title }}</h1>
      @for (article of articles(); track article.uuid) {
        <div>
          <a [routerLink]="'/' + article.full_slug">
            {{ article.content['title'] }}
          </a>
        </div>
      }
    </div>
  `,
})
export class ArticleOverviewComponent implements OnInit {
  readonly blok = input.required<ArticleOverviewComponentBlok>();
  private readonly articleService = inject(ArticleService);
  readonly articles = signal<Story[]>([]);

  async ngOnInit() {
    this.articles.set(await this.articleService.getArticles());
  }
}
```

This component calls `articleService.getArticles()` to fetch all stories with content type `article`.

## Create the article block

Add a new `src/app/components/article/article.component.ts` component to render the new article content type.

src/app/components/article/article.component.ts

```typescript
import { Component, ChangeDetectionStrategy, input } from '@angular/core';
import { type SbBlokData } from '@storyblok/angular';
import { SbRichTextComponent, type StoryblokRichTextJson } from '@storyblok/angular';

export interface ArticleBlok extends SbBlokData {
  title?: string;
  content?: StoryblokRichTextJson
}

@Component({
  selector: 'app-article',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [SbRichTextComponent],
  template: `
    <div>
       <h2>{{ blok().title }}</h2>
       <sb-rich-text [sbDocument]="blok().content" />
    </div>
  `,
})
export class ArticleComponent {
  readonly blok = input.required<ArticleBlok>();
}
```

To render rich text fields, use the `SbRichTextComponent` provided by the `@storyblok/angular` module. `StoryblokRichTextJson` defines the type for rich text nodes in Storyblok.

> [!NOTE]
> Learn more about handling rich text in Storyblok in the [fields concept](https://www.storyblok.com/docs/concepts/fields#rich-text) and [@storyblok/angular reference](https://www.storyblok.com/docs/libraries/js/angular-sdk#example-withstoryblokrichtextcomponents).

Register the `article` block and the `article-overview` block in the `storyblok.components.ts` file.

src/app/storyblok.component.ts

```typescript
import { type StoryblokComponentsMap } from '@storyblok/angular';

export const storyblokComponents: StoryblokComponentsMap = {
  page: () => import('./components/page/page.component').then((m) => m.PageComponent),
  teaser: () => import('./components/teaser/teaser.component').then((m) => m.TeaserComponent),
  grid: () => import('./components/grid/grid.component').then((m) => m.GridComponent),
  feature: () => import('./components/feature/feature.component').then((m) => m.FeatureComponent),
  'article-overview': () => import('./components/article-overview/article-overview.component').then((m) => m.ArticleOverviewComponent),
  article: () => import('./components/article/article.component').then((m) => m.ArticleComponent),};
```

Now the articles in the “Articles” folder should render the content.

## Handle referenced stories

In the `app.routes.ts` file, set the `resolve_relations` parameter to get the full object response of referenced stories.

src/app/app.routes.ts

```typescript
import { inject } from '@angular/core';
import { ActivatedRouteSnapshot, Routes } from '@angular/router';
import { StoryblokService } from '@storyblok/angular';

export const routes: Routes = [
  {
    path: '**',
    loadComponent: () =>
      import('./routes/catch-all/catch-all.component').then((m) => m.CatchAllComponent),
    resolve: {
      story: async (route: ActivatedRouteSnapshot) => {
        const slug = route.url.map((s) => s.path).join('/') || 'home';
        const client = inject(StoryblokService).getClient();
        const { data } = await client.stories.get(slug, {
          query: {
              version: 'draft',
              resolve_relations: 'featured-articles.articles',
                 },
        });
        return data?.story;
      },
    },
  },
];
```

> [!NOTE]
> Learn more in the [references concept](https://www.storyblok.com/docs/concepts/references) documentation.

Add `inlineRelations: true` to the Storyblok config in `app.config.ts` file to replace story UUIDs with resolved story objects automatically. To enable live preview for relations, pass `resolveRelations` option to the `withLivePreview` provider.

src/app/app.config.ts

```typescript
...
export const appConfig: ApplicationConfig = {
  providers: [
    provideRouter(routes, withComponentInputBinding()),
    provideStoryblok(
      {
        accessToken: environment.accessToken,
        region: 'eu', // 'eu', 'us', 'ap', 'ca', or 'cn'
        inlineRelations: true,
      },
      withStoryblokComponents(storyblokComponents),
      // Only required when using the live preview functionality
      withLivePreview({
         resolveRelations: ['featured-articles.articles'],
     }),
    ),
  ],
};
```

Next, create a new `src/app/components/featured-articles/featured-articles.component.ts` component.

src/app/components/featured-articles/featured-articles.component.ts

```typescript
import { Component, ChangeDetectionStrategy, computed, input } from '@angular/core';
import { RouterLink } from '@angular/router';
import { type SbBlokData, type Story } from '@storyblok/angular';

export interface FeaturedArticlesComponentBlok extends SbBlokData {
  articles?: Story[];
}

@Component({
  selector: 'app-featured-articles',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [RouterLink],
  template: `
    <div>
      <h1>Featured Articles</h1>

      @for (article of articles(); track article.uuid) {
        <div>
          <a [routerLink]="'/' + article.full_slug">
            {{ article.content['title'] }}
          </a>
        </div>
      }
    </div>
  `,
})
export class FeaturedArticlesComponent {
  readonly blok = input.required<FeaturedArticlesComponentBlok>();
   readonly articles = computed(() => (this.blok().articles ?? []) as Story[]
);
}
```

The `FeaturedArticlesComponent` renders each article title as a link. It uses an Angular computed signal to track the articles array and update the template automatically when the input changes.

Register this block in the `storyblok.component.ts` file.

src/app/storyblok.components.ts

```typescript
import { type StoryblokComponentsMap } from '@storyblok/angular';

export const storyblokComponents: StoryblokComponentsMap = {
  page: () => import('./components/page/page.component').then((m) => m.PageComponent),
  teaser: () => import('./components/teaser/teaser.component').then((m) => m.TeaserComponent),
  grid: () => import('./components/grid/grid.component').then((m) => m.GridComponent),
  feature: () => import('./components/feature/feature.component').then((m) => m.FeatureComponent),
  'article-overview': () => import('./components/article-overview/article-overview.component').then((m) => m.ArticleOverviewComponent),
  article: () => import('./components/article/article.component').then((m) => m.ArticleComponent),
 'featured-articles': () => import('./components/featured-articles/featured-articles.component').then((m) => m.FeaturedArticlesComponent),};
```

Now, this component will render links to the featured articles in the home page of the project.

## Related Resources

[Concept: Fields](/docs/concepts/fields#rich-text)

[Concept: References](/docs/concepts/references)

[@storyblok/angular Package Reference](https://www.storyblok.com/docs/libraries/js/angular-sdk#example-withstoryblokrichtextcomponents)

  

## Pagination

-   [Previous: Dynamic Routing in Angular](/docs/guides/angular/dynamic-routing)
-   [Next: Internationalization in Angular](/docs/guides/angular/internationalization)
