๐Ÿ’ป Angular & Angular Material 3์ผ๋งŒ์— ๋ฝ€๊ฐœ๊ธฐ - 2. Routing, RxJS

addiescodeยท2020๋…„ 8์›” 22์ผ
0
post-thumbnail

์–ด์ œ ์ฒซ Feature branch ํ‘ธ์‹œ๋ฅผ ๋งˆ์ณค๋‹ค.
๊ทธ๋™์•ˆ ๋ฆฌ์•กํŠธ๋กœ๋งŒ ๊ฐœ๋ฐœํ•˜๋‹ค๊ฐ€, ๋‹ค๋ฅธ ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์ด๋ ‡๊ฒŒ ๋‹จ๊ธฐ๊ฐ„์— ๊ฐœ๋ฐœํ•ด ๋ณธ ์ ์ด ์—†์–ด์„œ ์•„์ง๊นŒ์ง€ ์–ผ๋–จ๋–จํ•˜์ง€๋งŒ.. ๐Ÿคญ ๋•๋ถ„์— ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋”์šฑ ์ž์‹ ๊ฐ์žˆ๊ฒŒ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ๋˜๊ณ  ์š”์ฆ˜ ์ƒˆ๋กœ์šด ๋ฆฌ์•กํŠธ ์กฐํ•ฉ์œผ๋กœ ๋– ์˜ค๋ฅด๊ณ  ์žˆ๋Š” RxJS๋ฅผ ๊ฒฝํ—˜ํ•ด๋ณผ์ˆ˜ ์žˆ์–ด ๋”์šฑ ์ข‹์•˜๋˜ ๊ฒฝํ—˜์ด์—ˆ๋‹ค.

๋‹ค์Œ ๋ฏธ๋‹ˆ ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ๋Š” RxJS + React, Redux์ž…๋‹ˆ๋‹ค!


Netflix UI Engineering ์ฑ„๋„ Javascript Talks ๋งํฌ

์ด ํ”„๋กœ์ ํŠธ๋กœ ๊ฒฝํ—˜ํ•ด๋ณธ RxJS๋ฅผ React,Redux์™€ ์กฐํ•ฉํ•ด ๋ฏธ๋‹ˆํ”„๋กœ์ ํŠธ๋ฅผ ํ•ด๋ณด๋Š” ๊ฒŒ ๋‹ค์Œ์˜ ๋ชฉํ‘œ์ด๋‹ค.

๊ทธ๋Ÿผ ๋ณธ๋ฌธ ์‹œ์ž‘! ๐Ÿ™Œ

Angular์—์„œ์˜ Routing

๐Ÿค” Angular์—์„œ Routing์€ ํŠน๋ณ„ํ•˜๋‹ค..?

ํŠน๋ณ„ํ•˜๋‹ค.

๋ผ์šฐํŒ… ๋˜๋Š” ์‹œ์ ์—

  • ํŠน์ • ํšจ๊ณผ๋ฅผ ๊ฐ€์ง„ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„ฃ์„ ์ˆ˜ ๋„ ์žˆ๊ณ ,
  • ๊ถŒํ•œ(์ธ์ฆ/์ธ๊ฐ€)์„ ์ฒดํฌํ•ด ์„ ํƒ์ ์œผ๋กœ ๋ณด์—ฌ์ค„ ํ™”๋ฉด, ๋ธ”๋ฝ๋  ํ™”๋ฉด์„ ์ง€์ •ํ• ์ˆ˜๋„์žˆ๊ณ ,
  • feature root ๋ชจ๋“ˆ์—์„œ child route๋ฅผ ์ง€์ •ํ•ด์ฃผ์–ด lazy loading๋˜๋Š” ๋ชจ๋“ˆ์„ ๊ตฌํ˜„ํ• ์ˆ˜๋„์žˆ๋‹ค.

์ฆ‰, ์œ„์— ์ด์•ผ๊ธฐํ•œ feature๋“ค์ด ๋ฆฌ์•กํŠธ์—๋Š” ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋“  ์ดˆ๊ธฐ์— flatํ•œ (๋™๋“ฑํ•œ) ์œ„๊ณ„์งˆ์„œ๋ฅผ ๊ฐ€์ง€๊ณ  ์กฐ๊ฑด๋ถ€๋กœ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋…์— ๊ฐ€๊น๋‹ค๋ฉด, ์•ต๊ทค๋Ÿฌ์—์„œ๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ์ตœ์ƒ์œ„ ๋ชจ๋“ˆ์— ๋ชจ๋“  ๋ชจ๋“ˆ์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋˜, container ์—ญํ• ์„ ๋งก๊ฒŒ ๋˜๋Š” feature root ๋ชจ๋“ˆ๋งŒ์„ ์ •์˜ํ•ด ๊ทธ ๋ชจ๋“ˆ์— child routes๋ฅผ ์ง€์ •ํ•˜๊ณ  lazy load๋˜๋Š” ๋ฐฉ์‹์„ ์ฐจ์šฉํ•œ๋‹ค๋ฉด ๋ผ์šฐํŒ… ๋˜๋Š” ์‹œ์ ์— ์ž์‹ ๋ชจ๋“ˆ๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“  ํŒŒ์ผ์„ lazy load ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐœ๋…์ด๋‹ค.

์ตœ์ƒ์œ„ ๋ชจ๋“ˆ, feature root ๋ชจ๋“ˆ ์ •์˜ํ•˜๊ธฐ

AppModul์˜ ์—ญํ• 

์ตœ์ƒ์œ„ ๋ชจ๋“ˆ์€ ํ•ญ์ƒ AppModule์ด๋‹ค.
์ด AppModule์—์„  ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐ ์‹คํ–‰์— ๋กœ๋“œ๋˜์–ด์•ผํ•˜๋Š” ๋ชจ๋“  ๋ชจ๋“ˆ์„ ์ •์˜ํ•ด๋†“๋Š”๋‹ค.
์ฃผ์˜ํ• ์ ์€, AppRoutingModule์€ ํ•ญ์ƒ ๋งˆ์ง€๋ง‰์— import๋˜์–ด์•ผํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ด๊ฒƒ์€ html๋ฌธ์„œ ๋‚ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” script ํƒœ๊ทธ ์œ„์น˜๊ฐ€ ์œ ์˜๋ฏธํ•œ๊ฒƒ๊ณผ ๋น„์Šทํ•œ ์ด์œ ์ธ๋ฐ, route ์„ค์ •ํŒŒ์ผ์ด request path์™€ ์ผ์น˜ํ•˜๋Š” ์ง€ ํ™•์ธํ•˜๊ณ  ๊ทธ์™€ ๊ด€๋ จ๋œ ์ฒซ๋ฒˆ์งธ route๋ฅผ ๋กœ๋”ฉํ•˜๊ธฐ๋•Œ๋ฌธ์ด๋‹ค.

app.module.ts

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Router } from '@angular/router';

import { AppComponent } from './app.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';
import { ComposeMessageComponent } from './compose-message/compose-message.component';

import { AppRoutingModule } from './app-routing.module';
import { HeroesModule } from './heroes/heroes.module';
import { AuthModule } from './auth/auth.module';
import { MaterialModule } from './material';

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    HeroesModule,
    AuthModule,
    AppRoutingModule,
    MaterialModule
  ],
  declarations: [
    AppComponent,
    ComposeMessageComponent,
    PageNotFoundComponent
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
  }
}

ํ˜„์žฌ ๋‚ด app.module.ts๋ฅผ ๋ณด๊ฒŒ๋œ๋‹ค๋ฉด ์ด๋Ÿฌํ•˜๋‹ค.
์ด ํŒŒ์ผ์—์„œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ดˆ๊ธฐ ๋ Œ๋”๋ง์— ํ•„์š”ํ•œ ๋ชจ๋“  ์ž์›์„ ๋กœ๋“œํ•˜๊ฒŒ ๋œ๋‹ค.

์—ฌ๊ธฐ์„œ HeroesModule๋งŒ์ด ๋‚ด๊ฐ€ ์ •์˜ํ•œ feature root module์ด๋‹ค. AuthModule์€ ๊ถŒํ•œ ์ฒดํฌ๋ฅผ ์œ„ํ•œ ๋ชจ๋“ˆ๋กœ์„œ, Login Component์ด์šฉ์‹œ ์‚ฌ์šฉ๋œ๋‹ค.

Angular๋Š” ์ด๋Ÿฐ root์—ญํ• ์„ ํ•˜๋Š” ๋ชจ๋“ˆ๋งˆ๋‹ค routing๊ณผ ๊ด€๋ จ๋œ ๋กœ์ง์„ ์ž‘์„ฑํ•˜๋Š” routing-module ํŒŒ์ผ์„ ๋”ฐ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š”๋ฐ, app-routing.module.ts๋ฅผ ๋ณด๊ฒŒ ๋˜๋ฉด,
lazy loading๋˜๋Š” ๋ชจ๋“ˆ๋˜ํ•œ ์ •์˜ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

app-routing.module.ts

import { NgModule } from '@angular/core';
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';

import { ComposeMessageComponent } from './compose-message/compose-message.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

import { AuthGuard } from './auth/auth.guard';

const appRoutes: Routes = [
  {
    path: 'admin',
    loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule),
    canLoad: [AuthGuard]
  },
  {
    path: 'crisis-center',
    loadChildren: () => import('./crisis-center/crisis-center.module').then(m => m.CrisisCenterModule),
    data: { preload: true }
  },
  {
    path: 'compose',
    component: ComposeMessageComponent,
    outlet: 'popup'
  },
  { path: '', redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      {
        enableTracing: true, // <-- ๋””๋ฒ„๊ทธ ํ™œ์„ฑํ™”
        preloadingStrategy: PreloadAllModules
      }
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule { }

AppRoutingModule์˜ ์—ญํ• 

์ฆ‰, ์ด์ „ ์ดˆ๊ธฐ์‹คํ–‰๊ณผ ๊ด€๋ จ๋œ ๋ชจ๋“ˆ์„ ๋‹ด์€ app.module.ts์—์„œ ์ด ํŒŒ์ผ์— ๋Œ€ํ•œ ์ž์›์„ ๋กœ๋“œํ•  ์ˆ˜ ์žˆ๋Š” AppRoutingModule์„ import ๋ฐ loadํ•ด์ฃผ์—ˆ์œผ๋ฏ€๋กœ, ์ด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ feature root๋ชจ๋“ˆ๊ณผ ํ•ด๋‹น ๋ชจ๋“ˆ์— ๋Œ€ํ•œ ๊ถŒํ•œ๋ถ€์—ฌ ์—ฌ๋ถ€, childe routes์˜ ์ž์›๊ณผ ๊ทธ์— ๊ด€๋ จ๋œ ๋ชจ๋“  ๋กœ์ง์ด ์ •์˜๋œ ์ค‘์š”ํ•œ ํŒŒ์ผ์ด app.routing.module.ts์ธ๊ฒƒ์ด๋‹ค.

๋‚ด ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ, admin๊ณผ ๊ด€๋ จ๋œ ํŽ˜์ด์ง€๋Š” ๋ชจ๋‘ ๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ•ด์•ผ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•ด์ง€๋ฏ€๋กœ canLoad์— AuthGuard๋ฅผ ๋„ฃ์–ด ๊ถŒํ•œ๋ถ€์—ฌ์— ๊ด€๋ จํ•œ ํŒŒ์ผ์„ ์ž‘์„ฑํ–ˆ๊ณ ,

crisisCenter์˜ ๊ฒฝ์šฐ ์•ฑ ์ดˆ๊ธฐ ์‹คํ–‰์— ๊ด€๋ จ์žˆ์ง€๋Š” ์•Š์ง€๋งŒ crisis-center path๋กœ ๋ผ์šฐํŒ…ํ•ด์•ผ๋งŒ ๊ด€๋ จ ๋ชจ๋“ˆ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ ˆ์ด์ง€ ๋กœ๋”ฉ์„ ๊ตฌํ˜„ํ–ˆ๋‹ค.

import { NgModule } from '@angular/core';
import { RouterModule, Routes, PreloadAllModules } from '@angular/router';

import { ComposeMessageComponent } from './compose-message/compose-message.component';
import { PageNotFoundComponent } from './page-not-found/page-not-found.component';

import { AuthGuard } from './auth/auth.guard';

const appRoutes: Routes = [
  {
    path: 'admin',
    loadChildren: () =>
      import('./admin/admin.module').then((m) => m.AdminModule),
    canLoad: [AuthGuard],
  },
  {
    path: 'crisis-center',
    loadChildren: () =>
      import('./crisis-center/crisis-center.module').then(
        (m) => m.CrisisCenterModule
      ),
    data: { preload: true },
  },
  {
    path: 'compose',
    component: ComposeMessageComponent,
    outlet: 'popup',
  },
  { path: '', redirectTo: '/superheroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent },
];

@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes, {
      enableTracing: true, // <-- ๋””๋ฒ„๊ทธ ํ™œ์„ฑํ™”
      preloadingStrategy: PreloadAllModules,
    }),
  ],
  exports: [RouterModule],
})
export class AppRoutingModule {}

ํ”„๋กœ๊ทธ๋žจ ์ดˆ๊ธฐ ์‹คํ–‰ ์‹œ ๋กœ๋“œํ–ˆ๋˜ HeroesModule

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';

import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
import { HeroesRoutingModule } from './heroes-routing.module';
import { MaterialModule } from '../material';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    HeroesRoutingModule,
    MaterialModule
  ],
  declarations: [
    HeroListComponent,
    HeroDetailComponent
  ]
})
export class HeroesModule { }

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ HeroesModule์—์„œ ์ดˆ๊ธฐ ์‹คํ–‰ ์‹œ ํ•„์š”ํ•œ ๋ชจ๋“ˆ์„ importํ•  ์ˆ˜ ์žˆ๊ณ 
์ด๋•Œ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ทฐ์— ์“ฐ์ผ๊ฑด์ง€ ์„ ์–ธํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.(declarations)

๊ทธ๋‹ค์Œ HeroesRoutingModule์„ ๋ณด๊ฒŒ ๋˜๋ฉด

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { HeroListComponent } from './hero-list/hero-list.component';
import { HeroDetailComponent } from './hero-detail/hero-detail.component';

const heroesRoutes: Routes = [
  { path: 'heroes', component: HeroListComponent, data: { animation: 'heroes' }, redirectTo: '/superheroes' },
  { path: 'hero/:id', component: HeroDetailComponent, data: { animation: 'hero' }, redirectTo: '/superhero/:id' },
];

@NgModule({
  imports: [
    RouterModule.forChild(heroesRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class HeroesRoutingModule { }

์ด๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ HeroesModule์— ์„ ์–ธํ–ˆ๋˜ ์ปดํฌ๋„ŒํŠธ์™€ ๊ด€๋ จ๋œ ๋ผ์šฐํŒ…์„ ๊ฑธ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค.

AppRoutingModule์—์„œ ๋ผ์šฐํŒ… ์‹œ crisis-center๋ผ๋Š” path๋กœ ์ ‘๊ทผํ–ˆ๋˜ ๋ชจ๋“ˆ์„ ๋ณด๊ฒŒ ๋˜๋ฉด, ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ ์–ธํ•ด์ฃผ๊ณ  ๋ผ์šฐํŒ… ์‹œ ์ด์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ,

CrisisCenterModule

import { NgModule } from '@angular/core';

import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
import { CrisisListComponent } from './crisis/crisis-list/crisis-list.component';
import { CrisisCenterComponent } from './crisis-center/crisis-center.component';
import { CrisisDetailComponent } from './crisis/crisis-detail/crisis-detail.component';

import { CrisisCenterRoutingModule } from './crisis-center-routing.module';
import { MaterialModule } from '../material/material.module';

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    CrisisCenterRoutingModule,
    MaterialModule
  ],
  declarations: [
    CrisisCenterComponent,
    CrisisListComponent,
    CrisisCenterHomeComponent,
    CrisisDetailComponent
  ]
})
export class CrisisCenterModule { }

CrisisCenterRoutingModule

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { CrisisCenterHomeComponent } from './crisis-center-home/crisis-center-home.component';
import { CrisisListComponent } from './crisis/crisis-list/crisis-list.component';
import { CrisisCenterComponent } from './crisis-center/crisis-center.component';
import { CrisisDetailComponent } from '././crisis/crisis-detail/crisis-detail.component';

import { CanDeactivateGuard } from '../can-deactivate.guard';
import { CrisisDetailResolverService } from './crisis-detail-resolver.service';

const crisisCenterRoutes: Routes = [
  {
    path: '',
    component: CrisisCenterComponent,
    children: [
      {
        path: '',
        component: CrisisListComponent,
        children: [
          {
            path: ':id',
            component: CrisisDetailComponent,
            canDeactivate: [CanDeactivateGuard],
            resolve: {
              crisis: CrisisDetailResolverService
            }
          },
          {
            path: '',
            component: CrisisCenterHomeComponent
          }
        ]
      }
    ]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(crisisCenterRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class CrisisCenterRoutingModule { }

์ด๋Ÿฐ์‹์œผ๋กœ child routes๋ฅผ ์„ค์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค.
๋‹จ, ์ด๋•Œ์˜ child routes๋“ค์€

/crisis-list/์ž์‹๋ผ์šฐํŠธpath

๊ฐ์ฒด ์•ˆ์ชฝ์œผ๋กœ ์ ‘๊ทผํ• ์ˆ˜๋ก /๋’ค์— path๊ฐ€ ์ถ”๊ฐ€๋˜๋Š” ํ˜•์‹์ด๋ฏ€๋กœ
์˜๋„ํ•œ๋Œ€๋กœ default path๋ฅผ ๋„ฃ์–ด์ค€ ๊ฒŒ ๋งž๋Š”์ง€ ํ™•์ธํ•ด์•ผ ์‚ฝ์งˆ์„ ์‚ฌ์ „๋ฐฉ์ง€ํ•œ๋‹ค (ใ…Žใ…Žใ…Ž)

๋‚ด ๊ฒฝ์šฐ ์ด๋ฏธ ์ด์ „์— crisis-list๋กœ ๋ผ์šฐํŒ… ๋˜์—ˆ์„๋•Œ ์ด ๋ชจ๋“ˆ์„ ๋กœ๋“œํ•œ๋‹ค๊ณ  ์ •์˜ํ•ด์คฌ๋Š”๋ฐ,
์ด ํŒŒ์ผ default path์—์„œ ๋˜ crisis-list๋ผ๊ณ  ์ •์˜ํ•ด์ฃผ์–ด์„œ ๊ฒฐ๊ตญ์€ crisis-list/crisis-list๋กœ ๋ผ์šฐํŒ…ํ•ด์•ผ๋งŒ ์ฒซ๋ฒˆ์งธ ๋ทฐ๋ฅผ ๋ Œ๋”ํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํ™ฉ์„ ๋งŒ๋“ค์–ด ์‹ค์ˆ˜๋ฅผ ํ–ˆ๋‹ค.

๋ผ์šฐํŒ… ์‹œ ๋งŽ์€ ์‹œ๋„๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š”๊ฒŒ ์žฅ์ ์ด์ž ์–ด๋–ค ๋ฉด์œผ๋กœ๋Š” ๋‹จ์ ๊ฐ™๋‹ค. ๋ณต์žกํ•œ ์ˆœ์„œ๋„๋ฅผ ๊ทธ๋ฆด ์ˆ˜ ์žˆ์Œ๊ณผ ๋™์‹œ์— ๋‚ด๊ฐ€ ๋งก์€ feature์ด์™ธ์—๋„ ์ „์ฒด์ ์ธ ๊ตฌ์กฐ๋ฅผ ์ˆ™์ง€ํ•ด์•ผํ•œ๋‹ฌ๊นŒ..?

์–ด์จŒ๋“ , ์ด ๋‘๊ฐœ ๋ชจ๋“ˆ์„ entry file์˜ ํ•œ ์ข…๋ฅ˜๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ๋ชจ๋“ˆ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ชฝ์œผ๋กœ ์—๋Ÿฌ๊ฐ€ ๋‚œ๋‹ค๋ฉด module๊ณผ routing module ๋จผ์ € ํ™•์ธํ•˜๋Š” ๊ฑธ ์ถ”์ฒœํ•œ๋‹ค.

Observable ํƒ€์ž…์˜ Parameter ์ •๋ณด ์ ‘๊ทผ.

๋˜ ์ค‘์š”ํ•œ๊ฒƒ์€, Angular์—์„œ๋Š” routing์‹œ ์ค‘๊ฐ„์— ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋‚˜์ง€๋งŒ ์•Š๋Š”๋‹ค๋ฉด ๊ณ„์†ํ•ด์„œ component๊ฐ€ ์žฌ์‚ฌ์šฉ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ž˜์„œ route parameter๋“ค์„ Observable ํƒ€์ž…์œผ๋กœ ๋ฐ›๋Š”๋‹ค. ์ฆ‰, ๋ผ์šฐํ„ฐ๊ฐ€ ์ปดํฌ๋„ŒํŠธ์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๊ณ  route parameter๋“ค์€ ๋ผ์šฐํŒ…์ด ์ˆ˜ํ–‰๋ ๋•Œ๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ๋œ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ด ๋™์ž‘์€ ngOnInit()์ด๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ ์ดˆ๊ธฐํ™” ๋ฉ”์„œ๋“œ์—์„œ paramMap์ด๋ผ๋Š” ํ”„๋กœํผํ‹ฐ๋ฅผ ์˜ต์ €๋ฒ„๋ธ” ํƒ€์ž…์œผ๋กœ ๋ฐ›์•„ route parameter๊ฐ€ ๋ฐ”๋€”๋•Œ๋งˆ๋‹ค ์ด ์ƒํƒœ๋ฅผ ํƒ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ๊ตฌํ˜„๋˜์–ด์žˆ๋‹ค.

paramMap API๋Š” route parameter๋กœ๋ถ€ํ„ฐ ์ „ํ•ด์ ธ์˜ค๋Š” parameter์— ์ ‘๊ทผํ•  ๋•Œ ์“ฐ๋Š” API๋กœ, params.get('ํŒŒ๋ผ๋ฏธํ„ฐ์ด๋ฆ„') ์€ ์—†์„ ๊ฒฝ์šฐ null, ์žˆ์„ ๊ฒฝ์šฐ ํ•ด๋‹น value๋ฅผ stringํ˜•ํƒœ๋กœ ๋ฆฌํ„ดํ•œ๋‹ค.

router.navigate() - path param์ด ๋ฐ”๋€Œ๋Š” ๊ฒฝ์šฐ(์˜ต์ €๋ฒ„๋ธ” ์‚ฌ์šฉ)

1) router๋ฅผ import ๋ฐ›๋Š”๋‹ค.
2) router.navigate(['/path์ด๋ฆ„'], (์„ ํƒ-parm๊ฐ์ฒด)) ์œผ๋กœ ์ด๋™ ๊ฒฝ๋กœ๋ฅผ ๋„ฃ์–ด์ค€๋‹ค.
gotoHeroes(hero: Hero) {
  const heroId = hero ? hero.id : null;
  this.router.navigate(['/heroes', { id: heroId }]);
}

route.snapshot - path param์ด ๋ฐ”๋€Œ์ง€ ์•Š๋Š” ๊ฒฝ์šฐ

์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ตœ์ดˆ ๋ผ์šฐํŒ… ์ดํ›„์— ์ ˆ๋Œ€ ์žฌ์‚ฌ์šฉ๋˜์ง€ ์•Š์„๊ฑฐ๋ผ๋Š” ํŒ๋‹จ์ด ๋“ค๋ฉด ๊ตณ์ด ์ƒํƒœ๋ฅผ ์ง€์ผœ๋ณผ(Observe)ํ•  ํ•„์š”๊ฐ€ ์—†๊ฒŒ๋œ๋‹ค. ์ด๋Ÿด๋• ActicvatedRoute๋ฅผ import ๋ฐ›๊ณ  (์ƒ์„ฑ์ž์— key๋กœ ์ถ”๊ฐ€) snapshot์— ์ ‘๊ทผํ•ด์„œ ์ตœ์ดˆ์˜ router parameter map์˜ ๊ฐ’์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.

hero-detail ์ปดํฌ๋„ŒํŠธ

import { Component, OnInit } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';
import { HeroService } from '../hero.service';
import { Observable } from 'rxjs';
import { Hero } from '../hero';

@Component({
  selector: 'app-hero-detail',
  templateUrl: './hero-detail.component.html',
  styleUrls: ['./hero-detail.component.scss'],
})
export class HeroDetailComponent implements OnInit {

  hero$: Observable<Hero>;

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

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

}

Heroes ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•ด๋‹น id๋ฅผ ๊ฐ€์ง„ ๋””ํ…Œ์ผ ์ปดํฌ๋„ŒํŠธ๋กœ ์ด์šฉ(๋™์ ๋ผ์šฐํŒ…)ํ•˜๋ ค๋ฉด
๋‘๋ฒˆ์งธ ์ธ์ž์— id ๊ฐ’์„ ๋„ฃ์–ด์ค€๋‹ค. ์ด๋•Œ ๋ณดํ†ต์€ ngFor๊ฐ™์€ ๋””๋ ‰ํ‹ฐ๋ธŒ๋กœ ๋ฐ˜๋ณต๋ฌธ ์ถœ๋ ฅ์„ ํ•ด์ฃผ๊ณ  ์ด๋•Œ item์— ๋Œ€ํ•œ id๊ฐ’(๊ณ ์œ ๊ฐ’)์„ ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ์ด๋™ํ•œ๋‹ค.

๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” Material UI๋„ ํ™œ์šฉํ•ด์ฃผ์—ˆ๊ธฐ๋•Œ๋ฌธ์— dataSource์— ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋ฐ›์„ ์˜ต์ €๋ฒ„๋ธ” ํƒ€์ž…์„ ๋ช…์‹œํ•ด์ฃผ๊ณ  ngOninit()์—์„œ ์„œ๋น„์Šค์— ์žˆ๋Š” getHeroes()๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ๊ฐ’์„ ์ด ๋ณ€์ˆ˜์— ๋„ฃ์–ด์ฃผ๊ณ  ํ•ด๋‹น ๊ฒฐ๊ณผ๊ฐ’์ด dataSource์— ๋„ฃ์–ด์ ธ์„œ ์ด ์š”์†Œ ํ•˜๋‚˜ํ•˜๋‚˜์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๊ฐ’(๊ฐ์ฒด)์„ hero๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ๋ถ™์ด๊ณ  ํ•ด๋‹น ๊ฐ์ฒด์— ์ ‘๊ทผํ•ด ์›ํ•˜๋Š” id,name์„ ๊ฐ€์ ธ์˜จ ํ˜•ํƒœ์ด๋‹ค.

hero-list component

import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';

@Component({
  selector: 'app-hero-list',
  templateUrl: './hero-list.component.html',
  styleUrls: ['./hero-list.component.scss'],
})
export class HeroListComponent implements OnInit {
  heroes$: Observable<Hero[]>;
  selectedId: number;
  displayedColumns = ['id', 'name', 'button'];
  
  constructor(private service: HeroService, private route: ActivatedRoute) { }

  ngOnInit() {
    this.heroes$ = this.service.getHeroes();
  }
}

service ํŒŒ์ผ์—์„œ๋Š” ์ด์ „ ์‹œ๋ฆฌ์ฆˆ ์ฒซ๋ฒˆ์งธ ๊ธ€์—์„œ ๋งํ–ˆ๋˜ ์•ต๊ทค๋Ÿฌ ์ปจ์…‰๊ณผ ๊ฐ™์ด
๋ทฐ์™€ ์ง์ ‘์ ์œผ๋กœ ๊ด€๋ จ๋˜์ง€์•Š์€ ๋กœ์ง์„ ์ž‘์„ฑํ•ด์ค€๋‹ค.
๋””ํ…Œ์ผ๋กœ ์ด๋™์‹œ์—๋Š” getHero๋ฅผ ํ˜ธ์ถœํ•ด ํ•ด๋‹น ํ˜ธ์ถœ์— ๋Œ€ํ•œ ๊ฒฐ๊ณผ๋ฅผ hero-detail ์ปดํฌ๋„ŒํŠธ์— ํ‘œ์‹œํ•ด์ฃผ์–ด์•ผํ•˜๊ธฐ๋•Œ๋ฌธ์— ๊ทธ์— ํ•ด๋‹นํ•˜๋Š” ๋กœ์ง๋„ ์ž‘์„ฑํ•ด์ค€๋‹ค.

hero-service.ts

import { Injectable } from '@angular/core';
import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class HeroService {
  constructor(private messageService: MessageService) { }

  getHeroes(): Observable<Hero[]> {
    return of(HEROES);
  }
  getHero(id: number): Observable<Hero> {
    return of(HEROES.find(hero => hero.id === id));
    //Observable๋กœ ๋ณ€ํ™˜ํ•˜๊ธฐ์œ„ํ•ด RxJS๊ฐ€ ์ œ๊ณตํ•˜๋Š” of() ํ•จ์ˆ˜ ์‚ฌ์šฉ
  }
}

hero-list template

<article>
  <h2>HEROES</h2>
  <table mat-table [dataSource]="heroes$" class="mat-elevation-z8">
    <ng-container matColumnDef="id">
      <mat-header-cell *matHeaderCellDef> No. </mat-header-cell>
      <mat-cell *matCellDef="let hero">{{ hero.id }}</mat-cell>
    </ng-container>
    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
      <mat-cell *matCellDef="let hero">{{ hero.name }}</mat-cell>
    </ng-container>
    <ng-container matColumnDef="button">
      <mat-header-cell *matHeaderCellDef> Manage hero </mat-header-cell>
      <mat-cell *matCellDef="let hero"><a [routerLink]="['/hero', hero.id]"><button mat-flat-button
            color="primary">learn more</button></a></mat-cell>
    </ng-container>
    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
  </table>
</article>

route guard ์ถ”๊ฐ€? (๋‹ค์Œ ๊ธ€))

๋งˆ์ง€๋ง‰์œผ๋กœ... routing๋ ๋•Œ ๊ถŒํ•œ์„ ์ฒดํฌํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด AuthGuard๋ฅผ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค! ์ด์— ๊ด€๋ จ๋œ ์ž์„ธํ•œ ๋กœ์ง์€ ๋‹ค์Œ ๊ธ€์—์„œ,

Observer ํŒจํ„ด์œผ๋กœ ๋กœ๊ทธ์ธ ๋กœ๊ทธ์•„์›ƒ ๊ตฌํ˜„ํ•˜๊ธฐ + Routing auth ๊ถŒํ•œ ์ ์šฉํ•˜๊ธฐ๋ผ๋Š” ๋‚ด์šฉ์œผ๋กœ ์ถ”๊ฐ€ํ•  ์˜ˆ์ •์ด๋‹ค. ๐Ÿค“

0๊ฐœ์˜ ๋Œ“๊ธ€