잊지 않겠습니다.

'분류 전체보기'에 해당되는 글 430건

  1. 2018.11.29 Gradle 5.0에서의 querydsl Q-class 생성 1
  2. 2017.07.13 PKI 시스템 1
  3. 2016.12.26 nodejs, pm2, git을 이용한 Dockfile 구성
  4. 2016.10.20 gradle 정리 - queryDSL code generate (v 4.x) 5
  5. 2016.09.28 Dependency Injection
  6. 2016.09.23 Data Binding
  7. 2016.09.21 angular cli
  8. 2016.09.13 angular2 animation
  9. 2016.09.13 angular2 form
  10. 2016.04.19 angular2 + webpack 개발

gradle이 major update를 할 때마다 querydsl Q-class들의 생성방법이 계속해서 바뀌고 있습니다. 이제는 좀 더 자연스러운 방법으로 처리가 되네요. 더 이상 추가 task를 만들 필요없이 지원이 가능합니다.

변경된 build.gradle입니다. lombok querydsl을 모두 적용한 상태입니다.

dependencies {
    annotationProcessor(
            "com.querydsl:querydsl-apt:${rootProject.ext.querydslVersion}:jpa",
            "org.hibernate.javax.persistence:hibernate-jpa-2.1-api:1.0.2.Final",
            "javax.annotation:javax.annotation-api:1.3.2",
            "org.projectlombok:lombok"
    )
}

sourceSets {
    main.java.srcDirs += [ generated ]
}

tasks.withType(JavaCompile) {
    options.annotationProcessorGeneratedSourcesDirectory = file(generated)
}

clean.doLast {
    file(generated).deleteDir()
}

간단한 설명입니다. annotationProcessor는 compile시의 annotationProcessor를 gradle에서 자동으로 추가해줍니다. 이때 사용되는 각각의 jar를 추가해주면 됩니다. 기존의 generateQueryDsl을 전혀 할 필요가 없습니다. 다음 JavaCompile환경에 생성될 Q-class들의 위치를 지정해주고 그 위치를 sourceSets에 추가합니다. 마지막으로 clean으로 생성된 Q-class들을 제거해주는 코드를 넣어줍니다.

gradle에서 annotationProcessor를 지원함으로서 더욱 편하게 QueryDsl을 지원할 수 있게 되었습니다.

Posted by Y2K
,

PKI 시스템

Java 2017. 7. 13. 10:12

PKI

PKI에 대해서 정리할 필요성이 있어서 정리하게 되었습니다.

이론

  • RFC 2459에 정의 (Internet X.509 Public Key Infrastructure)
  • 암호화와 복호화를 위해 서로 다른 키를 사용하는 방식(비대칭 암호화방식)에서 사용되는 key 기반
  • 암호화를 위해 사용되는 Key: Public Key
  • 복호화를 위해 사용되는 Key: Private Key

Message Digest

  • 메세지 축약(message digest) - MD5(128bit), SHA1(160bit), SHA256
  • 아무리 긴 내용이라도 축약시키는 것이 가능

Symmetric Key Algorithm

  • OneKey - Symmetric Key를 이용해서 Encrypt와 Decrypt를 진행
  • 속도가 빠르다.
  • 3DES, AES

Asymmetric Key Algorithm

  • Two Key (PrivateKey, PublicKey)

  • 리만가설에 근거

  • RSA

  • 공개키 암호화/복호화

PlainText --(Public Key)--> EncryptedText
EncryptedText --(Private Key)--> PlainText
  • 서명/확인 (Sign/Verify)
PlainText --(Private Key)--> SignedValue
SignedValue --(Public Key)--> PlainText

PKI

  • Asymmetric Key Algorithm
  • Private Key, Public Key를 이용
  • Private Key는 충분히 보호받고, 외부에 노출되지 않는 상황
  • Public Key는 항시 노출이 가능한 상황
  • PrivateKey를 가진 상대방만 볼 수 있는 내용을 전달하고 싶은 경우에는 암호화/복호화(Encrypt/Decrypt)
  • PrivateKey를 가진 사람이 보낸 내용임을 확인하기 위한 방법이 서명/확인(Sign/Verify)

OpenSSL

OpenSource로 PKI를 지원

Private Key/Public Key 생성

#private key 생성
openssl genrsa -out server.private.pem 2048 

#(option) private key를 사용할 때 필요한 암호 제거
openssl rsa -in server.private.pem -out server.private.pem 

#private key와 쌍이 되는 public key 생성
openssl rsa -in server.private.pem -pubout -out server.public.pem 

Key 파일 형식

  • pem(Privacy Enhanced Mail): byte data를 Base64 HEX String으로 변경시켜서 가독성이 있도록 만든 Format
  • der(Distinguished Encoding Rules): byte data. Browser에서 사용되는 기본 형식
#DER -> PEM (private key)
openssl rsa -inform DER -outform PEM -in server.private.der -out server.private.pem 

#PEM -> DER (private key)
openssl rsa -inform PEM -outform DER -in server.private.pem -out server.private.der 
#DER -> PEM (public key)
openssl rsa -inform DER -outform PEM -pubin -in server.public.der -out server.public.pem 

#PEM -> DER (public key)
openssl rsa -inform PEM -outform DER -pubin -in server.public.pem -out server.public.der 

서버인증서

HTTPS를 지원하기 위해서는 서버에 대한 인증서를 요청해야지 됩니다. 인증된 기간이 요청서를 인증을 하게 되면 그것이 인증서가 됩니다.

1.요청서 작성

#요청서 작성
openssl req -new -days 500 -key server.private.pem -out server.csr 

2.요청서를 CA가 서명

#요청서를 CA가 서명
openssl x509 -req -days 500 -in server.csr -signkey ca.private.pem -out server.crt 

3.(option) PFX 파일

comodo등에서는 pfx파일이 주로 사용됩니다. pfx는 crt와 server.private.pemserver.public.pemca.public.pem이 결합되어 있는 형태입니다.

PFX = 요청서 + 서버PrivateKey + 서버PublicKey + CaPublicKey
#CA 개인키와 요청서와 서버 요청서 결합 (password 연결)
openssl pkcs12 -export -in server.crt -inkey ca.private.pem -out server.pfx 

#PFX 파일에서 server의 private key 추출
openssl pkcs12 -in server.pfx -nocerts -nodes -out server.private.pem 

#PFX 파일에서 server 요청서 추출
openssl pkcs12 -in server.pfx -clcerts -nokeys -out server.crt 

4.(option) JKS(java key store)

JKS 파일은 pfx를 통해서 얻어낼 수 있으며, server.private.pem과 server.public.pem을 모두 얻어낼 수 있습니다. java의 keytool을 이용합니다.

keytool -importkeystore -srckeystore server.pfx -srcstoretype pkcs12 -destkeystore server.jks -deststoretype jks


Posted by Y2K
,

nodejs + pm2 application을 구성한 dockfile을 구성하는 방법에 대해서 알아보도록 하겠습니다.

먼저 code를 어떤 방법으로 배포할지에 대한 process를 정하는 것이 중요합니다. 이는 Dockerfile을 어떻게 만들지에 대한 방향이 결정나게 됩니다.

배포 Process

제가 구성한 Docker를 이용한 배포 process는 다음과 같습니다.

  1. nvm 설치
  2. nvm을 이용한 node 설치
  3. pm2 설치
  4. ssh 인증 + git clone 을 통한 code download
  5. npm install
  6. .pm2/logs 위치에 따른 host 파일 공유
  7. docker run의 parameter pass를 통한 pm2 start json 파일 전송

1. nvm 설치 & node 설치 & pm2 설치

ubuntu image를 기본으로 하여 구성을 하도록 하겠습니다. nodejs application은 npm install을 통해서 node_modules을 다운로드 받아서 compile되는 환경이기 때문에 build환경역시 갖추는 것이 중요합니다.

FROM ubuntu # 빌드 환경 구성 RUN apt-get update RUN apt-get install -y wget build-essential keychain git python # NVM_DIR, NODE_VERSION에 따른 ENV 설정 ENV NVM_DIR /usr/local/nvm ENV NODE_VERSION 7.3.0 # NVM 다운로드 & 설치 RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] # nvm 구성 & node install RUN source $NVM_DIR/nvm.sh; \ nvm install $NODE_VERSION; \ nvm use --delete-prefix $NODE_VERSION; ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH # PM2 설치 RUN npm install -g pm2

2. ssh 인증 구성

ssh 인증을 위해서 private key를 image안에 COPY할 필요가 있습니다. 그리고 $(ssh-agent)값을 지정해줄 필요가 있습니다. 그런데, eval을 이용한 값 설정의 경우, Dockerfile에서 할 수 없습니다. 따라서 do-ssh.sh 실행 파일을 구성해서 처리할 image 내부에서 실행할 필요가 있습니다.

다음은 do-ssh.sh파일의 내용입니다.

eval "$(ssh-agent)" && ssh-agent -s ssh-add /root/.ssh/id_rsa
# .ssh Folder작성 & private key Copy RUN mkdir /root/.ssh COPY [ "id_rsa", "/root/.ssh/id_rsa" ] # SSH Key 권한 설정 - 권한이 600이 아닌경우, id_rsa key를 얻어내지 못합니다. RUN chmod 600 /root/.ssh/id_rsa # do-ssh.sh 파일 COPY COPY [ "do-ssh.sh", "/root/do-ssh.sh" ] # 실행권한 설정 RUN chmod 777 /root/do-ssh.sh # SHELL 변경 (bash -> sh) SHELL ["/bin/sh", "-c"] # do-ssh.sh RUN RUN /root/do-ssh.sh # SSH 접근 check. -o StrictHostKeyChecking=no option이 없으면 docker container에서 실행되지 않습니다. RUN ssh -T -v git@10.1.95.184 -o StrictHostKeyChecking=no

3. git code download & npm install

nodejs Application이 위치될 folder를 만들어주고, git clone을 통해서 code를 받고, npm install을 하는 구성입니다.

# apps folder 작성 RUN mkdir /apps # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] WORKDIR /apps RUN git clone git@10.1.95.184:/home/git/repo/fms-api-v2.git -o StrictHostKeyChecking=no WORKDIR /apps/fms-api-v2 # npm 설치 RUN npm install

npm install을 통해서 필요한 node module들을 모두 받아주고 build과정을 거치게 됩니다.

4. PORT 공유 & PM2 Log volume 구성

제가 만든 application은 5000, 8000번 port를 open시켜서 사용합니다. 사용되는 PORT를 EXPOSE 시켜주는 과정이 필요하게 됩니다. 그리고 pm2의 log 파일을 host에 기록하도록 volume을 추가합니다.

# expose 5000, 8000 port EXPOSE 8000 EXPOSE 5000 # VOLUME 설정 pm2 log folder VOLUME /root/.pm2/logs

5. 뒷처리과정

매우매우 위험한 파일을 container에 넣어둔것을 기억하고 있어야지됩니다. private key가 바로 그것이지요. 꼭 이 파일을 지워주는 과정이 필요합니다. 마지막으로pm2 실행에 대한 ENTRYPOINT와 CMD 설정이 필요합니다. 제가 만든 application은 설정 json파일에 따라서 동작환경이 변경되게 됩니다. 설정 json파일을 외부에서 parameter로 넣어줄 수 있도록 설정해주는 것이 필요합니다.

# Private Key 삭제 RUN rm /root/.ssh/id_rsa # ENTRYPOINT 설정 docker container에서 pm2를 실행시킬때는 pm2-docker를 통해서 실행합니다. ENTRYPOINT [ "pm2-docker" ] # CMD 설정 - parameter가 없는 경우, default 값을 지정하게 됩니다. CMD ["pm2/dev-system.json"]

6. build image & docker run

image를 만들기 위해서, docker build를 다음과 같이 구성합니다.

docker build -t nodejs-pm2-application .

이제 process가 진행되면 nodejs-pm2-application으로 image가 만들어지게 됩니다.

만들어진 image를 이용해서 container를 배포하는 명령어는 다음과 같습니다.

docker run -it --name node-app -p 5000:5000 -p 8000:8000 -v /logs/pm2:/root/.pm2/logs -t nodejs-pm2-application pm2/api-system1.json
  • -p option으로 5000, 8000번 port를 host에 연결합니다.
  • -v options으로 /root/.pm2/logs folder를 host의 /logs/pm2로 mount 시킵니다.
  • 마지막에 command로 /pm2/api-system1.json을 넣어 container의 COMMAND를 pm2-docker pm2/api-system1.json으로 변경시킵니다.

SUMMARY

Dockerfile을 구성하는 것은 Application의 형태에 따라, 개발환경에 따라 많은 차이를 가지고 오게 됩니다. nodejspythonruby와 같이 외부 library를 받아 compile하는 과정을 가지게 된다면 image내부에서 code를 직접 git으로 받아올 필요가 있습니다. 그러나, JAVAC#GO와 같이 universal compile이 되는 경우, Docker image를 만들때, compile시켜 ADD를 통해서 실행파일을 image안에 넣는것이 더 좋을것 같습니다.

다음은 전체 Dockerfile입니다.

FROM ubuntu # 빌드 환경 구성 RUN apt-get update RUN apt-get install -y wget build-essential keychain git python # NVM_DIR, NODE_VERSION에 따른 ENV 설정 ENV NVM_DIR /usr/local/nvm ENV NODE_VERSION 7.3.0 # NVM 다운로드 & 설치 RUN wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] # nvm 구성 & node install RUN source $NVM_DIR/nvm.sh; \ nvm install $NODE_VERSION; \ nvm use --delete-prefix $NODE_VERSION; ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH # PM2 설치 RUN npm install -g pm2 # .ssh Folder작성 & private key Copy RUN mkdir /root/.ssh COPY [ "id_rsa", "/root/.ssh/id_rsa" ] # SSH Key 권한 설정 - 권한이 600이 아닌경우, id_rsa key를 얻어내지 못합니다. RUN chmod 600 /root/.ssh/id_rsa # do-ssh.sh 파일 COPY COPY [ "do-ssh.sh", "/root/do-ssh.sh" ] # 실행권한 설정 RUN chmod 777 /root/do-ssh.sh # SHELL 변경 (bash -> sh) SHELL ["/bin/sh", "-c"] # do-ssh.sh RUN RUN /root/do-ssh.sh # SSH 접근 check. -o StrictHostKeyChecking=no option이 없으면 docker container에서 실행되지 않습니다. RUN ssh -T -v git@10.1.95.184 -o StrictHostKeyChecking=no # apps folder 작성 RUN mkdir /apps # Shell 변경 (sh -> bash) SHELL ["/bin/bash", "-c"] WORKDIR /apps RUN git clone git@10.1.95.184:/home/git/repo/fms-api-v2.git -o StrictHostKeyChecking=no WORKDIR /apps/fms-api-v2 # npm 설치 RUN npm install # expose 5000, 8000 port EXPOSE 8000 EXPOSE 5000 # VOLUME 설정 pm2 log folder VOLUME /root/.pm2/logs # Private Key 삭제 RUN rm /root/.ssh/id_rsa # ENTRYPOINT 설정 docker container에서 pm2를 실행시킬때는 pm2-docker를 통해서 실행합니다. ENTRYPOINT [ "pm2-docker" ] # CMD 설정 - parameter가 없는 경우, default 값을 지정하게 됩니다. CMD ["pm2/dev-system.json"]

Happy Coding~

Posted by Y2K
,

queryDSL이 4.x대로 변경이 되면서 package명에 큰 변화가 생겼습니다. 기존 com.mysema.querydsl 에서 com.querydsl로 package명이 바뀌어서 간략화 되었지요.

기존 querydsl 처리 부분에 대한 build.gradle을 변경한다면 다음과 같습니다.


dependencies {
compile("org.springframework.boot:spring-boot-starter-web") {
exclude module: "spring-boot-starter-tomcat"
}
compile 'com.graphql-java:graphql-java:2.1.0'
compile("org.springframework.boot:spring-boot-starter-jetty")
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile("junit:junit")
testCompile("org.springframework.boot:spring-boot-starter-test")
compile 'com.querydsl:querydsl-apt:4.1.4'
compile 'com.querydsl:querydsl-jpa:4.1.4'
compile 'org.springframework.data:spring-data-jpa:1.10.4.RELEASE'
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.2.3.Final'
compile "org.hibernate:hibernate-entitymanager:5.2.3.Final"
compile 'com.h2database:h2:1.4.187'
compile group: 'org.aspectj', name: 'aspectjrt', version: '1.8.9'
compile group: 'org.aspectj', name: 'aspectjweaver', version: '1.8.9'
compileOnly "org.projectlombok:lombok:1.16.12"
}


sourceSets {
main {
java {
srcDirs 'src/main/java', 'src/main/generated'
}
}
}

task generateQueryDSL(type: JavaCompile, group: 'build', description: 'Generates the QueryDSL query types') {
file(new File(projectDir, "/src/main/generated")).deleteDir()
file(new File(projectDir, "/src/main/generated")).mkdirs()
source = sourceSets.main.java
classpath = configurations.compile + configurations.compileOnly
options.compilerArgs = [
"-proc:only",
"-processor", "com.querydsl.apt.jpa.JPAAnnotationProcessor"
]
destinationDir = file('src/main/generated')
}

compileJava {
dependsOn generateQueryDSL
}

clean.doLast {
file(new File(projectDir, "/src/main/generated")).deleteDir()
}


기존의 generateQueryDSL task를 유지할 필요 없이, java compile option으로 QClass 생성을 넣어주는것으로 script를 간략화시키는것이 가능합니다. 또한 Q class가 존재할 때, 기존 Q Class를 지워주지 않으면 compile 시에 에러를 발생하게 되기 때문에 지워주는 작업역시 doFirst에 같이 실행하고 있습니다.

기존의 build.gradle보다 간략해보여서 전 개인적으로 맘에 드네요.

Posted by Y2K
,

Dependency Injection

angularjs2 2016. 9. 28. 15:17

Dependency Injection은 angular, angular2의 핵심 기능입니다.

이미 Server side 부분에서 자주 언급되고 있는 것으로, 특정 객체가 사용하는 객체군들을 만들어서 객체를 사용하는 쪽에서 골라 사용하는 개념이지요.

angularjs2는 자체 Dependency Injection Framework를 가지고 있습니다. 모든 ComponentServicePipe,Delegate는 Dependency Injection Framework를 통해 사용되어야지 됩니다.

Root Dependency Injection Tree

angularjs2 applicaiton은 한개 이상의 NgModule을 갖습니다. bootstrap에 연결된 이 NgModule에서 선언된providers import의 경우에는 전체 Global Root Dependency Injection Tree에 등록되게 됩니다.

NgModule에 등록된 providers와 import는 모든 Component에서 사용가능합니다. 각 Component에서 한정적으로만 사용될 Service나 PipeDelegate의 경우에는 각 Component별 provider에 등록해서 사용하면 됩니다.

@Injectable

@Injectable annotation은 DI 대상을 지정합니다. service에서만 @Injectable annotation이 사용되게 됩니다. 다른@Component@Pipe@Directive annotation 모두 @Injectable의 subset이기 때문에 따로 지정할 필요는 없습니다.

Inject Provider

기본적으로 providers에는 그 객체가 지정됩니다. 다음 코드는 일반적인 providers code입니다.

providers: [ Logger ]

이는 축약된 표현으로 원 표현은 다음과 같습니다.

providers: [ { provide: Logger, useClass: Logger } ]

Logger라는 객체를 얻어낼려고 하면 Logger를 넘겨준다는 표현식이 됩니다. 이를 좀 응용하면 다른 일들을 할 수 있습니다. Logger로 지정해두고, AnotherLogger를 inject 시킬 수 있다는 뜻이 됩니다. 다음과 같이요.

providers: [ AnotherLogger, { provide: Logger, useClass: AnotherLogger } ]

위 표현식은 Logger와 AnotherLogger 2개의 instance가 생성되게 됩니다. 모든 Logger를 AnotherLogger로 바꾸어 사용하고 싶은 경우에는 다음과 같이 선언합니다.

providers: [ AnotherLogger, { provide: Logger, useExisting: AnotherLogger } ]

지정된 객체는 singleton instance로 동작하는 것이 기본값입니다. 사용될 때마다 새로운 객체를 얻어내기 위해서는Factory를 생성해야지 됩니다. Factory는 function 형태로 다음과 같이 선언될 수 있습니다.

const anotherLoggerFactory = (logger: Logger) => { return new AnotherLogger(logger); };

이렇게 선언된 Factory를 다음과 같이 providers에 등록하면 이제 AnotherLogger는 사용될 때마다 신규 객체를 생성해서 처리가 되게 됩니다.

providers : [ { provide: AnotherLogger, useFactory: anotherLoggerFactory, deps: [ Logger ] } ]

Value Provider

직접적인 을 제공하고 싶을 때 사용할 수 있습니다. applicaiton의 여러 설정들이나 상수값들을 지정할 때 사용 가능합니다. 다음과 같은 값을 전달할 때 사용할 수 있습니다.

const appConfig = { apiUrl: 'http://testserver:8080/api', userToken: 'jkfla;sjdifpqiowjerkjskadj;fla' };

그런데, 이와 같은 값을 어떻게 하면 provider에 지정이 가능할지 의문이 듭니다. 위 은 객체가 아닙니다. 객체가 아니기 때문에 DI에서 이용되는 Class가 무엇인지 알 수 없습니다. 그래서 angular2에서는 OpaqueToken이라는 것을 제공하고 있습니다. OpaqueToken을 이용해서 다음과 같이 선언후, @Inject에서 OpaqueToken을 지정해주면 됩니다.

import { OpaqueToken } from '@angular/core'; export const APP_CONFIG = new OpaqueToken('app.config'); .... import { APP_CONFIG } from './config'; providers: [ { provide: APP_CONFIG, useValue: appConfig } ] .... constructor(@Inject(APP_CONFIG) config: any) { console.log(config.apiUrl); }

Manual Injector

직접적으로 객체를 DI tree에서 가지고 와서 처리하고 싶을 때도 있을 수 있습니다. sping에서 ApplicationContext에서 바로 객체를 들고오고 싶을 그럴 때와 같이요. 이러한 경우에는 다음과 같이 처리하면 됩니다.

export class InjectorComponent { car: Car = this.injector.get(Car); heroService: HeroService = this.injector.get(HeroService); hero: Hero = this.heroService.getHeroes()[0]; constructor(private injector: Injector) { } }

Root Provider에 이미 Injector 서비스가 등록이 되어 있습니다. 이 등록된 서비스를 가지고 와서 사용하면 됩니다. 개인적으로는 정말 추천하지 않는 방법이긴 합니다.;

SUMMARY

angular2는 다양한 방법의 DI를 제공하고 있습니다. Spring이나 CastleNinject등을 사용해보신 분들이라면 매우 익숙하겠지만, 지금까지 FrontEnd만 해본 사람들에게는 복잡한 내용임은 사실입니다. 그렇지만, DI는 angular2에서 가장 중요한 기능중 하나입니다. 객체를 어떻게 다루고, 관리할지에 대한 pattern을 결정하는 설정이니까요. 개인적으로는OpaqueToken이 매우 인상적인것 같습니다.

Posted by Y2K
,

Data Binding

angularjs2 2016. 9. 23. 00:26

angular2는 angular1과 다르게 한개의 Page가 여러개의 Component로 이루어집니다. 그리고 그 Component간의 데이터 전달은 Input/Output을 통해서 이루어집니다. 예전 ng-model이 이제 단방향으로 나뉘어져있다고 생각하면 쉽습니다. (ngModel과 같이 양방향 역시 존재합니다.)

기존 angular1에서의 directive에 데이터를 넣을때를 생각해보시면 쉽습니다. directive를 하나 선언하게 되면 그에 대한 scope의 범위를 다음과 같이 선언하게 됩니다.

scope: { ngModel: '=' }

위와 같이 선언된 ngModel은 html상에서 다음과 같이 사용되어질 수 있습니다.

<app-directive ng-model="models.data"></app-directive>

angular2에서는 Input과 Output을 명확하게 구분합니다. 이 부분이 어찌보면 angular1과 angular2간의 가장 큰 차이라고도 볼 수 있습니다.

기존 양방향 binding의 경우 performance의 문제가 발생했었고, 이는 angular1에서의 성능향상의 가장 큰 걸림돌이 되었던 dirty-watch의 문제점이기도 했습니다. 이 부분은 react에서도 적용되어 있는것으로, javascript에서의 refrencetype의 참조가 아닌, value로서 참조를 하게 됩니다. 이 부분은 매우 중요합니다. 결국 angular1에서의 양방향 reference binding의 문제로 binding된 model은 2개의 copy 본이 만들어지고 dirty-watch 과정을 통해 copy본과의 차이점을 알아내고 그 값을 다시 view에 binding하는 과정에서 angular1은 많은 시스템 자원을 소비하고 있었지만, angular2의 경우에는 이 과정이 없어지는거지요. binding자체가 값으로 set되는것이고, 그 set된 값은 변경되기 전에는 rendering과정을 거치지 않게 됩니다.

서론이 길었습니다. 이제 angular2에서의 DataBinding을 알아보도록 하겠습니다.

Data Binding의 종류

  • interpolation : {{}}로 표현되는 binding입니다. 값을 화면에 표시하는 용도로 사용됩니다.
  • property binding: []로 표현되는 binding입니다. Component의 property값을 set할때 사용됩니다.
  • event binding: ()로 표현됩니다. Component에서 발생되는 event를 get할때 사용됩니다.
  • two-way data binding: [()]로 표현됩니다. Component에서 값을 getset을 할 수 있습니다. 이는 ngModel directive를 이용해서만 사용 가능합니다.

먼저 간단한 2개의 Component를 통해, interpolationproperty bindingevent binding을 알아보도록 하겠습니다.

import { Component, OnInit } from '@angular/core'; import { TenantService } from '../shared/services'; @Component({ selector: 'app-feature', templateUrl: './feature.component.html', styleUrls: ['./feature.component.css'] }) export class FeatureComponent implements OnInit { tenantList: any[]; constructor(private tenantService: TenantService) { } ngOnInit() { this.tenantService.listAll().then(tenants => { this.tenantList = tenants; }); } selectedTenant(event) { console.log(event); } }
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import * as _ from 'lodash'; @Component({ selector: 'app-tenant-list', templateUrl: './tenant-list.component.html', styleUrls: ['./tenant-list.component.css'] }) export class TenantListComponent implements OnInit { @Input() tenants: any[]; @Output() select = new EventEmitter(); constructor() { } ngOnInit() { } selectTenant(tenantId: number) { const selectedTenant = _(this.tenants).find(tenant => tenant.id === tenantId); this.select.emit(selectedTenant); }; }

FeatureComponent와 TenantListComponent가 있습니다. FeatureComponent안에서 TenantListComponent가 Table로 구현되는 아주 간단한 구조입니다.

이에 따른 html은 각각 다음과 같습니다.

// feature.component.html <app-tenant-list [tenants]="tenantList" (select)="selectedTenant($event)"></app-tenant-list>
// tenant-list.component.html <table class="table table-hover"> <thead> <th>Id</th> <th>Name</th> </thead> <tbody> <tr *ngFor="let tenant of tenants" (click)="selectTenant(tenant.id)"> <td>{{tenant.id}}</td> <td>{{tenant.defaultInfo.name}}</td> </tr> </tbody> </table>

먼저 feature.component.html에서는 property binding과 event binding이 보여집니다. TenantListComponent에서 정의된 Property인 tenants가 [tenants]="tenantList"로 set을 하고 있는 것을 보실 수 있습니다.

event binding의 경우에는 다양합니다. 우리가 주로 알고 있는 web에서의 click과 같은 event들이 event binding으로 처리됩니다. 이는 tenant-list.component.html을 보면 확인이 가능합니다.

<tr *ngFor="let tenant of tenants" (click)="selectTenant(tenant.id)">

click event가 발생하게 되면 selectTenant method가 호출이 되게 됩니다. selectTenant method는 안에서 EventEmitter 객체를 통해 이벤트를 발생시킵니다. Component에서 외부로 노출되는 값은 모두 Event가 되게됩니다.

Summary

angular2는 4개의 binding type을 가지고 있습니다. interpolationevent bindingproperty bindingtwo-way binding이 있습니다. 4개의 용도는 다음과 같습니다.

  • interpolation: model의 값을 단순 display 할때 사용됩니다.
  • event bindingParent Component에서 Child Component의 값을 가지고 올때(get) 사용됩니다. - @Output과 같이 사용됩니다.
  • property bindingParent Component에서 Child Component의 값을 설정할 때(set) 사용됩니다. - @Input과 같이 사용됩니다.
  • two-way bindingComponent안에서 value와 rendering결과를 일치시킬 때, 사용됩니다. - input element와 같이 사용되는 경우가 많습니다.

Binding 부분은 angular2의 가장 핵심적인 부분입니다. 직접 예시 application을 만들어서 해보는것이 좋을 것 같습니다. 그럼 Happy Coding!

Posted by Y2K
,

angular cli

angularjs2 2016. 9. 21. 09:37

Angular-Cli

google에서 angular2와 같이 내놓은 angular2를 위한 cmd tool입니다. cmd tool중 가장 유명한 yo와 거의 동일한 기능을 제공합니다. 아직 beta version이기 때문에 발전의 여지는 많이 보이긴 하지만, angular2 application을 개발하기 위해서는 제 생각에는 반드시 angular cli를 통해서 개발 할 필요가 있습니다.

개인적인 이유의 근거는 다음과 같습니다.

  1. 표준 tool로 만들었기 때문에 모든 문서는 angular cli를 기본으로 나올 가능성이 높습니다.
  2. stackoverflow 와 같은 질의 응답 사이트의 내용 역시 angular cli를 기본으로 이야기하고 있습니다.
  3. third party tool역시 angular cli를 기반으로 나오기 쉽습니다.
  4. webpack을 기반으로 구성되어, 최적의 site를 만들기 좋습니다.
  5. 역으로 angular cli를 사용하지 않고, 직접 webpack 등의 설정을 하게 될 경우에 너무 힘듭니다. 진입장벽이 높아집니다.

angular cli는 기본적으로 다음 기능들을 가지고 있습니다.

  1. project 초기 생성
  2. component, service, pipe, directive 등의 생성
  3. gulp, grunt와 같은 build tool의 제공.
  4. scss, less와 같은 css preprocessor가 모두 지원

기본적으로 1.0.0-beta.15부터 webpack과 통합되어 있기 때문에 webpack에 대한 기본 지식이 있으면 좋습니다.

설치

npm install -g angular-cli

기타 tool들과 마찬가지로 npm을 이용해서 설치합니다. yo와는 다르게 다른 plugin들이 아직 제공되고 있지는 않군요.

project 초기 생성

ng new {{Project이름}}

위 command를 이용하면 Project이름으로 새로운 folder가 하나 생성되고, 기본적인 application 이 구성됩니다.

cd {{Project이름}} ng serve

를 실행하면 기본적인 angular2 application이 만들어집니다.

├── angular-cli.json ├── e2e │   ├── app.e2e-spec.ts │   ├── app.po.ts │   └── tsconfig.json ├── karma.conf.js ├── node_modules ├── package.json ├── protractor.conf.js ├── README.md ├── src │   ├── app │   ├── assets │   ├── environments │   ├── favicon.ico │   ├── index.html │   ├── main.ts │   ├── polyfills.ts │   ├── styles.css │   ├── test.ts │   ├── tsconfig.json │   └── typings.d.ts └── tslint.json

folder는 e2e와 src가 만들어지는데, e2e는 end to end test code가 위치하는 곳이고 개발은 주로 src에서 이루어지게 됩니다. yo에서는 이렇게 생성된 json 파일들을 조금 뜯어볼 필요성이 있는데, angular cli의 경우에는 거의 그럴 필요성이 느껴지지 않습니다. e2e 테스트를 위한 protractor.conf.js파일을 제외하면, 사용자가 건드릴 만한 파일들은 없습니다.

src에 만들어진 file 구성을 보면 component, spec, css, html 이 모두 갖추어져 있는것을 볼 수 있습니다. angular-cli는 개발자에게 component와 그에 따른 view file 들, 즉 css와 html이 같이 있는 구조를 강제합니다. 대부분의 angular2 tutorial에서 그리했듯이요. 기본적으로 만들어진 file의 내용은 다음과 같습니다.

import { Component, OnInit } from '@angular/core'; @Component({ selector: 'app-info', templateUrl: './info.component.html', styleUrls: ['./info.component.css'] }) export class InfoComponent implements OnInit { constructor() { } ngOnInit() { } }

매우 간단하지만, 타이핑이 귀찮은 것들은 많이 들어가 있는것을 볼 수 있습니다.

component, service, pipe, directive 생성

ng g {{type}} {{name}}

을 통해, 각각의 항목들을 만들어낼 수 있습니다. 여기서 재미있는것은 만약에 ng g component home.component를 통해 생성을 하면 /src/home folder가 기본으로 생깁니다. 이는 root folder에 너무 많은 파일들을 놓지 않으려는 배려로 볼 수 있겠네요. cmd를 통해서 생성하는 것들은 다음과 같습니다.

ScaffoldUsage
Componentng g component my-new-component
Directiveng g directive my-new-directive
Pipeng g pipe my-new-pipe
Serviceng g service my-new-service
Classng g class my-new-class
Interfaceng g interface my-new-interface
Enumng g enum my-new-enum

생성된 Component들은 모두 app.module.ts의 declarations에 기본적으로 추가되어 있습니다. 타 객체들은 추가되어있지 않으니, 상황에 따라 Component에 추가하는 것이 필요합니다.

route 지원

안타깝게도 angular-cli는 route를 생성하는 것을 지원하지 않습니다. 개발 방향을 잡고 있다니, 추후를 기대해봐도 좋을것 같습니다.

실행

ng serve

명령어로 실행이 가능합니다. 기본적으로 4200 port를 이용해서 처리되며, 파일 변경시 webpack watch를 통해 다시 build가 됩니다.

Proxy 설정

web application을 개발하게 되면 필연적으로 필요하게 되는 것이 Proxy입니다. 내부내에서 타 URL로 proxy를 통해 API를 호출하기 위해서는 내부 Proxy설정이 필요합니다. 이를 angular cli에서는 --proxy-config를 통해 해결하고 있습니다.

먼저, 기본적으로 angular cli에서 사용되는것은 webpack입니다. webpack dev server 설정에서의 proxy설정 방법과 완전 동일합니다. 그런데, webpack의 경우에는 webpack.config.js파일이 존재합니다. 그럼 angular cli는? 폴더의 어느곳을 뒤져봐도 위 파일은 존재하지 않습니다. angular cli는 webpack을 철저하게 wrapping해서 보여주기 때문이지요.

proxy를 구성하기 위해서는 추가 파일을 생성해서 처리해야지 됩니다. proxy.json파일을 따로 생성해서 처리하면 됩니다. 다음은 제 proxy.json파일입니다.

"/fms-api/v2"

2개의 proxy를 구성하였고, 구성된 proxy.json을 통해 실행시키기 위해서는 다음 cmd를 사용하면 됩니다.

ng serve --proxy-config proxy.json

이렇게 하면 실행창에 다음과 같은 구문이 나타납니다.

ykyoon@ykyoon ~/dev/code/cli-test1/my-dream-app $ ng serve --proxy-config proxy.json Could not start watchman; falling back to NodeWatcher for file system events. Visit http://ember-cli.com/user-guide/#watchman for more info. ** NG Live Development Server is running on http://localhost:4200. ** 10% building modules 2/2 modules 0 active[HPM] Proxy created: /fms-api/v2 -> http://localhost:8000 [HPM] Proxy rewrite rule created: "/fms-api/v2" ~> "" [HPM] Proxy created: /socket.io -> http://localhost:5000 4265ms building modules

Proxy에 설정에 대한 문서는 webpack dev server 설정과 동일합니다.

3rd party library

lodash

개인적으로는 jquery보다 더 필요한것이 이젠 lodash입니다. lodash 추가는 일반적으로 우리가 알고 있는 npm install lodash --save로 하는 것이 아니라 다음 command를 이용해서 해줘야지 됩니다.

npm install @types/lodash --save

그리고 lodash를 사용하고자 하는 곳에서 다음과 같이 사용합니다.

import * as _ from 'lodash'

jquery & bootstrap

jquery의 경우에는 조금 다릅니다. jquery의 경우에는 근간에는 거의 모든 외부 library들이 먼저 jquery가 추가된 후에 동작되는 것이 일반적인데, webpack에서 주로 사용하는 방식으로 import를 이용하면 이를 반영할수가 없습니다. 이렇게 먼저 추가되어야지 되는 library들이 있는 경우, angular-cli.json파일을 수정할 필요가 있습니다.

먼저 npm을 이용해서 jquery와 bootstrap을 추가합니다.

npm install jquery --save npm install bootstrap@next --save //bootstrap version 4

다음, script항목으로 들어가 추가되어야지 될 js파일을 순서대로 넣어줍니다.

"scripts": [ "../node_modules/jquery/dist/jquery.js", "../node_modules/tether/dist/js/tether.js", "../node_modules/bootstrap/dist/js/bootstrap.js" ],

마지막으로 styles에 bootstrap을 추가하면 완료됩니다.

"styles": [ "styles.css", "../node_modules/bootstrap/dist/css/bootstrap.css" ],

Summary

angular-cli는 매우 강력한 tool입니다. 또한 angular2 개발 방법의 표준을 제공하고 있습니다. 이는 강력하지는 않지만, 많은 3rd party library와 사용자들에게 영향을 주게 될 것입니다. 다른 3rd party library들의 사용방법이 angular-cli를 기준으로 문서가 만들어질 가능성이 매우 높습니다.

Posted by Y2K
,

angular2 animation

angularjs2 2016. 9. 13. 16:45

angular2에서의 animation은 state의 변경으로 제어가 가능합니다. 기존의 css를 지정해주었던 것에서 많은 변화가 생긴것이지요.

animation을 사용하기 위해서는 다음 모듈들을 import시켜야지 됩니다.

import { Component, Input, trigger, state, style, transition, animate } from '@angular/core';

후에 @Component에서 animations를 다음과 같이 정의합니다.

animations: [ trigger('showDetailed', [ state('summary', style({ height: '0px', display: 'none' })), state('detailed', style({ height: '250px', display: 'inherit' })), transition('summary <=> detailed', animate(250)), ]) ]

각 항목은 다음과 같습니다.

  • trigger(name): state 이름을 정합니다.
  • state(stateName): state의 상태이름을 지정하고, 상태이름일때의 style 를 지정합니다.
  • transaction: state 상태의 변경시에 동작할 animation을 지정합니다. 상태는 =><=> 을 지정합니다.

angular2에서의 animation은 상태의 변화시에 동작 이라고 정의하면 이해하기 쉽습니다.

간단히 showDetailed trigger의 summary와 detailed 상태의 변화에 있어서 summary일때의 최종 style과detailed상태일때의 최종 style을 지정해주고, state의 변경시에 발생되는 animation의 시간과 style을 정해주는 것으로 animation 효과를 넣을 수 있습니다.

주로 사용될 style들은 다음과 같습니다.

  • mouse-over: {transform: translateX(0) scale(1.1)}
  • mouse-leave: {transform: translateX(0) scale(1)}
  • show: { height: '200px', display: 'inherit' }
  • hide: { height: '0px', display: 'none' }

summary

angular2에서는 state라는 개념을 이용해서 animation을 넣어줍니다. 이는 객체의 상태 변화와 View에서의 animation을 적절히 조화시킬 수 있는 멋진 방법입니다. 그런데 이를 이용하기 위해서는 결국은 view component를 어떻게 잘 나누어서 설계하느냐에 대한 설계상의 이슈가 발생됩니다. 예전 개발 패턴대로 html 하나에 모두 때려 넣는것이 아닌, 하나하나의 Component로 Page 자체를 설계하는 View단에서의 객체지향적 개발 패턴이 필요합니다. 재미있어요.

Posted by Y2K
,

angular2 form

angularjs2 2016. 9. 13. 14:03

angular2 form

기존 angularjs에서 문제가 되었던 form이 크게 향상되었습니다. 기존 form의 문제점은 다음과 같습니다.

  1. form의 생성시기를 알 수 없습니다. div tag를 통해서 생성되는 ng-form은 생성시기를 알 수 없기 때문에, form을 검사하기 위해서는 null error를 꼭 check하는 것이 좋습니다.
  2. form error text가 html에 담긴다. form에서 발생되는 error를 표시하는 방법이 모두 html에 기술되어야지 됩니다. 이에 대한 구현의 문제는 html이 과도하게 길어지는 문제가 발생하게 되고, ng-if의 남발이 발생하게 됩니다.

angular2는 NgForm의 제어 영역을 component로 이동시켜서 기존의 html에서 제어되는 것이 아닌 component에서 제어되는 것으로 구현을 변경하였습니다. 물론, 기존과 같은 패턴 역시 사용가능합니다.

Form Validation - Old Pattern

기존 ngForm을 이용하는 방법과 거의 유사합니다.

<form #heroForm="ngForm" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" name="name" [(ngModel)]="hero.name" #name="ngModel" class="form-control" required minlength="4" maxlength="24"> <div *ngIf="name.errors && (name.dirty || name.touched)" class="alert alert-danger"> <div [hidden]="!name.errors.required"> Name is required </div> <div [hidden]="!name.errors.minlength"> Name must be at least 4 characters long. </div> <div [hidden]="!name.errors.maxlength"> Name cannot be more than 24 characters long. </div> </div> </div>
export class HeroFormTemplate1Component { powers = ['Really Smart', 'Super Flexible', 'Weather Changer']; hero = new Hero(18, 'Dr. WhatIsHisWayTooLongName', this.powers[0], 'Dr. What'); submitted = false; active = true; onSubmit() { this.submitted = true; } addHero() { this.hero = new Hero(42, '', ''); } }

각 form의 이름을 이용하고 [(ngModel)]을 이용해서 model과 값을 binding시켜 사용합니다. 여기서 주의할 점은 #name입니다. 이는 꼭 ngModel을 적어줘야지 되며, ngModel을 이용해서 heroForm과 binding이 되게 됩니다.

이 code의 최고 문제는 html에 error message가 그대로 노출된다는 점입니다. 이를 해결해주기 위해서는 다음과 같이 구성합니다.

Form Validation - new Pattern

새로운 패턴에서 확인할 점은 NgForm의 생성과 Error Message의 관리 포인트가 Component로 넘어왔다는 점입니다. 먼저 Component에서 FormBuilder를 Import합니다.

import { FormGroup, FormBuilder, Validators } from '@angular/forms';

그리고, ngOnInit에서 FormGroup을 생성시켜줍니다.

constructor(private fb: FormBuilder) { } ngOnInit(): void { this.buildForm(); } buildForm(): void { this.heroForm = this.fb.group({ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), ] ], 'alterEgo': [this.hero.alterEgo, [ Validators.required ]], 'power': [this.hero.power, Validators.required] }); this.heroForm.valueChanges .subscribe(data => this.onValueChanged(data)); this.onValueChanged(); // (re)set validation messages now }

FormBuilder.group은 각 Form element name에 따른 Validators를 갖습니다. Form Validation 조건들을 각 Component에서 처리가 가능해지는 것입니다. 또한 각 error에 대한 message 처리 역시 Component에서 가능하게 됩니다. 이는 위 코드 중 onValueChanged에서 처리 가능합니다.

onValueChanged(data?: any) { if (!this.heroForm) { return; } const form = this.heroForm; for (const field in this.formErrors) { this.formErrors[field] = ""; const control = form.get(field); if (control && control.dirty && !control.valid) { const messages = this.validationMessages[field]; for (const key in control.errors) { this.formErrors[field] += messages[key] + " "; } } }
<form [formGroup]="heroForm" *ngIf="active" (ngSubmit)="onSubmit()"> <div class="form-group"> <label for="name">Name</label> <input type="text" id="name" class="form-control" formControlName="name" required> <div *ngIf="formErrors.name" class="alert alert-danger"> {{ formErrors.name }} </div> </div> <div class="form-group"> <label for="alterEgo">Alter Ego</label> <input type="text" id="alterEgo" class="form-control" formControlName="alterEgo" required> <div *ngIf="formErrors.alterEgo" class="alert alert-danger"> {{ formErrors.alterEgo }} </div> </div> <div class="form-group"> <label for="power">Hero Power</label> <select id="power" class="form-control" formControlName="power" required> <option *ngFor="let p of powers" [value]="p">{{p}}</option> </select> <div *ngIf="formErrors.power" class="alert alert-danger"> {{ formErrors.power }} </div> </div> <button type="submit" class="btn btn-default" [disabled]="!heroForm.valid">Submit</button> <button type="button" class="btn btn-default" (click)="addHero()">New Hero</button> </form>

CustomeValidation 처리

CustomValidation의 경우에는 특수한 함수 형태를 반환해야지 됩니다. ValidatorFn을 반환하는 함수를 작성하고, 그 함수를 FormGroup.group 함수에 Binding시켜주면 됩니다.

import { ValidatorFn } from '@angular/forms'; forbiddenNameValidator(nameRe: RegExp): ValidatorFn { return (control: AbstractControl): { [key: string]: any } => { const name = control.value; const no = nameRe.test(name); return no ? { 'forbiddenName': { name } } : null; }; } this.heroForm = this.fb.group({ 'name': [this.hero.name, [ Validators.required, Validators.minLength(4), Validators.maxLength(24), this.forbiddenNameValidator(/bob/i) ] ], 'alterEgo': [this.hero.alterEgo, [ Validators.required ]], 'power': [this.hero.power, Validators.required] });

Summary

angular2에서의 FormValidation은 많은 발전을 가지고 왔습니다. 가장 큰 변화는 Component에서 Error Message 및 조건들을 관리하기 편해졌다는 것입니다. 기존 html에서 처리하는 방법 역시 가지고 있지만, 개인적으로는 최대한 사용하지 않는것이 좋아보입니다.

또한, 다국어 처리에 있어서도 더 나은 방법이 될 수 있습니다.

Posted by Y2K
,

Angular2 + webpack

기본 npm package 준비

{
  "name": "angular2-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "angular2": "^2.0.0-beta.15",
    "bluebird": "^3.3.5",
    "core-js": "^2.2.2",
    "lodash": "^4.11.1",
    "reflect-metadata": "^0.1.3",
    "rxjs": "^5.0.0-beta.6",
    "zone.js": "^0.6.11"
  },
  "devDependencies": {
    "awesome-typescript-loader": "^0.17.0-rc.6",
    "copy-webpack-plugin": "^2.1.1",
    "html-webpack-plugin": "^2.15.0"
  }
}

기본적으로 reflect-metadata, rxjs, zone.jsangular2와 같이 설치되어야지 된다.

npm install rxjs --save
npm install reflect-metadata --save
npm install zone.js --save

typescript 설정

npm install typings --global
  • tsd는 deprecated되었기 때문에 더이상 사용되지 않는다.

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "sourceMap": true
  },
  "exclude": [
    "node_modules",
    "typings/main",
    "typings/main.d.ts",
    "bower_components"
  ]
}

webpack 설정

기본적으로 다음 folder 구조를 따른다. (maven 과 유사)

├── dist
├── src
│   ├── app
│   └── public
├── package.json
├── tsconfig.json
├── typings.json
└── webpack.config.js
  • src/app: javascript application
  • src/public: html,css,image와 같이 static resource 구성

typescript를 지원하기 위해서 awesome-typescript-loader을 설치하고 loader에 다음과 같이 등록한다.

{
  test: /\.ts$/,
  loader: 'awesome-typescript-loader',
  exclude: /node_modules/
}

webpack.config.js

'use strict';

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var CopyWebpackPlugin = require('copy-webpack-plugin');

function buildConfig() {
  var isProd = false;
  const config = {
    output: {
      path: __dirname + '/dist',
      publicPath: isProd ? '/' : 'http://localhost:8080/',
      filename: isProd ? '[name].[hash].js' : '[name].bundle.js',
      chunkFilename: isProd ? '[name].[hash].js' : '[name].bundle.js'
    },
    resolve: {
      extensions: ['', '.webpack.js', '.web.js', '.ts', '.js'],
      modulesDirectories: [
        'node_modules'
      ]
    },
    devtool: 'cheap-source-map',
    module: {
      loaders: [
        {
          test: /\.ts$/,
          loader: 'awesome-typescript-loader',
          exclude: /node_modules/
        }
      ]
    },
    entry: {
      app: __dirname + '/src/app/app.ts'
    },
    plugins: [
      new webpack.NoErrorsPlugin(),
      new webpack.optimize.DedupePlugin(),
      new CopyWebpackPlugin([{ from: __dirname + '/src/public'}]),
      new HtmlWebpackPlugin({
        template: './src/public/index.html',
        inject: 'body'
      })
    ]
  };
  config.devServer = {
    contentBase: './dist',
    stats: 'minimal',
    outputPath: './dist'
  };

  return config;
}

module.exports = buildConfig();

app.ts

webpack에서 지정되는 entry point가 된다. main.ts, bootstrap.ts등 다양한 파일이름이 있지만, 개인적으로는 app.ts가 가장 좋은것 같다.

app.ts에서는 다음 세가지 기능을 한다.

  1. router 의 등록
  2. bootstrap 실행
  3. 첫페이지로 direction
/// <reference path="../../node_modules/angular2/typings/browser.d.ts" />

import 'core-js/es6';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import { bootstrap } from 'angular2/platform/browser';
import { HTTP_PROVIDERS } from 'angular2/http';
import { enableProdMode } from 'angular2/core';
import { Component } from 'angular2/core';
import { provide } from 'angular2/core';
import { Router, RouteConfig, ROUTER_DIRECTIVES, ROUTER_PROVIDERS, LocationStrategy, Location, HashLocationStrategy } from 'angular2/router';

import { HeroService } from './hero.service';
import { DashboardComponent } from './dashboard.component';
import { HeroesComponent, HeroesRouterInfo } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';

@RouteConfig([
  {
    path: '/heroes',
    name: 'Heroes',
    component: HeroesComponent,
  },
  HeroesRouterInfo,
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: DashboardComponent,
    useAsDefault: true
  },
  {
    path: '/detail/:id',
    name: 'HeroDetail',
    component: HeroDetailComponent
  },
])
@Component({
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <nav>
      <a [routerLink]="['Dashboard']">Dashboard</a>
      <a [routerLink]="['Heroes']">Heroes</a>
    </nav>
    <router-outlet></router-outlet>
  `,
  styleUrls: ['app.component.css'],
  directives: [ROUTER_DIRECTIVES]
})
class AppComponent {
  title: string;
  router: Router;
  location: Location;
  constructor(router: Router, location: Location) {
    this.title = 'This is Title';
    this.router = router;
    this.location = location;
  }
}

// enableProdMode();
bootstrap(AppComponent, [
  HTTP_PROVIDERS,
  ROUTER_PROVIDERS,
  provide(LocationStrategy, { useClass: HashLocationStrategy }),
  HeroService
]).catch(err => console.error(err));

app.ts의 경우에는 router가 추가 되고, 여러 서비스 Provider들이 추가되는 것 이외에는 큰 차이가 없을것같다. 기본적으로 기존 angularapp.js와 동일한 기능을 갖게 된다.

lodash 추가

typings install lodash --ambient --save
import * as _ from 'lodash'
Posted by Y2K
,