32
+ 한장현 [email protected] han41858.tistory.com 1

2016 W3C Conference #4 : ANGULAR + ES6

  • Upload
    -

  • View
    87

  • Download
    1

Embed Size (px)

Citation preview

Page 1: 2016 W3C Conference #4 : ANGULAR + ES6

+

한장현[email protected]

1

Page 2: 2016 W3C Conference #4 : ANGULAR + ES6

• 전 삼성SDS�선임

• TV플랫폼 JavaScript�어플리케이션구현

• 리테일솔루션서버&�프론트엔드구현

• 프리랜서 개발자

• han41858.tistory.com�블로그 운영

• Angular�2�번역서 집필중

• GDG�Korea�Web�Tech�운영진

한장현 (Janghyun Han)

2

Page 3: 2016 W3C Conference #4 : ANGULAR + ES6

3

Page 4: 2016 W3C Conference #4 : ANGULAR + ES6

ngular1.6.0-rc.2 safety-insurance

4

Page 5: 2016 W3C Conference #4 : ANGULAR + ES6

• 마술 같은 2-way binding

• HTML 표준을 기반으로 기능 확장

• 체계적인 컴포넌트, Web Component로 가는 길

• Front-end 전체를 커버하는 프레임워크

• Google 에서 관리, 개선

• 풍부한 사용자 삽질 경험

Angular를 쓰는 이유

5

Page 6: 2016 W3C Conference #4 : ANGULAR + ES6

Class, inheritance

import/export

Arrow function

Promise

ECMAScript 2015

6

Page 7: 2016 W3C Conference #4 : ANGULAR + ES6

• 간단해지는 코드, syntax sugar

• 클래스,상속, 모듈 구성⇒아키텍처 진화

• 브라우저들 지원 계속

• 결국엔 표준, transpiler는 거쳐갈 뿐

ES6를 쓰는 이유

7

Page 8: 2016 W3C Conference #4 : ANGULAR + ES6

8

Page 9: 2016 W3C Conference #4 : ANGULAR + ES6

ES6ES5

script src

<script src="bundle.js"></script>

"devDependencies": {"angular": "^1.5.8","babel-cli": "^6.18.0","babel-loader": "^6.2.7","babel-preset-es2015": "^6.18.0","webpack": "^1.13.3"

}

<script src="angular.js"></script><script src="index.js"></script>

"devDependencies": {"angular": "^1.5.8"

}

9

Page 10: 2016 W3C Conference #4 : ANGULAR + ES6

index.js

ES6ES5

(function () {var ngApp = angular.module('angular1es5', []);

})();

import angular from 'angular';

(() => {const ngApp = angular.module('angular1es6', []);

})();

10

Page 11: 2016 W3C Conference #4 : ANGULAR + ES6

Webpack

// this is ES5var webpack = require('webpack');

module.exports = {entry : [

'./index.js'],output : {

filename : 'build/bundle.js',sourceMapFilename: '[name].map'

},module : {

loaders : [{

test : /\.js$/,loader : 'babel?presets[]=es2015exclude : /node_modules/

},{

test : /\.pug$/,loader : 'pug-loader',exclude : /node_modules/

}]

},plugins : [

// new webpack.optimize.UglifyJsPlugin({minimize: true})]

};

webpack.config.js

"devDependencies": {"angular": "^1.5.8","angular-route": "^1.5.8","babel-cli": "^6.18.0","babel-loader": "^6.2.7","babel-preset-es2015": "^6.18.0","file-loader": "^0.9.0","pug": "^2.0.0-beta6","pug-loader": "^2.3.0","webpack": "^1.13.3","webpack-dev-server": "^1.16.2"

}

package.json

11

Page 12: 2016 W3C Conference #4 : ANGULAR + ES6

export default class HomeCtrl {constructor () {

console.log('HomeCtrl.constructor()');}

}

/view/homeCtrl.js

p this is home

/view/home.pug

import angular from 'angular';import ngRoute from 'angular-route';

import HomeCtrl from './view/homeCtrl';

const main = () => {console.log('main()');

const ngApp = angular.module('angular1es6', ['ngRoute']);

ngApp.config(($routeProvider, $locationProvider) => {console.log('this is angular config');

$routeProvider.when('/', {

template : require('./view/home.pug'),controller : 'HomeCtrl',controllerAs : 'Ctrl'

}).otherwise({

redirectTo : '/'});

// need to angular.js routing$locationProvider.html5Mode({

enabled : true,requireBase : false

});});

ngApp.controller('HomeCtrl', HomeCtrl);};

main();

index.js + ngRoute

12

Page 13: 2016 W3C Conference #4 : ANGULAR + ES6

webpack-dev-server

13

Page 14: 2016 W3C Conference #4 : ANGULAR + ES6

ngApp.directive('CustomDirective', () => new CustomDirective);

ngApp.filter('groupBy', GroupBy);

ngApp.service('CustomSvc', CustomSvc);

ngApp.controller('CustomCtrl', CustomCtrl);

Angular Components

ngApp.directive('CustomDirective', CustomDirective);

ngApp.filter('groupBy', GroupBy);

ngApp.service('CustomSvc', CustomSvc);

ngApp.controller('CustomCtrl', CustomCtrl);

function

Class

new Class

14

Page 15: 2016 W3C Conference #4 : ANGULAR + ES6

Filter

ES6ES5

ngApp.filter('uppercase', uppercase);

function uppercase () {return function (item) {

return item.toUpperCase();};

}

<script src="uppercase.filter.js"></script>

const uppercase = () => {return (input) => {

return input.toUpperCase();};

};

export default uppercase;

import uppercase from './uppercase.filter';

ngApp.filter('uppercase', uppercase);

15

Page 16: 2016 W3C Conference #4 : ANGULAR + ES6

.ctrlRootp this is home ctrlp {{ Ctrl.title }}

button(ng-click="Ctrl.select()") Select

export default class HomeCtrl {constructor () {

console.log('HomeCtrl.constructor()');

this.title = 'this is title';}

select () {console.log('HomeCtrl.select()');

}}

$routeProvider.when('/', {

template : require('./view/home.pug'),controller : 'HomeCtrl',controllerAs : 'Ctrl'

})

ngApp.controller('HomeCtrl', HomeCtrl);

function HomeCtrl ($scope) {console.log('home controller');

$scope.title = 'this is title';

$scope.select = function () {console.log('HomeCtrl.select()');

}}

Controller

ES6ES5

.ctrlRootp this is home ctrlp {{ title }}

button(ng-click="select()") Select

16

Page 17: 2016 W3C Conference #4 : ANGULAR + ES6

Service

ES6ES5

ngApp.service('myService', myService);

<script src="./view/myService.js"></script>

function myService () {this.testFnc = function () {

console.log('myService.testFnc()');};

return this;}

export default class MyService {constructor () {

console.log('MyService');}

testFnc () {console.log('MyService.testFnc()');

}}

ngApp.service('MyService', MyService);

export default class HomeCtrl {constructor (MyService) {

this.MyService = MyService;}

select () {this.MyService.testFnc();

}}

static 있어야 할 것 같지만 없어야 함17

Page 18: 2016 W3C Conference #4 : ANGULAR + ES6

ngApp.directive('myDirective', () => new MyDirective);

export default class MyDirective {constructor () {

console.log('MyDirective.constructor()');

this.restrict = 'E';this.template = '<p>message : {{ this.msg }}</p>';this.scope = {

msg : '@'};

}

link (scope) {console.log(scope.msg);

}}

ngApp.directive('myDirective', myDirective);

function myDirective () {return {

restrict : 'E',template : '<p>message : {{ msg }}</p>',scope : {

msg : '@'},controller : function ($scope) {

console.log('myDirective.controller()');console.log($scope.msg);

}}

}

<script src="myDirective.js"></script>

Directive

ES6ES5

directive 등록할 때 () => new18

Page 19: 2016 W3C Conference #4 : ANGULAR + ES6

Directive vs. Component

19

Page 20: 2016 W3C Conference #4 : ANGULAR + ES6

Directive vs. Component

const customInput = {bindings : {

model : '='},template : '<input ng-model="$ctrl.model"></input>',

controller : function () {this.$onInit = () => {}

}};

export default customInput;

ngApp.component('customInput', customInput);ngApp.directive('customInput', () => new customInput);

export default class customInput {constructor () {

this.restrict = 'E';this.scope = {

model : '='};

this.template = '<input ng-model="model"></input>';}

controller () {}

}

20

Page 21: 2016 W3C Conference #4 : ANGULAR + ES6

static delete (param) {const self = this;

return util.objValidate(param, {userID : Constants.TYPE.EMAIL

}, Constants.ERROR.USER_CTRL.NO_PARAMETER, log, 'delete()').then(param => self.isExists(param)).then(param => {

// delete recordsreturn recordCtrl.deleteAll({

userID : param.userID})

.then(() => {log('remove records ok');// param 자체를 다시 돌려주기 위해 Promise 필요return Promise.resolve(param);

});}).then(param => {

// delete cardsreturn cardCtrl.deleteAll({

userID : param.userID})

.then(() => {log('remove cards ok');return Promise.resolve(param);

});}).then(param => {

// delete assetsreturn assetCtrl.deleteAll({

userID : param.userID})

.then(() => {log('remove assets ok');return Promise.resolve(param);

});}).then(param => {

// delete userreturn User.remove({

userID : param.userID})

.then(() => {return Promise.resolve(param);

}, error => {log(error);return Promise.reject(new ERROR(Constants.ERROR.MONGOOSE.REMOVE_FAILED, log, 'delete()'));

});}).then(param => {

log(`delete ok : ${param.userID}`);return Promise.resolve(true);

});}

Promise

21

Page 22: 2016 W3C Conference #4 : ANGULAR + ES6

ngApp.config(($routeProvider, $locationProvider) => {// include stylesrequire('./view/home.styl');

$routeProvider.when('/', {

template : require('./view/home.pug'),controller : 'HomeCtrl',controllerAs : 'Ctrl'

}).otherwise({

redirectTo : '/'});

});

배포 : webpack

import가 아니므로 require 22

Page 23: 2016 W3C Conference #4 : ANGULAR + ES6

Angular 1 + ES6 + BDD= Hell

23

Page 24: 2016 W3C Conference #4 : ANGULAR + ES6

describe('homeCtrl.test', () => {it('module import', () => {

expect(true).to.be.true;});

});

λ karma start(node:7564) DeprecationWarning: Using Buffer without `new` will soon stop working. Use `new Buffer()`, or preferably `Buffer.from()`, `Buffer.allocUnsafe()` or `Buffer.alloc()` instead.18 11 2016 02:53:45.852:INFO [framework.browserify]: bundle built18 11 2016 02:53:45.941:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/18 11 2016 02:53:45.941:INFO [launcher]: Launching browser Chrome with unlimited concurrency18 11 2016 02:53:45.952:INFO [launcher]: Starting browser Chrome18 11 2016 02:53:47.283:INFO [Chrome 54.0.2840 (Windows 10 0.0.0)]: Connected on socket /#AjAqCwTlrwmVmV_sAAAA with id16857313Chrome 54.0.2840 (Windows 10 0.0.0): Executed 1 of 1 SUCCESS (0.005 secs / 0.001 secs)

BDD 시작

24

Page 25: 2016 W3C Conference #4 : ANGULAR + ES6

λ karma start(node:12196) DeprecationWarning: Using Buffer without `new` will soon stop working. Use `new Buffer()`, or preferably `Buffer.from()`, `Buffer.allocUnsafe()` or `Buffer.alloc()` instead.18 11 2016 02:19:10.237:INFO [framework.browserify]: bundle built18 11 2016 02:19:10.343:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/18 11 2016 02:19:10.343:INFO [launcher]: Launching browser Chrome with unlimited concurrency18 11 2016 02:19:10.353:INFO [launcher]: Starting browser Chrome18 11 2016 02:19:11.676:INFO [Chrome 54.0.2840 (Windows 10 0.0.0)]: Connected on socket /#sBpP4RL0XFZAwPtxAAAA with id 52822107Chrome 54.0.2840 (Windows 10 0.0.0) ERRORUncaught SyntaxError: Unexpected token importat test/homeCtrl.test.js:1

import HomeCtrl from '../view/homeCtrl';

describe('homeCtrl.test', () => {it('module import', () => {

console.log(HomeCtrl);

expect(true).to.be.true;expect(HomeCtrl).to.be.ok;

});});

λ karma start(node:11580) DeprecationWarning: Using Buffer without `new` will soon stop working. Use `new Buffer()`, or preferably `Buffer.from()`, `Buffer.allocUnsafe()` or `Buffer.alloc()` instead.18 11 2016 02:31:24.248:INFO [framework.browserify]: bundle built18 11 2016 02:31:24.339:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/18 11 2016 02:31:24.339:INFO [launcher]: Launching browser Chrome with unlimited concurrency18 11 2016 02:31:24.349:INFO [launcher]: Starting browser Chrome18 11 2016 02:31:25.657:INFO [Chrome 54.0.2840 (Windows 10 0.0.0)]: Connected on socket /#pvpyGrXqq2TZPTgmAAAA with id 30236974LOG: class HomeCtrl { ... }Chrome 54.0.2840 (Windows 10 0.0.0): Executed 1 of 1 SUCCESS (0.007 secs / 0.002 secs)

babel : node6

λ karma start(node:13196) DeprecationWarning: Using Buffer without `new` will soon stop working. Use `new Buffer()`, or preferably `Buffer.from()`, `Buffer.allocUnsafe()` or `Buffer.alloc()` instead.18 11 2016 02:58:38.638:INFO [framework.browserify]: bundle built18 11 2016 02:58:38.728:INFO [karma]: Karma v1.3.0 server started at http://localhost:9876/18 11 2016 02:58:38.729:INFO [launcher]: Launching browser PhantomJS with unlimited concurrency18 11 2016 02:58:38.738:INFO [launcher]: Starting browser PhantomJS18 11 2016 02:58:40.301:INFO [PhantomJS 2.1.1 (Windows 8 0.0.0)]: Connected on socket /#JeRwavdozVZCC8HJAAAA with id 75347319LOG: function HomeCtrl(MyService) { ... }PhantomJS 2.1.1 (Windows 8 0.0.0): Executed 1 of 1 SUCCESS (0.008 secs / 0.001 secs)

babel : es2015

ES6가 돌지 않는다…

+

25

Page 26: 2016 W3C Conference #4 : ANGULAR + ES6

var $httpBackend;

beforeEach(function () {module('angular1es5');inject(function (_$httpBackend_) {

$httpBackend = _$httpBackend_;});

});

module 선언, injection 불가

ES6ES5

const $injector, $httpBackend;

beforeEach(() => {$injector = angular.injector(['angular1es6']);$httpBackend = $injector.get('$httpBackend');

});

Object is not a constructor (evaluating 'module('angular1es6')')r:/temp/test/homeCtrl.test.js:15:9 <-

R:/temp/3871fde1c6cf6c302eeae7add18a3b02.browserify:22:9

26

Page 27: 2016 W3C Conference #4 : ANGULAR + ES6

ngMock vs. ngMockE2E

The ngMock module provides support to inject

and mock Angular services into unit tests. In

addition, ngMock also extends various core ng

services such that they can be inspected and

controlled in a synchronous manner within test

code.

The ngMockE2E is an angular module which

contains mocks suitable for end-to-end testing.

Currently there is only one mock present in this

module - the e2e $httpBackend mock.

ngMock ngMockE2E

Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of applications that use the $http service.

Fake HTTP backend implementation suitable for unit testing applications that use the $http service.

.when()

.expect()

.flush()

.verifyNoOutstandingExpectation()

.verifyNoOutstandingRequest()

.resetExpectations()

.when()

ngMockE2E에는 $location 없음 27

Page 28: 2016 W3C Conference #4 : ANGULAR + ES6

Promise + http.flush()

Chrome 54.0.2840 (Windows 10 0.0.0) homeCtrl.test http test ok FAILEDError: No pending request to flush !

at Function.$httpBackend.flush (node_modules/angular-mocks/angular-mocks.js:1799:34)at r:/temp/test/homeCtrl.test.js:36:17

promise 함수로 부르면 동작 안함flush() 타이밍 달라짐

flush는 Util에서 수행

const $injector = angular.injector(['angular1es6']);const $httpBackend = $injector.get('$httpBackend');const $http = $injector.get('$http');

beforeEach(() => {$httpBackend.expectGET('/test')

.respond(['this', 'is', 'GET', 'test', 'data']);});

it('ok', () => {return new Promise(resolve => {

$http.get('/test').then(result => {console.log('get().then()');console.log(result);console.log(result.data);resolve(true);

});

$httpBackend.flush();});

});

동작하는 코드

static post (uri, param, config) {// use new promise for flush()return new Promise((resolve, reject) => {

this.http.post(uri, param, config).then(result => {

resolve(result);}, error => {

console.error(error);reject(false);

});

if (this.httpBackend && this.httpBackend.flush) {this.httpBackend.flush();

}});

}

it('ok', () => {return new Promise(resolve => {

Promise.resolve().then(() => {

$http.get('/test').then(result => {console.log('get().then()');console.log(result);console.log(result.data);resolve(true);

});});

$httpBackend.flush();});

});

28

Page 29: 2016 W3C Conference #4 : ANGULAR + ES6

여러 test 파일 동시 실행

const $injector = angular.injector(['angular1es6']);const $httpBackend = $injector.get('$httpBackend');const $http = $injector.get('$http');

Chrome 54.0.2840 (Windows 10 0.0.0) SignInCtrl test logic submit() with ajax error response response error test FAILEDAssertionError: expected [Error: Unexpected request: POST /api/user/signInNo more request expected] to be an instance of ERROR

Chrome 54.0.2840 (Windows 10 0.0.0) SignUpCtrl test logic submit() error response response error test FAILEDAssertionError: expected [Error: [$rootScope:inprog] $digest already in progress

const ngApp = angular.module(appName, ['ngMock']); // working with $location, but not $httpBackend.whenPOST()...

var $injector = angular.injector(['MoneyBook']);

const $httpBackend = $injector.get('$httpBackend');const $http = $injector.get('$http');

ClientUtil.http = $http;ClientUtil.httpBackend = $httpBackend;

29

Page 30: 2016 W3C Conference #4 : ANGULAR + ES6

생성자 안에서 Promise

export default class HomeCtrl {constructor () {

console.log('HomeCtrl.constructor()');Promise.resolve(() => {

// do something});

}}

describe('constructor()', () => {it('ok', () => {

// expect what...?});

});

export default class HomeCtrl {constructor () {

console.log('HomeCtrl.constructor()');this.somethingPromise();

}

somethingPromise(){return Promise.resolve()

.then() => {// do something

});}

}

30

Page 31: 2016 W3C Conference #4 : ANGULAR + ES6

Promise + $scope.$apply()

export default class HomeCtrl {constructor () {

console.log('HomeCtrl.constructor()');

this.count = 0;}

select () {console.log('HomeCtrl.select()');

return Promise.resolve().then(() => {

this.count++;});

}}

export default class HomeCtrl {constructor ($scope) {

console.log('HomeCtrl.constructor()');

this.$scope = $scope;

this.count = 0;}

select () {console.log('HomeCtrl.select()');

return Promise.resolve().then(() => {

this.count++;

this.$scope.$apply();});

}}

31

Page 32: 2016 W3C Conference #4 : ANGULAR + ES6

+ =

32