Use Sanity CMS with Angular - blazed-space/fire-angular GitHub Wiki
Using Sanity CMS with Angular is much more challenging, but provides a more refined experience.
- Install Sanity Client & Image URL Builder
npm install @sanity/client @sanity/image-url @ng-web-apis/common rxjs
- Add config options to environment.ts
export const environment = {
production: false,
sanity: {
projectId: '< sanity project id>',
dataset: '< dataset, usually production|development >',
useCdn: true,
},
web: {
url: '<#< website-url >#>',
},
}
- Update typescript config files
- tsconfig.json
"compilerOptions": {
"allowSyntheticDefaultImports": true,
...
},
- tsconfig.app.json
"compilerOptions": {
"types": ["node"],
...
},
- Create Sanity Service
ng generate service SanityService
inside sanity-service.service.ts:
import { HttpClient } from '@angular/common/http';
import { Inject, Injectable } from '@angular/core';
import { WINDOW } from '@ng-web-apis/common';
import sanityClient, { ClientConfig, SanityClient } from '@sanity/client';
import imageUrlBuilder from '@sanity/image-url';
import { ImageUrlBuilder } from '@sanity/image-url/lib/types/builder';
import { SanityImageSource } from '@sanity/image-url/lib/types/types';
import { map, Observable } from 'rxjs';
import { environment } from '../environments/environment';
@Injectable({
providedIn: 'root',
})
export class SanityService {
private client: SanityClient;
private imageUrlBuilder: ImageUrlBuilder;
private clientConfig: ClientConfig = {
projectId: environment.sanity.projectId,
dataset: environment.sanity.dataset,
apiVersion: this.getApiVersion(),
useCdn: false,
};
constructor(private http: HttpClient, @Inject(WINDOW) private wnd: Window,) {
this.client = this.createClient();
this.imageUrlBuilder = imageUrlBuilder(this.client);
}
getImageUrlBuilder(source: SanityImageSource): ImageUrlBuilder {
return this.imageUrlBuilder.image(source);
}
fetch<T>(query: string): Observable<T> {
const url = `${this.generateSanityUrl()}${encodeURIComponent(query)}`
return this.http.get(url).pipe(map((response: any) => response.result as T));
}
private createClient(): SanityClient {
return sanityClient(this.clientConfig);
}
private generateSanityUrl(): string {
const { apiVersion, dataset, projectId } = this.clientConfig;
const baseUrl =
this.wnd.location.href.startsWith(environment.web.url) ||
this.wnd.location.href.startsWith('http://localhost:4200') ?
`https://${projectId}.api.sanity.io/` : `${this.wnd.location.origin}/api/`;
return `${baseUrl}${apiVersion}/data/query/${dataset}?query=`;
}
private getApiVersion(): string {
return `v${new Date().toISOString().split('T')[0]}`;
}
}
- Create Angular Pipe
- Create Sanity-Image-Pipe (sanity-image.pipe.ts):
import { Pipe, PipeTransform } from '@angular/core';
import { SanityService } from './sanity-service.service';
import { SanityImageSource } from '@sanity/image-url/lib/types/types';
@Pipe({ name: 'sanityImage' })
export class SanityImagePipe implements PipeTransform {
constructor(private sanityService: SanityService) {}
transform(value: SanityImageSource, width?: number): string {
if (width) {
return this.sanityService.getImageUrlBuilder(value).width(width).url();
}
return this.sanityService.getImageUrlBuilder(value).url();
}
}
- Then, insert the pipe into the declarations (in app.module.ts):
@NgModule({
declarations: [
...
SanityImagePipe,
...
]
})
- Import in your application:
import { Observable } from 'rxjs';
import { SanityService } from '../sanity-service.service';
- Use in your application:
export interface Movie {
_id: String,
title: String,
releaseDate: Date,
body: String,
poster: SanityImageSource, // = string | SanityReference | SanityAsset |
SanityImageObject etc.
}
@Component({
selector: 'app-movies',
template: `
<main class="container">
<div *ngFor="let movie of movies$ | async">
<img [src]="movie.poster | sanityImage:200" />
<h3>{{ movie.title }}</h3>
<p>Released on {{ movie.releaseDate | date }}</p>
</div>
</main>
`,
styleUrls: ['./movies.component.css']
})
export class MoviesComponent implements OnInit {
movies$: Observable<Movie[]>
constructor(private sanityService: SanityService ) {
this.movies$ = this.sanityService.fetch<Movie[]>(
`*[_type == "movie"]{
_id,
title,
releaseDate,
poster,
author->
}`
)
}
}
- Make HttpClient available to all modules
- Add import to app.module.ts:
import { HttpClientModule } from '@angular/common/http';
- Add import to NgModule decorator:
@NgModule({
...
imports:[ HttpClientModule ]
...
})
- Create "ToHTMLPipe" to convert block content into HTML
- First, install to-html package
npm i @portabletext/to-html
- Next, create to-html.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import {toHTML} from '@portabletext/to-html';
@Pipe({ name: 'toHTML' })
export class ToHTMLPipe implements PipeTransform {
constructor() {}
transform(value: any): string {
return toHTML(value);
}
}
- Then, insert the pipe into the declarations (in app.module.ts)
@NgModule({
declarations: [
...
ToHTMLPipe,
...
]
})
- Finally, use it as follows in your component.html file:
...
<div innerHTML="{{ post.body | toHTML }}" class="text-coolGray-800 p-10">
<!--
Content Will Go Here
-->
</div>
...