Skip to content

1. 기본 라우팅 작업


이 문서는 Angular 애플리케이션에 라우터를 추가하는 방법을 설명한다.


1. 라우팅 가능한 상태로 앱 생성하기

Angular CLI로 다음과 같은 명령을 실행하면 Angular 앱을 생성하면서 AppRoutingModule이라고 하는 라우팅 모듈을 함께 생성한다. 이 모듈은 애플리케이션의 라우팅 규칙을 관리하는 NgModule이다. routing-app이라는 이름으로 앱을 생성하는 명령은 이렇다.


ng new routing-app --routing --defaults


애플리케이션을 생성하는 과정에 CSS 전처리기를 사용할 것인지 물어본다. 이번 예제에서는 기본 CSS를 선택한다.


1) 라우팅할 컴포넌트 생성하기

Angular 라우터를 사용해서 화면을 전환하려면 컴포넌트가 적어도 2개는 있어야 한다. 다음 명령을 실행해서 first라는 이름으로 컴포넌트를 생성한다.


ng generate component first


그리고 다른 이름으로 두 번째 컴포넌트를 생성한다. 이번에는 second라는 이름으로 컴포넌트를 생성해 보자.


ng generate component second


Angular CLI는 컴포넌트를 생성할 때 자동으로 접미사를 붙이기 때문에, 컴포넌트 이름을 first-component라고 지정하면 실제 컴포넌트 클래스 이름은 FirstComponentComponenet가 된다.


Note

  • 이 가이드 문서는 Angular CLI로 생성한 Angular 앱을 다룬다.
  • 만약 Angular CLI를 사용하지 않는다면 index.html 파일의 <head> 태그에 <base href="/">를 추가해야 한다.
  • 이 태그를 추가하면 app 폴더가 애플리케이션 최상위 주소 "/"와 연결된다.


2) 컴포넌트 로드하기

새로 만든 컴포넌트를 사용하려면 AppRoutingModule 파일 제일 위쪽에서 이 컴포넌트들을 로드해야 한다:


AppRoutingModule (일부)
import { FirstComponent } from "./first/first.component";
import { SecondComponent } from "./second/second.component";


2. 기본 라우팅 규칙 정의하기

라우팅 규칙은 세 가지 요소로 구성된다.


AppModule이 정의된 파일에 AppRoutingModule을 로드하고 이 라우팅 모듈을 AppModuleimports 배열에 추가한다.


Angular CLI로 앱을 생성했다면 이 과정은 이미 처리되어 있다. 하지만 앱을 직접 생성했거나 이미 있는 앱을 기반으로 작업한다면 라우팅 모듈이 제대로 로드되었는지, imports 배열에 추가되었는지 꼭 확인해야 한다. 아래 코드는 --routing 플래그를 붙여서 Angular CLI로 앱을 생성했을 때 자동으로 생성된 AppModule 코드이다.


Angular CLI가 자동으로 생성한 AppModule
import { BrowserModule } from "@angular/platform-browser";
import { NgModule } from "@angular/core";
import { AppRoutingModule } from "./app-routing.module"; // AppRoutingModule을 로드합니다.
import { AppComponent } from "./app.component";

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule, // Angular CLI가 AppRoutingModule을 AppModule imports 배열에 자동으로 추가합니다.
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}


라우팅 모듈이 정의된 파일에 RouterModuleRoutes를 로드한다.


이 과정도 Angular CLI가 자동으로 처리했을 것이다. Routes 배열을 생성하는 것과 @NgModule()imports 배열과 exports 배열을 구성하는 것도 Angular CLI가 처리한다.


Angular CLI가 생성한 라우팅 모듈
import { NgModule } from "@angular/core";
import { Routes, RouterModule } from "@angular/router"; // 라우터 관련 심볼을 로드합니다.

const routes: Routes = []; // 라우팅 규칙은 이 배열에 등록합니다.

// NgModule의 imports, exports 배열에 RouterModule을 등록합니다.
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
})
export class AppRoutingModule {}


이제 라우팅 규칙을 Routes 배열에 등록한다.


라우팅 규칙(route)은 이 배열에 JavaScript 객체 형태로 등록하며, 이 객체는 프로퍼티가 2개 존재한다. 첫 번째로 path 프로퍼티는 라우팅 규칙에 해당하는 URL 주소를 지정한다. 그리고 component 프로퍼티는 해당 URL 주소에 연결될 컴포넌트를 지정한다.


AppRoutingModule (일부)
const routes: Routes = [
  { path: "first-component", component: FirstComponent },
  { path: "second-component", component: SecondComponent },
];


라우팅 규칙을 애플리케이션에 등록한다.


라우팅 규칙을 모두 정의했다면 이 규칙을 애플리케이션에 등록해야 한다. 먼저 두 컴포넌트와 연결되는 링크를 추가한다. 링크는 <a> 엘리먼트에 routerLink 어트리뷰트를 사용하는 방식으로 구현한다. 이 어트리뷰트에는 사용자가 링크를 클릭했을 때 이동할 주소를 지정한다. 그리고 컴포넌트 템플릿에 <router-outlet>을 추가한다. <router-outlet> 엘리먼트는 Angular가 애플리케이션 화면을 전환할 때 관련 컴포넌트가 표시될 위치를 지정하는 엘리먼트이다.


routerLink와 router-outlet이 추가된 템플릿
<h1>Angular Router App</h1>
<!-- 아래 링크를 클릭하면 AppRoutingModule에 등록된 라우팅 규칙에 따라 화면을 전환합니다. -->
<nav>
  <ul>
    <li><a routerLink="/first-component" routerLinkActive="active">First Component</a></li>
    <li><a routerLink="/second-component" routerLinkActive="active">Second Component</a></li>
  </ul>
</nav>
<!-- 라우팅된 화면은 <router-outlet> 위치에 표시됩니다. -->
<router-outlet></router-outlet>


1) 라우팅 규칙 적용 순서

Angular Router는 라우팅 규칙 중 첫 번째로 매칭되는 라우팅 규칙을 적용하기 때문에 라우팅 규칙을 등록하는 순서가 중요한데, 구체적인 라우팅 규칙을 가장 먼저 등록하고 덜 구체적인 라우팅 규칙을 나중에 등록하는 것이 좋다. 그래서 고정된 주소를 먼저 등록하며, 그 다음에 빈 주소를 등록하고, 마지막으로 기본 라우팅 규칙을 등록한다. 그리고 브라우저가 접속한 주소에 해당하는 라우팅 규칙이 하나도 없을 때 적용되는 와일드카드 라우팅 규칙은 가장 마지막에 작성한다.


3. 라우팅 규칙으로 전달된 정보 참조하기

때로는 사용자가 화면을 전환할 때 교체되는 컴포넌트 사이에 정보를 전달해야 할 때가 있다. 애플리케이션에서 식료품 목록을 보여주고 있다고 하자. 목록으로 표시된 개별 식료품에는 고유한 id 값이 있다. 사용자가 식료품 정보를 수정하기 위해 Edit 버튼을 클릭하면 EditGroceryItem 컴포넌트가 화면에 표시될 것이다. 이 때 새로 표시되는 컴포넌트는 사용자가 어떤 식료품을 선택했는지 알기 위해 해당 상품에 대한 id 값을 전달받아야 한다.


이런 데이터는 라우티 규칙을 통해 전달할 수 있다. ActivateRouter 인터페이스를 활용하면 된다.


라우팅 규칙으로 전달된 데이터를 참조해 보자:


컴포넌트가 정의된 파일에 ActivatedRouteParamMap 심볼을 로드한다.


In the component class (일부)
import { Router, ActivatedRoute, ParamMap } from "@angular/router";


import 구문으로 로드한 클래스를 활용하면 컴포넌트에 필요한 정보를 참조할 수 있다. 각각에 대해 자세하게 알아보려면 개별 API 문서를 참고한다:



생성자에 ActivatedRoute 인스턴스를 의존성으로 주입한다:


In the component class (일부)
constructor(
  private route: ActivatedRoute,
) {}


ngOnInit() 메서드에서 ActivatedRoute 객체 안에 있는 id 인자를 참조한다:


컴포넌트 코드 (일부)
ngOnInit() {
  this.route.queryParams.subscribe(params => {
    this.id = params['id'];
  });
}


4. 와일드카드(*) 라우팅 규칙 등록하기

완성도 높은 애플리케이션이라면 애플리케이션이 허용하지 않는 주소로 사용자가 접근하는 상황도 자연스럽게 처리해야 한다. 이 기능을 구현하려면 와일드카드 라우팅 규칙을 추가하면 된다. 이 규칙을 등록하면 사용자가 등록되지 않은 URL로 화면을 이동하려고 할 때 와일드카드 라우팅 규칙이 적용된다.


와일드카드 라우팅 규칙을 설정하려면 routes 배열에 다음 코드를 추가하면 된다.


AppRoutingModule (일부)
{ path: '**', component:  }


별표(*, asterisk) 2개가 사용된 **는 Angular가 와일드카드 라우팅 규칙을 구분하기 위한 문자열이다. 그리고 component에는 와일드카드 라우팅 규칙이 적용될 때 화면에 표시할 컴포넌트를 지정한다. 일반적으로는 PageNotFoundComponent와 같은 컴포넌트를 만들어서 404 에러 페이지를 표시하거나, 애플리케이션 최상위 주소로 리다이렉션하는 방법을 선택할 수 있다. 와일드카드 라우팅 규칙은 모든 URL과 매칭되기 때문에 라우팅 규칙 중 가장 나중에 등록해야 한다.


5. 404 에러 페이지 표시하기

404 에러 페이지를 표시하려면 와일드카드 라우팅 규칙을 등록할 때 component에 원하는 컴포넌트를 지정하면 된다:


AppRoutingModule (일부)
const routes: Routes = [
  { path: "first-component", component: FirstComponent },
  { path: "second-component", component: SecondComponent },
  { path: "**", component: PageNotFoundComponent }, // 404 에러 화면을 표시하는 와일드카드 라우팅 규칙
];


마지막에 등록된 path**인 라우팅 규칙이 와일드카드 라우팅 규칙이다. 이제 접속하려는 URL과 매칭되는 라우팅 규칙을 발견하지 못하면 이 라우팅 규칙이 적용되면서 PageNotFoundComponent가 화면에 표시된다.


6. 리다이렉션 설정하기

리다이렉션하는 라우팅 규칙을 등록하려면 path에 대상이 될 주소를 지정하고 redirectTo에 리다이렉션할 주소를 지정한 뒤에 pathMatch에 원하는 리다이렉션할 때 적용할 규칙을 지정한다.


AppRoutingModule (일부)
const routes: Routes = [
  { path: "first-component", component: FirstComponent },
  { path: "second-component", component: SecondComponent },
  { path: "", redirectTo: "/first-component", pathMatch: "full" }, // `first-component` 주소로 리다이렉트 합니다.
  { path: "**", component: PageNotFoundComponent }, // 404 에러 화면을 표시하는 와일드카드 라우팅 규칙
];


이 예제에서 세 번째 추가된 라우팅 규칙은 기본 주소로 접근했을 때 first-component 주소로 이동하도록 작성한 리다이렉션 라우팅 규칙이다. 이 규칙이 와일드카드 라우팅 앞에 온다는 것도 확인한다. 이 예제에서 작성한 path: ''는 애플리케이션을 접속하는 기본 URL('')을 의미한다.


7. 중첩 라우팅 규칙

애플리케이션이 점점 복잡해지다 보면 특정 컴포넌트 안에서 동작하는 라우팅 규칙을 추가하고 싶은 경우도 있다. 이렇게 중첩된 라우팅 규칙을 자식 라우팅 규칙(child route)이라고 한다. 라우팅 규칙을 중첩해서 적용하려면 컴포넌트에 <route-outlet>을 더 추가해야 한다. 왜냐하면 첫 번째 계층에서 동작하는 라우팅 규칙은 AppComponent<router-outlet> 안에서 동작하기 때문이다.


아래 예제 코드에는 자식 컴포넌트가 child-a, child-b 2개가 존재한다. 그리고 FirstComponent에는 <nav>가 존재하며 AppComponent에 있는 <router-outlet> 외에 또다른 <router-outlet>이 추가되어 있다.


In the template
<h2>First Component</h2>

<nav>
  <ul>
    <li><a routerLink="child-a">Child A</a></li>
    <li><a routerLink="child-b">Child B</a></li>
  </ul>
</nav>

<router-outlet></router-outlet>


자식 라우팅 규칙도 일반 라우팅 규칙과 마찬가지로 path, component 프로퍼티로 정의한다. 자식 라우팅 규칙은 부모 라우팅 규칙이 있고, 부모 라우팅 규칙의 children 배열에 정의한다는 점만 다르다.


AppRoutingModule (일부)
const routes: Routes = [
  {
    path: "first-component",
    component: FirstComponent, // 이 컴포넌트 템플릿에 <router-outlet>이 존재합니다.
    children: [
      {
        path: "child-a", // 자식 라우팅 규칙과 연결되는 주소
        component: ChildAComponent, // 라우터가 렌더링하는 자식 컴포넌트
      },
      {
        path: "child-b",
        component: ChildBComponent, // 또다른 자식 컴포넌트
      },
    ],
  },
];


8. 상대 주소 사용하기

상대 주소를 사용하면 현재 URL를 기준으로 라우팅할 주소를 지정할 수 있다. 아래 예제는 second-component로 이동하는 링크에 상대 주소를 적용한 예제 코드이다. FirstComponentSecondComponent는 라우팅 계층 트리에서 같은 계층이 있지만 FirstComponent 안에서 SecondComponent로 이동하는 기능을 제공하려고 한다. 결국 상위 계층으로 한 단계 이동한 후에 SecondComponent를 찾아야 한다. 이 때 SecondComponent로 이동하는 전체 경로를 사용하는 대신 상대 주소를 가리키는 방식으로 ../라는 표현을 사용했다.


In the template
<h2>First Component</h2>

<nav>
  <ul>
    <li>
      <a routerLink="../second-component">Relative Route to second component</a>
    </li>
  </ul>
</nav>
<router-outlet></router-outlet>


../와 비슷하게 ./, .를 사용하면 현재 라우팅 계층의 빈 주소를 가리킨다.


1) 상대 주소로 이동하기

상대 라우팅 규칙을 지정하려면 NavigationExtras 객체의 relativeTo 프로퍼티를 사용하면 된다. NavigationExtras 객체는 @angular/router에 정의되어 있는 객체이다.


화면을 이동할 때 relativeTo 옵션을 사용해 보자. 아래 예제에서 items로 지정된 링크 인자 배열 뒤에 객체 옵션을 추가하고 이 객체에 relativeTo 프로퍼티로 ActivatedRoute를 지정한다. 이 예제의 경우 this.route이다.


RelativeTo
goToItems() {
  this.router.navigate(['items'], { relativeTo: this.route });
}


그러면 navigate() 함수가 실행되면서 items 주소로 이동할 때 현재 라우팅 규칙에 대한 상대 주소로 이 주소를 처리한다.


9. 쿼리 인자, URL 조각 참고하기

때로는 쿼리 변수나 URL 조각 같은 라우팅 규칙 관련 정보에 접근해야 할 때가 있다. 히어로들의 여행 앱에서도 사용자가 히어로를 클릭하면 상세정보 화면으로 이동하는데, 이 때 히어로의 id를 인자로 받아서 화면을 구성한다.


먼저, 아래 심볼들을 컴포넌트 파일에 로드한다.


Component import statements (일부)
import { ActivatedRoute } from "@angular/router";
import { Observable } from "rxjs";
import { switchMap } from "rxjs/operators";


그리고 현재 활성화된 라우팅 규칙(ActivatedRoute)을 의존성으로 주입한다:


Component (일부)
constructor(private route: ActivatedRoute) {}


이제 컴포넌트 클래스 ngOnInit() 안에서 현재 활성화된 라우팅 규칙으로 전달되는 id 필드를 컴포넌트 클래스의 옵저버블 프로퍼티 heroes$에 할당해 보자. 이 때 히어로 목록은 배열로 존재하며, 관련 서비스가 이미 주입되어 있고, 화면을 표시하는 템플릿 코드도 이미 준비되어 있다고 하자.


Component 1 (일부)
heroes$: Observable;
selectedId: number;
heroes = HEROES;

ngOnInit() {
  this.heroes$ = this.route.paramMap.pipe(
    switchMap(params => {
      this.selectedId = Number(params.get('id'));
      return this.service.getHeroes();
    })
  );
}


다음에는 이동하려는 컴포넌트 파일에 이런 심볼들을 로드한다:


Component 2 (일부)
import { Router, ActivatedRoute, ParamMap } from "@angular/router";
import { Observable } from "rxjs";


컴포넌트 클래스의 생성자에 ActivatedRouteRouter를 의존성으로 주입하면 라우터 관련 정보를 참조할 수 있다:


Component 2 (일부)
hero$: Observable;

  constructor(
    private route: ActivatedRoute,
    private router: Router  ) {}

  ngOnInit() {
    const heroId = this.route.snapshot.paramMap.get('id');
    this.hero$ = this.service.getHero(heroId);
  }

  gotoItems(hero: Hero) {
    const heroId = hero ? hero.id : null;
    // 히어로 객체가 전달되면 id를 가져옵니다.
    // HeroList 컴포넌트로 이동합니다.
    this.router.navigate(['/heroes', { id: heroId }]);
  }


10. 지연 로딩

Angular 앱이 실행되는 시점에 로딩되지 않고 필요한 시점에 따로 로딩되는 모듈을 지연 로딩되는 모듈(lazy load module)이라고 한다. 모듈을 지연 로딩하면 앱 초기 실행시간이 짧아지기 때문에 사용자에게 좀 더 나은 사용성을 제공할 수 있다. 라우팅 규칙을 정의할 때 지연 로딩되는 모듈로 향하는 라우팅 규칙을 정의할 수 있다.


11. 허가되지 않은 접근 차단하기

라우팅 가드를 사용하면 허가되지 않은 앱 영역으로 사용자가 이동하는 것을 방지할 수 있다. Angular는 다음과 같은 라우팅 가드를 제공한다:


  • CanActivate
  • CanActivateChild
  • CanDeactivate
  • Resolve
  • CanLoad


라우팅 가드를 사용할 때는 컴포넌트가 없는(component-less) 라우팅 규칙을 따로 정의해서 자식 라우팅 규칙을 모두 보호하는 방법도 고려해볼만 하다.


Angular CLI로 가드를 생성하려면 이런 명령을 실행하면 된다:


ng generate guard your-guard


가드 클래스에는 가드가 동작하는 로직을 작성한다. 아래 예제는 CanActivate를 활용하는 가드 예제 코드이다.


Component (일부)
export class YourGuard implements CanActivate {
  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    // 인증 로직
  }
}


그리고 라우팅 모듈에서 routes 배열에 라우팅 가드가 동작할 프로퍼티를 지정하면 된다. 이번 예제에서는 canActivate 프로퍼티를 사용해서 해당 라우팅 규칙을 보호하는 방식으로 구현했다.


Routing module (일부)
{
  path: '/your-path',
  component: YourComponent,
  canActivate: [YourGuard],
}


12. 링크 변수 배열

링크 변수 배열(link parameters array)은 현재 라우팅과 관련해서 이런 정보를 담고 있다:


  • 표시되는 컴포넌트와 관련된 라우팅 규칙(route)
  • 라우팅 규칙 URL에 포함된 필수/옵션 라우팅 변수


RouterLink 디렉티브가 적용된 링크가 하나 있다고 하자:


src/app/app.component.ts (링크 엘리먼트)
<a [routerLink]="['/heroes']">Heroes</a>


이 링크가 동작하면서 인자를 함께 전달하려면 디렉티브에 바인딩되는 배열을 다음과 같이 수정하면 된다:


src/app/heroes/hero-list/hero-list.component.html (상세정보로 이동하는 링크)
<a [routerLink]="['/hero', hero.id]">
  <span class="badge">{{ hero.id }}</span>{{ hero.name }}
</a>


라우팅 인자는 { foo: 'foo' }와 같은 객체 형태도 전달할 수 있다:


src/app/app.component.ts (쿼리 인자)
<a [routerLink]="['/crisis-center', { foo: 'foo' }]">Crisis Center</a>


이 세 가지 예제를 활용하면 한 계층에서 발생하는 라우팅 동작을 거의 구현할 수 있다. 그런데 위기대응센터 화면처럼 자식 라우터가 있다면 조금 다르다.


아래 코드는 위기대응센터 화면에 사용했던 기본 자식 라우팅 규칙 링클르 정의한 코드이다.


src/app/app.component.ts (위기대응센터 기본 링크)
<a [routerLink]="['/crisis-center']">Crisis Center</a>


이 코드에서 이런 내용을 확인할 수 있다:


  • 배열의 첫 번째 항목은 부모 라우팅 규칙 /crisis-center를 의미한다.
  • 부모 라우팅 규칙에 전달되는 라우팅 인자는 없다.
  • 부모 라우팅 규칙의 기본 주소가 없다면 자식 라우팅 규칙 중 하나를 지정해야 한다.
  • 기본 주소를 CrisisListComponent와 연결했다면 /crisis-center/ 라고 지정해야 하지만, 마지막 슬래시(/)는 생략할 수 있다.


이번에는 위기대응센터 화면에 있는 링크 중에서 Dragon 위기로 이동하는 링크를 살펴보자:


src/app/app.component.ts (Dragon 링크)
<a [routerLink]="['/crisis-center', 1]">Dragon Crisis</a>


  • 배열의 첫 번째 항목은 부모 라우팅 규칙 /crisis-center를 의미한다.
  • 부모 라우팅 규칙에 전달되는 라우팅 인자는 없다.
  • 배열의 두 번째 항목은 자식 라우팅 규칙에 적용될 조건(/:id)를 의미한다.
  • 자식 라우팅 규칙에서는 라우팅 인자 id를 참조해서 상세정보를 가져온다.
  • Dragon 위기에 해당하는 id 값(1)은 배열 두번째 항목으로 전달한다.
  • 최종 주소는 /crisis-center/1이 된다.


그러면 AppComponent 템플릿을 이렇게 정의할 수 있다:


src/app/app.component.ts (템플릿)
template: `
  <h1 class="title">Angular Router</h1>
  <nav>
    <a [routerLink]="['/crisis-center']">Crisis Center</a>
    <a [routerLink]="['/crisis-center/1', { foo: 'foo' }]">Dragon Crisis</a>
    <a [routerLink]="['/crisis-center/2']">Shark Crisis</a>
  </nav>
  <router-outlet></router-outlet>
`;


정리하자면, 애플리케이션은 라우팅 계층의 깊이와 관계없이 자유롭게 화면을 전환할 수 있다. 링크 인자 배열을 활용하면 원하는 계층으로 이동하면서, 필수/옵션 라우팅 인자를 함께 전달할 수 있다.


13. LocationStrategy, 브라우저 URL 스타일

화면에 새로운 컴포넌트를 표시하게 되면 브라우저의 주소와 히스토리가 변경된다.


최신 HTML5 브라우저는 history.pushState API를 제공한다. 이 API를 활용하면 서버로 새로운 페이지 요청을 보내지 않으면서 브라우저의 주소나 히스토리를 변경할 수 있다. 그리고 Angular 라우터는 새로운 페이지 로드가 필요한 URL과 구별하지 않으면서 자연스러운 URL을 처리할 수 있다.


"HTML5 pushState" 스타일로 구성되는 위기대응센터 URL은 이렇다:


localhost:3002/crisis-center/


오래된 브라우저는 접근하는 URL이 변경될 때 발생하는 새 페이지 요청을 생략하기 위해 해시 기호(#)를 붙이는 방법을 사용하기도 한다. 물론 Angular에서는 이런 방식의 URL도 사용할 수 있다. 해시 URL 스타일로 구성되는 위기대응센터 URL은 이렇다:


localhost:3002/src/#/crisis-center/


두 가지 스타일 중 어떤 스타일을 사용할지는 Angular LocationStrategy 프로바이더를 지정하는 방법으로 결정할 수 있다.


  • PathLocationStrategy: HTML5 pushState 스타일을 사용한다. 이 값이 기본값이다.
  • HashLocationStrategy: 해시 URL 스타일을 사용한다.


기본 상태에서는 RouterModule.forRoot() 함수에서 LocationStrategy()PathLocationStrategy로 설정하고 있다. HashLocationStrategy를 사용하려면 앱을 부트스트랩할 때 이 값을 지정하면 된다.


14. URL 스타일 결정하기

URL 스타일은 프로젝트 개발 단계 이전에 결정하는 것이 좋다. 왜냐하면 애플리케이션이 운영 중이 상태에서는 제공되는 URL을 기준으로 사용자가 접근하기 때문이다.


대부분의 Angular 프로젝트는 기본 HTML5 스타일을 따른다. 이 스타일을 사용하면 사용자가 주소를 이해하기 쉽고 서버사이드 렌더링을 적용하기도 쉽다.


앱 페이지를 서버에서 미리 렌더링하고 제공하는 방식은 애플리케이션의 초기 실행 속도를 높이는 데에 큰 도움이 된다. 서버에서 렌더링하는 데 10초 이상이 걸리더라도 이 페이지가 미리 렌더링 된 후 사용자 디바이스에서 실행되는 것은 1초도 걸리지 않는다.


하지만 이 방식은 중간에 해시 기호(#)가 없는 URL 스타일일 때만 가능하다.


15. <base href>

브라우저는 화면을 전환할 때 브라우저의 history.pushState를 활용한다. pushState를 활용하면 앱 URL 주소를 localhost:4200/crisis-center처럼 구성할 수 있다. 앱 안에서 사용하는 URL과 서버에 요청하는 URL은 다르다.


최근에는 사용자들이 HTML5 스타일로 URL을 사용하기 때문에 최신 HTML5 브라우저도 대부분 pushState를 제공하고 있다.


Note

  • HTML5 스타일로 URL을 구성하는 것이 라우터 기본 설정이다.
  • 그리고 LocationStrategy, 브라우저 URL 스타일 섹션에서 설명한 것처럼 HTML5 스타일을 사용하는 것이 좋지만, 필요하다면 이전 스타일(#)을 사용할 수도 있다.


pushState 방식으로 화면을 전환하려면 <base href> 엘리먼트를 반드시 index.html 파일에 설정해야 한다. 앱에서 상대 URL로 무언가를 요청하면 <base href>에 지정된 값에 따라 CSS 파일이나 스크립트 파일, 이미지 파일을 로드한다.


<base> 엘리먼트는 <head> 태그 바로 뒤에 추가한다. 애플리케이션 최상위 경로가 app 폴더라면 index.html 파일을 이렇게 지정하면 된다:


src/index.html (base-href)
<base href="/" />


1) HTML5 URL, <base href>

URL은 여러 가지 요소로 구성되며, URL의 각 영역은 이렇게 나눠볼 수 있다:


foo://example.com:8042/over/there?name=ferret#nose
\_/   \______________/\_________/ \_________/ \__/
 |           |            |            |        |
스킴        도메인          경로          쿼리    프래그먼트


기본적으로 Angular 라우터는 HTML5 pushState 스타일을 사용하기 때문에 index.html 파일의 <head> 안에 <base href>를 꼭 지정해야 한다.


src/index.html (base-href)
<base href="/" />


이 태그가 없으면 브라우저가 로드하는 이미지 파일이나 CSS, 스크립트 파일을 앱 딥 링크(deep link)와 구별할 수 없다.


때로는 index.html 파일이나 <head> 엘리먼트를 수정할 수 있는 권한이 없는 경우가 있다.


이런 경우에도 HTML5 방식으로 URL을 다루려면 이렇게 하면 된다:


1] 라우터에 APP_BVASE_HREF 옵션을 지정한다.

2] CSS 파일, 이미지 파일, 스크립트 파일, 템플릿 파일 등 모든 리소스에 도메인부터 시작하는 주소를 사용한다.

3] <base href> path는 반드시 "/"로 끝나야 한다. 브라우저는 path 가장 마지막에 사용된 "/"는 자동으로 제거한다.

4] <base href>에 쿼리 부분이 추가되면 이 쿼리는 현재 화면에 지정된 경로가 없거나 쿼리가 없는 경우에만 동작한다. 이 말은, <base href>에 사용된 쿼리는 HashLocationStrategy를 사용했을 때만 사용된다는 것을 의미한다.

5] 현재 접속한 URL이 최상위 URL(도메인 기본 URL)이라면 <base href>는 사용되지 않는다. 이 경우에 Angular는 <base href>를 무시하고 APP_BASE_HREF에 설정된 값으로 링크를 처리한다.

6] <base href>에 사용된 프래그먼트는 절대 사용되지 않는다.


2) HashLocationStrategy

AppModule에서 RouterModule.forRoot()를 실행할 때 useHash: true 옵션을 지정하는 방법으로도 HashLocationStrategy 정책을 사용할 수 있다.


src/app/app.module.ts (해시 URL 정책)
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { Routes, RouterModule } from "@angular/router";

import { AppComponent } from "./app.component";
import { PageNotFoundComponent } from "./page-not-found/page-not-found.component";

const routes: Routes = [];

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot(routes, { useHash: true }), // .../#/crisis-center/
  ],
  declarations: [AppComponent, PageNotFoundComponent],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

References