Angular 101 - workshop 2nd

2woongjae@gmail.com

Mark Lee (이웅재)

  • Studio XID inc. ProtoPie Engineer
  • Seoul.JS 오거나이저
  • 타입스크립트 한국 유저 그룹 오거나이저
  • 일렉트론 한국 유저 그룹 운영진
  • Seoul.js 오거나이저
  • Microsoft MVP - Visual Studio and Development Technologies
  • Code Busking with Jimmy
    • https://www.youtube.com/channel/UCrKE8ihOKHxYHBzI0Ys-Oow

Angular Router

package.json

{
  ...,
  "dependencies": {
    "@angular/animations": "^5.0.0",
    "@angular/common": "^5.0.0",
    "@angular/compiler": "^5.0.0",
    "@angular/core": "^5.0.0",
    "@angular/forms": "^5.0.0",
    "@angular/http": "^5.0.0",
    "@angular/platform-browser": "^5.0.0",
    "@angular/platform-browser-dynamic": "^5.0.0",
    "@angular/router": "^5.0.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.5.2",
    "zone.js": "^0.8.14"
  },
  ...
}

angular 2 => 4 의 범인

  • 앵귤러에서 RouterModule 가져오기
    • import { RouterModule } from '@angular/router';
  • 사용하고자 하는 모듈에 import 하기
    • RouterModule.forRoot(<Route 배열>)
  • 템플릿에 커스텀 라우터 엘리먼트 추가
    • <router-outlet></router-outlet>

무작정 라우팅 처리하기

https://github.com/2woongjae/angular-router-basic1

  • 프로젝트 생성
    • ng new angular-router-basic1
  • 컴포넌트 추가
    • ng g c home
    • ng g c about
  • 라우터 설정
    • RouterModule 가져오기
    • Route 셋팅
      • '' => HomeComponent
      • 'about' => AboutComponent
    • app 모듈에 Router import
  • 실행
    • ng serve

app.module.ts

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

import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';

const routes: Routes = [
  {path: '', component: HomeComponent},
  {path: 'about', component: AboutComponent}
];

@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    AboutComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(routes)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.html

<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<router-outlet></router-outlet>
  • app.routes.ts 만들기
  • Route 리스트를 app.routes.ts 로 옮기기
  • RouterModule.forRoot(<Route 배열>) 만들어서, export
  • app.module.ts 에서 import 하여 설정하기

app.routes.ts

import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
    {path: '', component: HomeComponent},
    {path: 'about', component: AboutComponent}
];

export default RouterModule.forRoot(routes);

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import appRoutes from './app.routes';

import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';


@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    AboutComponent
  ],
  imports: [
    BrowserModule,
    appRoutes
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
  • 템플릿에서 a 태그를 추가합니다.
  • href => routerLink
  • 현재 경로일때,
    • routerLinkActive 에 class 설정
    • 해당 class 에 스타일 적용
  • 경로 매칭을 정확히 하고 싶을때,
    • routerLinkActiveOptions 처리
      • {exact: true}

app.component.html

<nav>
  <a routerLink="" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}">Home</a>
  <a routerLink="about" routerLinkActive="active">About</a>
  <a routerLink="developer" routerLinkActive="active">Developer</a>
</nav>
<router-outlet></router-outlet>

app.component.css

a.active {
    color: green;
    font-weight: bold;
}

params 설정 및 컴포넌트에서 사용하기

https://github.com/2woongjae/angular-router-basic4

  • Routes 에 :name 과 같이 설정하기
  • 사용하는 컴포넌트에서 ActivatedRoute 주입
  • 주입된 서비스 이용해서 :name 가져오기
    • 동기
      • snapshot
    • 비동기
      • rx

app.routes.ts

import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { DeveloperComponent } from './developer/developer.component';

const routes: Routes = [
    {path: '', component: HomeComponent},
    {path: 'about', component: AboutComponent},
    {path: 'developer', component: DeveloperComponent},
    {path: 'developer/:name', component: DeveloperComponent}
];

export default RouterModule.forRoot(routes);

developer.component.ts (동기)

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-developer',
  templateUrl: './developer.component.html',
  styleUrls: ['./developer.component.css']
})
export class DeveloperComponent implements OnInit {
  name;

  constructor(private route: ActivatedRoute) {
    console.log(route.snapshot.paramMap.get('name'));
    this.name = route.snapshot.paramMap.get('name');
  }

  ngOnInit() {
  }

}

developer.component.ts (비동기)

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import 'rxjs/add/operator/map';

@Component({
  selector: 'app-developer',
  templateUrl: './developer.component.html',
  styleUrls: ['./developer.component.css']
})
export class DeveloperComponent implements OnInit {
  name;

  constructor(private route: ActivatedRoute) {
    this.name = route.params.map(p => p.name);
  }

  ngOnInit() {
  }

}

/*

템플릿

{{ name | async }}

*/

Service

  • 컴포넌트
    • 뷰(템플릿)와 뷰를 위한 모델에 관련된 코드
  • 서비스
    • 컴포넌트 혹은 다른 서비스에서 사용하는 재사용 가능한 기능의 집합
    • 컴포넌트에 주입하거나, 모듈에 주입해서 사용 

Angular Dependency Injection

  • 'A 컴포넌트에서 S 라는 서비스를 사용한다' 라는 말은
    • A 에서 S 의 인스턴스를 직접 생상해서 사용 (X)
    • A 에 S 를 주입해주면 앵귤러에서 알아서 객체를 만들어서 사용할수 있도록 맡아서 처리 
      • 1. 컴포넌트에 서비스를 주입
      • 2. 모듈에 서비스를 주입
        • 모듈에 있는 모든 컴포넌트에서 같은 서비스를 사용
  • => 'A 컴포넌트에 S 서비스를 주입한다' 라고 한다.

Traditional

class EnginService {
    public start(): void {
        console.log('엔진 스타트');
    }
}

class WheelService {
    public start(): void {
        console.log('바퀴 스타트');
    }
}

class CarComponent {
    private _engine = new EnginService();
    private _wheel = new WheelService();

    constructor() {

    }

    public start(): void {
        this._engine.start();
        this._wheel.start();
    }
}

const car = new CarComponent();
car.start();

dependency vs injection (제어의 역전)

dependency injectior 가 바로 Angular

  • 디펜던시 인젝터는 앵귤러
  • 어떻게 인젝트 해줄건지는 우리의 설정
  • provider(s) 라는 이름으로 설정
  • 설정하는 방식은 같으나, 설정하는 위치가 다름
    • 1. 컴포넌트의 메타데이터
    • 2. 모듈의 메타데이터
  • ng new
  • 컴포넌트 2개 생성
    • ng g c home
    • ng g c company
  • 서비스 생성
  • 각각의 컴포넌트에 주입
  • 각각 다른 서비스가 생성 사용된다.

log.service.ts

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

@Injectable()
export class LogService {
  private _count = 0;

  constructor() { }

  public info(message: string): void {
    console.log('info', message, this._count);
    this._count++;
  }

}

home.component.ts

import { Component, OnInit } from '@angular/core';
import { LogService } from '../log.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
  providers: [LogService]
})
export class HomeComponent implements OnInit {

  constructor(private logService: LogService) { }

  ngOnInit() {
    this.logService.info('집에서 로그 서비스 사용');
  }

}

company.component.ts

import { Component, OnInit } from '@angular/core';
import { LogService } from '../log.service';

@Component({
  selector: 'app-company',
  templateUrl: './company.component.html',
  styleUrls: ['./company.component.css'],
  providers: [LogService]
})
export class CompanyComponent implements OnInit {

  constructor(private logService: LogService) { }

  ngOnInit() {
    this.logService.info('회사에서 로그 서비스 사용');
  }

}

app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<app-home></app-home>
<app-company></app-company>

둘다 카운트 0

  • ng new
  • 컴포넌트 2개 생성
    • ng g c home
    • ng g c company
  • 서비스 생성
  • 모듈에 주입
  • 모듈 내에서 하나의 서비스가 생성 사용된다.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { CompanyComponent } from './company/company.component';
import { LogService } from './log.service';


@NgModule({
  declarations: [
    AppComponent,
    HomeComponent,
    CompanyComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [LogService],
  bootstrap: [AppComponent]
})
export class AppModule { }

카운트가 올라간다...

  • 그냥 클래스
  • useValue
  • useClass
  • useExisting
  • useFactory

app.component.ts

import { Component, OnInit, Inject } from '@angular/core';
import { LogService } from '../log.service';
import { FactoryService } from '../factory.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
  providers: [
    LogService,
    {
      provide: 'githubUrl',
      useValue: 'https://github.com'
    },
    {
      provide: 'log',
      useExisting: LogService
    },
    {
      provide: 'factory',
      useFactory: (logService) => {
        return new FactoryService(logService, true);
      },
      deps: [LogService]
    }
  ]
})
export class HomeComponent implements OnInit {

  constructor(
    @Inject('githubUrl') private githubUrl,
    @Inject('log') private logService,
    @Inject('factory') private factoryService
  ) { }

  ngOnInit() {
    console.log(this.githubUrl);
    this.logService.info('안녕하세요');
    this.factoryService.log();
  }

}

log.service.ts

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

@Injectable()
export class LogService {

  constructor() { }

  public info(message: string): void {
    console.log('info', message);
  }

}

factory.service.ts

import { Injectable } from '@angular/core';
import { LogService } from './log.service';

@Injectable()
export class FactoryService {

  constructor(private logService: LogService, private isFactory: boolean) { }

  public log(): void {
    this.logService.info(`factory: ${this.isFactory}`);
  }
}

HttpModule, Http

  • HttpModule 추가
  • 컴포넌트에서 Http 가져오기
  • toPromise() 를 이용
  • 하지만 이렇게 컴포넌트에서 직접 처리하지 않고, 다른 서비스에 주입해서 사용하도록 권장합니다.

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import { HomeComponent } from './home/home.component';
import { HttpModule } from '@angular/http';


@NgModule({
  declarations: [
    AppComponent,
    HomeComponent
  ],
  imports: [
    BrowserModule,
    HttpModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

home.component.ts

import { Component, OnInit, Inject } from '@angular/core';
import { LogService } from '../log.service';
import { FactoryService } from '../factory.service';
import { Http } from '@angular/http';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css'],
  providers: [
    LogService,
    {
      provide: 'githubUrl',
      useValue: 'https://api.github.com/users'
    },
    {
      provide: 'log',
      useExisting: LogService
    },
    {
      provide: 'factory',
      useFactory: (logService) => {
        return new FactoryService(logService, true);
      },
      deps: [LogService]
    }
  ]
})
export class HomeComponent implements OnInit {

  data = [];

  constructor(
    @Inject('githubUrl') private githubUrl,
    @Inject('log') private logService,
    @Inject('factory') private factoryService,
    private http: Http
  ) { }

  ngOnInit() {
    this.logService.info(this.githubUrl);
    this.factoryService.log();
    this.http.get(this.githubUrl)
    .toPromise()
    .then(data => {
      this.data = data.json();
    })
    .catch(err => {
      console.log(err);
      this.data = [];
    });
  }

}

https://api.github.com/users

Directive

  • Structural Directive
    • Component 형
    • 템플릿에 DOM 을 추가, 제거 => 템플릿의 구조를 변경
      • *ngFor
      • *ngIf
      • *ngSwitch
  • Attribute Directive
    • DOM 의 모양과 동작을 수정
      • [ngModel]
      • [ngClass]
      • [ngStyle]
  • 사용자 정의 디렉티브
  • app.component.html 에 directive 사용해보기
    • *ngIf="isVisible"
    • *ngFor="let item of list"
  • app.component.ts 에 멤버 변수 설정

app.component.html

<!--The content below is only a placeholder and can be replaced.-->
<div style="text-align:center">
  <h1>
    Welcome to {{ title }}!
  </h1>
  <img width="300" alt="Angular Logo" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiAgICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0xNyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg==">
</div>
<h2 *ngIf="isVisible">Here are some links to help you start: </h2>
<ul>
  <li *ngFor="let item of list">
    <h2><a target="_blank" rel="noopener" [href]="item.url">{{ item.title }}</a></h2>
  </li>
</ul>

app.component.ts

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

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
  isVisible = false;
  list = [
    {url: 'https://angular.io/tutorial', title: 'Tour of Heroes'},
    {url: 'https://github.com/angular/angular-cli/wiki', title: 'CLI Documentation'},
    {url: 'https://blog.angular.io/', title: 'Angular blog'}
  ];
}

Pipe

  • 표현식에 개입해서 결과를 수정해서 보여주는 역할
  • 수많은 내장 파이프가 있다.
    • {{ expression | pipe }}
    • {{ expression | pipe | pipe }}
    • {{ expression | pipe:parameter1: parameter2 }}
  • 사용자 정의 파이프도 만들수 있다.
    • @Pipe

내장 파이프 알아보기

<p>{{ {'name': 'mark'} | json }}</p>
<p>{{ 'markzzang' | slice:0:4 }}</p>
<p>{{ ['mark', 'anna'] | slice:0:1 }}</p>
<p>{{ 'mark' | uppercase }}</p>
<p>{{ 'Mark' | lowercase }}</p>
<p>{{ 'mark' | uppercase | lowercase }}</p>
<p>{{ 1234566789012 | date:'yyyy-MM-dd' }}</p>
<!-- <p>{{ data | async }}</p> -->

내장 파이프 결과

[코드버스킹] Angular 101 - 2

By Woongjae Lee

[코드버스킹] Angular 101 - 2

코드버스킹 Angular 101 - 두번째

  • 1,258