Angular Integration
This guide explains how to integrate WP Block to HTML with Angular applications.
Setting Up an Angular Project
First, create a new Angular project if you don't have one already:
ng new my-wp-angular-app
cd my-wp-angular-app
Next, install WP Block to HTML:
npm install wp-block-to-html
Basic Integration
The most straightforward way to use WP Block to HTML with Angular is by converting WordPress blocks to HTML strings and using Angular's [innerHTML]
property to render them:
// src/app/services/wordpress.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { convertBlocks } from 'wp-block-to-html';
@Injectable({
providedIn: 'root'
})
export class WordPressService {
private apiUrl = 'https://your-wordpress-site.com/wp-json/wp/v2';
constructor(private http: HttpClient) {}
getPost(slug: string): Observable<any> {
return this.http.get<any[]>(`${this.apiUrl}/posts?slug=${slug}&_fields=id,title,excerpt,content,blocks`)
.pipe(
map(posts => {
if (!posts || posts.length === 0) {
throw new Error('Post not found');
}
const post = posts[0];
// Process content based on what's available
let content = '';
if (post.content) {
// Process the WordPress blocks
content = convertBlocks(post.content, {
cssFramework: 'bootstrap', // Match your Angular project's CSS framework
contentHandling: 'html'
});
} else if (post.content?.rendered) {
// Use pre-rendered content if blocks aren't available
content = post.content.rendered;
}
return {
id: post.id,
title: post.title.rendered,
excerpt: post.excerpt.rendered.replace(/<[^>]+>/g, ''),
content: content
};
})
);
}
}
Then, create a component to display the post:
// src/app/components/post/post.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WordPressService } from '../../services/wordpress.service';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({
selector: 'app-post',
template: `
<div class="container mt-4" *ngIf="post">
<h1 [innerHTML]="post.title"></h1>
<div [innerHTML]="safeContent"></div>
</div>
<div class="container mt-4" *ngIf="!post && !error">
<p>Loading...</p>
</div>
<div class="container mt-4 alert alert-danger" *ngIf="error">
<p>{{ error }}</p>
</div>
`,
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {
post: any = null;
safeContent: SafeHtml | null = null;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private wpService: WordPressService,
private sanitizer: DomSanitizer
) {}
ngOnInit(): void {
const slug = this.route.snapshot.paramMap.get('slug');
if (!slug) {
this.error = 'No post slug provided';
return;
}
this.wpService.getPost(slug).subscribe({
next: (post) => {
this.post = post;
this.safeContent = this.sanitizer.bypassSecurityTrustHtml(post.content);
},
error: (err) => {
this.error = 'Error loading post: ' + err.message;
}
});
}
}
Make sure to configure your Angular routing:
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { PostComponent } from './components/post/post.component';
import { HomeComponent } from './components/home/home.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'posts/:slug', component: PostComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
And update your app.module.ts
to include the HttpClientModule:
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';
import { PostComponent } from './components/post/post.component';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
PostComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Creating a Dedicated WordPress Module
For larger applications, you might want to create a dedicated WordPress module:
// src/app/wordpress/wordpress.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { WordPressService } from './services/wordpress.service';
import { PostComponent } from './components/post/post.component';
import { PostListComponent } from './components/post-list/post-list.component';
@NgModule({
declarations: [
PostComponent,
PostListComponent
],
imports: [
CommonModule,
HttpClientModule
],
exports: [
PostComponent,
PostListComponent
],
providers: [
WordPressService
]
})
export class WordPressModule { }
Creating a Specialized Component for WordPress Blocks
You can create an Angular component specifically for rendering WordPress blocks:
// src/app/wordpress/components/wp-blocks/wp-blocks.component.ts
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { convertBlocks } from 'wp-block-to-html';
@Component({
selector: 'app-wp-blocks',
template: '<div [innerHTML]="safeContent"></div>',
styles: [':host ::ng-deep img { max-width: 100%; height: auto; }']
})
export class WpBlocksComponent implements OnChanges {
@Input() blocks: any[] | null = null;
@Input() cssFramework: 'bootstrap' | 'tailwind' | 'foundation' | 'bulma' | 'none' = 'bootstrap';
@Input() fallbackContent: string | null = null;
safeContent: SafeHtml | null = null;
constructor(private sanitizer: DomSanitizer) {}
ngOnChanges(changes: SimpleChanges): void {
this.renderContent();
}
private renderContent(): void {
let content = '';
if (this.blocks && this.blocks.length > 0) {
content = convertBlocks(this.blocks, {
cssFramework: this.cssFramework,
contentHandling: 'html'
});
} else if (this.fallbackContent) {
content = this.fallbackContent;
}
this.safeContent = this.sanitizer.bypassSecurityTrustHtml(content);
}
}
And use it in your post component:
// Updated post.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WordPressService } from '../../services/wordpress.service';
@Component({
selector: 'app-post',
template: `
<div class="container mt-4" *ngIf="post">
<h1 [innerHTML]="post.title"></h1>
<app-wp-blocks
[blocks]="post.content"
[fallbackContent]="post.content?.rendered"
[cssFramework]="'bootstrap'"
></app-wp-blocks>
</div>
<div class="container mt-4" *ngIf="!post && !error">
<p>Loading...</p>
</div>
<div class="container mt-4 alert alert-danger" *ngIf="error">
<p>{{ error }}</p>
</div>
`,
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {
post: any = null;
error: string | null = null;
constructor(
private route: ActivatedRoute,
private wpService: WordPressService
) {}
ngOnInit(): void {
const slug = this.route.snapshot.paramMap.get('slug');
if (!slug) {
this.error = 'No post slug provided';
return;
}
this.wpService.getPostRaw(slug).subscribe({
next: (post) => {
this.post = post;
},
error: (err) => {
this.error = 'Error loading post: ' + err.message;
}
});
}
}
Update the service to provide the raw blocks:
// Update WordPressService to add a getPostRaw method
getPostRaw(slug: string): Observable<any> {
return this.http.get<any[]>(`${this.apiUrl}/posts?slug=${slug}&_fields=id,title,excerpt,content,blocks`)
.pipe(
map(posts => {
if (!posts || posts.length === 0) {
throw new Error('Post not found');
}
return {
id: posts[0].id,
title: posts[0].title.rendered,
excerpt: posts[0].excerpt.rendered.replace(/<[^>]+>/g, ''),
content: posts[0].content,
blocks: posts[0].blocks
};
})
);
}
Server-Side Rendering with Angular Universal
For improved performance and SEO, you can use Angular Universal with WP Block to HTML:
- First, add Angular Universal to your project:
ng add @nguniversal/express-engine
- Update your
wordpress.service.ts
to support server-side rendering:
// src/app/services/wordpress.service.ts
import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { isPlatformServer } from '@angular/common';
import { convertBlocks } from 'wp-block-to-html';
@Injectable({
providedIn: 'root'
})
export class WordPressService {
private apiUrl = 'https://your-wordpress-site.com/wp-json/wp/v2';
private isServer: boolean;
constructor(
private http: HttpClient,
@Inject(PLATFORM_ID) platformId: Object
) {
this.isServer = isPlatformServer(platformId);
}
getPost(slug: string): Observable<any> {
return this.http.get<any[]>(`${this.apiUrl}/posts?slug=${slug}&_fields=id,title,excerpt,content,blocks`)
.pipe(
map(posts => {
if (!posts || posts.length === 0) {
throw new Error('Post not found');
}
const post = posts[0];
// Process content based on what's available
let content = '';
if (post.content) {
// Process the WordPress blocks with SSR optimizations when on server
content = convertBlocks(post.content, {
cssFramework: 'bootstrap',
contentHandling: 'html',
ssrOptions: {
enabled: this.isServer,
optimizationLevel: 'balanced',
lazyLoadMedia: true
}
});
} else if (post.content?.rendered) {
// Use pre-rendered content if blocks aren't available
content = post.content.rendered;
}
return {
id: post.id,
title: post.title.rendered,
excerpt: post.excerpt.rendered.replace(/<[^>]+>/g, ''),
content: content
};
})
);
}
}
Handling Styles
Using with Bootstrap
If you're using Bootstrap with Angular, make sure to install it:
npm install bootstrap
And include it in your angular.json
file:
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss"
]
Then configure WP Block to HTML to use Bootstrap:
convertBlocks(blocks, {
cssFramework: 'bootstrap',
// other options...
});
Using with Tailwind CSS
For Tailwind CSS, first set it up with Angular:
npm install tailwindcss postcss autoprefixer
npx tailwindcss init
Configure your tailwind.config.js
:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{html,ts}",
],
theme: {
extend: {},
},
plugins: [],
}
Update your styles.scss
:
@tailwind base;
@tailwind components;
@tailwind utilities;
Then configure WP Block to HTML to use Tailwind:
convertBlocks(blocks, {
cssFramework: 'tailwind',
// other options...
});
Advanced Features
Custom Block Transformer
You can create custom block transformers for specific WordPress blocks:
// src/app/wordpress/transformers/custom-transformer.ts
import { BlockTransformer } from 'wp-block-to-html';
export const customParagraphTransformer: BlockTransformer = {
transform: (block, options) => {
if (block.blockName !== 'core/paragraph') {
return null;
}
const classes = options.cssFramework === 'bootstrap'
? 'lead my-4'
: 'text-lg my-4';
const content = block.innerContent[0] || '';
return `<p class="${classes}">${content}</p>`;
}
};
// Usage in your service or component
import { convertBlocks } from 'wp-block-to-html';
import { customParagraphTransformer } from './transformers/custom-transformer';
const content = convertBlocks(blocks, {
cssFramework: 'bootstrap',
blockTransformers: [
customParagraphTransformer
]
});
Creating a Global Configuration Provider
For consistent configuration across your application:
// src/app/wordpress/services/wp-config.service.ts
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class WpConfigService {
readonly conversionOptions = {
cssFramework: 'bootstrap' as const,
contentHandling: 'html' as const,
ssrOptions: {
enabled: true,
optimizationLevel: 'balanced' as const,
lazyLoadMedia: true
}
};
getConversionOptions() {
return { ...this.conversionOptions };
}
}
// Usage in components
constructor(private wpConfigService: WpConfigService) {}
ngOnInit() {
const options = this.wpConfigService.getConversionOptions();
const content = convertBlocks(blocks, options);
}
Performance Optimization
For optimal performance in Angular applications:
import { convertBlocks } from 'wp-block-to-html';
// In your component or service
const content = convertBlocks(blocks, {
cssFramework: 'bootstrap',
ssrOptions: {
enabled: true,
optimizationLevel: 'maximum',
lazyLoadMedia: true,
criticalPathOnly: true,
preconnect: true
},
incrementalOptions: {
enabled: true,
initialRenderCount: 5,
batchSize: 3,
renderDelay: 100
}
});
Example: Complete Angular Post Component
Here's a complete example of an Angular component that fetches and displays WordPress content:
// src/app/components/post/post.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { WordPressService } from '../../services/wordpress.service';
import { DomSanitizer, SafeHtml, Title, Meta } from '@angular/platform-browser';
import { switchMap } from 'rxjs/operators';
import { of } from 'rxjs';
@Component({
selector: 'app-post',
template: `
<div class="container mt-4">
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a routerLink="/">Home</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ post?.title || 'Post' }}</li>
</ol>
</nav>
<div *ngIf="post">
<h1 class="display-4 mb-3" [innerHTML]="post.title"></h1>
<div class="lead mb-4" *ngIf="post.excerpt" [innerHTML]="post.excerpt"></div>
<hr>
<div class="post-content" [innerHTML]="safeContent"></div>
</div>
<div class="d-flex justify-content-center my-5" *ngIf="loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div class="alert alert-danger" *ngIf="error">
<h4 class="alert-heading">Error</h4>
<p>{{ error }}</p>
<button class="btn btn-outline-danger" (click)="retryLoading()">Try Again</button>
</div>
<div class="related-posts mt-5" *ngIf="relatedPosts && relatedPosts.length > 0">
<h3 class="mb-4">Related Posts</h3>
<div class="row">
<div class="col-md-4" *ngFor="let relatedPost of relatedPosts">
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">{{ relatedPost.title }}</h5>
<p class="card-text">{{ relatedPost.excerpt }}</p>
<a [routerLink]="['/posts', relatedPost.slug]" class="btn btn-primary">Read More</a>
</div>
</div>
</div>
</div>
</div>
</div>
`,
styleUrls: ['./post.component.scss']
})
export class PostComponent implements OnInit {
post: any = null;
safeContent: SafeHtml | null = null;
loading = true;
error: string | null = null;
relatedPosts: any[] = [];
private currentSlug: string | null = null;
constructor(
private route: ActivatedRoute,
private wpService: WordPressService,
private sanitizer: DomSanitizer,
private titleService: Title,
private metaService: Meta
) {}
ngOnInit(): void {
this.route.paramMap.pipe(
switchMap(params => {
this.loading = true;
this.error = null;
this.currentSlug = params.get('slug');
if (!this.currentSlug) {
this.error = 'No post slug provided';
this.loading = false;
return of(null);
}
return this.wpService.getPost(this.currentSlug);
})
).subscribe({
next: (post) => {
if (post) {
this.post = post;
this.safeContent = this.sanitizer.bypassSecurityTrustHtml(post.content);
// Set meta tags for SEO
this.titleService.setTitle(post.title);
this.metaService.updateTag({ name: 'description', content: post.excerpt });
// Load related posts
this.loadRelatedPosts(post.id);
}
this.loading = false;
},
error: (err) => {
this.error = 'Error loading post: ' + err.message;
this.loading = false;
}
});
}
loadRelatedPosts(postId: number): void {
this.wpService.getRelatedPosts(postId, 3).subscribe({
next: (posts) => {
this.relatedPosts = posts;
},
error: () => {
// Silent fail for related posts
this.relatedPosts = [];
}
});
}
retryLoading(): void {
if (this.currentSlug) {
this.loading = true;
this.error = null;
this.wpService.getPost(this.currentSlug).subscribe({
next: (post) => {
this.post = post;
this.safeContent = this.sanitizer.bypassSecurityTrustHtml(post.content);
this.loading = false;
// Load related posts
this.loadRelatedPosts(post.id);
},
error: (err) => {
this.error = 'Error loading post: ' + err.message;
this.loading = false;
}
});
}
}
}
Add the related posts method to your service:
// Add to WordPressService
getRelatedPosts(postId: number, count: number = 3): Observable<any[]> {
return this.http.get<any[]>(
`${this.apiUrl}/posts?exclude=${postId}&per_page=${count}&_fields=id,title,slug,excerpt`
).pipe(
map(posts => posts.map(post => ({
id: post.id,
title: post.title.rendered,
slug: post.slug,
excerpt: post.excerpt.rendered.replace(/<[^>]+>/g, '').slice(0, 100)
})))
);
}
Next Steps
- Check out the Server-Side Rendering guide for more advanced SSR techniques.
- Learn about CSS Framework Integration for styling options.
- Explore Performance Optimization strategies for high-traffic sites.
- Read about Custom Block Transformers to customize block rendering for your Angular app.