Back to Dynamic Webapps
Angular Integration
Angular Integration Guide
Integrate SEO Sniper articles into your Angular application using services and components with full TypeScript support.
Articles Service
typescript
// services/articles.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, map, shareReplay } from 'rxjs';
import { environment } from '../environments/environment';
export interface Article {
id: string;
slug: string;
title: string;
metaTitle: string;
metaDescription: string;
contentHtml: string;
publishedAt: string;
featuredImageUrl?: string;
}
interface ArticlesResponse {
articles: Article[];
}
@Injectable({
providedIn: 'root'
})
export class ArticlesService {
private readonly apiUrl = environment.seoSniperApiUrl;
private articlesCache$?: Observable<Article[]>;
private headers = new HttpHeaders({
'Authorization': `Bearer ${environment.seoSniperApiToken}`
});
constructor(private http: HttpClient) {}
getArticles(): Observable<Article[]> {
if (!this.articlesCache$) {
this.articlesCache$ = this.http
.get<ArticlesResponse>(
`${this.apiUrl}/api/v1/articles`,
{ headers: this.headers }
)
.pipe(
map(response => response.articles),
shareReplay(1)
);
}
return this.articlesCache$;
}
getArticleBySlug(slug: string): Observable<Article | undefined> {
return this.getArticles().pipe(
map(articles => articles.find(a => a.slug === slug))
);
}
clearCache(): void {
this.articlesCache$ = undefined;
}
}Blog List Component
typescript
// components/blog-list/blog-list.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterLink } from '@angular/router';
import { Observable } from 'rxjs';
import { ArticlesService, Article } from '../../services/articles.service';
@Component({
selector: 'app-blog-list',
standalone: true,
imports: [CommonModule, RouterLink],
template: `
<div class="max-w-4xl mx-auto py-12 px-4">
<h1 class="text-4xl font-bold mb-8">Blog</h1>
<div class="grid gap-8" *ngIf="articles$ | async as articles">
<article
*ngFor="let article of articles"
class="border rounded-lg overflow-hidden"
>
<img
*ngIf="article.featuredImageUrl"
[src]="article.featuredImageUrl"
[alt]="article.title"
class="w-full h-48 object-cover"
/>
<div class="p-6">
<a [routerLink]="['/blog', article.slug]">
<h2 class="text-2xl font-semibold hover:text-blue-600">
{{ article.title }}
</h2>
</a>
<p class="text-gray-600 mt-2">
{{ article.metaDescription }}
</p>
<time class="text-sm text-gray-500 mt-4 block">
{{ article.publishedAt | date:'longDate' }}
</time>
</div>
</article>
</div>
</div>
`
})
export class BlogListComponent implements OnInit {
articles$!: Observable<Article[]>;
constructor(private articlesService: ArticlesService) {}
ngOnInit(): void {
this.articles$ = this.articlesService.getArticles();
}
}Article Component
typescript
// components/article/article.component.ts
import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ActivatedRoute } from '@angular/router';
import { Meta, Title } from '@angular/platform-browser';
import { Observable, switchMap, tap } from 'rxjs';
import { ArticlesService, Article } from '../../services/articles.service';
@Component({
selector: 'app-article',
standalone: true,
imports: [CommonModule],
template: `
<article
*ngIf="article$ | async as article"
class="max-w-3xl mx-auto py-12 px-4"
>
<header class="mb-8">
<h1 class="text-4xl font-bold mb-4">{{ article.title }}</h1>
<time class="text-gray-500">
{{ article.publishedAt | date:'longDate' }}
</time>
</header>
<!-- Render HTML content -->
<div
class="prose prose-lg max-w-none"
[innerHTML]="article.contentHtml"
></div>
</article>
`
})
export class ArticleComponent implements OnInit {
article$!: Observable<Article | undefined>;
constructor(
private route: ActivatedRoute,
private articlesService: ArticlesService,
private title: Title,
private meta: Meta
) {}
ngOnInit(): void {
this.article$ = this.route.paramMap.pipe(
switchMap(params => {
const slug = params.get('slug')!;
return this.articlesService.getArticleBySlug(slug);
}),
tap(article => {
if (article) {
this.updateMeta(article);
}
})
);
}
private updateMeta(article: Article): void {
this.title.setTitle(article.metaTitle);
this.meta.updateTag({ name: 'description', content: article.metaDescription });
this.meta.updateTag({ property: 'og:title', content: article.metaTitle });
this.meta.updateTag({ property: 'og:description', content: article.metaDescription });
if (article.featuredImageUrl) {
this.meta.updateTag({ property: 'og:image', content: article.featuredImageUrl });
}
}
}Routing Configuration
typescript
// app.routes.ts
import { Routes } from '@angular/router';
import { BlogListComponent } from './components/blog-list/blog-list.component';
import { ArticleComponent } from './components/article/article.component';
export const routes: Routes = [
{ path: 'blog', component: BlogListComponent },
{ path: 'blog/:slug', component: ArticleComponent },
{ path: '', redirectTo: 'blog', pathMatch: 'full' }
];Angular Universal for SSR
For better SEO, consider using Angular Universal for server-side rendering. This ensures search engines can crawl your content properly.