HTML52017. 3. 3. 12:19


https://angular.io/docs/ts/latest/cli-quickstart.html


시작부터 정리 시작!


CLI로 프로젝트를 시작하는 방법에 대한 안내이다.


1. 개발환경 설정


NodeJs와 NPM설치

node는 6.9.x이상, npm은 3.x.x이상


angular cli는 npm install -g @angular/cli 로 설치


2. 프로젝트 생성


ng new my-app


3. Serve the app


cd my-app

ng serve --open


ng serve는 서버를 기동하고 파일을 감시하고 리빌드한다.


4. 이제 너의 콤포넌트들을 코딩하기 시작해라..


프로젝트 파일 리뷰


src 폴더


root 폴더 

Posted by 삼스
HTML52017. 3. 3. 10:48


Crisis 예제에서 "Contact"버튼으로 메세지를 보낼 수 있는 팝업뷰를 띄우려고 한다.

팝업은 앱내에서 다른 페이지전환이 있어도 유지되기를 원한다. 사용자가 명시적으로 닫기전까지는


지금까지는 단일 아웃렛과 차일드아우트까지 사용하는 법에 대해 알아보았다. 라우터는 이름없는 아웃렛은 템플릿내에 하나만 허용한다.

하지만 템플릿은 이름을 부여(네임드아웃렛)하면 여러개의 아웃렛을 사용할 수 있다. 각 명명된 아웃렛은 자신면의 콤포넌트를 갖는 라우트셋을 가질수 있다. 여러개의 아웃렛이 서로다은 라우트를 가지고 동시에 컨텐츠를 표시할 수 있다.


AppComponent에 "popup"이라고 이름 붙인 아웃렛을 추가한다.


src/app/app.component.ts (outlets) <router-outlet></router-outlet> <router-outlet name="popup"></router-outlet>


팝업이 보여질 위치가 된다.


두번째 라우트


명명된 아웃렛은 두번째라우트의 타겟이다.


두번째 라우트도 첫번째 라우트와 유사하다. 몇가지점에서 다른데.

  • 서로 독립적이다.
  • 다른 라우트와 조화된다.
  • 명명된 아웃렛에 표시된다.

ComposeMessageComponent를 src/app/compose-message.component.ts에 추가한다.


아래와 같은 화면이다.



src/app/compose-message.component.ts
import { Component, HostBinding } from '@angular/core';
import { Router }                 from '@angular/router';
import { slideInDownAnimation }   from './animations';
@Component({
  moduleId: module.id,
  templateUrl: './compose-message.component.html',
  styles: [ ':host { position: relative; bottom: 10%; }' ],
  animations: [ slideInDownAnimation ]
})
export class ComposeMessageComponent {
  @HostBinding('@routeAnimation') routeAnimation = true;
  @HostBinding('style.display')   display = 'block';
  @HostBinding('style.position')  position = 'absolute';
  details: string;
  sending: boolean = false;
  constructor(private router: Router) {}
  send() {
    this.sending = true;
    this.details = 'Sending Message...';
    setTimeout(() => {
      this.sending = false;
      this.closePopup();
    }, 1000);
  }
  cancel() {
    this.closePopup();
  }
  closePopup() {
    // Providing a `null` value to the named outlet
    // clears the contents of the named outlet
    this.router.navigate([{ outlets: { popup: null }}]);
  }
}


src/app/compose-message.component.html <h3>Contact Crisis Center</h3> <div *ngIf="details"> {{ details }} </div> <div> <div> <label>Message: </label> </div> <div> <textarea [(ngModel)]="message" rows="10" cols="35" [disabled]="sending"></textarea> </div> </div> <p *ngIf="!sending"> <button (click)="send()">Send</button> <button (click)="cancel()">Cancel</button> </p>


다른 콤포넌트들과 비슷해 보인다. 다른게 두가지가 있는데. send 메서드가 1초동안 대기했다가 팝업을 닫고 있고 closePopup은 "popup"아웃렛을 null로 호출하고 있다.

다른 콤포넌트들과 마찬가지로 AppModule에 declareations에 ComposeMessageComponent를 추가한다.


두번째 라우트 추가


AppRoutingModule에 라우트를 추가한다.

src/app/app-routing.module.ts (compose route)
{
  path: 'compose',
  component: ComposeMessageComponent,
  outlet: 'popup'
},

outlet속성이 추가되었다. ComposeMessageComponent가 popup outlet에 표시될것이다.

html코드내에서는 아래와 같이 호출한다.

src/app/app.component.ts (contact-link) <a [routerLink]="[{ outlets: { popup: ['compose'] } }]">Contact</a>

 compose 라우트가 popup 라우트에 고정되었도 RouterLink디렉티브에 라우트를 지정하기에는 충분하지 않다. 링크파라메터를 지정하고 속성바인딩으로 RouterLink에 바인딩해야 한다.

링크파라메터에는 outlets속성이 객체로 하나 존재하며 popup 아웃렉 속성과 값으로 다른 링크파라메터가  compose 라우트가 지정되어 있다.

이제 사용자가 링크를 클릭하면 compose 라우트가 popup아웃렛에 표시될것이다.


두번째 라우트 네비게이션 : 라우트머징


Crisis Center에서 Contact를 클릭하면 아래와 같은 주소가 찍히는 것을 확인할 수 있다.


http://.../crisis-center(popup:compose)


관심이 쏠리는것이 바로 ... 부분인데.


  • cirsis-center는 첫번째 네비게이션
  • 괄호부분은 두번째 네이게이션
  • 두번째 네비게이션은 popup 아웃랫과 라우트를 의미

 Heros링크를 누르면 이번에 주소가 


http://.../heros(popup:compose)


첫번째주소부분이 변경되었다. 두번째 라우트는 동일하다.

라우터는 첫번째와 두번째 브랜치를 유지한다. 그리고 URL를 표현해준다.

수많은 아웃렉과 라우트를 탑레벨이나 하위레벨로 만들어낼 수 있다. 라우터는 알어서 url을 만들어낸다.


두번째 라우트 정리


아웃렛의 콤포넌트는 새로운 콤포넌트로 이동하기전까지는 계속 유지된다. 두번째 아웃렛도 마찬가지이다. 

모든 두번째 아웃렛은 자체 네비게이션을 가지며 독립적으로 수행된다. 다른 아웃렛의 변경이 영향을 미치지 않는다는 것이다. 그래서 Crisis나 Heroes로 이동해도 Compose는 계속 보여질 수 있는 것이다.

위 예에서 보면 아래와 같이 popup을 제거하는 것을 알 수 있다.


src/app/compose-message.component.ts (closePopup)
closePopup() {
  // Providing a `null` value to the named outlet
  // clears the contents of the named outlet
  this.router.navigate([{ outlets: { popup: null }}]);
}




Posted by 삼스
HTML52017. 3. 3. 09:37


이번엔 라우트 안의 라우트를 알아볼것이다.


앱을 개발하다보면 화면단위 혹은 기능단위로 피쳐를 구성하게 된다.
보통 피쳐는 폴더에 대응되며 하나의 피쳐에는 아래 그림에서와 같이 여러개의 콤포넌트들을 포함할 수 있다.



위 그림에서 App Root Component는 Feature A, B, C를 가지며 Feature A는 A1, A2, A3을 가진다.

여기서 A1, A2, A3이  바로 차일드 라우트에 해당한다.


다음 코드는 앵귤러가이드문서의 예이다.


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

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

const crisisCenterRoutes: Routes = [
  {
    path: 'crisis-center',
    component: CrisisCenterComponent,
    children: [
      {
        path: '',
        component: CrisisListComponent,
        children: [
          {
            path: ':id',
            component: CrisisDetailComponent
          },
          {
            path: '',
            component: CrisisCenterHomeComponent
          }
        ]
      }
    ]
  }
];

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


Feature에 해당하는게 CrisisCenter이고 그 안에 CrisisCenterComponent가 Feature의 루트콤포넌트이고 CrisisListComponent, CrisisDetailComponent, CrisisCenterHomeComponent가 있다.

RouterModule.forChild메서드를 사용한것을 눈여겨 보아야 한다. forRoot가 아니다.


이렇게 정의한 모듈은 AppModule즉 루트모듈에 추가해준다. 이 때 앞선 포스트에서도 언급한데로 import순서가 url pattern matching 의 순서와 동일하다는것에 유의해야 한다.


상대적인 네비게이션


crisis center feature를 빌드후 detail component를 보려면 /로 시작하는 절대 주소로 접근하게 된다. 

라우터는 라우트설정의 맨 위에서부터 절대주소를 매칭한다.

절대경로를 통해서 Crisis center feature에 접근이 가능하지만 상위라우팅구조에 링크가 고정된다. 만일 상위 경로(/crisis-center)를 변경하게 되면 링크파라메터를 반드시 변경해주어야 한다.

this.router.navigate(['/hero', hero.id]); // 이런코드가 존재한다면 '/hero'부분을 수정해주어야 한다는 의미로 보임.


이는 현재 URL segment에 상대경로를 정의함으로써 디펜던시로부터 자유로울수 있다. feature영역내에서의 네비게이션은 상위 라우트경로에 상관없이 유지될수 있다.


./ 또는 /가 없이 시작하면 현재 레벨을 의미

../ 는 한단게 상위 레벨을 의미


Router.navigate 메서드로 상대경로로 이동하려면 어떤 라우트트리에 있는지에 대한 정보가 필요하기 때문에 ActivatedRoute를 제공해야 한다.

링크파라메터배열을 링크한 후에 relativeTo속성에 ActivatedRoute를 셋해준다. 그러면 라우터는 현재 액티브된 라우트위치에 기반하여 URL을 계산해낸다.


항상 절대주소를 사용하려면 navigateByUrl메서드를 사용하면 된다.


다음은 Crisis detail을 상대주소로 접근하는 예이다.


src/app/crisis-center/crisis-list.component.ts
constructor(
  private service: CrisisService,
  private route: ActivatedRoute,
  private router: Router
) {}

onSelect(crisis: Crisis) {
  this.selectedId = crisis.id;

  // Navigate with relative link
  this.router.navigate([crisis.id], { relativeTo: this.route });
}


Router서비스가 아니고 RouterLink를 사용하게 된다면 동일한 링크파라메터를 사용한다. relativeTo속성이 보이지 않는데 이는 RouterLink가 내장하고 있다.


template: `

  <ul class="items">

    <li *ngfor="let crisis of crises | async">

      <a [routerlink]="[crisis.id]" [class.selected]="isSelected(crisis)">

        <span class="badge">{{ crisis.id }}</span>

        {{ crisis.name }}

      </a>

    </li>

  </ul>`


CrisisDetailComponent에서  뒤로가기를 하여 다시 리스트로 가고자 한다면 상대경로 이동으로 아래와 같이 시도할 수 있다.


src/app/crisis-center/crisis-detail.component.ts (relative navigation)
// Relative navigation back to the crises
this.router.navigate(['../', { id: crisisId, foo: 'foo' }], { relativeTo: this.route });


Posted by 삼스
HTML52017. 3. 2. 22:20


https://angular.io/docs/ts/latest/guide/router.html


라우트에 파라메터 적용하기


이전 예제에서 정의한 라우트정의를 다시 보자


{ path: 'hero/:id', component: HeroDetailComponent }


:id는 path에서 라우트파라메터의 슬롯을 생성한다. 이 경우 라우터는 이 슬롯에 hero의 id를 삽입한다.


heroid 15번의 상세화면으로 이동하고자 한다면 아래와 같이 접근하면 된다.


localhost:3000/hero/15



다음은 hero list를 표시하는 템플릿이다.


template: `

  <h2>HEROES</h2>

  <ul class="items">

    <li *ngFor="let hero of heroes | async"

      (click)="onSelect(hero)">

      <span class="badge">{{ hero.id }}</span> {{ hero.name }}

    </li>

  </ul>


  <button routerLink="/sidekicks">Go to sidekicks</button>

`


그리고 hero를 클릭했을 때 호출된 함수는 아래와 같다.


  onSelect(hero: Hero) {

    this.router.navigate(['/hero', hero.id]);

  }


this.router를 사용하기 위해서는 생성자에서 Router를 inject해야 한다.


constructor(

  private router: Router,

  private service: HeroService

) {}


위와 같이 하면 Hero를 선택하면 HeroDetailComponent에 id값이 파라메터로 전달된다. 코드를 보면 알겠지만 파라메터는  배열로 전달된다.

HeroDetailComponent에서는 id값에 어떻게 접근할 수 있을까?

ActivatedRoute서비스로 가능하다.

ActivatedRoute : 라우팅정보를 위한 원스탑서비스이다.
라우팅서비스에서 얻어지는 ActivatedRoute는 다음과 같은 정보를 제공한다.

url: 라우트된  path의 obserserable로 라우트경로의 배열료 구성된다.
data : 라두트에서 제공되는 데이터 객체를 포함하는 observerable
params : 옵셔널파라메터
queryParams : 모든 라우트에서 유효한 query parameter
fragment : 모들 라우트에서 유효한 URL fragment
outlet : 라우트를 렌더할때 사용되는 RouterOutlet
routeConfig : origin path를 포함하는 라우트에 사용되는 설정정보
parent : 자식 라우트의 경우 부모의 ActivatedRoute
firstChild : 자식 라우트가 있을 경우 첫번째 ACtivatedRoute
children : 현재 라우트 이하 모든 라우트들

다음과 같이 Router, ActivatedRoute, Params를 import 해야 한다.

import { Router, ActivatedRoute, Params } from '@angular/router';

switchMap연산자를 import하면 Observerable 라우트 파라메터들을 처리할 수 있다.

import 'rxjs/add/operator/switchMap';

해당 모듈을 사용하기 위해 inject를 구현한다.

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

이 후 ngOnInit에서 ActivatedRoute서비스를 통해서 파라메터에 접근이 가능하다.

ngOnInit() {
  this.route.params
    // (+) converts string 'id' to a number
    .switchMap((params: Params) => this.service.getHero(+params['id']))
    .subscribe((hero: Hero) => this.hero = hero);
}

파라메터가 Observerable로 제공되기 때문에 switchMap연산자를 id파라메터를 이름으로 접근하여 HeroService에 전달이 가능하다.
subscribe메서드로 id가 변경되면 감지해서 Hero 정보를 다시 셋할 수 있다.
이렇게 하면 id정보의 변경을 감지하여 화면은 유지한체 정보를 업데이트 할 수 있다.
shapshot의 경우는 non-observerable로 무조건 list에서만 detail로 진입한다면 사용이 가능하다.

ngOnInit() {
  // (+) converts string 'id' to a number
  let id = +this.route.snapshot.params['id'];

  this.service.getHero(id)
    .then((hero: Hero) => this.hero = hero);
}





Posted by 삼스
HTML52017. 3. 2. 21:49


다음으로는 세가지에 대해 알아보겠다.

  • App과 route들을 module을 사용해서 feature 영역에 배치하기
  • 콤포넌트에서 다른 콤포넌트로 네이게이트하기
  • 필수 또는 옵셔널한 정보를 라우트파라메터로 전달하기

대부분의 앱을 개발하다보면 업무별로 구분하기 위해서 폴더들을 생성하여 그 아래에 소스들을 배치하게 된다.

src/app/heroes |- hero-detail.component.ts |- hero-list.component.ts |- hero.service.ts |- heroes.module.ts


heros.module.ts는 아래와 같이 정의되어 있다.


import { NgModule }       from '@angular/core';
import { CommonModule }   from '@angular/common';
import { FormsModule }    from '@angular/forms';
import { HeroListComponent }    from './hero-list.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroService } from './hero.service';
@NgModule({
  imports: [
    CommonModule,
    FormsModule,
  ],
  declarations: [
    HeroListComponent,
    HeroDetailComponent
  ],
  providers: [ HeroService ]
})
export class HeroesModule {}


heros feature에는 두개의 콤포넌트가 있다. herolist와 herodetail. 리스트뷰는 조회된 결과를 표시하고 detail은 선택된 hero의 상세정보를 표시한다. list에서 detail로 넘어갈때는  hero id를 넘겨야 한다.


다음은 라우트 설정이다.


src/app/heroes/heroes-routing.module.ts

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroListComponent }    from './hero-list.component';
import { HeroDetailComponent }  from './hero-detail.component';
const heroesRoutes: Routes = [
  { path: 'heroes',  component: HeroListComponent },
  { path: 'hero/:id', component: HeroDetailComponent }
];
@NgModule({
  imports: [
    RouterModule.forChild(heroesRoutes)
  ],
  exports: [
    RouterModule
  ]
})
export class HeroRoutingModule { }


두개의 콤포넌트에 대한 라우팅설정을 완료하였다. 하나는 path에 파라메터를 전달할 수 있도록 정의하였다. 그리고 HeroRoutingModule을 export하였다.

그리고 RouterModule에 등록하고 있는데  AppRoutingModule과 다른점은 forRoot가 아니라 forChild를 사용한것이다. feature 모듈에서는 forChild를 사용해야 한다. forRoot는 앱레벨이다.

이제 HerosModule에 추가해야 한다.


import { NgModule }       from '@angular/core';
import { CommonModule }   from '@angular/common';
import { FormsModule }    from '@angular/forms';
import { HeroListComponent }    from './hero-list.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroService } from './hero.service';
import { HeroRoutingModule } from './heroes-routing.module';
@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    HeroRoutingModule
  ],
  declarations: [
    HeroListComponent,
    HeroDetailComponent
  ],
  providers: [ HeroService ]
})
export class HeroesModule {}

앱 메인라우터 설정과 중복이 있더라도 feature 라우터 우선으로 동작하는데는 문제가 없으나 중복은 좋지 않기 때문에 메인 라우터에 중복이 있다면 제거한다.


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

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

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

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


HorosModule로 별도로 분리된 모듈의 경우 AppModule에 추가해주어야 한다.

app.module.ts에 imports에 추가해준다.

app.module.ts에 import되는 순서에 따라 라우팅시 패턴매칭의 순서가 정해진다. 원하는대로 동작시키길 원한다면 순서에 유의해야 한다.



Posted by 삼스
HTML52017. 3. 2. 20:41


Angular routing & navigation


한뷰에서 다른 뷰로 넘어갈수 있도록 해준다.


개요


브라우저의 네비게이션모델은 다음과 같다.

- 주소바에 주소를 입력하면 해당 페이지로 이동한다.

- 페이지내의 링크를 누르면 해당 페이지로 이동한다.

- 뒤로가기/앞으로가기 버튼을 누르면 히스토리상의 앞/뒤 페이지로 이동한다.


앵귤러라우터는 이 모델을 빌어서 구현되었다. URL주소를 분석하여 클라이언트뷰 네비게이션 명령을 수행한다. 추가 파라메터를 전달하여 해당 페이지의 콘텐츠를 멀 표시할지 결정하는데 도움을 줄수 있다. 

라우터를 페이지에 링크할 수 있고 사용자가 클릭하면 해당 뷰로 네비게이트 할것이다. 


<base href>

대개의 라우팅애플리케이션은 index.html에 <head>내에 첫번째로 <base>엘리먼트를 추가한다. 이 값은 라우터가 네비게이션 url을 어떻게 조합할지 결정한다.


<baser href="/">


라우터는 옵셔널 서비스로 앵귤러 기본 콤포넌트는 아니다 따라서 아래와 같이 추가해주어야 한다.


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


설정하기


라우팅 앵귤러 앱은 하나의 싱글톤 Router 서비스를 갖는다. 브라우저 URL이 변경되면 해당 표시할 콤포넌트를 결정하기 위해 해당 Route를 찾는다.

Router는 설정해주기전에는 Route를 갖지 않는다. 아래 예는 4개의 route를 설정, RouterModule.forRoot메소드롤 설정하고 AppModule의 imports 배열에 추가한다.


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)

    // other imports here

  ],

  ...

})

export class AppModule { }


appRoutes는 라우팅방법에 대해 기술한다. 그리고 imports에 RouterModule.forRoot네 파라메터로 넘기게 된다.


각 Route는 하나의 콤포넌트에 URL path하나를 맵핑한다. path는 '/'로 시작하지 않는다. 뷰간의 네비게이션시 상대적/절대적 경로를 만드는데 사용된다.


:id는 route파라메터의 토큰으로 /hero/42같은 URL에서 42가 id 파라메터에 해당하게 된다. HeroDetailComponent는  hero정보를 표시하기 위해 id값을 취해서 정보를 표시한다.


data 속성은 해당 route와 연관된 데이터를 위치시키는데 사용된다. 활성화된 각 route에서 접근이 가능하다. 페이지타이틀이나 로그텍스트(방문페이지표시), 또는 읽기전용의 스테틱데이터를 저장하는데 사용하는게 권장된다.


빈 path의 경우 URL이 빈경우에 해당하며 일반적으로 시작화면이 된다. 위 예의 경우 디폴트 라우드는 /heros URL로 redirect되고 HeroListComponent가 표시되게 된다.


** path는 와일드카드로 라우터는 요청된 URL이 매칭되는 경로가 없는 경우 표시된다. 404-Not Found에 대해 특정 페이지로 이동시켜서 변경이 가능하다.


route설정의 순서는 라우터가 매칭되면 이 후의 것들은 무시가 되기 때문에 전략적으로 설정해야 한다.



Router outlet

Router설정에 따라서 표시할 콤포넌트가 결정되면 해당 내용은 RouterOutlet에 표시된다.


<router-outlet></router-outlet>



Router links

라우팅된 페이지가 표시된 후 다른 페이지로 이동은 어떻게 할까? 브라우저 주소바에 URL을 입력하여 이동이 가능하나 대부분 페이지내에서 클릭을 통해서 이동하게 된다.


template: `

  <h1>Angular Router</h1>

  <nav>

    <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>

    <a routerLink="/heroes" routerLinkActive="active">Heroes</a>

  </nav>

  <router-outlet></router-outlet>

`


anchore 태그의 RouterLink 디렉티브는 라우팅을 제어할 수 있게 해준다. URL은 미리 결정되기 때문에 하드코딩으로 routerLink를 지정할 수 있다.


RouterLinkActive 디렉티브는 현재 선택된 라우터를 구별할 수 있게 해준다. active CSS 클랙스가 액티브 되었을 때 사용되게 된다.



Router state

네이게이션 라이프사이클이 완료된 후에 라우터는 현재 상태를 반영하는 ActivatedRoute객체의 트리를 만들어낸다.  Router서비스와 routerState속성을 사용하는 곳 어느곳에서나 RouterState에 접근이 가능하다.


요약

앱은 설정된 라우터를 하나 갖게된다. 라우터가 생성하는 뷰는 RouterOutlet에 표시되게 되고 RouterLink로 특정 라우트로 이동할 수 있다.


Router Part 

 Meaning

 Router

유효한 URL의 콤포넌트를 표시하고 한 콤포넌트에서 다른 콤포넌트로의 네비게이션을 관리한다. 

 RouterModule

앵귤러에서 분리된 모듈로 필요한 서비스프로바이더와 디렉티브들을 제공한다. 

 Routes

Route들의 배열로 URL경로와 콤포넌트간의 맵핑테이블이다. 

 Route

라우터가 URL패턴에 따라 콤포넌트를 어떻게 다룰지에 대해 정의한다. 대부분의 라우트는 경로와 콤포넌트로 구성된다. 

 RouterOutlet

라우터가 뷰를 표시할 directive(<router-outlet>)이다. 

 RouterLink

디렉티브로 클릭가능한 라우트 엘리먼트(routerLink)이다.  

 RouterLinkActive

css class로 routerLink가 active/inactive될때 적용된다. 

 ActivatedRoute

서비스로 각 라우트콤포넌트에 라우트파라메터, 스테틱데이터, resolve data, global query params그리고 global fragment정보를 제공한다.

 RouterState

route tree로부터 router의 상태정보를 제공한다. 

 Link parameter 배열

라우팅 명령으로 처리되는 배열로 RouterLink에 바인드하거나 Router.navigate메서드로 배열을 전달할 수 있다. 

 Routing component

RouterOutlet과 함께 앵귤러 콤포넌트로 라우터네비게이션에 기반하여 뷰를 표시한다. 



위에서 설명한것으로 라우팅을 적용한 앱을 만들어 볼수 있다.

자세한 설명이 필요하다면 링크 참조(https://angular.io/docs/ts/latest/guide/router.html)



Posted by 삼스
HTML52017. 2. 16. 14:47


https://medium.com/@daviddentoom/angular-2-lazy-loading-with-webpack-d25fe71c29c1#.ik88lsre3



Home과 About페이지를 갖는 앱이 있다고 하자 대부분의 유저는 About페이지를 거의 열어보지 않을것이다. 자 이게 lazy loading이 필요한 이유이다. About페이지는 미리 로딩할 필요가 없는 것이다.


프로젝트 설정


/app

 app.component.ts

 app.component.html

 app.module.ts

 app.routing.ts

 /home

    home.component.ts

    home.component.html

    home.module.ts

    home.routing.ts  

  /+about --> Lazy loaded module

    about.component.ts

    about.component.html

    about.module.ts

    about.routing.ts



1. 로더 설치


angular2-router-loader를 사용해야 한다. 아래 명령으로 설치한다.


npm install angular2-router-loader--save-dev


이번엔 webpack 설정파일에 로더를 추가한다.


webpack.config.js


loaders: [

  {

    test: /\.ts$/,

    loaders: [

      ‘awesome-typescript-loader’, 

      ‘angular2-template-loader’, 

      ‘angular2-router-loader’]

   },

   ...

]


2. 상위 콤포넌트에 라우팅설정을 추가한다.


보통 라우팅 설정을 자기자신의 모듈내에서 정의한다. lazy loading되는 모듈은 상위콤포넌트 즉 호출하는 모듈에서 설정해주어야 한다.


app.routing.ts

const routes: Routes = [
 { path: '', redirectTo: '/home', pathMatch: 'full',
 { path: 'about', loadChildren: './+about/about.module#AboutModule' }
];


"about" 라우트하나를 설정하고 angular에게 "about"라우트로 페이지를 로드할 수 있도록 알려준다. #AboutModule은 모듈명으로 참조되고 +를 앞에 붙이는 것은 lazy load될거라는 규약이다.


3. Lazy Module 작성


lazy module은 none lazy module과 거의 동일하게 동작한다. 한가지 큰 차이점이 있는데 상위모듈에서 about(/about)경로를 선언했기 때문에 경로를 다시 정의할 필요가 없다. 우리가 해야 하는것은 로드되어야 할 디폴트 콤포넌트를 설정해주는 것이다.


./+about/about.routing.ts

const routes: Routes = [

 { path: '', component: AboutComponent },

];


됐다.

앱을 실행하고 about페이지로 이동하면 새로운 chunk가 로드되는것을 볼수 있을 것이다.



The source code can be found here

Posted by 삼스
HTML52017. 2. 15. 16:06


Ionic Project를 위한 스크립트


설치는 이렇게


npm install @ionic/app-scripts@latest --save-dev


디폴트 설정


아래와 같은 기본설정으로 빠르게 앱을 빌드하게 해준다.

  • Multi-core processing tasks in parallel for faster builds
  • In-memory file transpiling and bundling
  • Transpiling source code to ES5 JavaScript
  • Ahead of Time (AoT) template compiling
  • Just in Time (JiT) template compiling
  • Template inlining for JiT builds
  • Bundling modules for faster runtime execution
  • Treeshaking unused components and dead-code removal
  • Generating CSS from bundled component Sass files
  • Autoprefixing vendor CSS prefixes
  • Minifying JavaScript files
  • Compressing CSS files
  • Copying src static assets to www
  • Linting source files
  • Watching source files for live-reloading
기본설정을 통해 개발에 집중할 수 있게 해준다.

Ionic framework의 소스는 모듈로 구성되어 있고 다양한 번들러와 빌드프로세스로 패키징될 수 있다. 이 프로젝트의 목적은 ionic app을 더 쉽게 개발하게 하기 위한것이다. 

npm Scripts

외부 테스트러너에 의존하기보다 Ionic App Script는 npm script로 실행되는데 적합하다. package.json파일에 아래와 같이 기본설정이 되어 있다.

"scripts": {
    "ionic:build": "ionic-app-scripts build",
    "ionic:serve": "ionic-app-scripts serve"
  },

package.json에 기술된 build script를 실행되도록 하려면 다음과 같이 수행한다.

npm run build

Custom Configuration

개발자에게 대게는 기본설정으로 충분할것이다. 하지만 Ionic App Script는 다양한 테스크들을 설정하거나 오버라이드하는 다양한 방법을 제공한다.  Custom하지 않으면 항상 디폴트가 적용된다.

package.json Config

package.json파일을 Ionic project가 사용하는데 config 프로퍼티가 커스텀 설정을 위해 사용된다. 아래는 그 예이다.

  "config": {
    "ionic_bundler": "rollup",
    "ionic_source_map_type": "source-map",
    "ionic_cleancss": "./config/cleancss.config.js"
  },

Command-line Flags

기억해야 해! 프로젝트의 package.json파일의 scripts프로퍼티에서 ionic-app-scripts를 어떻게 실행했는지. 자 우리는 이제 각 스크립트에 command-line flag들을 추가할 수 있다. 또는 새로운 스크립트를 커스텀플래그와 함께 정의할 수 도 있지.

"scripts": {
    "build": "ionic-app-scripts build --rollup ./config/rollup.config.js",
    "minify": "ionic-app-scripts minify --cleancss ./config/cleancss.config.js",
  },

동일한 커맨드라인 플래그는 npm run에도 동일하게 적용될 수 있지

npm run build --rollup ./config/rollup.config.js

Overriding Config Files

config filepackage.json configcmd-line flag
Babiliionic_exp_babili--babili
CleanCssionic_cleancss--cleancss or -e
Copyionic_copy--copy or -y
Closureionic_closure--closure or -l
Generatorionic_generator--generator or -g
NGCionic_ngc--ngc or -n
Rollupionic_rollup--rollup or -r
Sassionic_sass--sass or -s
TSLintionic_tslint--tslint or -i
UglifyJSionic_uglifyjs--uglifyjs or -u
Watchionic_watch--watch
Webpackionic_webpack--webpack or -w

Overriding Config Values


config valuespackage.json configcmd-line flagdefaultsdetails
root directoryionic_root_dir--rootDirprocess.cwd()The directory path of the Ionic app
src directoryionic_src_dir--srcDirsrcThe directory holding the Ionic src code
www directoryionic_www_dir--wwwDirwwwThe deployable directory containing everything needed to run the app
build directoryionic_build_dir--buildDirbuildThe build process uses this directory to store generated files, etc
temp directoryionic_tmp_dir--tmpDir.tmpTemporary directory for writing files for debugging and various build tasks
node_modules directoryionic_node_modules_dir--nodeModulesDirnode_modulesNode modules directory
ionic-angular directoryionic_angular_dir--ionicAngularDirionic-angularionic-angular directory
ionic-angular entry pointionic_angular_entry_point--ionicAngularEntryPointindex.jsentry point file for ionic-angular
bundlerionic_bundler--bundlerwebpackChooses which bundler to use: webpack or rollup
source map typeionic_source_map_type--sourceMapTypesource-mapChooses the webpack devtool option. eval and source-map are supported
generate source mapionic_generate_source_map--generateSourceMaptrueDetermines whether to generate a source map or not
tsconfig pathionic_ts_config--tsconfig{{rootDir}}/tsconfig.jsonabsolute path to tsconfig.json
app entry pointionic_app_entry_point--appEntryPoint{{srcDir}}/app/main.tsabsolute path to app's entrypoint bootstrap file
app ng module pathionic_app_ng_module_path--appNgModulePath{{srcDir}}/app/app.module.tsabsolute path to app's primary NgModule
app ng module classionic_app_ng_module_class--appNgModuleClassAppModuleExported class name for app's primary NgModule
clean before copyionic_clean_before_copy--cleanBeforeCopyfalseclean out existing files before copy task runs
output js fileionic_output_js_file_name--outputJsFileNamemain.jsname of js file generated in buildDir
output js map fileionic_output_js_map_file_name--outputJsMapFileNamemain.js.mapname of js source map file generated in buildDir
output css fileionic_output_css_file_name--outputCssFileNamemain.cssname of css file generated in buildDir
output css map fileionic_output_css_map_file_name--outputCssMapFileNamemain.css.mapname of css source map file generated in buildDir
bail on lint errorionic_bail_on_lint_error--bailOnLintErrornullSet to true to make stand-alone lint commands fail with non-zero status code
write AoT files to diskionic_aot_write_to_disk--aotWriteToDisknullSet to true to write files to disk for debugging
print dependency treeionic_print_original_dependency_tree--printOriginalDependencyTreenullSet to true to print out the original dependency tree calculated during the optimize step
print modified dependency treeionic_print_modified_dependency_tree--printModifiedDependencyTreenullSet to true to print out the modified dependency tree after purging unused modules
print webpack dependency treeionic_print_webpack_dependency_tree--printWebpackDependencyTreenullSet to true to print out a dependency tree after running Webpack
experimental parse deeplink configionic_experimental_parse_deeplinks--experimentalParseDeepLinksnullSet to true to parse the Ionic 3.x deep links API for lazy loading (Experimental)
experimental manual tree shakingionic_experimental_manual_treeshaking--experimentalManualTreeshakingnullSet to true to purge unused Ionic components/code (Experimental)
experimental purge decoratorsionic_experimental_purge_decorators--experimentalPurgeDecoratorsnullSet to true to purge unneeded decorators to improve tree shakeability of code (Experimental)
experimental closure compilerionic_use_experimental_closure--useExperimentalClosurenullSet to true to use closure compiler to minify the final bundle
experimental babiliionic_use_experimental_babili--useExperimentalBabilinullSet to true to use babili to minify es2015 code
convert bundle to ES5ionic_build_to_es5--buildToEs5trueConvert bundle to ES5 for for production deployments


Posted by 삼스
HTML52016. 10. 6. 14:12


http://www.joshmorony.com/a-simple-guide-to-navigation-in-ionic-2/



Ionic1이나 Angular1 백그라운드가 다면 URL, state등으로 라우팅을 Ionic2에서도 사용은 가능하다 하지만 많은 주의가 필요하므로 권장하지 않는다.

Ionic2는 View를 push/pop하는 네비게이션 스택으로 운영된다.


이를 구현하는 방법을 배우기 전에 어떻게 동작하는지에 대해 먼저 이해해보자.


Push/Pop


root page가 고양이 그림이 그려진 종이가 테이블에 올려져 있다고 상상해보자.

이제 개그림이 그려진 종이를 올려보자. 그러면 개그림이 제일 위에 있게 된다. 그리고 고양이는 여전히 개그림 아래에 놓여져 있게 된다.

이번에 소그림이 그려진 종이를 올려보자. 소그림이 제일 위에 있고 고양이과 개그림도 여전히 놓여져 있다.

이제 개그림을 보려면 쌓여진 종이의 맨위 그림을 버리면 된다. 이어서 고양이그림을 보려면 한번더 맨위의 그림을 버리면 된다.

히스토리를 관리하는 아주 편리한 네비게이션방안이고 차일드뷰가 많은 경우 이해하기 쉽다. 하지만 항상 그런것은 아니다. 어떤 경우는 바로 이전페이지가 아니라 다른 페이지로 이동하고자 할 경우도 발생하기 마련이다(로그인 페이지에서 메인페이지로 이동하는경우).

이 경우 우린느 root page를 변경할 수 있다. 

위 예에서 소그림을 root page로 할 수 있다.


언제 push하고 언제 root page로 해야 하나?


현재뷰의 차일드로 전환하고자 하는 경우 또는 새로운 뷰에서 이전뷰로 돌아오기를 원한다면 push를 해야한다.

현재뷰의 차일드가 아니거나 애플리케이션의 다른 섹션으로의 이동이라면 root page를 변경해야 한다.

root page와 root component는 다르다. root component(보통 app.component.ts)가 root page를 정의한다. 


Ionic2 네비게이션 기초


NavController를 import해야 한다.


import { NavController } from 'ionic-angular';


그리고 생성자에서 inject해야 한다.


@Component ({

  templateUrl: 'home.html',

})

export clas MyPage {

  constructor(public navCtrl: NavController) {


  }

}


NavController의 레퍼런스가 생기고 클래스내 어디에서든지 사용이 가능하다. 이제 어떻게 push/pop을 하는지 보자


페이지를 push하려면 


this.navCtrl.push(SecondPage);


앞에서 inject한 navCtrl로 push하였다. 이렇게 하려면 SecondPage가 import되어야 한다.


import { SecondPage } from '../second-page/second-page';


이제 코드내에서 push를 호출하면 새로운 페이지가 표시될것이고 nav bar에 자동으로 백버튼이 생성이 될것이다.

pop을 언제 할것인지 고민하지 말라. 백버튼을 누르면 알어서 pop해줄것이다.


수동으로 이전 페이지로 이동하고자 한다면 명시적으로 pop을 호출한다.


this.navCtrl.pop();


이번엔 root page를 변경한는 방법이다. app.ts에 rootPage를 정의하였을 것이다.


rootPage: any = MyPage;


root component의 rootPage변수에 root page를 셋팅할것이다. root page를 변경하려면 NavController의 setRoot함수를 호출하면 그만이다.


this.navCtrl.setRoot(SecondPage);



Ionic2에서 페이지간 데이터 전송


모바일애플리케이션에서는 보통 페이지간의 데이터를 전달할 방법이 필요하다. Ionic2에서는 NavParams로 가능하다. push로 페이지 호출할때 파라메터를 생성하여 전달하는 방법이 있다. 이 방법은 setRoot시에도 동일하게 적용된다.


this.navCtrl.push(SecondPage, { thing1: data1, thing2: data2});


추가 파라메터가 더해진것 말고 이전과 사용법이 동일하다. 호출되는 페이지에서는 NavParams를 생성자에서 inject해야 한다.


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

import { NavController, NavParams } from 'ionic-angular';


@Component ({ templateUrl: 'second-page.html'})

export class SecondPage {

  constructor(public navCtrl: NavController, public navParams: NavParams) {


  }

}


이제 파라메터에 접근하려면 아래와 같이 하면 된다.


this.navParams.get('things');


Posted by 삼스
HTML52014. 6. 20. 15:36


http://www.elabs.se/blog/66-using-websockets-in-native-ios-and-android-apps

이 샘플은 WebSocket으로 실시간으로 browser, iOS, android 클라이언트와 통신하는 방법에 대한 것이다.

서버는 웹소켓서버를 운영하고 여러가지 플랫폼과 통신을 수행이 가능해진다. 전체 소스는  https://github.com/elabs/mobile-websocket-example 을 참고하라.


서버

아주 간단한 웹소켓서버의 예는 아래와 같다.EM-WebSocket gem을 사용하고 Ruby로 구현되었다.


equire "em-websocket" EventMachine.run do @channel = EM::Channel.new EventMachine::WebSocket.start(host: "0.0.0.0", port: 8080, debug: true) do |ws| ws.onopen do sid = @channel.subscribe { |msg| ws.send(msg) } @channel.push("#{sid} connected") ws.onmessage { |msg| @channel.push("<#{sid}> #{msg}") } ws.onclose { @channel.unsubscribe(sid) } end end 

end 

웹소켓연결을 8080포트에서 수락한고 새로운 클라이언트가 연결되면 내부 채널을 구독한다. 클라이언트가 데이터를 이 채널에 전송하여 채널에 데이터가 유효해질때마다 모든 클라이언트에 데이터를 전송한다. 클라이언트는 구독ID를 통해 구별이 가능하다. 클라이언트가 연결이 끊어지면 채널의 구독을 중단한다.

위코드를 실행하려면 bundle install , ruby server.rb을 순서대로 실행한다.

Browser client


$(document).ready(function() { ws = new WebSocket("ws://" + location.hostname + ":8080/"); ws.onmessage = function(event) { $("#messages").append("<p>" + event.data + "</p>"); }; ws.onclose = function() { console.log("Socket closed"); }; ws.onopen = function() { console.log("Connected"); ws.send("Hello from " + navigator.userAgent); }; $("#new-message").bind("submit", function(event) { event.preventDefault(); ws.send($("#message-text").val()); $("#message-text").val(""); }); 

}); 

서버에 8080포트로 연결을 하고 연결이 되었을 때, 메세지가 도달하였을 때, 그리고 연결이 닫혔을떄의 콜백을 구현하였다.

새로운 메세지를 작성하고 메세지를 전송하기 위한 submit form도 작성하였다.

이 코드는 모던브라우져만 지원한다. 오래된 브라우져에서는 Flash로 가능하다.


iOS client

SocketRocket이라는 라이브러리로 웹소켓을 사용할 수 있다. 이것은 Cocoapods로 설치가 가능하다. 먼저 Cocoapods를 설치해야 한다.

Podfile을 아래와 같이 iOS프로젝트가 있는 폴더에서 만든다.

platform :ios, "7.0"

pos "SocketRocket" 

Cocoapods가 설치되어 잇지 않다면 gem install cocoapods 을 실행하고 pod setup을 실행 이어서 pod install을 Podfile이 들어 있는 iOS프로젝트 폴더에서 실행한다.

관련 코드는 ViewController.m에 웹소켓서버에 연결하는 코드를 작성한다.


-(void) connectWebSocket {

  webSocket.delegate = nil;

  webSocket = nil;

  NSString *urlString = @"ws://localhost:8080";

  SRWebSocket *newWebSocket  = [[SRWebSocket alloc] initWithURL initWithURL:[NSURL URLWithString:urlString]];

  newWebSocket.delegate = self;

  [newWebSocket open];
}

localhost부분을 서버주소로 바꾸면 된다. 

다음은 SRWebSocketDelegete protocol의 구현이다.

- (void)webSocketDidOpen:(SRWebSocket *)newWebSocket {

  webSocket = newWebSocket;

  [webSocket send:[NSString stringWithFormat:@"Hello from %@", [UIDevice currentDevice].name]];

}

- (void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error {

  [self connectWebSocket];

}

- (void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean {

  [self connectWebSocket];

}

- (void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message {

  self.messagesTextView.text = [NSString stringWithFormat:@"%@\n%@", self.messagesTextView.text, message];

} 

브라우져에서 사용하는 자바스크립트 API와 완전 비슷하다.

메세지를 보낼때는 다음과 같이


-(IBAction)sendMessage:(id)sender {

  [webSocket send:self.messageTextField.text];

  self.messageTextField.text =nil;

} 


Android client

Android Studio를 사용한다면 Java WebSockets라이브러리를 gradle로 설치한다

dependencies {

  compile "org.java-websocket:Java-WebSocket:1.3.0"

} 

ADT를 사용한다면 maven으로 java WebSocket을 설치하던가 아니면 별도로 배포되는 jar을 받아서 사용하던가 하면 된다


private void connectWebSocket() {

  URI uri;

  try {

    uri = new URI("ws://websockethost:8080");

  } catch (URISyntaxException e) {

    e.printStackTrace();

    return;

  }


  mWebSocketClient = new WebSocketClient(uri) {

    @Override

    public void onOpen(ServerHandshake serverHandshake) {

      Log.i("Websocket", "Opened");

      mWebSocketClient.send("Hello from " + Build.MANUFACTURER + " " + Build.MODEL);

    }


    @Override

    public void onMessage(String s) {

      final String message = s;

      runOnUiThread(new Runnable() {

        @Override

        public void run() {

          TextView textView = (TextView)findViewById(R.id.messages);

          textView.setText(textView.getText() + "\n" + message);

        }

      });

    }


    @Override

    public void onClose(int i, String s, boolean b) {

      Log.i("Websocket", "Closed " + s);

    }


    @Override

    public void onError(Exception e) {

      Log.i("Websocket", "Error " + e.getMessage());

    }

  };

  mWebSocketClient.connect();

} 

websockethost는 수정하라.

메세지 보 낼때는 이렇게

public void sendMessage(View view) {

  EditText editText = (EditText)findViewById(R.id.message);

  mWebSocketClient.send(editText.getText().toString());

  editText.setText("");

}

android.permission.INTERNET권한이 있어야 하고 앱타겟을 Android 4.0+이다.













Posted by 삼스