Hey there, Angular devs!
Let’s talk about content management in modern web development. Sanity CMS has become a powerful solution for handling structured content, and one of its most interesting features is Portable Text, a flexible JSON-based format for rich text.
Now, getting Portable Text to play nice with Angular can be a bit tricky. But don’t worry, we’ve got a secret weapon: the @limitless-angular/sanity library. This package is designed to make your life easier when working with Sanity and Angular.
What’s so great about @limitless-angular/sanity? Well, it gives you two main benefits:
A full Portable Text implementation for Angular
An image loader designed specifically for use with Sanity.
In this guide, we’ll walk you through setting up an Angular app that can render Portable Text content from Sanity CMS using this library. By the end, you’ll have a functioning Angular component that can elegantly display your Sanity content.
Ready to dive in? Let’s go!
Before We Start
Make sure you’ve got:
Some basic Angular and TypeScript knowledge
Node.js and npm installed on your machine
A Sanity CMS project with some Portable Text content to play with
Step 1: Set Up Your Angular Project
First, let’s create a new Angular project:
cd sanity-portable-text-demo
Step 2: Install Required Dependencies
Install our library and the Sanity client:
Step 3: Set Up Your Sanity Connection
Create a new file src/app/sanity-client.ts:
export const client = createClient({
projectId: ‘your-project-id‘,
dataset: ‘your-dataset‘,
useCdn: true, // set to `false` to bypass the edge cache
apiVersion: ‘2023-05-03‘, // use current date (YYYY-MM-DD) to target the latest API
});
Don’t forget to replace ‘your-project-id’ and ‘your-dataset’ with your actual Sanity details!
Step 4: Create a Content-Fetching Service
Let’s create a service to handle our Sanity queries. Create a new file src/app/content.service.ts:
import { client } from ‘./sanity-client‘;
import { Observable, from } from ‘rxjs‘;
@Injectable({
providedIn: ‘root‘
})
export class ContentService {
getPortableTextContent(documentId: string): Observable<any> {
return from(client.fetch(`*[_id == $documentId][0].content`, { documentId }));
}
}
This service will fetch your Portable Text content from a specific Sanity document.
Note: This example assumes that your Sanity document has a content property that contains the Portable Text block object for rendering. If your document structure differs, you may need to adjust the query accordingly.
Step 5: Build Your Content Display Component
Now, let’s create a component that’ll display your Portable Text content:
Update src/app/portable-text-display/portable-text-display.component.ts:
import { PortableTextComponent, PortableTextComponents } from ‘@limitless-angular/sanity‘;
import { ContentService } from ‘../content.service‘;
import { toSignal } from ‘@angular/core/rxjs-interop‘;
import { LinkComponent } from ‘./link.component‘;
@Component({
selector: ‘app-portable-text-display‘,
standalone: true,
imports: [PortableTextComponent],
template: `
@if (portableTextContent(); as content) {
<div portable-text [value]=”content” [components]=”customComponents”></div>
} @else {
<p>Hold on, content’s loading…</p>
}
`,
})
export class PortableTextDisplayComponent {
private contentService = inject(ContentService);
portableTextContent = toSignal(
this.contentService.getPortableTextContent(‘your-document-id‘)
);
customComponents: PortableTextComponents = {
marks: {
link: LinkComponent,
},
};
}
Remember to replace ‘your-document-id’ with an actual ID from your Sanity project.
Step 6: Create a Custom Link Component
Create a new file src/app/portable-text-display/link.component.ts:
import { PortableTextMarkComponent } from ‘@limitless-angular/sanity‘;
interface LinkMark {
_type: ‘link‘;
href: string;
}
@Component({
selector: ‘a‘,
standalone: true,
template: `<ng-container #children />`,
host: {
‘[href]‘: ‘value()?.href‘,
‘[target]‘: ‘target()‘,
‘[rel]‘: ‘rel()‘,
},
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LinkComponent extends PortableTextMarkComponent<LinkMark> {
target = computed(() =>
(this.value()?.href ?? ”).startsWith(‘http‘) ? ‘_blank‘ : undefined,
);
rel = computed(() =>
this.target() === ‘_blank‘ ? ‘noindex nofollow‘ : undefined,
);
}
This little component extends the PortableTextMarkComponent and adds some additional functionality for external links.
Step 7: Showcase in Your Main App
Finally, let’s use our new component. Update src/app/app.component.ts:
import { PortableTextDisplayComponent } from ‘./portable-text-display/portable-text-display.component‘;
@Component({
selector: ‘app-root‘,
standalone: true,
imports: [PortableTextDisplayComponent],
template: `
<h1>Check Out This Sanity Portable Text Magic!</h1>
<app-portable-text-display />
`,
})
export class AppComponent {}
Step 8: Run Your Application!
You’re all set! Let’s see your application in action:
Navigate to http://localhost:4200 in your browser. If all goes well, you should see your Sanity content rendered on the page!
Wrapping Up
And there you have it! You’ve just built an Angular app that can handle Sanity’s Portable Text content from Sanity CMS. We’ve used some pretty cool Angular features along the way:
Standalone components
The new built-in control flow syntax
Dependency injection with the inject function
Signals with toSignal from ‘@angular/core/rxjs-interop’
A custom link component with computed properties
Remember, this is just the beginning. You can customize how different blocks render, add styling, or even get more complex with your Sanity queries. The @limitless-angular/sanity library provides much flexibility for handling various types of Portable Text content.
If you want to dive deeper, check out these resources:
The @limitless-angular/sanity GitHub repo
The @limitless-angular/sanity NPM package
Sanity.io docs
Angular docs
Now go forth and create some awesome content-driven Angular apps! Happy coding!