잊지 않겠습니다.

angular2 animation

angularjs2 2016.09.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 xyzlast Y2K

angular2 form

angularjs2 2016.09.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 xyzlast 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 xyzlast Y2K


티스토리 툴바