본문 바로가기

개발 공부/Angular

앵귤러 메타데이터 관리 (2)

서버 데이터 기반 동적 타이틀: 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 사용
  },
];
  1. /posts/123로 네비게이션할 때
  2. postTitleResolver가 실행 → PostService.getPost(123) 호출
  3. 응답의 post.title로 문자열을 만든 뒤
  4. TitleStrategy.buildTitle() → updateTitle() 순서로 호출되며
  5. 최종 <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