서버 데이터 기반 동적 타이틀: Route.title + ResolveFn + inject
정적인 문자열만으로는 부족한 경우가 많습니다.
예를 들어:
- /posts/:id → 실제 글 제목을 가져와서 글 제목 | MyApp으로 노출하고 싶을 때
Angular 18 기준으로 Route.title은 문자열뿐 아니라 ResolveFn<string>도 받을 수 있습니다. 이걸 이용하면, 라우팅 단계에서 API를 호출해서 타이틀을 만들어 줄 수 있습니다.
PostService 예시
// post.service.ts
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map, Observable } from 'rxjs';
export interface Post {
id: number;
title: string;
content: string;
}
@Injectable({ providedIn: 'root' })
export class PostService {
private readonly http = inject(HttpClient);
getPost(id: number): Observable<Post> {
return this.http.get<Post>(`/api/posts/${id}`);
}
}
ResolveFn<string>으로 title 구성
// post-title.resolver.ts
import { ResolveFn, ActivatedRouteSnapshot } from '@angular/router';
import { inject } from '@angular/core';
import { map } from 'rxjs';
import { PostService } from './post.service';
export const postTitleResolver: ResolveFn<string> = (
route: ActivatedRouteSnapshot,
) => {
const postService = inject(PostService);
const idParam = route.paramMap.get('id');
const id = idParam ? Number(idParam) : 0;
return postService.getPost(id).pipe(
map((post) => `${post.title}`), // 접미사는 TitleStrategy에서 붙이도록 분리
);
};
라우트에 title로 resolver 등록
// app.routes.ts
import { Routes } from '@angular/router';
import { PostDetailPageComponent } from './pages/post-detail-page.component';
import { postTitleResolver } from './post-title.resolver';
export const routes: Routes = [
// ...
{
path: 'posts/:id',
component: PostDetailPageComponent,
title: postTitleResolver, // <-- 문자열 대신 ResolveFn 사용
},
];
- /posts/123로 네비게이션할 때
- postTitleResolver가 실행 → PostService.getPost(123) 호출
- 응답의 post.title로 문자열을 만든 뒤
- TitleStrategy.buildTitle() → updateTitle() 순서로 호출되며
- 최종 <title>은 글 제목 | MyApp 형태로 세팅됩니다.
Meta 태그까지 한 번에: SeoService 레이어 만들기
지금까지는 <title>에 집중했는데, 실제로는 타이틀 + description + OG 태그를 한 세트로 관리하는 경우가 많습니다.
컴포넌트마다 Meta.updateTag를 직접 호출하면 중복이 심하니, 중앙 집중형 SeoService를 만들고 여기서 모두 관리하게 하는 패턴이 좋습니다.
SeoService 구현
// seo.service.ts
import { Injectable, inject } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
export interface SeoConfig {
title?: string; // 페이지 타이틀 (App 이름은 TitleStrategy에서)
description?: string;
url?: string;
image?: string;
}
@Injectable({ providedIn: 'root' })
export class SeoService {
private readonly title = inject(Title);
private readonly meta = inject(Meta);
private readonly DEFAULT_IMAGE = '/assets/og-default.png';
update(config: SeoConfig) {
const {
title,
description,
url,
image = this.DEFAULT_IMAGE,
} = config;
if (title) {
this.title.setTitle(title);
this.meta.updateTag({ property: 'og:title', content: title });
this.meta.updateTag({ name: 'twitter:title', content: title });
}
if (description) {
this.meta.updateTag({ name: 'description', content: description });
this.meta.updateTag({ property: 'og:description', content: description });
this.meta.updateTag({ name: 'twitter:description', content: description });
}
if (url) {
this.meta.updateTag({ property: 'og:url', content: url });
}
if (image) {
this.meta.updateTag({ property: 'og:image', content: image });
this.meta.updateTag({ name: 'twitter:image', content: image });
}
}
}
컴포넌트에서 사용
// post-detail-page.component.ts
import { Component, OnInit, inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SeoService } from '../core/seo.service';
@Component({
standalone: true,
selector: 'app-post-detail-page',
template: `<h1>{{ postTitle }}</h1>`,
})
export class PostDetailPageComponent implements OnInit {
private readonly route = inject(ActivatedRoute);
private readonly seo = inject(SeoService);
postTitle = '';
ngOnInit(): void {
const data = this.route.snapshot.data['post']; // resolver에서 내려줬다고 가정
this.postTitle = data.title;
this.seo.update({
title: `${data.title} | MyApp`,
description: data.summary,
url: `https://myapp.com/posts/${data.id}`,
image: data.thumbnailUrl,
});
}
}
언제 어떤 패턴을 쓰면 좋나?
컴포넌트에서 바로 inject(Title/Meta)
- 페이지 수가 적고
- SEO/OG를 빡세게 관리하지 않을 때
- 빠르게 MVP를 만들 때
Route.title + 기본 TitleStrategy
- 각 페이지의 <title>만 route config에 간단히 적어두고 싶을 때
- | MyApp 같은 공통 규칙은 필요 없을 때
커스텀 TitleStrategy + Route.title
- 모든 페이지에 동일한 접두/접미사, 기본 타이틀 규칙을 적용하고 싶을 때
- Next.js generateMetadata처럼 "라우팅 시 자동으로 타이틀을 빌드"하는 느낌을 내고 싶을 때
ResolveFn<string>을 활용한 동적 타이틀
- 게시글/상품 상세처럼, 서버 데이터 기반으로 타이틀을 만들어야 할 때
- 라우터 레벨에서 미리 데이터 fetch → 타이틀 생성까지 끝내고 싶을 때
SeoService로 Meta/OG/Twitter 카드 일괄 관리
- SEO, SNS 공유 카드까지 포함해서 브랜드 레벨에서 일관된 규칙을 적용하고 싶을 때
- 멀티 서비스, 멀티 도메인(예: 메인 서비스 + 블로그 + 스토어)을 하나의 패턴으로 관리하고 싶을 때
'개발 공부 > Angular' 카테고리의 다른 글
| Angular validationForm 2 예시 (0) | 2025.12.16 |
|---|---|
| Angular validationForm (0) | 2025.12.14 |
| 앵귤러 메타데이터 관리 (1) (0) | 2025.11.30 |
| Angular inject() (0) | 2025.09.01 |
| Signals vs RxJS (0) | 2025.08.23 |