본문 바로가기
Angular.js

[Angular] router 이해하기 (feat. 중첩 라우팅, snapshot/params/ queryParams, 라우터 가드-ing)

by 찬찬2 2023. 1. 31.

회사 레거시 코드를 분석하면서 routing에 대한 코드를 보았다. React와 큰 틀에서는 별로 다른게 없지만, 문법적인 차이가 조금 있다.

 

routing의 근본적인 목적은 컴포넌트에서 컴포넌트로 이동하기 위함이다. 보통 홈페이지에서 다른 페이지로의 이동을 의미하지만 결국 컴포넌트에서 다른 컴포넌트로 이동하는 개념이다.

 

routing을 하기 위해서는 두 가지 단계가 있다. ①routing과 관련된 객체들을 import해오는 준비단계. ②목적지를 설정하고 목적지에 도달했을때 보여줄 컴포넌트를 설정하는 것. 즉 자동차를 타고 네비게이션에 목적지를 입력하는 것이다. 

 

① 준비단계 (impoorting router & set NgModule metadata)

case by case이지만 보통 "ng new my-app"으로 routing을 포함시켜 새로운 프로젝트를 생성하면 app.module.ts(root module)에 AppRoutingModule이 자동으로 imports된 것을 볼 수 있다.

이 AppRoutingModule은 오로지 routing과 관련된 코드들만 담고 있는 module이기 때문에 따로 분리되어 관리하는게 유지보수 시 효과적인 것 같다.

 

■ 핵심은 "@angular/router"에서 import해온 RouterModule을 반드시 root module의 @NgModule의 메타데이터에 포함시켜야 한다는 것이다. 그리고 forChild 또는 forRoot 메서드를 사용해 경로들이 담긴 정보를 인자로 넘겨줘야 한다.

 

/* root module - app.module.ts */
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

export const routes: Routes = [];

@NgModule({
  imports: [RouterModule.forRoot(routes)]
})

 

■ 바로 위에서 언급한 "경로들이 담긴 정보"는 변수에 따로 담아야 하고 "@angular/router"에서 import해온 Route라는 객체타입을 입혀줘야 한다.

 

② 목적지를 설정하고 목적지에 도달했을때 보여줄 컴포넌트를 설정

목적지를 설정하는 방법은 바로 위에서 언급한 route 배열에 객체 리터럴로 넣어주면 된다.

 

import { Acomponent } from './acomponent/acomponent.component';
import { Bcomponent } from './bcomponent/bcomponent.component';
import { Dcomponent } from './dcomponent/dcomponent.component';
import { Ccomponent } from './ccomponent/ccomponent.component';
import { Ecomponent } from './ecomponent/ecomponent.component';

const routes: Routes = [
  { path: 'A', component: Acomponent },
  { path: 'B', component: Bcomponent },
  {
    path: '',
    redirectTo: 'A',
    pathMatch: 'full'
  },
];

 

1계층에서의 routing 설정은 위와 같이 routes 배열에 path, component, etc...가 담긴 객체리터럴을 넣어주면된다.(옵션들은 공식문서 참고하기) 그리고 목적지에 도착했을때 보여줄 컴포넌트를 import해와야 한다. 공식문서에서는 이 단계를 "라우팅 규칙을 설정"하는 단계라고 한다. (Defining a basic route)

 

 

일반적인 1계층에서의 routing 구성은 이러하다. 그렇다면 routing이 중첩된 형태(nest)는 어떨까? 중첩되었다는 것은 path가 하나 이상이라는 말이다. 그렇기 때문에 router-outlet도 중첩된 수 만큼 존재 할 것이다.

localhost:4200/A로 접근했을때 AppComponent(root)에서 path A를 인식하고 Acomponent를 렌더했다.

그리고 더 나아가 123이라는 파라미터가 추가적으로 전달되었다. (localhost:4200/A/123) 이제 Acomponent 안에서 다른 컴포넌트를 렌더해야 하는 상황이다.

 

 

중첩된 routing은 위와 같이 부모 routing에 children(배열객체) 속성을 넣고 부모와 똑같은 형태로 넣어주면 된다. 이때 path에 colon( : ) 부호를 넣고 원하는 parameter명을 설정해야 한다. 이제 localhost:4200/A 다음에온 123은 router에서 "id"라는 parameter명으로 인지하고 저장할 것이다.

 

이렇게 전달받은 :id parameter의 정보는 어떻게 읽을 수 있을까? 공식문서에 따르면 router가 제공하는 ActivatedRoute의 메서드 중 snapshot을 통해 읽을 수 있다. 위 그림을 보면 snapshot 외에 queryParams, params 두 개가 더 존재한다. 차이점을 설명하기 전 결과 부터 말하자면, snapshot과 params 메서드만 id parameter를 읽을 수 있다. queryParams는 빈객체만 반환한다.

queryParams는 말 그대로 query 형태의 parameter만 읽을 수 있다. 예를 들어 localhost:4200/A?id=123 이어야 한다는 말이다. 그런데!! snapshot과 params 메서드도 읽어진다!! 그런데 딱 1회만 조회된다. 그 말은, id=123에서 위 3가지 메서드 모두가 id parameter를 읽었지만 id=123으로 Ccomponent가 렌더된 상태에서 routerLink 또는 Router.navigate로 id parameter의 다시 변경할 경우 queryParameter를 제외한 snapshot과 params는 id parameter를 읽지 않는다. 메서드가 실행조차 되지 않는다. 

 

 


// Acomponent
subLink(id: number){
    this.router.navigate(['/A/E'], { queryParams: { id, name: 'chanki'}});
    // this.router.navigate(['./', id], { relativeTo: this.route });
    console.log('hi');
  }


// Ecomponent
ngOnInit(){
    // #1 - snapshot
    console.log("snapshot paramMap: ", this.route.snapshot.paramMap.get('id'));
    
    // #2 - queryParams
    this.route.queryParams.subscribe(queryParams => {
      console.log("queryParams: ", queryParams);
    });
    
    // #3 - params
    this.route.params.subscribe(routeParams => {
      console.log("routeParams: ", routeParams);
      this.userId = routeParams['id'];
    });
  }

 

위 코드를 실행하면 위에 있는 GIF 처럼 작동된다. 반대로 localhost:4200/A/1의 형태로 실행했을떄 첫 렌더 후 Router.navigate로 localhost:4200/A/2로 :id parameter값을 변경시켰을때 queryParams와 snapshot이 최초 1회 실행되고 그 다음 부터는 실행되지 않는다. (url의 parameter값은 잘바뀜)

params 메서드와 queryParams 메서드는 parameter의 형태에 따라 작동/작동하지 않는다. 그렇다면 snapshot은 무엇일까? snapshot은 사진과 관련된 단어로서 쉽고 빠르게 사진을 찍을때 쓰는 단어이다. 

즉 여기서의 snapshot은 parameter의 형태에 상관없이 그냥 출력에만 목적을 두고 있는 단순한 메서드인듯하다. 반면 params와 queryParams 메서드는 parameter의 형태를 계속 주시하는 프로세스가 포함되어 있는 메서드인듯 하다.

공식문서대로 snapshot를 사용해 parameter값이 바뀔때 마다 컴포넌트안에 동적으로 새로운 view를 보여주는 작업을 하다가 parameter가 바뀌어도 컴포넌트가 안바뀌길래 자체적으로 실험하고 구글링해서 얻은 결과다.

 

참고링크

 

AcivatedRoute를 해부해 보면 queryParams와 params는 Observable타입이지만 snapshot은 아니다.

snapshot의 타입 ActivateRouteSnapshot을 보면, "The current snapshot of this route" 라고 설명하고 있다. 즉 snapshot은 현재만 바라고, queryParams와 params는 비동기의 성격(observable)으로 parameter를 주시하고 있던 것.

 

 

 

※ 문득... ngDocheck, ngOnChanges는 왜 감지하지 못할까. 아무래도 이 녀석들은 데이터 바인딩과 관련해 데이터가 변경되었을때 change-detection process에 의해 실행되지만 위에 저 녀석들은 URL, browser history와 관련되어 있어서 그런듯 하다.

 

라우터 가드에 대한 튜토리얼 (공식문서)

댓글