반응형

1. routerModule 등록

2. routerLink 사용하기

3. router 코딩하기

4. 상대주소 사용하기

5. 유저 불러오기(예제)

6. Query Parameter & Fragment

7. Child(Nested)Routes

8. Redirecting & Wildcard Routes

9. Outsourcing the Foute Configuration

10. Guard

11. Data with resolve Guard


1. app.module.ts (등록하기)

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

const appRoutes: Routes = [
  { path: '', component: HomeComponent},
  { path: 'users', component: UsersComponent},
  { path: 'servers', component: ServersComponent},
]

imports: [
  BrowserModule,
  FormsModule,
  RouterModule.forRoot(appRoutes)

path는 맨 앞의 / 를 제외합니다. ("localhost:4200/")가 이미 앞에 있음으로, "/"를 생략하고 입력해 줍니다.

RouterModule의 forRoot에 선언한 route를 등록합니다.

 

main.html

<div class="row">
  <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
    <app-home></app-home>
  </div>
</div>
<div class="row">
  <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
    <app-users></app-users>
  </div>
</div>
<div class="row">
  <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
    <app-servers></app-servers>
  </div>
</div>

아래와 같이 변경 간단하게 줄일 수 있습니다.

<div class="row">
  <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
    <router-outlet></router-outlet>
  </div>
</div>

 

router-outlet은 directive랍니다.

 

하지만 이렇게만 바꾸면, 새로운 페이지가 로딩됩니다.

물론, 네비게이션 클릭시, 
혹은 주소 입력시 서버에 해당 주소로 이동하도록 요청을 보내고, 
서버에서 페이지를 내려 받기 때문에, 
리로딩이 되는것이 맞습니다.(주소창에 해당 주소를 치는것과 같은 효과) 하지만 베스트가 아닙니다.

스테이트를 잃어버리기 때문에 사용자에게 제공하려는 서비스가 아닙니다.


main.html(네비게이션)

<div class="row">
  <div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
    <ul class="nav nav-tabs">
      <li role="presentation" class="active"><a href="/">Home</a></li>
      <li role="presentation"><a href="/servers">Servers</a></li>
      <li role="presentation"><a href="/users">Users</a></li>
    </ul>
  </div>
</div>

href 속성 대신, routerLink라는 directive를 사용합니다.

<ul class="nav nav-tabs">
  <li role="presentation" class="active"><a routerLink="/">Home</a></li>
  <li role="presentation"><a routerLink="/servers">Servers</a></li>
  <li role="presentation"><a [routerLink]="'/users'">Users</a></li>
</ul>

[routerLink]으로 property(속성)으로 사용할 경우 자바스크립트에선 invaild name이기 때문에 ''로 한번 더 감싸야 합니다.

 

또 다른 방법으로, 

<li role="presentation"><a [routerLink]="['/users']">Users</a></li>

[] 배열안에 여러가지를 추가 할 수 있습니다. 이 방법을 사용한다면, 매우 쉽게 복잡한 경로를 추가할 수 있습니다.

 

routerLink 사용 시 주의 사항,

절대 경로 : "/"로 /로 시작.

상대 경로 : "./어딘가"이나, "../저긴가"와 같이 /로 시작하지 않습니다.

 

css클라스를 이용하지 않고,

routerLinkActive를 이용한다면, bootstrap없이 사용가능합니다.

 

각 리스트 아이템: <li>에 routerLinkActive를 추가해줍니다.

<li 
	role="presentation" 
	routerLinkActive="active"
	[routerLinkActiveOptions]="{exact: true}">
  <a routerLink="/">Home</a>
</li>

만약 [routerLinkActiveOptions]="{exact: true}"가 없다면,

bootstap은 "/" 가 앞에 있기 때문에 home의 네비게이션이 active인 상태로 만들어 버립니다.

 

exact는 정확히 / 만 있을 경우를 해당 주소로 인식하게 만드는 기능을 합니다.


3. ts파일로 작동하기

import { Router } from "@angular/router";

constructor(private router: Router) { }

onLoadServers() {
  // 복잡한 경로 계산이 가능해짐.
  this.router.navigate(['/servers'])
}

4. 상대주소

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

  onReload() {
    this.router.navigate(['somewhere'],{relativeTo: this.route})
  }

somewehere이라는 상대주소를 사용했습니다.

router.navigate의 2번째 파라미터에 {}자바스크립트 객체를 넣고,

relativeTo속성에 현재 위치가 어디인지를 나타냅니다.

현재 어떤 라우터가 작동하고있는지(ActivatedRoute) 알려줍니다.

 

*relativeTo가 없을 경우, root에서 추가하기 때문에, 원하지 않는 곳으로 이동할 수 있습니다.


5. 유저 불러오기

 

app.module.ts

const appRoutes: Routes = [
  { path: 'users/:id/:name', component: UsersComponent},// dynamic part of the path :'s meaning
]

path에 ":"는 다이나믹한 값을 받아올 수 있게 합니다.

 

ts

export class UserComponent implements OnInit {
  user: {id: number, name: string};

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.user = {
      id: this.route.snapshot.params['id'],
      name: this.route.snapshot.params['name']
    };
    this.route.params
      .subscribe(
        (params: Params) => {
          this.user.id = params['id'];
          this.user.name = params['name'];
        }
      );
  }
}

HTML

<p>User with ID {{user.id}} loaded.</p>
<p>User name is {{user.name}}</p>
<hr>
<a [routerLink]="['/users',10, 'Anna']">Load Anna (10)</a>

Snapshot: 일회성 사용시(the no-observable alternative)

특정 컴퍼넌트의 재사용이 필요 없다면, snapshot을 사용하세요.

router는 항상 새로운 인스턴스를 생성합니다.

 

router.snapshot은 router 최초 값(initial value)으로 작동합니다.

스냅샷을 이용한다면, subscribing이나 observable operator를 추가하지 않고도 쉽게 읽고 쓰기를 진행할 수 있습니다.

this.route.params
  .subscribe(
    (params: Params) => {
      this.user.id = params['id'];
      this.user.name = params['name'];
    }
  );

위 코드 없이 Load Anna (10) 링크를 클릭하면,

URL은 바뀌지만, HTML의 내용은 변하지 않음을 확인할 수 있습니다.

 

Angular는 UserComponent(동일 컴포넌트)에서 <a> link를 누를경우 re-rendering을 하지 않습니다. 

이는 Angular의 default 동작으로, Angular는 컴포넌트 전체를 destroy하고 recreate하지 않는 답니다.

그러기에 rendering을 다시 할 수 있도록, subscribe를 사용합니다.

 

*참고: this.route.params는 observables입니다.

기본적으로, observables는 third-party 패키지로 추가된 기능입니다.

Angular의 기능은 아니지만 어씽크(asynchronous) 작업을 쉽게 지원하기 때문에 많이 사용하고 있습니다.

link를 누르는 것은 어씽크 작업입니다.

불러온 route의 현재 패러미터값은 언제 변경될 모르고 얼마나 걸릴지도 모르기 때문입니다.

또한 링크를 누르지 않을 수 도 있기 때문에, 무한정 기다리거나 코드를 없애서도 안됩니다.

 

*참고: OnDestroy()

꼭 해야할 필요는 없지만, 알아두어야 할 기능이 있습니다. life cycle의 ngDestroy()입니다.

Angular는 background에서 해당 컴포넌트를 destroy합니다. 동시에 subscription을 제거해줍니다.

만약 하지 않는다면, subscription을 제거하지 않는다면, 해당 컴포넌트는 제거되지만, subscription은 계속 남아 있게 됩니다.

import { Subscription } from 'rxjs/Subscription';

export class UserComponent implements OnInit, OnDestroy {
  user: {id: number, name: string};
  paramsSubscription: Subscription;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.paramsSubscription = this.route.params
      .subscribe(
        (params: Params) => {
          this.user.id = params['id'];
          this.user.name = params['name'];
        }
      );
  }

  ngOnDestroy() {
    this.paramsSubscription.unsubscribe();
  }

}

6. Query Parameter & Fragment

 

?mode=editing#loading

을 추가하려면 어떻게 해야할까요?

 

1. app.module.ts / path 추가

const appRoutes: Routes = [
  { path: 'servers/:id/edit', component: EditServerComponent}
]

2. servers.component.html / routerLink, queryParams, fragment

<div class="row">
  <div class="col-xs-12 col-sm-4">
    <div class="list-group">
      <a
        [routerLink]="['/servers', 5, 'edit']"
        [queryParams]="{allowEdit: '1'}"
        fragment="loading"
        href="#"
        class="list-group-item"
        *ngFor="let server of servers">
        {{ server.name }}
      </a>
    </div>
  </div>
</div>

routerLink는 path에 추가된 조건과 동일하게 맞추어 줍니다.

 

queryParams는 routerLink의 'edit'뒤에 파라미터를 추가할 수 있지만, queryParams라는 directive를 사용해보겠습니다.

javascript 객체에 key-value 페어를 이용합니다.

fragment도 추가할 수 있습니다.

 

3. home.component.html & Home.component.ts / 코딩으로 routing

 

HTML

<button class="btn btn-primary" (click)="onLoadServers(1)">Load Server 1</button>

1 이라는 서버로 이동하는 매쏘드로 변경합니다.

 

onLoadServers(id: number) {
  this.router.navigate(['/servers', id, 'edit'], {queryParams: {allowEdit: '1'}, fragment: 'loading'})
}

2번과 같이 key-value로 추가합니다.

 

relativeTo를 사용했지만, 위와 같이도 가능합니다.


7. Child(Nested)Routes


app.module.ts

const appRoutes: Routes = [
  { path: '', component: HomeComponent},
  { path: 'users', component: UsersComponent},
	  { path: 'users/:id/:name', component: UsersComponent},
  
  { path: 'servers', component: ServersComponent},
	  { path: 'servers/:id', component: ServerComponent},
	  { path: 'servers/:id/edit', component: EditServerComponent}
]
const appRoutes: Routes = [
  { path: '', component: HomeComponent},
  { path: 'users', component: UsersComponent, children: [
    { path: ':id/:name', component: UserComponent},
  ]},
  { path: 'servers', component: ServersComponent, children: [
    { path: ':id', component: ServerComponent},
    { path: ':id/edit', component: EditServerComponent}
  ]}
]

children으로 중복되는 경로를 보기 쉽게 해줍니다.

component가 선언되어 있기 때문에, 해당 HTML도 변경되어야합니다.

 

servers.component.HTML

<div class="col-xs-12 col-sm-4">
  <router-outlet></router-outlet>
    <!-- <button class="btn btn-primary" (click)="onReload()">Reload Page</button> -->
    <!-- <app-edit-server></app-edit-server> -->
    <!-- <hr> -->
    <!-- <app-server></app-server> -->
</div>

users.componente.HTML

<div class="col-xs-12 col-sm-10 col-md-8 col-sm-offset-1 col-md-offset-2">
  <router-outlet></router-outlet>
</div>

8. Redirecting & Wildcard Routes

 

app.module.ts

const appRoutes: Routes = [
  { path: '', component: HomeComponent},
  { path: 'users', component: UsersComponent, children: [
    { path: ':id/:name', component: UserComponent},
  ]},
  { path: 'servers', component: ServersComponent, children: [
    { path: ':id', component: ServerComponent},
    { path: ':id/edit', component: EditServerComponent}
  ]},
  { path: 'not-found', component: PageNotFoundComponent},
  { path: '**', redirectTo: '/not-found'}
]

path: '**' 와일드 카드는 무조건 맨 아래에 있어야 합니다.

만약 맨 위에 위치하게 된다면, 모든 경로는 리다이랙트 됩니다.

 

*Tip:

{ path: '', redirectTo: '/somewhere-else', pathMatch: 'full' } 을 사용해야 합니다.

Angulart의 매칭 방법(matching strategy)은 "prefix"입니다.

Angular는 입력한 URL이 해당 path로 시작하는지 체크합니다. 물론 매번 ''으로 시작하기 때문에(중요: whitespace는 존재하지 않음으로, 언제나 "nothing"입니다.)

그러므로 매칭 방법을 "full"로 변경해야 합니다.


9. Outsourcing the Foute Configuration

 

9.1 module.ts파일을 만듭니다.

@NgModule({
	imports: [
		RouterModule.forRoot(appRoutes)
	],
	exports: [RouterModule]
})

export class AppRoutingModule {

}

9.2 app.module.ts

imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ]

imports에 추가합니다.

 

routing 정보를 파일로 만들면, 파일 정리 및 가독성을 높일 수 있습니다.


10. Guard

Authentication 을 Fake로 간단히 만들고, 어떤 페이지에서 이탈할 경우, 정말 나갈 것인지 묻도록 합니다.

 

10.1. auth.service | 로그인 기능

export class AuthService {
    loggedIn = false;

    isAuthenticated() {
        const promise = new Promise(
            (resolve, reject) => {
                setTimeout(()=> {
                    resolve(this.loggedIn);
                }, 800);
            }
        );
        return promise;
    }

    login() {
        this.loggedIn = true;
    }

    logout() {
        this.loggedIn = false;
    }
}

로그 in/out은 home.component를 통해 간단하게 조작합니다.

onLogin() {
    this.authService.login();
  }

  onLogout() {
    this.authService.logout();
  }

10.2 auth-guard.service | 접근 통제

import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router, CanActivateChild } from "@angular/router";
import { Observable } from "rxjs";
import { Injectable } from "@angular/core";
import { AuthService} from "./auth.service";

@Injectable()
export class AuthGuard implements CanActivate, CanActivateChild {
    constructor(private authServlice: AuthService, private router: Router) {}
    canActivate(route: ActivatedRouteSnapshot,
                state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
        return this.authServlice.isAuthenticated()
            .then(
                (authenticated: boolean) => {
                    if (authenticated) {
                        return true;
                    } else {
                        this.router.navigate(['/']);
                    }
                }
            )
        }
    canActivateChild(route: ActivatedRouteSnapshot,
                    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this.canActivate(route, state);
    }
}

canActivateChild매쏘드는 일정 페이지 이상의 접근을 제한합니다.

app-routing.module.ts

{ path: 'servers',
      //   canActivate: [AuthGuard], 
      canActivateChild: [AuthGuard],
      component: ServersComponent, 
      children: [
      { path: ':id', component: ServerComponent},
      { path: ':id/edit', component: EditServerComponent, canDeactivate: [CanDeactivateGuard]}
    ]},

10.3 이탈 방지 코드

can-deactivate-guard.service.ts

import { Observable } from "rxjs/Observable";
import { ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";

export interface CanComponentDeactivate {
    canDeactivate: () => Observable<boolean> | Promise<boolean> | boolean;
}

export class CanDeactivateGuard implements CanDeactivate<CanComponentDeactivate> {
    canDeactivate(component: CanComponentDeactivate,
        currentRoute: ActivatedRouteSnapshot,
        currentState: RouterStateSnapshot,
        nextState?: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
            return component.canDeactivate();
        }
}

ㅠㅠ 실력이 없어서... 위 코드를 따라하기만 하네요... 설명하고 싶은데 못하겠어요... 어플 만들고나서 이해가 필요한 부분이네요!

그 때쯤이면 어느정도 실력이 쌓여서 지금 풀어보는 것보다 더 이해도가 높겟쪄? ㅎㅎ

 

edit-server.component에서 실수로 뒤로가기를 누르거나, 업데이트 등 할 때 사용자에게 한번 더 묻습니다.

canDeactivate(): Observable<boolean> | Promise<boolean> | boolean {
    if (!this.allowEdit) {
      return true;
    }
    if ((this.serverName !== this.server.name || this.serverStatus !== this.server.status) && !this.chagesSaved) {
      return confirm('Do you want to discard the changes?');
    } else {
      return true;
    }
  }

물론 app-routing.module.ts에 추가하고, app.module에 provider로 등록해야합니다.

providers: [ServersService, AuthService, AuthGuard, CanDeactivateGuard],

11. Data with resolve Guard

11.1. static data를 route로 전달하기

 

11.1.1. routing.module.ts 에 코드 추가 data: {message: '원하는 문구'}

{ path: 'not-found', component: ErrorPageComponent, data: {message: 'Page not found'} },

11.1.2. route로 data 불러오기

export class ErrorPageComponent implements OnInit {

  errorMessage: string;

  constructor(private route: ActivatedRoute) { }

  ngOnInit() {
    this.route.data.subscribe(
      (data: Data) => {
        this.errorMessage = data['message'];
      }
    )
  }
}

 

11.2. Resolving 다이나믹 데이터

11.2.1. resolver 만들기

import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from "@angular/router";
import { Observable } from "rxjs";
import { ServersService } from "../servers.service";
import { Injectable } from "@angular/core";

interface Server {id: number, name: string, status: string}

@Injectable()
export class ServerResolver implements Resolve<Server> {
  constructor(private serversService: ServersService) {}
  resolve(route: ActivatedRouteSnapshot, status: RouterStateSnapshot): Observable<Server> | Promise<Server> | Server {
    return this.serversService.getServer(+route.params['id']);
  }
}

interface Resolve 를 구현 했기 대문에 route와 status의 스냅샷이 필요합니다.

+route.params['id']는 getServer가 number 타입을 원하기 때문에 꼼수로 앞에다 +를 추가하였습니다.

 

11.2.2. app.module.ts 에 providers 추가하기

providers: [ServersService, AuthService, AuthGuard, CanDeactivateGuard, ServerResolver],

 

11.2.3. routing.module.ts에 resolve추가

{ path: ':id', component: ServerComponent, resolve: {server: ServerResolver} },

 

11.2.4. 코드 적용하기

  ngOnInit() {
    this.route.data.subscribe(
      (data: Data) => {
        this.server = data['server'];
      }
    )
    // const id = +this.route.snapshot.params['id'];
    // this.server = this.serversService.getServer(id);
    // this.route.params
    //   .subscribe(
    //     (params: Params) => {
    //         this.server = this.serversService.getServer(+params['id']);
    //     }
    //   )
  }

기존의 코드를 주석처리 합니다.

route를 통해 data가 변경될 때 마다 받아 옵니다.

 

만약 /servers입력시,  index.html로 돌아간다면

@NgModule({
    imports: [
        RouterModule.forRoot(appRoutes, {useHash: true})
    ],
    exports: [RouterModule]
})

hash가 주소창 앞에 붙여

#이전의 주소와 #이후의 주소로 구별하여, 실제 server에 접근하지 않도록 방지해 줍니다.

 

 

 

 

 

 

 

 

 

 

 

출처: https://www.udemy.com/share/1000imAkEeeVhSQ34=/

Angular 8 (formerly Angular 2) - The Complete Guide

제제작자 Maximilian Schwarzmülle

routing-final.zip
0.06MB

 

반응형

'프로그래밍 > Angular' 카테고리의 다른 글

Observable & Subject  (0) 2019.06.26
Router 정리  (0) 2019.06.25
Providers - Hierarchical Injector  (0) 2019.06.16
custom attribute Directive  (0) 2019.06.09
style 스타일링  (0) 2018.09.05

+ Recent posts