'전체 글'에 해당되는 글 32건

  1. 2018.10.28 Angular 기본 구조 이해 #2
  2. 2018.10.28 Angular 기본 구조 이해 #1

앞에서는 페이징 단위에서의 이벤트 처리 및 DI를 설명했는데요.

이어서 Injection과 Routing을 설명하겠습니다.

 

 

※ Service Injection

component와 module이 angular 프로젝트에서 반드시 정의되어야 하는 것에 비해

Service depandency injection 및 Routing은 부가적인 요소입니다. 없어도 작동은 하죠.

다만 Spring 같이 견고한 App을 원하신다면 반드시 필요한 요소겠지요?

앞의 단계가 jsp 코딩을 통한 MVC 모델 1 에 비유된다면

이번 단계는 Spring의 핵심 기능을 사용하는 것과 같다고 생각하시면 됩니다.

 

Service 클래스의 예시 입니다.

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

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes(): Promise<Hero[]> {
    return Promise.resolve(HEROES);
  }
}

Service 클래스는 전형적인 typescript 코딩으로만 구성됩니다.
대신 클래스 선언 바로 앞에서 @Injectable()을 선언하는데요.

이 선언부를 통해 Module이 해당 서비스를 DI의 대상으로 판단합니다.

 

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

import { AppComponent }        from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot([
      {
        path: 'heroes',
        component: HeroesComponent
      }
    ])
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  providers: [
    HeroService
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}

app.module.ts에서 @NgModule의 providers 속성에 추가해주면 해당 서비스를 사용할 수 있습니다.

물론 각 컴포넌트 클래스의 providers 속성에도 추가를 해줘야 하고요.

 

※ Routing

네트워크 조금 아시는 분들은 라우터라는 장비가 떠오르실겁니다.

외부에서 들어온 패킷의 헤더를 해석해서 목적지 컴퓨터의 IP주소를 찾고 패킷을 전달하죠.

 

여기서의 Routing도 비슷한 의미입니다.

다만 여기서의 Routing은 컴퓨터가 아니라 Applcation의 URL을 대상으로 하죠.

또한, 물리적인 라우터와는 다르게 url <-> 컴포넌트 로 1:1 매칭입니다.

 

HTTP 요청을 보낼때 도메인에 해당하는 Root address 뒤에 요청별로 식별자를 더 붙여서 URL로 사용하죠.

도메인이 DNS 및 라우터가 수행하는 네트워크 상에서의 서비스 식별자라면,

URL의 뒷 부분은 서비스 내의 각 요청에 대한 구분자이자 식별자가 됩니다.

 

Angular에서의 Routing은 URL 뒷부분을 식별해서 해당 요청을 수행하는 Component를 로딩합니다.

이후 Component에 정의된 템플릿이 뿌려지고, user event를 수행할 준비를 하게 되는거죠.

 

이 Routing을 정의하는 방법은 두가지가 있습니다.

AppModule을 정의하는 @NgModule decorator에 Routing 모듈을 바로 추가해서 정의하거나,

Routing 모듈을 구체화 한 Custom 모듈을 @NgModule에 추가하는 방식 중에 선택해서 씁니다.

 

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'hero/:id',      component: HeroDetailComponent },
  {
    path: 'heroes',
    component: HeroListComponent,
    data: { title: 'Heroes List' }
  },
  { path: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
    // other imports here
  ],
  ...
})
export class AppModule { }

위의 코드는 @NgModule에 바로 Routing을 정의한 것입니다.

해당 path로 호출시 path 마다 지정한 컴포넌트르 호출하라는 방식이죠.

 

커스텀 모듈도 Routing 구성은 똑같습니다.

소스 코드 예시를 보죠.

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

import { CrisisListComponent }   from './crisis-list.component';
import { HeroListComponent }     from './hero-list.component';
import { PageNotFoundComponent } from './not-found.component';

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes',        component: HeroListComponent },
  { path: '',   redirectTo: '/heroes', pathMatch: 'full' },
  { path: '**', component: PageNotFoundComponent }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes,
      { enableTracing: true } // <-- debugging purposes only
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

 

보시면 @NgModule 로 커스텀 모듈을 재정의할 뿐 클래스 내부에서의 코딩은 없습니다.

이제 AppModule의 @NgModule decorator에 다음과 같이 추가해주면 됩니다.

 

imports: [

...

, AppRoutingModule

]

 

이렇게 Routing을 AppModule 정의에서 분리시켰습니다.

물론 Component 선언은 AppRoutingModule, AppModule 둘 다에 해줘야 하는 불편함은 있네요.

이부분은 확인한 다음에 수정하겠습니다.

 

Angular의 기본구조는 이정도로 정리를 마치겠습니다.

참고한 문서는 다음과 같습니다.

1. https://angular.io/guide/router

2. https://angular.io/guide/dependency-injection

Posted by kevin.jeong.
,

이제 Angular의 기본 구조를 정리하려고 합니다.

읽기 전에 Angular Documentation에 있는 튜토리얼을 먼저 진행하시는 것을 추천합니다.

설명만 읽었을땐 개념이 와닫지 않았는데, 튜토리얼을 직접 해보면서 머리속에 정리가 되었었네요.

 

그럼 Angular CLI 를 설치하셨다는 가정하에 진행하겠습니다.

※ 튜토리얼 개발환경 설치 및 실행

git clone https://github.com/angular/quickstart.git quickstart
cd quickstart
npm install
npm start

다음 명령어를 차례대로 입력해주면 초기 프로젝트 환경의 설치 및 실행은 끝납니다.

첫번째 명령어는 git에서 해당 소스코드를 로컬에서 지정한 디렉토리로 복사하는 것이고,

두번째는  복사한 디렉토리로 이동, 세번째는 Angular 실행을 위해 프로젝트 폴더에 NPM 모듈 설치를 지시하고,

마지막 명령이 typescript 컴파일 및 프로젝트 실행 명령입니다.

보통 .ts 파일을 수정하면 자동으로 재 컴파일 및 실행이 되나,

컴파일 도중 에러가 발생하기 시작하면 이러한 작동이 멈춰버리는 경우가 있습니다.

그럴땐 작동중인 angular 및 node.js 모듈을 kill 명령으로 모두 종료하고 다시 npm start 명령을 입력해서 작동시켜야 합니다.

 

※ Bootstrapping

Angular에서 Application을 시작하면 Root Module의 @NgModule decorator에 입력된 metadata를 읽어

Component, Service 등 요소의 구조를 구성합니다. 이러한 일련의 과정을 Angular에서는 bootstrapping 으로 명칭합니다.

이를 통해 Root Module인 AppModule 클래스가 재구성되어 실행되며, 정의된 컴포넌트에 접근을 할수 있게 되죠.

이러한 @NgModule은 app.module.ts 파일에 정의합니다.

 

※ app.module.ts 파일 예시

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

import { AppComponent }        from './app.component';
import { HeroDetailComponent } from './hero-detail.component';
import { HeroesComponent }     from './heroes.component';
import { HeroService }         from './hero.service';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    RouterModule.forRoot([
      {
        path: 'heroes',
        component: HeroesComponent
      }
    ])
  ],
  declarations: [
    AppComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  providers: [
    HeroService
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule {
}

 

보시면 AppModule 클래스를 직접 작성하지 않고, @NgModule 안에서 구성을 선언하도록 되어 있습니다.

프로젝트의 실질적인 초기화 및 환경설정이 이 안에서 이루어지는 걸로 보시면 됩니다.

imports - Angular 라이브러리를 불러와서 사용하는 경우, 해당하는 모듈 명을 기재합니다.

declarations - 프로젝트 안에서 작성하여 사용할 컴포넌트를 선언하는 부분입니다.

providers - 각 컴포넌트 안에 Injection을 걸어 사용할 서비스 클래스를 명시하는 부분입니다.  Spring을 공부하셨다면 이해하시기 쉬울겁니다.

bootstrap - 정확한 의미는 잘 모르겠습니다. 다만 여기에 지정된 Component는 메인 페이지의 역할을 하는거 같습니다. 보통 이 부분은 수정하지 않습니다.

 

여기서 잠깐 Spring, Struts 등의 자바 MVC 프레임워크를 생각해보면 DAO, Service 같은 객체를 통해서 Data 처리를 별도로 분리했었습니다.

또한 Spring 같은 경우는 DI라는 패턴을 통해 싱클톤으로 객체의 의존관계를 주입했었죠?

그 DI가 Angular에서도 비슷하게 사용됩니다.

 

위에서부터 보였던 @NgModule, @Component 같은 decorator가 바로 DI를 정의하는 역할을 합니다.

 

@NgModule decorator는 모듈안에서 Component와 Service 및 외부 라이브러리 모듈 등의 의존관계를 정리하며,

@Component decorator는 Component별로 Service, template, style 등의 의존 관계를 정의합니다.

 

Component라는 요소는 하나의 페이징 단위에 해당하며,

고유한 태그 명을 갖고 있기 때문에 다른 컴포넌트에서도 호출해서 쓸수 있으며,

 

각 페이지의 html tag, client action, css style을 모두 포함하는 요소입니다.

 

Component는 @Component decorator로 selector, template, style, providers를 지정할 수 있습니다.

select - 외부에서 해당 컴포넌트를 호출하기 위해 사용하는 명칭입니다.

template - 브라우저에 실제로 표시되는 HTML 마크업을 정의하는 요소입니다.

templateUrls - HTML 마크업을 별도의 파일로 분리한 경우 해당 파일의 path를 지정할때 씁니다.

styles - HTML 마크업의 CSS 스타일을 정의하는 요소입니다.

styleUrls - CSS를 별도의 파일로 분리한 경우 해당 파일의 path를 지정할때 씁니다.

providers - 컴포넌트 내에서 사용할 서비스 클래스를 명시합니다.

 

import { Component }   from '@angular/core';
 
import { Hero }        from './hero';
import { HeroService } from './hero.service';
 
@Component({
  selector: 'hero-list',
  template: `
  <div *ngFor="let hero of heroes">
    {{hero.id}} - {{hero.name}}
  </div>
  `
})
export class HeroListComponent {
  heroes: Hero[];
 
  constructor(heroService: HeroService) {
    this.heroes = heroService.getHeroes();
  }
}

※ app.component.ts 파일 예시

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

export class Hero {
  id: number;
  name: string;
}

const HEROES: Hero[] = [
  { id: 11, name: 'Mr. Nice' },
  { id: 12, name: 'Narco' },
  { id: 13, name: 'Bombasto' },
  { id: 14, name: 'Celeritas' },
  { id: 15, name: 'Magneta' },
  { id: 16, name: 'RubberMan' },
  { id: 17, name: 'Dynama' },
  { id: 18, name: 'Dr IQ' },
  { id: 19, name: 'Magma' },
  { id: 20, name: 'Tornado' }
];
 
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <h2>My Heroes</h2>
    <ul class="heroes">
      <li *ngFor="let hero of heroes"
        [class.selected]="hero === selectedHero"
        (click)="onSelect(hero)">
        <span class="badge">{{hero.id}}</span> {{hero.name}}
      </li>
    </ul>
    <div *ngIf="selectedHero">
      <h2>{{selectedHero.name}} details!</h2>
      <div><label>id: </label>{{selectedHero.id}}</div>
      <div>
        <label>name: </label>
        <input [(ngModel)]="selectedHero.name" placeholder="name"/>
      </div>
    </div>
  `,
  styles: [`
    .selected {
      background-color: #CFD8DC !important;
      color: white;
    }
    .heroes {
      margin: 0 0 2em 0;
      list-style-type: none;
      padding: 0;
      width: 15em;
    }
    .heroes li {
      cursor: pointer;
      position: relative;
      left: 0;
      background-color: #EEE;
      margin: .5em;
      padding: .3em 0;
      height: 1.6em;
      border-radius: 4px;
    }
    .heroes li.selected:hover {
      background-color: #BBD8DC !important;
      color: white;
    }
    .heroes li:hover {
      color: #607D8B;
      background-color: #DDD;
      left: .1em;
    }
    .heroes .text {
      position: relative;
      top: -3px;
    }
    .heroes .badge {
      display: inline-block;
      font-size: small;
      color: white;
      padding: 0.8em 0.7em 0 0.7em;
      background-color: #607D8B;
      line-height: 1em;
      position: relative;
      left: -1px;
      top: -4px;
      height: 1.8em;
      margin-right: .8em;
      border-radius: 4px 0 0 4px;
    }
  `]
})
export class AppComponent {
  title = 'Tour of Heroes';
  heroes = HEROES;
  selectedHero: Hero;
 
  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }
}

 

 

예시 코드를 보시면 (click)으로 클릭 이벤트 발생시의 action을 정의하면서 해당 컴포넌트에 선언된 함수를 호출하도록 하고 있습니다.

컴포넌트에서 선언한 정의한 메소드를 통해 페이징 단위의 이벤트를 처리할 수 있는 방식이네요.

 

 

@Component decorator 내 providers 속성과 Component 클래스 내의 constructor를 통해

해당 서비스 클래스를 사용한다는 것을 명시하고 있습니다.

자바에서 Spring, Struts를 사용할때와는 달리 해당 클래스에 직접 명시를 하네요.

이 부분에서 java와 angular의 차이가 보이는 군요.

angular는 설정파일을 최소화하고 각각의 컴포넌트를 Single View Applcation으로 사용한다는 취지를 살린게 보입니다.

이런 방식의 차이에 대해서 여러분들의 호불호가 상당히 갈릴것으로 보이네요.

저같은 경우는 변경의 단위가 컴포넌트 파일 하나로 줄어들기 때문에 더 신호하지만, 의견 차이가 분명히 있을 거 같네요.

 

Service, Component, Module의 관계를 그림으로 설명하겠습니다.

 

Component와 브라우저에서 보이는 페이지는 아래 그림으로 설명이 되겠습니다.

Component에 정의된 template으로 view에 표현될 마크업이 정의되며,

Component와 실제 html 사이에는 위의 화살표 방향의 binding으로 연결됩니다.

이를 통해 html에서 실제로는 Component에 있는 데이터를 보여주고, Component에 정의된 action을 호출하죠.

 

Injector 및 Routing에 대해서는 다음 포스트로 이어서 설명하겠습니다.

참고한 문서는 다음과 같습니다.

1. https://angular.io/guide/architecture

2. https://angular.io/guide/bootstrapping

3. https://angular.io/guide/setup

Posted by kevin.jeong.
,