Search results for 'IT Tech/Angular'

Angular Test Tips (Mock)

2019. 6. 1. 15:35

한동안 개발 잉여력이 떨어져서 오래 쉬었네요.

회사에서 바쁘면 집에서 개발을 덜하게 됩니다.

개발을 천천히 하니 글 쓸 일을 더욱 없다는게...

 

그렇다해서 지금 바쁘지 않다는건 아니지만.

더 밀리면 안쓰고 넘어갈 것 같아서 기록합니다.

기록하다보면 더 정리되는 기분도 있구요.

 

TDD를 연습하자는 취지로 진행중인데,

소스가 많아질 수록 점점 Test가 어려워집니다.

Angular 또한 예외는 아니구요.

 

그 어려움을 덜어낸 방법 2가지 공유합니다.

 

1. Component Mocking

 

자식(Child) 컴포넌트를 포함하는

부모(Parent) 컴포넌트를 테스트하다보면

점점 테스트 설정이 무거워집니다.

 

부모 컴포넌트의 의존성 뿐 아니라

자식 컴포넌트의 의존성을 모두 주입해야하니까요.

그래서 자식컴포넌트를 Mock으로 주입하고

자식의 의존성은 생략해버리는 것이 유용합니다.

 

제 프로젝트는 아래와 같은 포함관계가 있습니다.

Budget > Calendar > Day Component

Budget 컴포넌트의 테스트가 비대해지는 중이죠.

그래서 Calendar 컴포넌트를 Mock을 했습니다.

의존성도 덜어냈구요.

그 방법은 아래와 같습니다.

 

1) Mock 컴포넌트의 선언

 

테스트(spec.js)에 가짜(Mock) 컴포넌트를 선언합니다. 실제와 동일한 selector를 가지도록 말이지요. Input, Output과 같이 테스트가 필요한 부분은 포함합니다.

...
@Component({
  selector: 'app-calendar',
  template: 'mock calendar'
})
class MockCalendarComponent {
  @Input('date') date: Date;
  @Output('dateChange') dateChange: EventEmitter = new EventEmitter();
}

 

2) Mock 컴포넌트의 사용

 

하위 컴포넌트를 추가해주었던 declarations에 선언해준 Mock 컴포넌트를 넣습니다.

describe('BudgetComponent', () => {
  let component: BudgetComponent;
  let fixture: ComponentFixture;
  let testableDailyExpenseState: Observable;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [BudgetComponent, MockCalendarComponent],
      imports: [
      ...
      ])]
    }).compileComponents();
  }));
  ...

 

이제 Calendar나 Day 컴포넌트의 의존성은 Budget 컴포넌트의 테스트에 더이상 필요가 없습니다.

자식 컴포넌트를 위한 설정 코드들도 덜어집니다.

 

2. Override Private Property

 

사실 이 방법은 좋은 방법은 아닙니다.

부득이하게 쓸 경우가 있어서 정리해둡니다.

NGXS를 쓰다보니 테스트가 난해하더군요.

Store 값을 변경하기가 까다로워서요. 

 

Selector로 가져오는 값이 private이기 때문.

Javascript의 Object.defineProperty를 이용했습니다. 이미 component에 정의된 속성을 수정하고 테스트용 값을 넣어주는 방식입니다.

beforeEach(() => {
    ...
    Object.defineProperty(component, 'dailyExpense$', { writable: true });
    let dailyExpenseState = {
      //test data
      ...
    }
    testableDailyExpenseState = of(dailyExpenseState)
    component.dailyExpense$ = testableDailyExpenseState;
    ...
  });

위와 같이 component의 private 속성에

원하는 테스트 값을 넣는 것이 가능합니다.

 

위와 같은 방법을 이용하여 Angular의 테스트를 조금 더 수월하게 하였습니다.

 

Angular 테스트 코드가 점점 무거워지고,

수많은 의존성과 설정에 불편하신 분들께

도움이 되었으면 좋겠습니다.

 

전체 소스는 아래 github에서 보실 수 있습니다.

소스주소: https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Dialog(Material Design)  (0) 2019.01.01
Angular Component Communication (Output)  (0) 2018.12.16
Angular Component Communication (Input)  (0) 2018.12.02
Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12

TechTrip IT Tech/Angular

Angular Dialog(Material Design)

2019. 1. 1. 23:24

이번에는 Material Dialog를 추가해보겠습니다.

Dialog는 아래와 같이 동작합니다.


Dialog 동작화면


부모창이 어두워집니다. (dimmed)

모달(modal) 창이 뜨고 입력한 값이 부모로 전달되죠.


Material의 장점은 역시 미리 구현된 동작입니다.

 - 모달(modal)과 부모 컴포넌트간 데이터 전달

 - 모달(modal) 이외 영역 클릭시 닫기

 - ESC로 창닫기

 - 웹접근성 고려(자동 포커스)

   (창을 닫고 아이콘이 포커스 되는 그 부분)


그럼 코드로 살펴보겠습니다.


1. Import MatDialogModule


app module에 MatDialogModule을 추가합니다.

@angular/material 모듈에 포함되어있습니다.

import { MatDialogModule } from '@angular/material';
...

@NgModule({
  ...
  imports: [
    ...
    MatDialogModule,
    ...
  ],
  ...
})
export class AppModule { }


2. Dialog Component 생성


Dialog에 구성될 화면을 만듭니다.

저는 컴포넌트를 따로 분리하였습니다.

컴포넌트 생성은 굉장히 쉽죠?

(ng g c '이름' Angular 새로 Component 만들기)


Dialog는 부모 컴포넌트로 data를 전달합니다.

MatDialogRef 라는 의존성을 주입합니다.

(여기서 설명했었죠? Angular Service 만들기)

import { Component } from '@angular/core';
import { MatDialogRef } from '@angular/material'
...

@Component({
  selector: 'app-consumption',
  templateUrl: './consumption.component.html',
  styleUrls: ['./consumption.component.css']
})
export class ConsumptionComponent {

  constructor(private dialogRef: MatDialogRef) { }
  ...
  
  onCancel(){
    this.dialogRef.close();
  }

  onSave(){
    const returnData = {
      amount: this.consumptionForm.controls.amount.value,
      desc: this.consumptionForm.controls.desc.value
    }
    this.dialogRef.close(returnData)
  }
}

cancel 버튼과 save 버튼에 이벤트를 연결했죠.

dialogRef의 close 메서드에 data를 전달합니다.

화면 Template은 생략하겠습니다.

그저 Form 컨트롤일뿐.

(Angular Forms)


3. Dialog Component Factory에 등록


그럼 생성해준 Dialog는 동적으로 만들어집니다.

이런 컴포넌트들은 다른 설정이 필요해요.

app module에 entryComponents 라는 것이죠.

...

@NgModule({
  ...
  entryComponents: [
    ConsumptionComponent
  ],
  ...
})
export class AppModule { }

위와 같은 설정으로 Dialog를 그릴 수 있게됩니다.

Component Factory에 추가가 되기 때문이죠.


4. Dialog 표출


부모 컴포넌트에서 Dialog를 보여주면 됩니다.

아래와 같이 가능합니다.

import { MatDialog } from '@angular/material';
import { ConsumptionComponent } from '../consumption/consumption.component';
...

@Component({
  ...
})
export class BudgetComponent implements OnInit {
  ...
  constructor(public store: Store, public dialog: MatDialog) { }
  ...

  onAdd() {
    const dialogRef = this.dialog.open(ConsumptionComponent, {
        width: '250px'
    });

    dialogRef.afterClosed().subscribe(result => {
        if (result) {
            let amount: number = Number(result.amount);
            let desc: string = result.desc;
            this.addConsumption(new Consumption(amount, desc));
        }
    });
  }
  ...
}

MatDialog 의존성 주입이 되었구요.

(여기서 설명했었죠? Angular Service 만들기)

MatDialog의 메서드를 호출해주면 됩니다.

open이라는 메서드입니다.

dialog에 사용할 컴포넌트와 정보를 넣었죠.

width이외에 data도 전달할 수 있습니다.


open 메서드는 dialogRef를 리턴합니다.

dialogRef를 이용해 닫혔을 때 동작을 정의합니다.

observer 패턴이에요.

result라는 객체로 dialog에서 전달한 값을 받습니다.


조금 복잡하지만 크게 수고를 덜 수 있습니다.

물론 디자인을 변경하려면....

나중에 필요해지면 해보도록 하죠.


전체 소스는 아래 링크에서 볼 수 있습니다.

https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Test Tips (Mock)  (0) 2019.06.01
Angular Component Communication (Output)  (0) 2018.12.16
Angular Component Communication (Input)  (0) 2018.12.02
Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12

TechTrip IT Tech/Angular

Angular Component Communication (Output)

2018. 12. 16. 19:30

지난 번에는 부모(Parent)에서 자식(Child)으로 

Input을 통해 데이터를 전달하였습니다.

Angular Component Communication (Input)


이번에는 반대로 자식에서 부모

Output을 통해 데이터를 전달해보겠습니다.


달력 컴포넌트에서 날짜를 선택하면

부모 컴포넌트로 선택한 날짜를 전달할게요.


1. Import Output, EventEmitter


자식에서 부모로 이벤트를 전달하는 형태에요.

Output과 EventEmitter 모듈을 사용합니다.

angular/core 모듈 안에 있습니다. 

import { ... Output, EventEmitter } from '@angular/core';
...
@Component({
  selector: 'app-calendar',
  templateUrl: './calendar.component.html',
  styleUrls: ['./calendar.component.css']
})
export class CalendarComponent implements OnInit {
  ...
}


2. Output 정의 및 emit 함수 생성


Input과 비슷한 형태의 어노테이션을 사용합니다.

괄호 안에 부모로 전달할 이벤트 이름을 적습니다.

그리고 EventEmitter 타입으로 초기화했어요.

마지막으로 날짜 선택 이벤트를 부모로 전달할 함수를 만들었습니다.

참고로 selectDay는 날짜 click과 연결된 메서드입니다.

...
@Component({
  ...
})
export class CalendarComponent implements OnInit {
  ...
  @Output('dateChange') dateChange: EventEmitter = new EventEmitter();
  ...
  selectDay(day){    
    ...
    this.dateChange.emit(new Date(this.selectedYear, this.selectedMonth, day));
  }
  ...
}


3. 부모 템플릿에서 Event Binding


자식에서 전달하는 Event를 부모에서 받도록 해줍니다.

이벤트 바인딩과 동일하게 설정하면 됩니다.

데이터는 $event에 담겨 전달됩니다.

<div class="budget">

    <app-calendar [date]="date" 
         (dateChange)="onDateChange($event)"></app-calendar>
    ...
</div>

이벤트를 부모 컴포넌트의 함수와 연결해주었습니다.

전달되는 $event를 콘솔에 찍도록 했죠.

원했던 데이터가 전달되는 것을 확인할 수 있습니다.


Output 동작화면


전체 소스는 아래 주소에서 확인가능합니다.

https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Test Tips (Mock)  (0) 2019.06.01
Angular Dialog(Material Design)  (0) 2019.01.01
Angular Component Communication (Input)  (0) 2018.12.02
Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12

TechTrip IT Tech/Angular

Angular Component Communication (Input)

2018. 12. 2. 22:41

지인 디자이너의 도움으로 디자인을 좀 수정했습니다.

그러다보니 컴포넌트 안에 컴포넌트가 들어갔어요.

Date picker 보다는 달력을 이용하기로 했습니다.

아래 처럼요.


변경 디자인 초안


그리하여 달력 컴포넌트를 만들었습니다.

상위 컴포넌트와 달력이 서로 통신해야하죠.

부모(Parent), 자식(Child) 관계로 부르는데요.


저의 경우에는 아래와 같습니다.

달력을 싸고 있는 컴포넌트가 부모(Parent)

달력 컴포넌트가 자식(Child) 입니다.


부모 -> 자식 방향의 데이터 전달부터 볼게요.

@Input 이라는 annotation을 사용합니다.


자식(Child)입장에서 Input이 되겠죠?

자식 컴포넌트에 먼저 작업을 해줍니다.


1. 자식 컴포넌트에 Import Input


달력 컴포넌트에 Input을 Import 합니다.

@angular/core 모듈에 있어요.


2. 자식 컴포넌트에 @Input 추가


달력 컴포넌트 안에 @Input을 추가합니다.

@Input의 이름을 지정해줄 수 있습니다.

지정하지 않으면 기본 값은 변수명입니다.

이름이 변수명과 같아서 생략해도 됩니다.

저는 명시적으로 적었어요.


코드로 볼게요.

import { Component, Input, ... } from '@angular/core';
...

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

  @Input('date') date: Date;

  ...
}


3. 부모 템플릿에서 데이터 전달


자식(Child) 컴포넌트에 @Input이 추가되었죠.

부모에서 데이터를 넣어줄 차례입니다.

매우 간단합니다.

Template의 Property 바인딩을 이용하면 됩니다.

<div class="budget">
    <app-calendar [date]="date" 
                  (dateChange)="onDateChange($event)">
    </app-calendar>
    ...
</div>

@Input의 date에 부모 컴포넌트의 date를 연결합니다.

주의할 점은 Input에 이름을 지정했을 경우

그 이름이 property 이름이 됩니다.


당연히 부모 컴포넌트에 date가 있어야겠죠.

기본으로 오늘 날짜로 셋팅해주었습니다.

...

@Component({
    selector: 'app-budget',
    templateUrl: './budget.component.html',
    styleUrls: ['./budget.component.css']
})
export class BudgetComponent implements OnInit {    
    date: Date = new Date();
    ...
}

아래처럼 달력 컴포넌트에 오늘 날짜가 표시됩니다.

아직 디자인은 엉성하지만요.


달력 컴포넌트 실행 화면


다음에는 자식 -> 부모로 방향을 알아볼게요.


전체소스는 아래에서 볼 수 있습니다.


소스주소: https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Dialog(Material Design)  (0) 2019.01.01
Angular Component Communication (Output)  (0) 2018.12.16
Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12
Angular Router (NGXS)  (0) 2018.11.05

TechTrip IT Tech/Angular

Angular Test Headless

2018. 11. 18. 20:59

Angular의 테스트가 좀 번거롭게 느껴졌습니다.

매번 Chrome 브라우저가 뜨는 것이 귀찮았죠.


그리고 한 번만 실행하고 싶었습니다.

기본적으로는 코드 변경 시마다 반복하거든요.


그리고 가장 귀찮았던건 진행률...

깔끔하게 표출되지 않습니다.

길고 지저분하게 찍혀요.

에러가 나면 로그 보기도 어렵게말이죠.


가장 귀찮았던 것의 해결방법부터 보죠.


1. Progress 제거


사실 매우 간단하게 해결되었습니다.

ng test에 옵션이 있거든요.

바로 --no-progress 옵션

ng test --no-progress 

위 명령어를 통해 해결!


2. 한 번만 실행


이것도 옵션으로 해결될 줄 알았습니다.

그런데 잘 안되더군요.

문서의 --single-run 이란 옵션

그런데 동작하지 않습니다.

Unknown Option이란 경고만...


그래서 karma.conf.js 파일을 수정합니다.

singleRun이란 속성값을 true로 바꿉니다.

옵션 변경을 통해 해결!


3. 브라우저 실행 안함


두 가지 방법이 있었습니다.

1) Headless Chrome을 사용하는 방법

2) Phantom.js를 이용하는 방법


저는 후자가 마음에 들더라구요.

jest를 이용할 때 Phantom을 써서 그런지..

반가운 마음도 ^^


사용방법은 간단합니다.


1) dependency 설치

npm install --save-dev karma-phantomjs-launcher

위 명령어로 설치해줍니다.


2) karma.conf.js 수정

아래와 같이 변경해주면 됩니다.

아래 코드에 highlight 된 부분이 중요한 부분이에요.

module.exports = function (config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-phantomjs-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage-istanbul-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client:{
      clearContext: false
    },
    coverageIstanbulReporter: {
      dir: require('path').join(__dirname, 'coverage'), 
      reports: [ 'html', 'lcovonly' ],
      fixWebpackSourcePaths: true
    },
    angularCli: {
      environment: 'dev'
    },
    reporters: ['progress', 'kjhtml'],
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: true,
    browsers: ['PhantomJS'],
    singleRun: true,

    phantomjsLauncher: {
      exitOnResourceError: true
    }
  });
};

require('karma-phantomjs-launcher')

browsers: ['PhantomJS']


바로 요 두 부분의 코드입니다.


아래는 깔끔해진 콘솔에서의 테스트 모습이에요.


테스트 실행 화면


브라우저 테스트가 번거로우셨거나

테스트 콘솔이 길어서 불편하셨던 분은

저처럼 바꿔보시는 걸 추천드립니다.


CI 구성하시는 분들은 필수!

*CI(Continuous Integration)


'IT Tech > Angular' 카테고리의 다른 글

Angular Component Communication (Output)  (0) 2018.12.16
Angular Component Communication (Input)  (0) 2018.12.02
Angular Pipes  (0) 2018.11.12
Angular Router (NGXS)  (0) 2018.11.05
Angular Router  (0) 2018.10.31

TechTrip IT Tech/Angular

Angular Pipes

2018. 11. 12. 20:36

Pipe는 매우 간단하지만 효과적인 기능입니다.

Data를 사용자에게 더 잘 보여주는 것이에요.


사실 프로젝트에서 슬쩍 사용하고 있었습니다.

어떤 것이냐구요?

바로 금액을 표시하던 부분!


Angular Pipes 활용 예시


위에 보면 원표시(₩)가 있죠?

단위를 텍스트로 일일이 표시하기 귀찮습니다.


날짜는 어떻구요.

11월 12일, 2018년 이럼 좋지만.

보통 Date 형식의 Data는 어떻죠?

Mon Sep 12 2018 00:00:00 GMT+0900

못생겼습니다.


이것을 보기 좋게 바꾸는 것도 일이죠.

이럴 때 Angular의 Pipe를 이용합니다.


먼저 사용방법은 아래와 같습니다.

<div class="budget-balance">
  <div class="balance-total">
    <span class="balance-title">Balance</span>
    <span class="balance-amount">
      {{balance | currency:'KRW':'symbol'}}</span>
  </div>
  <div class="division-line"></div>
  <div class="budget-total">
    <span class="budget-title">Today's Budget</span>
    <span class="budget-amount">
      {{budget | currency:'KRW':'symbol'}}</span>
  </div>
  <div class="division-line"></div>
</div>

String Interpolation에 pipe를 추가합니다.

{{ 데이터 | Pipe }}

String Interpolation은 초반에 다루었죠?

Angular 데이터 바인딩 I (String Interpolation)


중요한 부분은 Pipe 입니다.

저는 Currency Pipe를 이용했습니다.

파이프이름:설정:설정... 과 같은 구조입니다.

Pipe마다 설정이 다르니 문서를 참조해야합니다.


기본제공되는 파이프는 아래와 같습니다.


DatePipe

예: {{ birthday | date:"MM/dd/yy" }}

UpperCasePipe

예: {{ value | uppercase }} 

LowerCasePipe

예: {{ value | lowercase }} 

CurrencyPipe

예: {{ money | currency:'KRW':'symbol'}} 

PercentPipe

예: {{ 0.259 | percent }}


물론 스스로 pipe를 만들 수도 있습니다.

자주 쓰는건 만들어둬도 좋겠죠?

전 아직 기본만 쓰면 충분하더라구요. ^^


정보출처:

https://angular.io/guide/pipes

'IT Tech > Angular' 카테고리의 다른 글

Angular Component Communication (Input)  (0) 2018.12.02
Angular Test Headless  (0) 2018.11.18
Angular Router (NGXS)  (0) 2018.11.05
Angular Router  (0) 2018.10.31
Angular Forms (Validation)  (0) 2018.10.14

TechTrip IT Tech/Angular

Angular Router (NGXS)

2018. 11. 5. 21:19

Angular의 화면 이동에 대한 부분입니다.

지난 글에서 RouterModule을 다뤄봤는데요.

(Angular Router)


브라우저에 주소를 입력하여 이동했었죠?

오늘은 NGXS의 플러그 인을 사용하겠습니다.


사이트의 이동경로는 중요한 상태중 하나입니다.

그것을 스토어에서 관리하면 장점이 있지요.

사용자가 접속한 주소를 컴포넌트가 알 수 있구요.

어느 경로를 통해 왔는지도 파악 가능합니다.

물론 사이트 내의 이동경로 뿐이지만요.


그럼 코드를 통해 살펴보겠습니다.


1. Import NgxsRouterPluginModule


플러그인을 사용하기 위해 모듈을 Import 합니다.

NgxsRouterPluginModule 입니다.

그리고 @NgModule 의 Imports에 추가합니다.

RouterModule 설정은 그대로 둡니다.

...
import { NgxsRouterPluginModule } from '@ngxs/router-plugin'

const appRoutes : Routes = [
  { path: '', component: BudgetComponent },
  { path: 'config', component: SettingComponent }
]

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


2. Component에 Navigate 메소드 추가


이제 경로 이동을 추가할 컴포넌트로 갑니다.

저는 Header에 경로 이동 코드를 넣겠습니다.

Store와 Navigate 모듈을 Import 하구요.

Store에 Navigate 객체를 dispatch 합니다.

마치 action 동작을 수행하는 것처럼요.

(Angular State Management (NGXS) Code)

...
import { Store } from '@ngxs/store';
import { Navigate } from '@ngxs/router-plugin';

@Component({
  ...
})
export class HeaderComponent implements OnInit {
  constructor(private store: Store) { }
  moveTo(path){
    this.store.dispatch(new Navigate([path]))
  }
}

참! 생성자에 Store 의존성을 주입하구요.

Service를 사용할 때와 동일합니다.

(Angular Service 만들기)

Angular의 강점인 의존성 주입이죠?


3. Template에 method 바인딩


로고 아이콘, 타이틀을 클릭하면 루트로

설정 아이콘을 클릭하면 설정으로 보냅니다.

아래와 같이 click에 바인딩을 해주었습니다.

<mat-toolbar color="primary">
  <mat-icon (click)="moveTo('/')">account_balance</mat-icon>
  <span class="application-title" 
      (click)="moveTo('/')">Daily Account Log</span>
  <span class="fill-remaining-space"></span>
  <mat-icon 
      (click)="moveTo('/config')">settings</mat-icon>
</mat-toolbar>

그러면 로고를 클릭할 때 moveTo가 호출되구요.

path에 따라 화면을 이동합니다.


route 이동 동작화면(ngxs)


동작화면을 보면 깜빡임도 없죠?

<router-outlet> 부분만 다시 rendering 됩니다.

필요한 부분(Component)만 바꿔치기 되는 거죠.


소스주소: https://github.com/jsrho1023/account-book


정보출처: 

https://angular.io/guide/router

https://ngxs.gitbook.io/ngxs/plugins/router

'IT Tech > Angular' 카테고리의 다른 글

Angular Test Headless  (0) 2018.11.18
Angular Pipes  (0) 2018.11.12
Angular Router  (0) 2018.10.31
Angular Forms (Validation)  (0) 2018.10.14
Angular Forms (Simple)  (0) 2018.10.07

TechTrip IT Tech/Angular

Angular Router

2018. 10. 31. 19:40

이번은 새로운 페이지의 추가, 이동 입니다.


웹사이트는 여러 페이지로 이루어져 있습니다.

사용자는 페이지를 이동해가며 사이트를 이용합니다.

Angular에서의 페이지 이동은 Router를 통합니다.


역시 코드와 함께 살펴보겠습니다.


1. Component 생성


새로운 페이지를 구성할 컴포넌트를 생성합니다.

angular-cli 를 이용하여 생성하겠습니다.

(Angular 새로 Component 만들기)


ng g c setting


실행하면 setting 컴포넌트가 생성됩니다.


2. Import Routes, RouterModule


angular 모듈을 import 합니다.

app.module.ts 파일을 수정합니다.

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

간단하죠?


3. Routes, RouterModule 설정


이제 컴포넌트와 주소를 연결해줄 Routes를 만듭니다.

자바스크립트 객체의 배열로 생성하면 됩니다.

{ path: 주소이름, component: 컴포넌트 }

주소이름은 맨 앞의 / 를 생략한 상태로 씁니다.

/는 빈 문자열 /config는 'config' 라고 입력합니다.


그리고 NgModule의 imports에 RouterModule을 추가합니다.

생성한 Routes를 인자로 forRoot 메서드를 호출합니다.

import { Routes, RouterModule } from '@angular/router';
...
const appRoutes : Routes = [
  { path: '', component: BudgetComponent },
  { path: 'config', component: SettingComponent }
]

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

위와 같이 설정하면 아래와 같이 맵핑됩니다.

{서버주소} 는 BudgetComponent

{서버주소}/config 는 SettingComponent



4. Template 변경


이제 주소에 따라 다른 컴포넌트가 보여져야합니다.

따라서 화면에도 변경이 필요하겠죠?

<router-outlet> 이라는 태그를 사용합니다.

이곳에 컴포넌트를 render 하라는 directive 에요.

<div class="mat-app-background">
    <div class="mat-elevation-z4">
        <app-header></app-header>
    </div>
    <router-outlet></router-outlet>
</div>

위와 같이 하면 완성입니다.

주소에 따라 다른 컴포넌트가 보여집니다.


router 동작 화면


사실 위처럼 주소를 외워서 이동하지는 않죠?

다음에는 화면 버튼을 통한 이동을 해볼게요.

로딩하는 하얀 화면 동작도 없어질 겁니다.


소스주소: https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Pipes  (0) 2018.11.12
Angular Router (NGXS)  (0) 2018.11.05
Angular Forms (Validation)  (0) 2018.10.14
Angular Forms (Simple)  (0) 2018.10.07
Angular State Management (NGXS) Code  (0) 2018.09.22

TechTrip IT Tech/Angular

Angular Forms (Validation)

2018. 10. 14. 11:51

이번에는 Angular Forms 모듈을 좀 더 활용해보도록 하죠.

유효성 검증에 대해 다뤄볼까 합니다.


보통은 keyup이나 focusout event를 받아서 처리합니다.

Angular에서는 Validator라는 것을 사용합니다.


0. Component에 Import Validators


Validators 모듈을 가져옵니다.

import { FormControl, Validators } from '@angular/forms';
...

@Component({
  ...
})
export class BudgetComponent implements OnInit{ ... }


1. Form Control에 Validators 추가


Form Control을 생성할 때 Validators를 추가합니다.

import { FormsControl, FormGroup, Validators } from '@angular/forms';
...
@Component({
    ...
})
export class BudgetComponent implements OnInit {
    ...
    consumptionForm = new FormGroup({
        amount: new FormControl('', [
            Validators.required,
            Validators.pattern(/^\d+$/)
        ]),
        desc: new FormControl('')
    });
    ...
}

FormControl 생성자에 2번째 변수로 넣어줬죠.

값을 입력했는지 여부를 required로 검증하구요.

Regular Expression 패턴 검증도 합니다.

amount에 숫자만 들어가는지 검증합니다.


2. Template에 Feedback 추가


유효성에 따라 적절한 Feedback을 줍니다.

<form class="consumption-form" (ngSubmit)="onAdd()" 
[formGroup]="consumptionForm">
  <mat-form-field>
    <input matInput
           formControlName="amount"
           placeholder="Amount" 
           type="text">
      <mat-error 
          *ngIf="consumptionForm.controls.amount.hasError('pattern') 
          && !consumptionForm.controls.amount.hasError('required')">
        <strong>Numbers</strong> only
      </mat-error>
      <mat-error
          *ngIf="consumptionForm.controls.amount.hasError('required')">
        Amount is <strong>required</strong>
      </mat-error>
  </mat-form-field>
  ...
  <button [disabled]="!consumptionForm.valid" 
          mat-stroked-button 
          color="accent" type="submit">Add</button>
</form>

mat-error가 아닌 span이어도 괜찮습니다.

material design 적용을 위한 태그입니다.

중요한 부분은 *ngIf directive 입니다.

(http://dschci.tistory.com/89)

FormControl에 오류가 있을 때만 표시되죠.


그리고 FormGroup도 영향을 받습니다.

FormGroup내 FormControl에 오류가 있다면

FormGroup도 유효하지 않은 상태가 되죠.

그에 따라 버튼을 disable 시켰습니다.


Angular에는 다양한 Validators가 준비되어있습니다.

자주쓰는 email 이나 최소, 최대길이 등이죠.

Validator를 직접 만들수도 있습니다.

boolean을 리턴하는 ValidatorFn을 만들면 되죠.

class Validators {
  static min(min: number): ValidatorFn
  static max(max: number): ValidatorFn
  static required(control: AbstractControl): ValidationErrors | null
  static requiredTrue(control: AbstractControl): ValidationErrors | null
  static email(control: AbstractControl): ValidationErrors | null
  static minLength(minLength: number): ValidatorFn
  static maxLength(maxLength: number): ValidatorFn
  static pattern(pattern: string | RegExp): ValidatorFn
  static nullValidator(control: AbstractControl): ValidationErrors | null
  static compose(validators: (ValidatorFn | null | undefined)[] | null): ValidatorFn | null
  static composeAsync(validators: (AsyncValidatorFn | null)[]): AsyncValidatorFn | null
}


Validation이 적용된 화면은 아래와 같습니다.


동작 화면


작업 중인 소스는 아래에서 확인가능합니다.


소스주소: https://github.com/jsrho1023/account-book


정보출처:

https://angular.io/api/forms/Validators



'IT Tech > Angular' 카테고리의 다른 글

Angular Router (NGXS)  (0) 2018.11.05
Angular Router  (0) 2018.10.31
Angular Forms (Simple)  (0) 2018.10.07
Angular State Management (NGXS) Code  (0) 2018.09.22
Angular State Management (NGXS)  (0) 2018.09.15

TechTrip IT Tech/Angular

Angular Forms (Simple)

2018. 10. 7. 19:43

프론트엔드 개발에서 Form은 필수적으로 사용됩니다.

로그인을 한다든지, 질문이나 댓글을 남기는 등 말이지요.


Angular 역시 Form을 쉽게 개발하도록 돕습니다.

데이터 바인딩, 유효성 검증, 에러 처리 관점에서요.

그럼 코드와 함께 보도록 하겠습니다.


0. Import ReactiveForms Module 


익숙한 과정이죠?

Angular의 모듈을 가져옵니다.

이번에는 Form에 관련된 모듈입니다.


app.module.ts 파일을 편집합니다.

import { FormsModule } from '@angular/forms';
...

@NgModule({
  ...
  imports: [
    ReactiveFormsModule,
... ], ... }) export class AppModule { }


1. Component ts 파일에 form control 추가


이제 컴포넌트 파일에 FormControl을 만들어줍니다.

html(템플릿)에 바인딩하기 위함입니다.

import { FormControl, FormGroup } from '@angular/forms';
...

@Component({
    ...
})
export class BudgetComponent implements OnInit {
    ...
    consumptionForm = new FormGroup({
        amount: new FormControl(''),
        desc: new FormControl('')
    });
    ...
}

보통 form은 여러개가 묶여있죠?

그룹핑 하기 위한 FormGroup도 사용하였습니다.



2. Template html에 form 태그 추가


말씀드렸듯이 html에 form을 추가하고 바인딩합니다.

fromGroup 속성에 컴포넌트의 FormGroup을 바인딩합니다.

익숙한 Property Binding이죠?

(http://dschci.tistory.com/81)

그리고 각각의 input에 FormControl을 할당해줍니다.

formControlName이라는 속성에 이름을 넣습니다.

저는 각각 amount, desc라고 명명했죠.

...
<form class="consumption-form" 
            (ngSubmit)="onSubmit()" 
            [formGroup]="consumptionForm">
  <mat-form-field>
    <input matInput
           formControlName="amount" 
           placeholder="Amount"
           type="text" required>
  </mat-form-field>
  <mat-form-field>
    <input matInput
           formControlName="desc" 
           placeholder="Description" 
           type="text">
  </mat-form-field>
  <button mat-raised-button color="primary" type="submit">Add</button>
</form>
...

material을 위한 태그와 속성은 없어도 동작합니다.

(mat-form-field, matInput)

그럼 이제 마지막으로 submit 동작을 정의해줍니다.


3. Component에 submit 로직 추가


위 html에 보면 submit 타입의 버튼이 있습니다.

그 버튼이 눌렸을 때의 동작입니다.

form의 ngSubmit에 바인딩이 있죠.

ngForm의 ngSubmit Event Binding입니다.

(http://dschci.tistory.com/83)

컴포넌트의 onSubmit 메서드와 연결되어있죠.


아래 코드입니다.

control에 입력된 값에 접근하는 방법입니다.

FormGroup이 컴포넌트에 정의되어있죠?

쉽게 아래와 같이 접근 가능합니다.

...
@Component({
    ...
})
export class BudgetComponent implements OnInit {
    ...
    onSubmit() {
        let amount: number = Number(this.consumptionForm.controls.amount.value)
        let desc: string = this.consumptionForm.controls.desc.value
        ...
    }
}

위는 간단한 바인딩을 통한 Form 사용이었습니다.

동작화면은 FormsModule 기능이 좀 더 사용되었습니다.


Angular Forms 동작 화면


다음에는 데이터 검증 및 에러 피드백에 대해서 써볼게요.


소스주소: https://github.com/jsrho1023/account-book

'IT Tech > Angular' 카테고리의 다른 글

Angular Router  (0) 2018.10.31
Angular Forms (Validation)  (0) 2018.10.14
Angular State Management (NGXS) Code  (0) 2018.09.22
Angular State Management (NGXS)  (0) 2018.09.15
Npm 거슬리는 pacakge-lock.json?  (2) 2018.09.02

TechTrip IT Tech/Angular