블로그 이미지
윤영식
Full Stacker, Application Architecter, KnowHow Dispenser and Bike Rider

Publication

Category

Recent Post

2014. 6. 3. 15:23 AngularJS/Start MEAN Stack

Angular Team에서 End-To-End 테스트를 위하여 별도로 개발한 프레임워크가 Protractor 이다. protractor는 WebDriverJs기반으로 만들어 졌다. 





Protractor 설정

  - Chrome 최신 버전 사용

  - protractor 설치 및 Chrome에서 selenium standalone server로 구동하기 위한 driver도 설치한다 

$ npm install -g protractor

$ webdriver-manager update 


  - selenium server를 기동하는 방법 : 4444 port로 listen한다 ( 호출 :  http://localhost:4444/wd/hub )

$ webdriver-manager start

seleniumProcess.pid: 55938

6월 02, 2014 5:08:16 오후 org.openqa.grid.selenium.GridLauncher main

정보: Launching a standalone server

Setting system property webdriver.chrome.driver to /usr/local/lib/node_modules/protractor/selenium/chromedriver

17:08:16.913 INFO - Java: Oracle Corporation 24.51-b03

17:08:16.915 INFO - OS: Mac OS X 10.9.3 x86_64

17:08:16.935 INFO - v2.41.0, with Core v2.41.0. Built from revision 3192d8a

17:08:17.079 INFO - Default driver org.openqa.selenium.ie.InternetExplorerDriver registration is skipped: registration capabilities Capabilities [{platform=WINDOWS, ensureCleanSession=true, browserName=internet explorer, version=}] does not match with current platform: MAC

17:08:17.145 INFO - RemoteWebDriver instances should connect to: http://127.0.0.1:4444/wd/hub

17:08:17.146 INFO - Version Jetty/5.1.x

17:08:17.148 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]

17:08:17.149 INFO - Started HttpContext[/selenium-server,/selenium-server]

17:08:17.149 INFO - Started HttpContext[/,/]

17:08:17.510 INFO - Started org.openqa.jetty.jetty.servlet.ServletHandler@4b8e282c

17:08:17.510 INFO - Started HttpContext[/wd,/wd]

17:08:17.517 INFO - Started SocketListener on 0.0.0.0:4444

17:08:17.517 INFO - Started org.openqa.jetty.jetty.Server@3e578b06


  - 잘 동작하지 않는다면 Path에 JAVA_HOME을 설정한다. 



Protractor 환경 설정

  - 루트에 protractor-e2e.conf.js 파일 생성

  - 설정 : baseUrl은 grunt serve 시에 기동되는 port 번호

exports.config = {

  seleniumAddress: 'http://localhost:4444/wd/hub',

  capabilities: {'browserName': 'chrome'},

  specs: ['test/client/e2e/**/*.js'],

  baseUrl: 'http://localhost:9000',

  jasmineNodeOpts: {

    onComplete: null,

    isVerbose: true,

    showColors: true,

    includeStackTrace: true,

    defaultTimeoutInterval: 10000

  },

  framework: 'jasmine'

};

  - 체크

$ webdriver-manager start 

$ protractor protractor-e2e.conf.js

Using the selenium server at http://localhost:4444/wd/hub

[launcher] Running 1 instances of WebDriver


Finished in 0.001 seconds

0 tests, 0 assertions, 0 failures


[launcher] chrome passed

  - grunt를 이용하여 테스트 할 수 있도록 Gruntfile.js 에 설정. run: {..} 안의 options에 configFile 설정이 중요함 

$ npm install grunt-protractor-runner --save-dev


// grunt.initConfig 안에 설정

  protractor: {

      options: {

        keepAlive: true

      },

      run: {

        options: {

          configFile: "protractor-e2e.conf.js", // Target-specific config file

          args: {} // Target-specific arguments

        }

      }

  },


// task 설정

  grunt.registerTask('test', function(target) {

    if (target === 'server') {

      return grunt.task.run([

        'env:test',

        'mochaTest'

      ]);

    }


    else if (target === 'client') {

      return grunt.task.run([

        'concurrent:test',

        'autoprefixer',

        'karma',

        'protractor'

      ]);

    }


    else grunt.task.run([

      'test:server',

      'test:client'

    ]);

  });


// grunt test 에 첨부 

$ grunt test 또는 grunt test:client 수행 (단, 수행전에 webdriver-manager start 수행해 놓은 상태이어야함)


Running "protractor:run" (protractor) task

Using the selenium server at http://localhost:4444/wd/hub

[launcher] Running 1 instances of WebDriver


Finished in 0 seconds

0 tests, 0 assertions, 0 failures

[launcher] chrome passed

Done, without errors.


Execution Time (2014-06-02 15:43:51 UTC)

protractor:run  2.2s  ▇▇ 100%

Total 2.3s

  - 또는, grunt 설정을 하지 않았을 경우는 다음과 같이도 테스트 가능하다. 하지만 가급적 grunt를 이용하자.  

$ webdriver-manager start

$ protractor protractor-e2e.conf.js



End-to-End 테스트 코드 작성방식

  - 테스트 코드를 작성하기 위한 몇가지 개념을 익힌다. (위키)

> browser : webdriver를 감싼 객체. navigation 과 page information 을 갖음

> element : html element를 찾고 상호작용을 위한 helper method 

> by : element locator collection

> protractor : webdriver namespace를 wrapping한 protractor namespace


  - 샘플 코드 

    + browser.get : page loading 

    + element : page에서 element를 찾아줌 

    + by 종류 (소스 참조)

  • by.binding searches for elements by matching binding names, either from ng-bind or {{}} notation in the template.
  • by.model searches for elements by input ng-model.
  • by.repeater searches for ng-repeat elements. For example, by.repeater('phone in phones').row(11).column('price')returns the element in the 12th row (0-based) of the ng-repeat = "phone in phones" repeater with the binding matching{{phone.price}}.

describe('angularjs homepage', function() { it('should greet the named user', function() { // Load the AngularJS homepage. browser.get('http://www.angularjs.org'); // Find the element with ng-model matching 'yourName' - this will // find the <input type="text" ng-model="yourName"/> element - and then // type 'Julie' into it. element(by.model('yourName')).sendKeys('Julie'); // Find the element with binding matching 'yourName' - this will // find the <h1>Hello {{yourName}}!</h1> element. var greeting = element(by.binding('yourName')); // Assert that the text element has the expected value. // Protractor patches 'expect' to understand promises. expect(greeting.getText()).toEqual('Hello Julie!'); }); });


실제 End-to-End 테스트 코드

  - 로그인의 경우 e2e 코드 작성예 (필수참조)

'use strict';


var LoginPage = function() {

this.email = element(by.model('user.email'));

this.password = element(by.model('user.password'));


this.setEmail = function(email) {

this.email.sendKeys(email);

};


this.setPassword=  function(password) {

this.password.sendKeys(password);

};


this.url = function() {

browser.get('/#/signin');

};


this.login = function() {

return element(by.buttonText('button')).click();

};

};


describe('Login Page', function() {

it('should successfully login', function(){

var loginPage = new LoginPage();

loginPage.url();

loginPage.setEmail('test@test.com');

loginPage.setPassword('test');

loginPage.login().then(function(response){

console.log(response);

}, function(error){


})

});

});


  - 수행 : grunt test:client 

    + 수행 에러 발생함 : to be continue...



<참조>

  - Protractor 테스트 방법

  - Protractor Test PPT (필독)

  - Unit, E2E ToDo 예제

  - Protractor Grunt 설정하기

  - End-To-End Test 하기 예제

  - grunt-protractor-runner 

  - Angular Test Pattern

posted by 윤영식
2014. 6. 2. 10:09 AngularJS/Start MEAN Stack

프로트앤드 개발을 하면서 테스트는 어떻게 할 수 있는지 알아본다. 앵귤러에서는 테스트 관련 내용까지도 포함하고 있어서 개발 테스트의 편의성을 함께 제공하고 있다. 


사전 준비

  generator-angular를 설치하게 되면 앵귤러 프레임워크에 카르마(Karma) 테스트 프레임워크와 Mock 객체등이 포함되어 있다. 과거에는 Karma를 통해서 단위(Unit) 테스트와 E2E (End-To-End) 테스트 두가지를 하였으나, 최근에는 Karma로는 단위 테스트만을 수행하고 Protractor라는 새로운 테스트 프레임워크를 통해서 E2E 테스트를 수행한다 


  - karma.conf.js : Unit Test 환경파일. 내부적으로 Jasmine (http://pivotal.github.io/jasmine/) 프레임워크를 사용한다

  - karma-e2e.conf.js : E2E Test 환경파일 (

  - "grunt test" 를 통해 수행


Karma가 Jasmine 테스트 프레임워크를 사용하고, 필요한 files 목록을 정의하며 Chrome 브라우져에서 8080 포트를 사용하여 테스트함. angular.js 와 angular-mocks.js 파일은 필 첨부 (angular-mocks.js 안에 module, inject 펑션 존재)

// karma.conf.js

// Karma configuration

// http://karma-runner.github.io/0.10/config/configuration-file.html


module.exports = function(config) {

  config.set({

    // base path, that will be used to resolve files and exclude

    basePath: '',


    // testing framework to use (jasmine/mocha/qunit/...)

    frameworks: ['jasmine'],


    // list of files / patterns to load in the browser

    files: [

      'app/bower_components/angular/angular.js',

      'app/bower_components/angular-mocks/angular-mocks.js',

      'app/bower_components/angular-resource/angular-resource.js',

      'app/bower_components/angular-cookies/angular-cookies.js',

      'app/bower_components/angular-sanitize/angular-sanitize.js',

      'app/bower_components/angular-route/angular-route.js',

      'app/scripts/*.js',

      'app/scripts/**/*.js',

      'test/mock/**/*.js',

      'test/spec/**/*.js'

    ],


    // list of files / patterns to exclude

    exclude: [],


    // web server port

    port: 8080,


    // level of logging

    // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG

    logLevel: config.LOG_INFO,


    // enable / disable watching file and executing tests whenever any file changes

    autoWatch: false,

    // Start these browsers, currently available:

    // - Chrome

    // - ChromeCanary

    // - Firefox

    // - Opera

    // - Safari (only Mac)

    // - PhantomJS

    // - IE (only Windows)

    browsers: ['Chrome'],


    // Continuous Integration mode

    // if true, it capture browsers, run tests and exit

    singleRun: false

  });

};


  Gruntfile.js 의 test Task 내역 : grunt test 

// Test settings

karma: {

   unit: {

      configFile: 'karma.conf.js',

      singleRun: true

  }

}


... 중략 ..

grunt.registerTask('test', [

    'clean:server',

    'concurrent:test',

    'autoprefixer',

    'connect:test',

    'karma'

  ]);



1. Jasmine 테스트 프레임워크 

  describe ~ it 으로 BDD를 표현한다. BDD는 TDD의 2세대 버전 정도로 이해하자. 보다 자세한 사항은 위키 참조 (http://en.wikipedia.org/wiki/Behavior-driven_development

 describe("A suite", function() {

  it("contains spec with an expectation", function() {

    expect(true).toBe(true);

  });

});


 describe로 시작하는 것을 Suite (슈트)라고 부르고, it 으로 시작하는 것을 spec (스펙)이라고 부른다. 슈트안에 여러개의 스펙이 있을 수 있다. beforeEach(), afterEach()를 통해서 스펙 수행시 마다 환경 초기값 셋업(Setup)이나 마무리 작업(TearDown)을 수행할 수 있다. 스펙 안에는 expect 기대값에 대한 다양한 메서드를 제공한다.

toEqual()

toBe(), not.toBe()

toBeTruthy(), toBeFalsy()

toContain(), not.toContain()

toBeDefined(), toBeUndefined()

toBeNull()

toBeNaN()

toBeGreaterThan()

toBeLessThan()

toBeCloseTo() : 소수점

toMatch() : 정규표현식

toThrow()

 ...


  보다 자세한 사항은 Jasmine 문서를 참조한다 (http://jasmine.github.io/2.0/introduction.html)



2. 앵귤러 Service 테스트 준비

  yo를 통하여 앵귤러 코드를 완성하게 되면 자동으로 테스트 코드까지 생성된다. SessionService에 대한 테스트 코드는 /test/spec/services 폴더 밑에 동일한 session-service.js 파일이 존재한다. module 호출 후에 inject 가 올 수 있으나 inject 후에 module 호출은 올 수 없다. _ (underscore)를 양쪽에 붙이는 것은 angular프레임워크에 테스트를 위하여 해당 서비스를 사용함을 알려준다. 

describe('Service: SessionService', function () {

  // load the service's module

  beforeEach(module('meanstackApp'));


  // instantiate service

  var SessionService;

  beforeEach(inject(function (_SessionService_) {

    SessionService = _SessionService_;

  }));


  it('should do something', function () {

    expect(!!SessionService).toBe(true);

  });


});

 

 grunt test 명령을 수행하면 에러가 발생할 것이다. 그것은 index.html에 필요에 의해 추가한 .js파일을 Karma 환경파일에 추가하지 않았기 때문이다. controller 쪽에서도 에러가 발생하는데 기본 테스트 코드에서 발생하므로 불필요한 코드를 삭제한다. 

 // karma.conf.js 

  files: [

      'app/bower_components/jquery/jquery.js',

      'app/bower_components/bootstrap/dist/js/bootstrap.js',

      'app/bower_components/angular/angular.js',

      'app/bower_components/angular-mocks/angular-mocks.js',

      'app/bower_components/angular-resource/angular-resource.js',

      'app/bower_components/angular-cookies/angular-cookies.js',

      'app/bower_components/angular-sanitize/angular-sanitize.js',

      'app/bower_components/angular-route/angular-route.js',

      'app/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',

      'app/bower_components/angular-ui-router/release/angular-ui-router.js',

      'app/scripts/*.js',

      'app/scripts/**/*.js',

      'test/mock/**/*.js',

      'test/spec/**/*.js'

    ],


  singleRun: true


// Controller 테스트 파일에서 불필요한 하기 코드 삭제 

  it('should attach a list of awesomeThings to the scope', function () {

    expect(scope.awesomeThings.length).toBe(3);

  });



3. 앵귤러 Service 테스트를 위한 Mock 사용

  SessionService 테스트 수행전에 공통적으로 사용하고 있는 Factory나 Service에 대한 테스트 코드를 작성한 후 SessionService를 최종 테스트 한다  


  - msRequestFactory : 원하는 JSON 객체가 생성되는지 테스트 

  - msRestfulApi : $httpBackend 이용하여 요청 처리 테스트

  - SessionService : $httpBackend 이용하여 요청 처리하고 SessionInfo의 정보 유무 테스트 


msRequestFactory 통하여 만들어진 JSON 객체가 원하는 key:value 값을 가지고 있는지 확인한다. expect().toEqual()을 사용한다 

 'use strict';


describe('Service: msRequestFactory', function () {


  // load the service's module

  beforeEach(module('meanstackApp'));


  // instantiate service

  var msRequestFactory;

  beforeEach(inject(function ($injector) {

    msRequestFactory = $injector.get('msRequestFactory');

  }));


  it('should exist', function () {

    expect(!!msRequestFactory).toBe(true);

  });


  it('should make JSON', function () {

    var request = msRequestFactory.createRequest('user', 'login', '');

    expect(request).toEqual({

      'area' : 'user',

      'resource' : 'login',

      'id' : '',

      'request' : {}

    });

  });


});

  

msRestfulApi는 $resource를 통해 서버에 요청을 보낸다. 서버 요청에 대한 Mock을 만들어서 응답을 줄 수 있는 기능으로 앵귤러는 $httpBackend를 제공한다. 즉, 실서버가 없을 경우 XHR 또는 JSONP 요청없이도 실서버에 요청한 것과 같은 응답을 줄 수 있다. $httpBackend의 유형은 두가지 이다


  - request expectation : 요청 JSON 데이터의 검증 원하는 결과가 왔는지 설정. expect().respond()

  - backend definition : 가상의 응답정보를  설정. when().respond()


서버의 요청은 모두 async 처리가 되므로 테스트코드 작성하기가 애매하다. 따라서 $httpBackend.flush()를 호출하는 시점에 request가 전달되므로, 테스트 코드 작성시 적절한 위치에 놓아야 한다. API 참조 (http://docs.angularjs.org/api/ngMock.$httpBackend)

// 로그인 서비스 테스트에서 계속 에러가 발생하여 Re-Thinking 중이다. 

// 에러 코드 

'use strict';


describe('Service: msRestfulApi', function () {


  // load the service's module

  beforeEach(module('meanstackApp'));


  // instantiate service

  var msRestfulApi, httpBackend, scope, resource;

  beforeEach(inject(function ($injector) {

    msRestfulApi = $injector.get('msRestfulApi');

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

    scope = $injector.get('$rootScope');

    resource = $injector.get('$resource');


    httpBackend.when('POST', '/api/v1/user/login').respond(200, {'id': 1});

    

  }));


  it('should call test', function() {

    var request = {

      'area' : 'user',

      'resource' : 'login',

      'id' : '',

      'request' : {}

    };


    msRestfulApi.login(request, function(response){

      expect(response.id).toBe(1);

      console.log(response);

    }, function(error){

      console.log(error);

    });


    httpBackend.flush();

    expect(r.id).toBe(1);


  });


});

  


<참조>

  - Jasmine 문법

  - Jasmine 소개

  - Karma 테스트 하기

  - Protractor Seed 소개 : Protractor WebDriver 업데이트 

  - WebDriverJS 소개 

posted by 윤영식
2014. 5. 28. 21:20 AngularJS/Start MEAN Stack

다국어 처리를 Angular.js 기반인 클라이언트단에서 처리하는 방법에 대해 알아본다. angular-translateangular-gettext 두가지가 존재하는데 여기서는 angular-gettext를 살펴보도록 한다 



준비사항

  - 먼저 poedit을 설치한다. 언어 번역을 일괄적으로 관리할 수 있는 툴이다. 기본 영문에서 한글 번역내역을 입력하여 언어별로 .po 파일을 관리한다 

  - angular-gettext 모듈을 사용한다. --save 옵션넣어서 bower.json 업데이트 한다. 인스톨 가이드를 참조한다 

$ bower install angular-gettext --save

  - grunt-angular-gettext 를 설치한다. --save-dev 옵션. 해당 모듈은 .po파일을 생성하거나 생성된 내용을 angular 자바스크립트로 변환을 담당한다 

$ npm install grunt-angular-gettext --save-dev



Grunt Config 수정

  - grunt-angular-gettext를 통하여 poedit에서 읽을 수 있는 원본 파일(.pot)을 생성하고, 원본에서 다른 언어의 번역을 넣은 파일(.po)을 읽어서 angular 형식의 자바스크립트 파일을 생성한다. 

  - Gruntfile.js 첨부내역

    + nggettext_extract : 다국어 지원이 필요한 .html을 읽어서 원본 origin_template.pot 파일 생성 위치와 파일명 지정

    + nggettext_compile : 다국어 .po 파일을 읽어서 angular 형식의 파일을 만들 위치와 파일명 지정. 

                                   별도의 모듈로 gettext_translation을 적용한다 

    // Translation for multi-lang

    nggettext_extract: {

      pot: {

        files: {

          'app/translation/po/origin_template.pot': [

            'app/index.html',

            'app/views/**/*.html',

            'app/domain/**/*.html'

          ]

        }

      },

    },

    nggettext_compile: {

      all: {

        options: {

          module: 'gettext_translation'

        },

        files: {

          'app/translation/translation.js': ['app/translation/po/*.po']

        }

      },

    }, 



다국어 지원 자바스크립트 생성 작업

  - html 수정 : 다국어 지원이 필요한 부분에 translate 애트리뷰트를 넣는다. 한줄은 <span></span> 태그를 사용한다

    <form name="signupForm" novalidate>

        <div class="session-signup-main-subject">

            <span translate>Create an Account</span>

        </div>

        <div class="session-signup-name-subject">

            <span translate>Name</span> 

            <span style="color:red" ng-show="focusName" translate> @Please input your full name.</span>

        </div>

        <input type="text" name="user_name" placeholder="{{'Enter Full Name'|translate}}" class="session-signup-name-input" ng-model="user.name" required sg-focus="focusName">

        <div class="session-signup-email-subject">

            <span translate>Email</span> 

            <span style="color:red" ng-show="focusEmail" translate> @Please input your email.</span>

        </div>

        <input type="email" name="user_email" placeholder="{{'Enter Email'|translate}}" class="session-signup-email-input" ng-model="user.email" required sg-focus="focusEmail">

        <div class="session-signup-password-subject">

            <span translate>Password</span> 

            <span style="color:red" ng-show="focusPassword" translate> @Please input your password.</span>

        </div>

        <input type="password" name="user_password" placeholder="{{'Enter Password'|translate}}" class="session-signup-password-input" ng-model="user.password" required sg-focus="focusPassword">

        <div class="session-signup-have">

            <span translate>Have an account?</span>

        </div>

        <a ui-sref="signin" class="session-signup-login-btn">

            <span translate>Log-in</span>

        </a>

        <button class="session-signup-create-btn" ng-click="signup(signupForm)">

            <div class="session-signup-create-btn-text">

                <span translate>Create New Account</span>

            </div>

        </button>

    <form> 


  - grunt를 통해서 html의 translate 을 해석하여 origin_template.pot파일을 생성한다

$ grunt nggettext_extract


  - origin_template.pot 파일을 poedit로 import 한다. 한글을 언어로 설정하여 번역을 한후, ko_KR.po 파일을 생성한다.

    + 새 번역 만들기 클릭 후 origin_template.pot파일 선택한 후 번역 언어 선택함  

    

    + 번역을 입력하고 "다른 이름으로 저장하기..."를 선택하여 ko_KR.po 파일을 만든다 

    


  - 번역된 ko_KR.po 파일 내역을 translate.js 파일로 만들기 

$ grunt nggettext_compile 


// ko_KR.po 파일 내역

msgid ""

msgstr ""

"Project-Id-Version: \n"

"POT-Creation-Date: \n"

"PO-Revision-Date: 2014-05-27 16:20+0900\n"

"Last-Translator: \n"

"Language-Team: \n"

"Language: ko_KR\n"

"MIME-Version: 1.0\n"

"Content-Type: text/plain; charset=UTF-8\n"

"Content-Transfer-Encoding: 8bit\n"

"X-Generator: Poedit 1.6.5\n"

"X-Poedit-Basepath: .\n"

"Plural-Forms: nplurals=1; plural=0;\n"


#: app/domain/session/signup/signup.html

msgid "@Please input your email."

msgstr "@이메일주소를 입력해 주세요."


#: app/domain/session/signup/signup.html

msgid "@Please input your full name."

msgstr "@전체 이름을 입력해 주세요."


#: app/domain/session/signup/signup.html

msgid "@Please input your password."

msgstr "@전체 패스워드를 입력해 주세요."


#: app/domain/session/signup/signup.html

msgid "Create New Account"

msgstr "신규 계정 생성"


#: app/domain/session/signup/signup.html

msgid "Create an Account"

msgstr "계정 생성"


#: app/domain/session/signup/signup.html

msgid "Email"

msgstr "이메일"


#: app/domain/session/signup/signup.html

msgid "Enter Email"

msgstr "이메일 입력"


#: app/domain/session/signup/signup.html

msgid "Enter Full Name"

msgstr "이름 입력"


#: app/domain/session/signup/signup.html

msgid "Enter Password"

msgstr "패스워드 입력"


#: app/domain/session/signup/signup.html

msgid "Have an account?"

msgstr "계정이 있나요?"


#: app/domain/session/signup/signup.html

msgid "Log-in"

msgstr "로그인"


#: app/domain/session/signup/signup.html

msgid "Name"

msgstr "이름"


#: app/domain/session/signup/signup.html

msgid "Password"

msgstr "패스워드"



// translate.js 파일로 ko_KR.po 파일을 기반으로 생성된 내역 

angular.module('gettext_translation').run(['gettextCatalog', function (gettextCatalog) {

/* jshint -W100 */

    gettextCatalog.setStrings('ko_KR', {"@Please input your email.":"@이메일주소를 입력해 주세요.","@Please input your full name.":"@전체 이름을 입력해 주세요.","@Please input your password.":"@전체 패스워드를 입력해 주세요.","Create New Account":"신규 계정 생성","Create an Account":"계정 생성","Email":"이메일","Enter Email":"이메일 입력","Enter Full Name":"이름 입력","Enter Password":"패스워드 입력","Have an account?":"계정이 있나요?","Log-in":"로그인","Name":"이름","Password":"패스워드"});

/* jshint +W100 */

}]);


  - 별도의 모듈 gettext_translation을 생성하고 index.html에 파일들을 추가한다  

// translationModule.js 을 생성

angular.module('gettext_translation', []); 


// index.html에 <script>도 추가한다 

<script src="bower_components/angular-gettext/dist/angular-gettext.js"></script>

<script src="translation/translationModule.js"></script>

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


  - 메인 애플리케이션인 App.js 에 gettext와 gettext_translation모듈을 추가하고, run 메소드에 언어 설정을 한다

var App = angular.module('studyGpsApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ui.router',

  'ui.bootstrap',

  'restangular',

  'gettext',

  'gettext_translation'

]);


App.run(function ($rootScope, gettextCatalog) {

    //translate for multi-lang

    $rootScope.setLang = function(lang, isDebug) {

      if(lang) {

        gettextCatalog.currentLanguage = lang;

      } else {

        gettextCatalog.currentLanguage = 'ko_KR';

      }

      gettextCatalog.debug = isDebug;

    }

    // init

    $rootScope.setLang('ko_KR', true);


  - 결과화면

  



지속적인 번역 파일 만들기 

  - 만일 화면이 추가되어 신규 번역이 필요할 경우 다음과 같이 수행을 하면 추가된 부분만 따로 설정 할 수 있다. 

  - poedit을 사용하게 되면 html 마다 반복적으로 사용되는 용어의 중복을 unique하게 관리 할 수 있고, 별도의 .js 코딩을 할 필요가 없다.

  - 많은 언어를 관리해야 한다면 poedit을 통해 관리 편의성을 얻을 수 있다

// 먼저 origin_template.pot 파일을 생성한다 

$ grunt nggettext_extract


// poedit 에서 ko_KR.po 파일을 open 한 후에 다음의 메뉴를 선택하고 origin_template.pot를 선택하면 새롭게 추가된 번역할 내역이 자동으로 ko_KR.po 파일에 추가되어 나온다 

// 이제 <언어별>.po 파일들을 translate.js 파일로 만든다. 

$ grunt nggetext_compile 


* angular-translate 모듈이 있는데 해당 모듈은 html 화면에 기본 랭귀지가 아닌 KEY값을 넣어서 바인딩해야 하는 번거로움이 존재하고, poedit와 같은 도구를 통한 번역의 편리성을 제공하지 않는다. 



Gulp를 사용할 때

  - gulp용의 angular-gettext를 설치한다. 물론 gulp도 설치한다. 

$ npm install gulp 

$ npm install gulp-angular-gettext

  

  - gulpfile.js 의 설정 내역

    + gulp-rename 플러그인을 이용해서 파일명을 바꾼다. 

    + translation 할때는 format을 javascript로 한다. 포맷은 json과 javascript가 있다.

var rename = require('gulp-rename');

var gettext = require('gulp-angular-gettext');


gulp.task('lang', ['extract', 'translation']);

gulp.task('extract', function() {

  return gulp.src(['./www/app/**/*.html'])

             .pipe(gettext.extract('origin_template.pot', {}))

             .pipe(gulp.dest('./www/lib/common/translation/po/'));

});


gulp.task('translation', function() {

  return gulp.src('./www/lib/common/translation/po/*.po')

             .pipe(gettext.compile({

                module: 'mb.translation',

                format: 'javascript'

             }))

             .pipe(rename('lib/common/translation/translation.js'))

             .pipe(gulp.dest('./www'));

});




<참조>

  - AngularJS Multi-Language Support

  - AngularJS GetText Homepage

  - Grunt Angular GetText GitHub

  - angular-translate Hompage


posted by 윤영식
2014. 2. 4. 10:43 AngularJS/Start MEAN Stack

메뉴의 우측에 로그인을 생성하고 로그인 화면을 트위터 부트스트랩을 이용하여 만들고 로그인 처리를 한다. 


사전 준비 

  트위터 부트스트랩을 이용하여 먼저 화면 프로토 타입을 만든다. 그리고 앵귤러 Controller를 코딩한 후 서버와 통신을 위한 앵귤러 Service를 만들자. 그리고 필요에 따라 Framework 요소로 필요한 것을 만들어 간다. 하나의 업무처리를 위하여 HTML+Controller+Service 를 기억억한다.  


  - 비즈니스 HTML View 작성

  - 비즈니스 Controller 코딩

  - 비즈니스 Service 코딩



1. 로그인 메뉴 추가하기

  메뉴는 menu.html 로 별도 구성하였고, 맨 하단에 로그인이 안되었을 때와 로그인이 되었을 때를 나타내는 <li> 태그를 넣어준다. currentUser는 로그인 성공후 $rootScope에 저장되는 사용자 객체이다. <li> 태그는 ng-hide/ng-show에의해 보여졌다 안보여졌다 한다.

     ... 중략 ...

          <li class="divider"></li>

          <li class="dropdown-header">Members</li>

          <li data-ng-class="{ active: $state.includes('gurumember') }">

            <a ui-sref="gurumember"><i class="fa fa-users "></i> 멤버소개</a>

          </li>

        </ul>

      </li>

    </ul>


    <ul class="nav navbar-nav navbar-right">

      <li ng-hide="false">

        <a href="#" ui-sref="login">Login</a>

      </li>

      <li ng-show="false">

        <a href="#" ng-click="logout()">

          [{{ currentUser.name }}] Logout

        </a>

      </li>

    </ul>


  </div>

</div>



2. 로그인 화면 구성

  ng-hide="false" 이므로 현 상태에서는 "Login" 메뉴가 나오고, 메뉴 클릭시 ui-sref="login" 에 따라 views/login.html 화면으로 이동을 한다.  Bootsnipp에서 login 을  (http://bootsnipp.com/search?q=login) 검색하면 다양한 로그인 창 디자인이 나오므로 활용해 본다.


  - 로그인을 할 때 컨트롤러에서 처리할 메서드를 정의한다 

    submitLogin()

  - 정보 객체를 정의한다  

    login = { email: 'xxx', password: 'xxx' }

  - type 과 required 지정을 통해 필수항목 유효성 체크를 한다 

<div class="row">

  <div class="col-md-4 col-md-offset-4">

    <h3 class="text-center">LOGIN</h3>

    <form name="loginForm" ng-submit="submitLogin()">

      <div class="form-group">

        <div class="input-group">

          <span class="input-group-addon">

            <i class="fa fa-envelope-o"></i>

          </span>

          <input id="login_email" type="email" name="email" class="form-control input-lg" placeholder="Email" ng-model="login.email" required>

        </div>

      </div>


      <div class="form-group">

        <div class="input-group">

          <span class="input-group-addon">

            <i class="fa fa-asterisk"></i>

          </span>

          <input id="login_password" type="password" name="password" class="form-control input-lg" placeholder="Password" ng-model="login.password" required>

        </div>

      </div>


      <button type="submit" class="btn btn-primary btn-lg btn-block btn-shadow">Login</button>

    </form>

  </div>

</div>


  우측 상단의 "Login" 메뉴 클릭시 출력되는 최종화면이다 



3. 로그인 앵귤러 Controller 개발 

  로그인 화면에 대한 처리를 하는 컨트롤러는 LoginCtrl (app/scripts/controllers/login.js) 이다. 앵귤러의 컨트롤러는 뷰와 모델 객체를 주고 받으며 처리하는 역할만을 담당하도록 만든다. 즉, 서버와 연결하고 처리하는 것은 앵귤러 서비스를 만들고 주입(DI)을 받아 사용한다. 로그인이 성공했다고 가정하여 UI-Router의 $state를 이용하여 main 화면으로 이동하는 코드를 넣었다. 

 'use strict';


angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  function ($scope, $state) {

   $scope.submitLogin = function() {

    console.log($scope.login);

     $state.go("main");

   }

  }]);



로그인 앵귤러 Service 개발 

  로그인 정보를 받아서 서버와 통신하는 모듈을 만들도록 한다. Node.js와 RESTful 방식으로 요청을 처리한다. 앵귤러에서는 HTTP 요청 처리를 위하여 $http와 이를 보다 추상화한 $resource를 제공한다. 하지만 클라이언트와 서버사이의 통신을 위하여 몇가지 공통모듈을 만들 필요가 있다. 


  - 서버와 주고받는 요청/응답을 추상화한 객체 

  - $resource를 추상화한 서비스   



1. 서버 응답/요청 객체 추상화 

  자바의 J2EE 스펙중 Servlet 스펙을 보면 요청/응답을 처리하는 서블릿은 HTTPServletResponse/HTTPServletRequest가 있다. 이와 유사하게 앵귤러와 Node.js에 사이에 주고 받는 JSON 객체를 추상화 한다. MS를 MEANStack의 약어로 공통모듈의 경우 앞에 붙여 사용한다. 요청객체는 앵귤러 Factory 로 개발한다.  


  - 요청객체 : MSRequest 를 통해 정형화된 형태의 JSON 객체를 생성한다 

  - 응답객체 : MSResponse 는 서버로 부터 전달되는 정형화된 형태의 JSON 객체이다 


  RESTful API를 만들기 위한 몇가지 원칙을 지키고 그외에는 변형하여 사용토록 한다. URI 정의시 몇가지 원칙만을 가지고 진행토록한다. 복잡하게 하지 말고 Stateless하면서 Resource 접근에 대해서 명사를 사용하며 동사적 의미는 HTTP POST, GET, PUT, DELETE를 사용하며, Collection의 경우는 복수를 사용한다. 즉 더 복잡한 부분이 나오면 그때 가서 확장하거나 별도 구성하여 사용하고 몇가지 원칙을 정해서 사용토록 한다 


  - prefix 로 버전을 반드시 붙인다 : api/v1

  - 3단계의 정의를 한다 : /:area/:resource/:id 

    + area : authentication, person, tech, guru 등의 resource 영역 구분

    + resource : login, mylevel(myfollowing), angularjs(nodejs, expressjs, mongodb), month(member)같은 좀 더 구체적인 구분

    + id : GET, PUT, DELETE의 경우 옵션적으로 사용

    + 그외의 데이터는 Key=Value 파라미터 값으로 넘긴다 


// MSRequest 생성 및 개발 

$ yo angular:factory ms-factory

   create app/scripts/services/ms-factory.js

   create test/spec/services/ms-factory.js


// ms-factory.js

angular.module('meanstackApp')

  .factory('msRequestFactory', function () {

  

  var createRequest = function(area, resource, id, request) {

    if (!request)

      request = {};


    return {

      "area" : area,

      "resource" : resource,

      "id" : id,

      "request" : request

    };

  };


  return {

    createRequest : createRequest

  };


});


// 공통 호출 팩토리 생성

$ yo angular:factory ms-restful-api

   create app/scripts/services/ms-restful-api.js

   create test/spec/services/ms-restful-api.js


// ms-restful-api.js 

angular.module('meanstackApp')

  .factory('msRestfulApi', ['$resource', function ($resource) {

  var prefixUrl = '/api/v1';


  return $resource(

      prefixUrl + '/:area/:resource/:id', 

     {

       area : "@area",

       resource : "@resource",

       id : "@id"

     },

     {

      'get':    {method:'GET', isArray:true},

      'save':   {method:'POST'},

      'update': {method:'PUT'},

      'delete': {method:'DELETE'},

      'login':  {method:'POST'}

    });

  }]);


  $resource의 파라미터는 첫번째 URI 두번째 URI의 구분자에 맵핑되는 JSON, 세번째는 사용자 정의 메소드이다. 



2. 로그인 Service 개발 

  컨트롤에서 로그인 관련 서버호출을 위하여 MSRequest 팩토리와 RESTful하게 호출할 수 있는 팩토리를 만들었다. 이를 활용하는 앵귤러 Service 코딩해 보자. 


  - 사용자 로그인을 처리하는 서비스를 만든다.

  - 서비스는 View에서 Controller를 통하여 로그인 정보(params)를 받아서 Service로 넘긴다.

  - 성공적으로 완료되면 Controller에서 넘겨준 펑션(successCallback)을 수행한다. 


Service는 순수하게 서버와의 요청 처리만을 담당하고 Controller는 HTML View에서 넘어온 Model 값의 핸들링과 이후 Service에서 성공을 하면 실행할 successCallback을 갖는다. successCallback은 UI Routing 또는 View의 변경을 수행한다. Service와 Controller 서로의 역할을 정확히 나눔으로써 유지보수성을 높이도록 하였다. 


// 로그인 관련 서비스를 만든다 

$ yo angular:service session-service

   create app/scripts/services/session-service.js

   create test/spec/services/session-service.js


// session-service.js

angular.module('meanstackApp')

  .service('SessionService', [

  'msRequestFactory', 

  'msRestfulApi', 

  '$log',

  function SessionService(msRequestFactory, msRestfulApi, $log) {

    

    this.login = function(paramssuccessCallback) {

    // 1) create request

    var request = msRequestFactory.createRequest('authentication', 'login', '');

   

    // 2) set params

    request.params = {

    email: params.email,

    password: params.password

    }


    // 3) call ajax 

    msRestfulApi.login(request, 

    function(response) {

    // success

    successCallback(response);

    }, 

    function(error){

    // fail

    $log.error('Server Exception is ', error);

    })

  };


  }]);


  사용자가 로그인을 한후 사용자 정보를 담는 SessionInfo 객체를 관리하는 서비스를 만들어 보자. 

  - 사용자 정보 저장소로 HTML5의 localStorage를 우선 사용한다 

  - 현재 사용자 정보를 $rootScope.currentUser에 담아서 애플리케이션 전체 영역에서 사용토록 한다 

// 세션 정보 생성 

$ yo angular:service session-info

   create app/scripts/services/session-info.js

   create test/spec/services/session-info.js


// session-info.js

angular.module('meanstackApp')

  .service('SessionInfo', ['$rootScope', function SessionInfo($rootScope) {

    this.localStorageKey = "__SESSION_INFO";

    try {

      $rootScope.currentUser = JSON.parse(localStorage.getItem(this.localStorageKey) || "{}");

    } catch(e) {

      $rootScope.currentUser = {};

    }


    this.getCurrentUser = function() {

      return $rootScope.currentUser;

    }


    this.isUserSignedIn = function() {

      if(this.getCurrentUser() && this.getCurrentUser().id) {

        return true;

      } else {

        return false;

      }

    };


    this.setUserInfo = function(info) {

      angular.extend($rootScope.currentUser, info);

      localStorage.setItem(this.localStorageKey, JSON.stringify($rootScope.currentUser));

    };


    this.reset = function() {

      $rootScope.currentUser = {};

      localStorage.setItem(this.localStorageKey, JSON.stringify($rootScope.currentUser));

    };

  }]);



  SessionService에서 로그인 요청이 성공하면 SessionInfo를 호출하는 코드를 넣도록 한다. 

// SessionInfo를 사용하는 SessionService 수정 

angular.module('meanstackApp')

  .service('SessionService', [

   'msRequestFactory', 

   'msRestfulApi', 

   'SessionInfo',

   '$log',

   function SessionService(msRequestFactory, msRestfulApi, SessionInfo, $log) {

    

    this.login = function(params, successCallback) {

     // 1) create request

     var request = msRequestFactory.createRequest('authentication', 'login', '');

    

     // 2) set params

     request.params = {

     email: params.email,

     password: params.password

     }


     // 3) call ajax 

     msRestfulApi.login(request, 

     function(response) {

       // success

        SessionInfo.reset();

                   SessionInfo.setUserInfo(response);

        successCallback(response);

     }, 

     function(error){

       // fail

                  alert('Login Failed');

       $log.error('Server Exception is ', error);

     })

   };


  }]);



3. 로그인 Controller 개발

  이제 앵귤러 Service가 만들어 졌으니 Service를 사용하는 Controller를 코딩해 보자. 로그인 Service 가 성공하면 메인화면으로 이동한다.  

// login.js 의 기존 코드 

angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  function ($scope, $state) {

   $scope.submitLogin = function() {

    console.log($scope.login);

     $state.go("main");

   }

  }]);


// SessionService를 사용하는 수정 코드 

angular.module('meanstackApp')

  .controller('LoginCtrl', [

  '$scope', 

  '$state', 

  'SessionService',

  'SessionInfo',

  '$log',

  function ($scope, $state, SessionService, SessionInfo, $log) {


   $scope.submitLogin = function() {

    SessionService.login($scope.login, changeState);

   }


   function changeState(response){

    alert(SessionInfo.getCurrentUser().name + '님 반갑습니다 :)');

        $state.go("main");

   }

  }]);


최종적으로 Chrome 개발자 도구의 console에 아무런 에러가 없으면 모든 서비스와 컨트롤러가 앵귤러 초기화가 잘 되었다는 신호이다. 로그인을 해보면 아마도 404에러가 날 것이다. 아직 서버가 준비되지 않았기 때문인데, 이는 다음장에서 테스트 코드를 작성해서 확인해 보록한다. 다음과 같인 나오면 성공이다. 



4. 로그인 html 수정

  SessionInfo에는 사용자가 로그인 상태인지 체크하는 메소드가 존재한다. 이를 menu.html 에 적용하자 

<ul class="nav navbar-nav navbar-right">

<li ng-hide="SessionInfo.isUserSignedIn()">

<a href="#" ui-sref="login">Login</a>

</li>

<li ng-show="SessionInfo.isUserSignedIn()">

<a href="#" ng-click="logout()">

[{{ currentUser.name }}] Logout

</a>

</li>

</ul>


위의 코드는 해당 브랜치에 존재한다. 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/angular_step03_login-ctrl-service

posted by 윤영식
2014. 1. 30. 11:15 AngularJS/Start MEAN Stack

UI-Router를 이용한 화면 라우팅 설정을 app/scripts/app.js 메인 애플리케이션 파일에 설정하였다. 설정 내용중 각 라우팅되는 HTML View의 제어를 담당하는 Controller를 yeoman을 사용해서 생성해 보자 



HTML과 Controller 

  앵귤러는 MVW 프레임워크라고 하는데 여기서 M은 model 이고 V는 View, W은 Whatever를 의미하여 무엇이든지 올 수 있다는 의미이다. Whatever에는 싱글톤패턴의 서비스, 팩토리 또는 필터, 디렉티브(Directive)등이 올 수 있다. 


  - Model : Controller의 $scope를 통한 자동 two-way binding 을 한다 

  - View : HTML에 $scope의 모델들을 설정하거나 앵귤러의 디렉티브를 이용하여 직관적인 설정한다  


  앵귤러의 데이터 모델은 바로 Scope에 의해서 연결이 되며, app.js 가 최초 수행 되면 $rootScope가 생성되고 그 하위로 $scope가 생성된다. 이들은 계층적으로 연결되어 상위의 scope 데이터에 접근할 수 있다. 만일 Chrome 브라우져를 사용한다면 scope 객체들의 계층구조를 보기 위하여 별도의 크롬도구를 설치해야 한다. 


 1) AngularJS Batarang: https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk 설치 

 2) 크롬 개발자도구 : 윈도우(Ctrl+Shift+i), 맥(Command+Option+i) 연다 

 3) 맨 우측의 AngularJS를 선택하고 Enable 옵션을 체크한다 

 4) < Scope (003) 에서 003은 앵귤러가 Scope를 생성하고 부여한 아이디이고 클릭하면 우측에 Scope에 저장된 정보를 볼 수 있다.



1. 화면별 컨트롤러 생성하기 

  Yeoman에서 앵귤러 프레임워크의 기능을 자동 생성해 주는 것은 yo 명령이다. 우리는 meanstack 애플케이션 생성을 위하여 "generator-angular"를 설치하였는데 yo 뒤의 명령은 관례에 따라 "yo angular:<기능> <명칭>"으로 사용할 수 있다. 


  - yo의 generator가 만일 generator-mean 이고 geneartor-mean안에 <기능>으로 controller가 있다면 

  - yo mean:controller sample 을 수행하면 보통은 컨트롤러 코드와 컨트롤러 테스트 코드가 자동 생성된다

  -즉 "yo <generator- 두의 명칭>:<genertor가 제공하는 기능명칭>  <사용자정의 명칭>" 으로 명령은 구성된다 

  - <사용자정의 명칭> 에서 이름을 지정할 때는 "user-biz" 라고 명칭을 주면 index.html에는 "user-biz.js" 파일이 추가 되고, 기능의 명칭은 "UserBiz<기능약어>" 식으로 명칭이 만들어 진다.


  generator-angular 기능 목록은 홈페이지를 참조한다 (https://github.com/yeoman/generator-angular) 컨트롤러 생성시 테스트 코드 파일이 자동으로 생성된다 

// yo angular:controller <명칭> 으로 각 메뉴의 컨트롤러를 생성한다.

$ yo angular:controller my-level

   create app/scripts/controllers/my-level.js

   create test/spec/controllers/my-level.js


$ yo angular:controller my-qna

   create app/scripts/controllers/my-qna.js

   create test/spec/controllers/my-qna.js


$ yo angular:controller my-following

   create app/scripts/controllers/my-following.js

   create test/spec/controllers/my-following.js


$ yo angular:controller tech-area

   create app/scripts/controllers/tech-area.js

   create test/spec/controllers/tech-area.js


$ yo angular:controller ranking

   create app/scripts/controllers/ranking.js

   create test/spec/controllers/ranking.js


$ yo angular:controller member

   create app/scripts/controllers/member.js

   create test/spec/controllers/member.js


$ yo angular:controller login

   create app/scripts/controllers/login.js

   create test/spec/controllers/login.js


// index.html에 자동으로 추가된 것을 볼 수 있다.

<!-- build:js({.tmp,app}) scripts/scripts.js -->

<script src="scripts/app.js"></script>

<script src="scripts/controllers/main.js"></script>

<script src="scripts/controllers/my-level.js"></script>

<script src="scripts/controllers/my-qna.js"></script>

<script src="scripts/controllers/my-following.js"></script>

<script src="scripts/controllers/tech-area.js"></script>

<script src="scripts/controllers/ranking.js"></script>

<script src="scripts/controllers/member.js"></script>

<script src="scripts/controllers/login.js"></script>

<!-- endbuild -->


  컨트롤러를 생성했는데 - 또는 . 를 사용하면 앞뒤의 문자들의 첫글자가 대문자로 변형되어 설정이 되고 뒤에 "Ctrl" 이 자동으로 붙는다 

// app/scripts/controllers/my-level.js 소스

'use strict';


angular.module('meanstackApp')

  .controller('MyLevelCtrl', function ($scope) {

    $scope.awesomeThings = [

      'HTML5 Boilerplate',

      'AngularJS',

      'Karma'

    ];

  });

  

  이제 app/scripts/app.js에 컨트롤러를 설정해 보자 

$stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: 'MainCtrl'

      })

      .state('login', {

        url: '/login',

        templateUrl: 'views/login.html',

        controller: 'LoginCtrl'

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: 'MyLevelCtrl'

      })

      .state('myqna', {

        url: '/myqna',

        templateUrl: 'views/myqna.html',

        controller: 'MyQnaCtrl'

      })

      .state('myfollowing', {

        url: '/myfollowing/{userId:[0-9]{1,4}}',

        templateUrl: 'views/myfollowing.html',

        controller: 'MyFollowingCtrl'

      })

      .state('techarea', {

        url: '/techarea/{techId:[0-9]{1,4}}',

        templateUrl: 'views/techarea.html',

        controller: 'TechAreaCtrl'

      })

      .state('gururanking', {

        url: '/gururanking',

        templateUrl: 'views/gururanking.html',

        controller: 'RankingCtrl'

      })

      .state('gurumember', {

        url: '/gurumember',

        templateUrl: 'views/gurumember.html',

        controller: 'MemberCtrl'

      });


  my-following이나 tech-area를 보면 아이디를 설정하였고 myfollowing.html에서 $stateParam을 사용하기위해 meanstackApp 모듈의 run() 에서 $rootScope에 $stateParams를 저장한다. 그리고 페이지가 전환 되기 시작할 때와 전환하여 로딩 완료되었을 때의 이벤트를 처리하기 위한 Listener를 등록한다. 

// myfollowing.html 

<div class="row">

  <div class="col-md-12">

    <h1>myfollowing.html</h1>

     User ID: {{$stateParams.userId}}

  </div>

</div>


// app/scripts/app.js

.run(['$rootScope', '$state', '$stateParams', 

  function ($rootScope, $state, $stateParams) {

    $rootScope.$state = $state;

    $rootScope.$stateParams = $stateParams;

  }]);


  - html에서 $stateParams 를 찾기위하여 자신의 $scope.$stateParams를 찾는다 없으면 다시 상위의 $rootScope.$stateParams을 찾게 된다. 따라서 클라이언트 애플리케이션 전역에서 사용할 내용들이 있다면 $rootScope에 저장한다. 

  - myfollowing 메뉴를 선택하면 Scope (009) 의 아이디값이 변경된다. 메뉴를 누를 때마다 Scope의 아이디가 변경되는데 이는 클라이언트 화면이 변경될 때 이전 Scope는 destroy되고 새로운 Scope가 생성되기 때문이다. 즉, 앵귤러가 화면 라우팅이되고 해당 화면의 컨트롤러에 신규 $scope 을 생성하여 DI (Dependency Injection)해준다 



2. 컨트롤러 코드 변경하기 

  애플리케이션 배포시에는 "grunt build"를 수행하는데 이때 .js에 대한 단축화(minification)이 수행되어 변수명을 알 수 없는 코드로 바꾸어준다. 앵귤러에서는 $로 시작하는 명칭이 변경이 되면 안되기 때문에 yo로 생성된 코드를 그대로 사용할 수 없다.  '$scope' 문자를 지정해 주면 function($scope) {} 가 최소화 되어 function($ab){} 로 바뀌어도 DI시에 '$scope'로 지정된 $scope 객체가 주입된다.

 angular.module('meanstackApp')

 .controller('MyFollowingCtrl', [

  '$scope',

  function ($scope) {

    ... 중략 ...

  }]);

  

  다른 컨트롤러도 해당 방식으로 전부 수정한다. 소스는 anuglar_setp02_insert-ctrl-menu 브랜치를 체크아웃한다 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/anuglar_setp02_insert-ctrl-menu

posted by 윤영식
2014. 1. 29. 23:56 AngularJS/Start MEAN Stack

CSS Framework과 클라이언트 UI Routing을 위한 라이브러리 설치가 끝났으니 이제 본격적으로 메인화면을 만들어 보도록 하죠. 메인 화면의 디자인은 CSR에서 내용을 참조하여 Twitter Bootstrap의 반응형 웹 디자인 메뉴로 구성한다. 



전체 구조 이해하기 

  우선 메뉴를 만들기 전에 index.html의 전체 구조를 이해하자. SPA 방식의 싱글페이지 애플리케이션 개발이란 index.html의 일부 DOM을 변경함으로써 화면을 전화하여 보여주는 것이다. 초기 index.html 레이아웃은 단순화 하여 사용토록 한다. 


  - 메뉴 html을 별도 파일로 분리 

  - 메뉴 html에 링크 설정하기 

  - 메뉴에 링크된 화면의 html 파일 생성 


// index.html 기존 설정

<body ng-app="meanstackApp">


    <!-- Add your site or application content here -->

    <div class="container" ng-view=""></div>


</body>



1. 메뉴관련 html 파일 분리

  우선 메뉴관련 파일을 분리해서 시작한다. app/views/menu.html 파일을 생성하고 index.html 파일에 include 한다.

<!-- Add your site or application content here -->

<!-- menu -->

<div ng-include="' /views/menu.html '"></div>

  menu.html 안에 이제 부트스트랩의 반응형 웹 디자인 메뉴를 만들어 보자. 반응형 웹 디자인 메뉴란 해상도에 따라 메뉴의 형태가 자동으로 변하는 것을 말한다. 주로 데스크톱과 모바일기기의 해상도에 맞게 메뉴 형태가 변한다. 처음에 부트스트랩을 직접 수작업을 해보는 것이 도움이 되지만 여기서는 부트스트랩용 저작도구인  Bootply(http://bootply.com/)를 사용해서 만들어 본다.  


  - Bootply의 "Drag-and-Drop" 가운데 메뉴를 선택한다

  - Bootstrap 3.0 에서 "Basic starter"를 선택한다 

  - html에서 메뉴에 대한 부분만 취한다 



 menu.html 에서 <div class="navbar navbar-inverse navbar-fixed-top"> 내용은 index.html의 ng-include가 있는 <div>에 class설정한다. 

// index.html

<div class="navbar navbar-inverse navbar-fixed-top" ng-include="' /views/menu.html '"></div>


// menu.html 

  <div class="container">

    <div class="navbar-header">

      <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

        <span class="icon-bar"></span>

        <span class="icon-bar"></span>

        <span class="icon-bar"></span>

      </button>

      <a class="navbar-brand" href="#">Brand</a>

    </div>

    <div class="collapse navbar-collapse">

      <ul class="nav navbar-nav">

        <li class="active"><a href="#">Home</a></li>

        <li><a href="#about">About</a></li>

        <li><a href="#contact">Contact</a></li>

      </ul>

    </div><!--/.nav-collapse -->

  </div>

  

  "grunt serve"로 수행을 해보면 입력한 메뉴 구조를 볼 수 있다. 화면의 사이즈를 줄이면 메뉴형태가 변화는 것 또한 볼 수 있다. 

  - 데스크톱 해상도

  - 모바일 해상도


2. 메뉴의 링크 설정하기 

  menu.html 파일안에 링크에 대해 설정한다. 요구사항 정의를 보면 MEANStack을 위한 Q&A 서비스를 만드는 것이다. 하기와 같은 대분류 메뉴와 소분류 메뉴를 링크설정해 본다 

  

  - 나의 Q&A : 나의 질문 답변, 내가 팔로잉한 사람

  - Tech 글 : MEAN Stack 관련 메뉴

  - 명예의 전당 : 이번달 명예의 전당(인기글), 멤버소개 


부트스트랩은 기본적인 사항은 홈페이지를 통해 몇시간 정도 공부하거나 오픈 튜토리얼 사이트에서 익히고 시작하는 것이 좋다. 그리고 응용에 관련된 부분은 Bootsnipp (http://bootsnipp.com/) 에서 유용한 형태를 취하여 사용해 본다. 여기서는 부트스트랩의 기본적인 Navigation bar 구조를 참조한다.

참조: http://getbootstrap.com/components/#navbar


// menu.html 변경 내용

<div class="container">

  <div class="navbar-header">

    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

      <span class="icon-bar"></span>

      <span class="icon-bar"></span>

      <span class="icon-bar"></span>

    </button>

    <a class="navbar-brand" href="#"> MEANStack.net</a>

  </div>

  <div class="collapse navbar-collapse">

    <ul class="nav navbar-nav">

      

      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown"> My Area <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Status</li>

          <li>

            <a href="#"> My Level: 평민</a> 

          </li>

          <li>

            <a href="#"> 질문: 3, 답변: 1</a>

          </li>

          <li class="divider"></li>

          <li class="dropdown-header">Following</li>

          <li>

            <a href="#"> 윤영식</a>

          </li>

        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown"> Tech Area <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">SPA</li>

          <li>

            <a href="#"> Angular.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Middleware</li>

          <li>

            <a href="#"> Node.js</a>

          </li>

          <li>

            <a href="#"> Express.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">NoSQL</li>

          <li>

            <a href="#"> MongoDB</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Eco Tools</li>

          <li>

            <a href="#"> Yeoman</a>

          </li>

        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown"> GuruGuru <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Ranking</li>

          <li>

            <a href="#"> 명예의 전당</a>

          </li>

          <li class="divider"></li>

          <li class="dropdown-header">Members</li>

          <li>

            <a href="#"> 멤버소개</a>

          </li>

        </ul>

      </li>


    </ul>

  </div><!--/.nav-collapse -->

</div>


 다시 "grunt serve"를 통하여 메뉴가 정상적으로 만들어 졌는지 확인한다 



3. 메뉴 링크에 라우팅 설정하기 

  메뉴구조를 만들었으니 이제 UI-Router(https://github.com/angular-ui/ui-router) 방식에 따라 라우팅을 설정해 보자. 앵귤러 라우팅 순서는 먼저 서버에서 HTML파일을 불러온 후 앵귤러에서 HTML파일을 파싱하여 앵귤러 코드를 앵귤러 컨텍스트에 포함시키고 DOM을 변경한다. 

  

  - 서버에 HTML 파일 요청하기 

  - HTML 파일 파싱하여 앵귤러 컨텍스트에 포함시키기

  - DOM을 변경하여 화면에 표현하기 


먼저 "My Level: 평민" 메뉴를 대상으로 설정을 하면 나머지는 동일 과정을 반복하게 된다. 

1) mylevel.html 파일을 app/views 폴더 밑에 생성한다 

<div class="row">

  <div class="col-md-12">

    <h1>mylevel.html</h1>

  </div>

</div>


2) <li> 태그에 현재 선택이 되면 class="active" 부트스트랩 클래스를 추가하기 위하여 앵귤러의 ng-class를 사용한다. 

  - 라우킹 명칭을 "mylevel" 로 정한다 

  - (data-)ng-class 안에 표현식을 넣어서 현재 라우팅 명칭이 mylevel (true)이면 active 클래스를 적용하고 false이면 적용하지 않는다

  - ui-sref 는 ui-router에서 사용하는 라우팅 변경 링크 속성으로 href를 대체한다 

// 기존 설정

<li>

    <a href="#"> My Level: 평민</a> 

</li>


// 변경 설정 

<li data-ng-class="{ active: $state.includes('mylevel') }">

   <a ui-sref="mylevel"> My Level: 평민</a> 

</li>

  

3) 이제 HTML 파일의 위치와 기타 정보를 설정한다

  - app/scripts/app.js 가 meanstack 서비스의 메인 애플리케이션 파일이 된다. 앵귤러가 최초 수행이 되면 애플리케이션에서 사용하는 $rootScope를 만들게 되는데 app.js 메인 애플리케이션 레벨에서 사용하는 Global Scope 이다. 단 브라우져를 재로딩하면 초기화 된다. 

  - ui-router 설정은 홈페이지(https://github.com/angular-ui/ui-router)를 참조한다.

'use strict';


angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

.config(['$stateProvider', '$urlRouterProvider',  function ($stateProvider, $urlRouterProvider) {


    $urlRouterProvider.otherwise("/");


    $stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: ''

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: ''

      });

  }])


  - $urlRouterProvider.otherwise('/') 는 설정되지 않은 uri 요청이 들어올 경우를 처리한다. 여기서는 main 으로 간다 

  - main.html은 meanstack을 만들 때 생성된 html이다 

  - 메뉴에서 "나의 레벨"을 클릭하면 "mylevel" state의 url로 변경되고 templateUrl에 설정한 html을 서버에 요청한다 

  - HTML을 파싱하여 앵귤러 컨텍스트에 포함시키고 앵귤러 코드에 대한 처리는 controller에서 담당하나 아직은 설정하지 않았다

  - mylevel.html의 파싱된 DOM은 index.html의 "<div ui-view class="container"></div>" ui-view 하위 <div>에 자동 주입된다. 


설정이 완료되었으면 "나의 레벨" 메뉴를 클릭해 본다 


ui-view 속성이 있는 <div>에 mylevel.html 파일의 내역의 표현이 약간 위로 올라갔다. index.html과 main.css를 수정한다 

// app/index.html 기존 

<div class="navbar navbar-inverse navbar-fixed-top" ng-include="' /views/menu.html '"></div>


// app/index.html 수정

<div class="navbar navbar-default navbar-static" ng-include="' /views/menu.html '"></div>


// app/views/main.html 수정 

<div class="row">

  <div class="col-md-12">

    <h1>main.html</h1>

    <h2> Welcome to MEANStack.net</h2>

  </div>

</div>


// app/sytles/main.css 의 모든 내역을 삭제함 


  최종 결과 화면




4. 메뉴에 적절한 아이콘 설정하기 

 다른 메뉴들도 라우팅 명칭을 정하고 menu.html과 app.js에 라우팅 설정을 하고 각 메뉴의 기본 파일을 app/views/ 폴더 밑에 생성하여 연결한다. 그리고 메뉴에 FontAwesome의 아이콘을 설정한다. 


  - FontAwesome 사이트에서 원하는 아이콘을 선택한다 : http://fortawesome.github.io/Font-Awesome/icons/

  - 아이콘 태그를 넣는다 : 예) <i class="fa fa-check-square"></i> fa-check-square

// app/views/menu.html

<div class="container">


  <div class="navbar-header">

    <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">

      <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span>

    </button>

    <a ui-sref="main" class="navbar-brand"><i class="fa fa-th-large"></i> MEANStack.net</a> 

  </div>

  

  <div class="collapse navbar-collapse">

    <ul class="nav navbar-nav">

      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">

          <i class="fa fa-spinner fa-spin"></i> My Area <b class="caret"></b>

        </a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Status</li>

          <li data-ng-class="{ active: $state.includes('mylevel') }">

            <a ui-sref="mylevel"><i class="fa fa-eye"></i> My Level: 평민</a> 

          </li>

          <li data-ng-class="{ active: $state.includes('myqna') }">

            <a ui-sref="myqna"><i class="fa fa-comments"></i> 질문: 3, 답변: 1</a>

          </li>

          

          <li class="divider"></li>

          <li class="dropdown-header">Following</li>

          <li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '1' }">

            <a ui-sref="myfollowing({userId:'1'})"><i class="fa fa-thumb-tack"></i> 박유진</a>

          </li>

          <li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '2' }">

            <a ui-sref="myfollowing({userId:'2'})"><i class="fa fa-thumb-tack"></i> 이규원</a>

          </li>

          <li data-ng-class="{ active: $state.includes('myfollowing') && $stateParams.userId == '3' }">

            <a ui-sref="myfollowing({userId:'3'})"><i class="fa fa-thumb-tack"></i> 윤영식</a>

          </li>


        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">

        <i class="fa fa-pencil-square-o"></i> Tech Area <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">SPA</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '1' }">

            <a ui-sref="techarea({techId:'1'})"><i class="fa fa-file-text-o"></i> Angular.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Middleware</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '2' }">

            <a ui-sref="techarea({techId:'2'})"><i class="fa fa-file-text-o"></i> Node.js</a>

          </li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '3' }">

            <a ui-sref="techarea({techId:'3'})"><i class="fa fa-file-text-o"></i> Express.js</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">NoSQL</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '4' }">

            <a ui-sref="techarea({techId:'4'})"><i class="fa fa-file-text-o"></i> MongoDB</a>

          </li>


          <li class="divider"></li>

          <li class="dropdown-header">Eco Tools</li>

          <li data-ng-class="{ active: $state.includes('techarea') && $stateParams.techId == '5' }">

            <a ui-sref="techarea({techId:'5'})"><i class="fa fa-file-text-o"></i> Yeoman</a>

          </li>

        </ul>

      </li>


      <li class="dropdown">

        <a href="" class="dropdown-toggle" data-toggle="dropdown">

        <i class="fa fa-smile-o"></i> GuruGuru <b class="caret"></b></a>

        <ul class="dropdown-menu">

          <li class="dropdown-header">Ranking</li>

          <li data-ng-class="{ active: $state.includes('gururanking') }">

            <a ui-sref="gururanking"><i class="fa fa-sitemap"></i> 명예의 전당</a>

          </li>

          <li class="divider"></li>

          <li class="dropdown-header">Members</li>

          <li data-ng-class="{ active: $state.includes('gurumember') }">

            <a ui-sref="gurumember"><i class="fa fa-users "></i> 멤버소개</a>

          </li>

        </ul>

      </li>

    </ul>

  </div>

</div> 



// app/scripts/app.js 

angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

.config(['$stateProvider', '$urlRouterProvider',  function ($stateProvider, $urlRouterProvider) {


    $urlRouterProvider.otherwise("/");


    $stateProvider

      .state('main', {

        url: '/',

        templateUrl: 'views/main.html',

        controller: ''

      })

      .state('login', {

        url: '/login',

        templateUrl: 'views/login.html',

        controller: ''

      })

      .state('mylevel', {

        url: '/mylevel',

        templateUrl: 'views/mylevel.html',

        controller: ''

      })

      .state('myqna', {

        url: '/myqna',

        templateUrl: 'views/myqna.html',

        controller: ''

      })

      .state('myfollowing', {

        url: '/myfollowing/{userId:[0-9]{1,4}}',

        templateUrl: 'views/myfollowing.html',

        controller: ''

      })

      .state('techarea', {

        url: '/techarea/{techId:[0-9]{1,4}}',

        templateUrl: 'views/techarea.html',

        controller: ''

      })

      .state('gururanking', {

        url: '/gururanking',

        templateUrl: 'views/gururanking.html',

        controller: ''

      })

      .state('gurumember', {

        url: '/gurumember',

        templateUrl: 'views/gurumember.html',

        controller: ''

      });


  }])


  - ui-router에서 myfollowing({userId:'1'}) 는 파라미터를 넘기는 방식이다 형식) ui-sref='stateName({param:value, param:value})

  - ui-router API : https://github.com/angular-ui/ui-router/wiki/Quick-Reference


현재까지 진행된 내역을 GitHub에 올려 놓았다. 다음은 router에 앵귤러 controller를 만들고 router에 설정해 보자 

https://github.com/MEAN-STACK/MEANStack-Bookwork/tree/angular_step01_making-index


posted by 윤영식
2014. 1. 29. 07:48 AngularJS/Start MEAN Stack

Git, Node.js, Yeoman등 기본적인 개발환경을 설치하였다면 소프트웨어 요구사항 정의서 (Software Requirements Specification, SRS) 에서 정의한 화면을 CSS Framework을 사용하여 만들어 보자. 여기서는 가장 많이 사용하고 있는 Twitter Bootstrap을 사용토록 한다. 



사전준비

  이전 글에서 yeoman의 yo 명령을 통하여 angular 프로젝트를 "meanstack"이름으로 생성하였다. meanstack 하위에 app 폴더가 앵귤러 기반의 SPA (Single Page Application) 프론트앤드 애플리케이션의 ROOT 폴더가 된다. 


  - 반응형 웹 디자인 기반의 메뉴구조를 구성한다 

  - 메뉴를 클릭하면 해당하는 html 파일로 라우팅한다 

  - 메뉴에 아이콘을 추가한다 




1. Twitter Bootstrap 설치

 Twitter Bootstrap은 meanstack을 생성하면서 app/index.html에 자동 추가된다. 

  1) <!-- build:css ... --> : 주석은 제거하면 안된다. grunt build 시에 css 압축할 때 사용하는 주석이다. (build:js 도 동일) 

  2) <!-- bower:css .. --> : 주석은 제거하면 안된다. grunt build 시에 자동으로 bower install 한 내역을 추가한다. (bower:css 도 동일) 

// index.html

    <!-- build:css styles/vendor.css -->

    <!-- bower:css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />

    <!-- endbower -->

    <!-- endbuild -->


   .. 중략 ..


    <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

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

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <!-- endbower -->

    <!-- endbuild -->

  * "yo angular meanstack" 명령으로 twitter bootstrap이 자동 설치가 되면 twitter bootstrap이 사용하는 fonts 폴더를 app/fonts로 자동 복사해 준다. 이것은 grunt build시에 모든 css가 app/styles/*.css로 압축되고 bootstrap의 css가 사용하는 font는 ../fonts를 참조하기 때문이다. 따라서 배포를 위해서는 app/fonts 폴더가 존재 해야한다.




2. Angular UI-Bootstrap 설치

  Twitter Bootstrap을 html에 적용하려면 class 정보를 입력해야 한다. 이를 좀 더 직관적인 html tag 형식으로 쓸 수 있도록 AngularUI 팀에서 Directives를 만들었다. 예로 tab을 적용하가 위하여 <tab> 태그를 html에 사용하면 angular-bootstrap 모듈이 <tab> 를 class="tab" 형태로 바꿔서 DOM에 적용하는 것이다. html을 좀 더 직관적으로 작성할 수 있다는 장점이 있다. 

$ bower install angular-bootstrap --save

angular-bootstrap#0.10.0 app/bower_components/angular-bootstrap

└── angular#1.2.10


  bower를 통해 angular-bootstrap을 설치 후 "grunt build" 또는 "grunt serve" 를 실행하면 index.html에 필요 파일이 자동 추가된다. 자동 추가되는 파일의 정보는 bower_components/angular-bootstrap/bower.json 파일에 지정되어 있다.

// index.html 파일 

   <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

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

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>

    <!-- endbower -->

    <!-- endbuild -->


  angular-bootstrap은 별도의 앵귤러 모듈이므로 애플리케이션에서 사용하려면 모듈 의존성을 설정해야 한다. meanstack 서비스의 메인 소스는 app/scripts/app.js 이다. 여기에 모듈 의존성을 설정한다. 

  - 형식 : angular.module('<ApplicationName>', [<의존성 모듈 열거>]);

// app/bower_components/angular-bootstrap/ui-bootstrap-tpl.js 파일 상단

angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);



// app/scripts/app.js 안에 추가 

'use strict';

angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap'

])

  .config(function ($routeProvider) {

    .. 중략 ..

  });



3. Angular UI-Router 설치 

클라이언트 화면 전환을 위한 라우팅 기능은 앵귤러에서 제공하는 기본적인 라우터를 사용하지 않고 AngularUI팀에서 역시 제공하는 ui-router (https://github.com/angular-ui/ui-router)를 사용한다. ui-router는 한 화면에서 멀티 view 설정으로 원하는 부분들의 DOM을 변경처리할 수 있다. bower를 통해 ui-router v0.2.7 버전을 설치한다. ui-router v0.2.8 버전설치에 오류가 존재하기 때문이다. 또한 설치전에 Angular.js 버전을 현재 (2014.1.28기준) 최신버전인 v1.2.10 으로 업데이트한다.

// bower.json 파일에서 angular 관련 버전을 1.2.10 으로 지정한다 

$ vi bower.json

{

  "name": "meanstack",

  "version": "0.0.0",

  "dependencies": {

    "angular": "1.2.10",

    "json3": "~3.2.6",

    "es5-shim": "~2.1.0",

    "jquery": "~1.10.2",

    "bootstrap": "~3.0.3",

    "angular-resource": "1.2.10",

    "angular-cookies": "1.2.10",

    "angular-sanitize": "1.2.10",

    "angular-route": "1.2.10",

    "angular-ui-router": "0.2.7"

  },

  "devDependencies": {

    "angular-mocks": "1.2.10",

    "angular-scenario": "1.2.10"

  }

}


// angular 버전 업데이트

$ bower update 

angular-resource#1.2.10 app/bower_components/angular-resource

└── angular#1.2.10

.. 중략..

angular-scenario#1.2.10 app/bower_components/angular-scenario

└── angular#1.2.10


// ui-router 설치 1번을 선택한다 

$ bower install angular-ui-router#0.2.7 --save

    1) angular-ui-router#0.2.7 which resolved to 0.2.7

    2) angular-ui-router#~0.2.8 which resolved to 0.2.8 and has meanstack as dependants

[?] Answer: 1

angular-ui-router#0.2.7 app/bower_components/angular-ui-router

└── angular#1.2.10


   bower를 통해 ui-router를 설치 후 "grunt build" 또는 "grunt serve" 를 실행하면 index.html에 필요 파일이 자동 추가된다. 자동 추가되는 파일의 정보는 bower_components/angular-bootstrap/bower.json 파일에 지정되어 있다. 

    <!-- build:js scripts/vendor.js -->

    <!-- bower:js -->

    <script src="bower_components/jquery/jquery.js"></script>

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

    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>

    <script src="bower_components/angular-resource/angular-resource.js"></script>

    <script src="bower_components/angular-cookies/angular-cookies.js"></script>

    <script src="bower_components/angular-sanitize/angular-sanitize.js"></script>

    <script src="bower_components/angular-route/angular-route.js"></script>

    <script src="bower_components/angular-bootstrap/ui-bootstrap-tpls.js"></script>

    <script src="bower_components/angular-ui-router/release/angular-ui-router.js"></script>

    <!-- endbower -->

    <!-- endbuild -->


  ui-router 또한 별도의 모듈이므로 app/scripts/app.js 안에 모듈 의존성을 설정한다 

'use strict';


angular.module('meanstackApp', [

  'ngCookies',

  'ngResource',

  'ngSanitize',

  'ngRoute',

  'ui.bootstrap',

  'ui.router'

])

  .config(function ($routeProvider) {

  .. 중략 ..

  });



4. FontAwesome Icon 설치

  다양한 아이콘을 사용하기 위하여 bootstrap과 별도로 fontawesom(http://fortawesome.github.io/Font-Awesome/) 라이브러리를 설치한다. 텍스트 정보를 좀 더 직관적으로 인지할 수 있도록 다양한 아이콘을 적용할 수 있다. 

$ bower install font-awesome --save

bower font-awesome#*            cached git://github.com/FortAwesome/Font-Awesome.git#4.0.3

font-awesome#4.0.3 app/bower_components/font-awesome


  설치 후 app/bower_components/font-awesome/ 폴더 밑으로 bower.json 이 존재하지 않으므로 필요한 파일을 index.html에 직접 기입해야 한다. index.html에 font-awesome.css 파일을 추가하자. 추가시 <!-- bower:css --> 영역에 넣으면 grunt build(grunt serve)시에 직접 넣은 정보는 자동 삭제되므로 <!-- bower:css --> 가 없는 <!-- build:css --> 안에 추가한다. 

     <!-- build:css styles/vendor.css -->

    <!-- bower:css -->

    <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />

    <!-- endbower -->

    <!-- endbuild -->


    <!-- build:css({.tmp,app}) styles/main.css -->

    <link rel="stylesheet" href="bower_components/font-awesome/css/font-awesome.css" />

    <link rel="stylesheet" href="styles/main.css">

    <!-- endbuild -->


  'grunt build'를 하게 되면 font-awesome의 css는 app/styles/main.css 파일로 묶이게 된다. font-awesome.css 소스를 보면 css 상위의 fonts 폴더의 파일을 참조한다. 따라서 font-awesome의 fonts 폴더안의 파일들을 app/fonts 밑으로 복사한다.

Twitter Bootstrap과 Font awesome의 css가 참조하는 파일들 목록

$ cd app/fonts

$ ls 


fontawesome-webfont.woff

fontawesome-webfont.ttf

fontawesome-webfont.svg

fontawesome-webfont.eot

FontAwesome.otf

glyphicons-halflings-regular.woff

glyphicons-halflings-regular.ttf

glyphicons-halflings-regular.svg

glyphicons-halflings-regular.eot


이제 필요한 라이브러리들이 index.html에 추가되었다. 다음장에서는 Twitter Bootstrap을 이용하여 반응형 웹 디자인(Responsive Web Design : RWD) 메뉴와 전체 화면 레이아웃을 만들어 보자.

posted by 윤영식
2014. 1. 26. 07:50 AngularJS/Start MEAN Stack

yeoman은 자바스크립트 기반의 프론트앤드 개발을 도와주는 자동화 툴이다. 자바에서 요즘 많이 사용되는 Maven과 유사하다. 



1. 사용이유

  서비스 개발의 규모가 커지고 자바스크립트의 각 모듈별의 버전 의존성을 관리하려면 어떻게 해야 할까? 그리고 필요한 자바스크립트 모듈을 다운로드하기 위하여 각 사이트를 방문하여 다운로드 해야할까? 기본적인 코드 골격과 코딩 컨벤션은 최초에 어떻게 해야할까? 개발하고 나서 테스트 및 빌드 자동화는 어떻게 해야할까? 이러한 의문에 대한 해답을 주는 것이 Yeoman이다.

  - 스케폴딩 생성 : 기본적인 코드 골격을 만들어 주고 이후 추가되는 앵귤러 코드는 명령을 통해 생성한다 

  - 라이브러리 의존성 관리 : 프론트앤드에서 사용하는 다양한 jquery 모듈과 angular.js 모듈의 설치 및 버전 관리를 한다 

  - 테스트 및 빌드 자동화 : 테스트 코드를 작성하였다면 테스트 수행 및 코드 압축과 문법오류 검사 그리고 배포파일의 생성을 한다 

  - 사전 점검 : 프론트단의 코드를 Node.js기반 수행하여 브라우져에서 개발한 화면을 사전에 테스트 점검 가능하다



2. 설치하기  

  설치는 의외로 간단하다. 사전 준비로 Node.js를 설치 하였다면 NPM (Node Package Manager)을 통해 설치한다. yo, bower, grunt 의 특징은 자바스크립트로 개발하여 node.js기반위애 구동하는 도구이다 

// 1) yo 설치 

$ npm install -g yo


// 2) bower 설치 

$ npm install -g bower


// 3) grunt 설치 

$ npm install -g grunt-cli

  * http://yeoman.io, http://bower.iohttp://gruntjs.com 에서 설치관련 자세한 사항을 참조한다. 



3. 주요 기능 

  yeoman은 크게 3가지 기능으로 구분된다. 각 기능의 구분이 명확하고 사용하는 목적이 틀리기 때문에 잘 알아 두기 바란다. 기본적으로 yo를 yeoman팀에서 자체적으로 만들었고, bower와 grunt는 이미 존재하였던 도구로써 yeoman이라는 이름으로 통합한 것이다. 따라서 bower와 grunt를 별개의 독립적인 도구로 사용할 수도 있다


  yo 

  - 프론트앤드 개발을 위한 기본 구조를 만들어 준다. yeoman에서는 스켈폴딩 코드를 만들어 준다고 말한다 

  - 스케폴딩 파일을 생성하려면 목적에 맞는 제너레이터를 사전에 설치해야 한다.

    1) 제너레이터 명칭 구성 : generator-<UserDefine> 로서 UserDefine 명칭을 정한다. 예) generator-angular

    2) npm 을통하여 글로벌 설치한다 

    3) UserDeinfe 명칭이 angular 라면 yo 명령 다음에 제너레이터의 구분을 위하여 항시 해당 UserDefine 명령이 온다 

        yo angular <Subject>  또는 yo angular:<SubCommand>  <Subject>

    4) yo angular <Subject> 를 통해 초기 프로젝트의 명칭을 정하고 기본 골격을 생성한다 

    5) yo angular:<SubCommand> <Subject> 를 통해 기본골격 밑으로 기능을 추가한다. SubCommand는 제너레이터에 따라 사용자 정의 할 수 있다 

  - 프로젝트에 필요한 골격 코드 생성을 위한 자신만의 제너레이터를 만들 수 있다. 

  - 사용법

    1) angular 제너레이터 설치

    2) 프로젝트 폴더생성

    3) angular 프로젝트 생성을 하고 Sass 미사용, Bootstrap을 사용체크하면 필요한 파일과 모듈을 자동 설치한다

    4) 프로젝트 기본 골격코드 자동 생성하면 app 폴더가 프론트 개발 ROOT 폴더가 된다

// 1) 

$ npm install -g generator-angular


// 2) 

$ mkdir meanstack & cd meanstack


// 3) 

$ yo angular meanstack

     _-----_

    |       |

    |--(o)--|   .--------------------------.

   `---------´  |    Welcome to Yeoman,    |

    ( _´U`_ )   |   ladies and gentlemen!  |

    /___A___\   '__________________________'

     |  ~  |

   __'.___.'__

 ´   `  |° ´ Y `


Out of the box I include Bootstrap and some AngularJS recommended modules.


[?] Would you like to use Sass (with Compass)? No

[?] Would you like to include Twitter Bootstrap? Yes

[?] Which modules would you like to include? (Press <space> to select)

❯⬢ angular-resource.js

 ⬢ angular-cookies.js

 ⬢ angular-sanitize.js

 ⬢ angular-route.js


// 4) 


bower

  - bower는 트위터에서 개발한 프론트앤드 라이브러리 설치 및 버전 의존성 관리 도구이다 

  - yo를 통하여 스케폴딩 파일이 생성하면 bower 사용을 위한 기본 환경파일도 자동 생성된다 

    1) 환경파일은 bower.json 으로 현재 설치된 라이브러리 명칭과 버전을 자동 기록한다 

    2) 라이브러리 설치 위치정보는 .bowerrc 에서 변경한다. 기본값으로 "bower_components" 를 사용한다 

// 1) bower.json 최초 설치 정보 

{

  "name": "meanstack",

  "version": "0.0.0",

  "dependencies": {

    "angular": "1.2.6",

    "json3": "~3.2.6",

    "es5-shim": "~2.1.0",

    "jquery": "~1.10.2",

    "bootstrap": "~3.0.3",

    "angular-resource": "1.2.6",

    "angular-cookies": "1.2.6",

    "angular-sanitize": "1.2.6",

    "angular-route": "1.2.6"

  },

  "devDependencies": {

    "angular-mocks": "1.2.6",

    "angular-scenario": "1.2.6"

  }

}


// 2) .bowerrc 위치정보

{

    "directory": "app/bower_components"

}


  - 사용법 

   1) 검색 : "bower search <명칭>"

   2) 설치 : "bower install <명칭>  --save (또는 --save-dev)"

   3) 보기 : "bower list"

   3) 도움말 : "bower help" 또는 "bower help <명령어>"

$ bower list

bower check-new     Checking for new versions of the project dependencies..

meanstack#0.0.0 ~/meanstack

├── angular#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

├─┬ angular-cookies#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

├─┬ angular-mocks#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-resource#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-route#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-sanitize#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ angular-scenario#1.2.6 (latest is 1.2.11-build.2184+sha.319dd1a)

│ └── angular#1.2.6

├─┬ bootstrap#3.0.3

│ └── jquery#1.10.2 (2.1.0 available)

├── es5-shim#2.1.0 (latest is 2.3.0)

├── jquery#1.10.2 (latest is 2.1.0)

└── json3#3.2.6 (latest is 3.3.0)

  * angular 개발시점에 정식 릴리즈된 가장 최신버전을 사용할 예정이고 bower를 통하여 업데이트할 것이다. 


Grunt

  - grunt는 자바의 ant와 같은 기능을 수행한다

  - 다양한 플러그인을 통하여 기능을 첨부하여 확장할 수 있다. 

  - yo을 통해 프로젝트 골격 코드생성시 Gruntfile.js 환경파일이 기본 생성된다 

  - 또한 골격 코드 생성시 기본적으로 사용되는 플러그인은 node_moudles 폴더에 자동 설치된다 

// 1) node_modules에 기본 설치된 grunt 플로그인 


// 2) Gruntfile.js 내역 중 명령 일부

module.exports = function (grunt) {

  // Load grunt tasks automatically

  require('load-grunt-tasks')(grunt);


  // Time how long tasks take. Can help when optimizing build times

  require('time-grunt')(grunt);


  // Define the configuration for all the tasks

  grunt.initConfig({


    // Project settings

    yeoman: {

      // configurable paths

      app: require('./bower.json').appPath || 'app',

      dist: 'dist'

    },

    ... 중략...


grunt.registerTask('serve', function (target) {

    if (target === 'dist') {

      return grunt.task.run(['build', 'connect:dist:keepalive']);

    }


    grunt.task.run([

      'clean:server',

      'bower-install',

      'concurrent:server',

      'autoprefixer',

      'connect:livereload',

      'watch'

    ]);

  });


  grunt.registerTask('server', function () {

    grunt.log.warn('The `server` task has been deprecated. Use `grunt serve` to start a server.');

    grunt.task.run(['serve']);

  });


  grunt.registerTask('test', [

    'clean:server',

    'concurrent:test',

    'autoprefixer',

    'connect:test',

    'karma'

  ]);


  grunt.registerTask('build', [

    'clean:dist',

    'bower-install',

    'useminPrepare',

    'concurrent:dist',

    'autoprefixer',

    'concat',

    'ngmin',

    'copy:dist',

    'cdnify',

    'cssmin',

    'uglify',

    'rev',

    'usemin',

    'htmlmin'

  ]);


  grunt.registerTask('default', [

    'newer:jshint',

    'test',

    'build'

  ]);

};


  - 사용법

   1) 테스트 : grunt test

   2) 빌드 : grunt build

   3) 프리뷰 : grunt serve

// 1) 

$ grunt test

Running "clean:server" (clean) task


Running "concurrent:test" (concurrent) task


    Running "copy:styles" (copy) task

    Copied 1 files


    Done, without errors.



    Execution Time (2014-01-25 22:42:19 UTC)

    loading tasks   4ms  ▇▇▇▇ 25%

    copy:styles    10ms  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 63%

    Total 16ms


Running "autoprefixer:dist" (autoprefixer) task

Prefixed file ".tmp/styles/main.css" created.


Running "connect:test" (connect) task

Started connect web server on 127.0.0.1:9001.


Running "karma:unit" (karma) task

INFO [karma]: Karma v0.10.9 server started at http://localhost:8080/

INFO [launcher]: Starting browser Chrome

WARN [watcher]: Pattern "/Users/nulpulum/prototyping/yeomain/meanstack/test/mock/**/*.js" does not match any file.

INFO [Chrome 32.0.1700 (Mac OS X 10.9.1)]: Connected on socket nUaNX14YFrQJ_aeDkWM5

Chrome 32.0.1700 (Mac OS X 10.9.1): Executed 1 of 1 SUCCESS (2.184 secs / 0.033 secs)


Done, without errors.



Execution Time (2014-01-25 22:42:17 UTC)

concurrent:test  2.2s  ▇▇▇▇▇▇▇▇▇▇▇26%

karma:unit       6.2s  ▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇ 73%

Total 8.4s


// 2) 브라우져가 자동 실행된다 

$ grunt serve 


Yeoman에 대한 이해와 기본적인 설치, 사용법을 익혔다. 프론트 앤드 개발을 하면서 계속 사용하게 되므로 잘 익혀 놓도록 하자 


posted by 윤영식
2014. 1. 2. 13:06 AngularJS/Start MEAN Stack

개인 블로그를 통하여 정보를 공유하는 일에서 한발 더 나아가 함께 글을 올리고 글에 대한 Q&A를 하는 웹앱 서비스를 만들어 보고 싶다. 내가 로그인을 했을 때 처음 보여지는 UI는 SNS 형태이며 내가 올린 질문(Question)에 대한 답변이나 커뮤니티안에서 활동하는 재능 기부자를 팔로잉 하면 해당 글들이 먼저 보여졌으면 좋겠다. 즉, 글에 대한 개인화를 원하며 재미있는 재능 기부 놀이를 해보고 싶다. 




1. 기존 커뮤니티 서비스

  - 특정 재능 기부자가 글을 연재하여 올린다

  - 여러 Q&A 게시판 형태를 취한다  

  - 내가 원하는 것을 Search 하여 찾아 보아야 한다 

  - PC에 최적화 되어있다. 어떤 것은 모바일에서 볼 수도 있다

   글올렸으니 알아서 찾아 읽어봐 ^^;



2. MEAN Stack 커뮤니티 서비스 

  - SNS 형태를 취하여 모바일기기(Tab, Phone) 중심으로 만들어진다 

  - MEAN Stack에 대한 글을 연재하는 재능 기부 개발자들이 있고, 이중 몇명을 following 할 수 있다.

  - 내가 연재글이나 별도의 란에 질문(Question)을 달면 MEAN Stack 재능 기부 개발자들에게 자동 Notify가 된다. 

    (단, 별도의 질문은 지정된 분류만을 할 수 있다. Concern Area)

  - 답변을 준 재능 기부자에게 점수를 통해 고마움을 표현하면 해당 점수는 재능 기부자의 인기도 순위에 영향을 준다.

  - 또한 연재 글에 대해서도 모든 사람은 한번 점수를 줄 수 있다.

  - 인기도는 천민, 일반, 귀족, 왕족, 신(구루) 등의 구분으로 명예의 전당에 공개되어 구루로 추앙된다. 게임처럼 놀고 싶다. 

  - 해당 인기도는 한달에 한번 reset되고 이달의 구루에겐 모종의 선물을 수여한다.(뭘 주지? ^^)

  - 즉, 답변이나 글연재를 많이 한다고 구루가 되지 않고 해당 답변, 글에 메겨진 점수를 통해 인기도 순위와 등급은 결정된다 

  - 가입한 누구나 특정 분류의 재능 기부자가 될 수 있다 

   재능 기부하면서 함께 재미 있게 놀고 싶다. 



3. Mockup

  - 서비스의 명칭 

    + Community Planet : 새로운 혹성의 커뮤니티. 점수통해 자신만의 영역을 만들어 간다. 우리는 디지털 이주민.

      

  - 메인 화면 

    + 좌측은 모바일의 Sliding 메뉴로 구성된다 

    + 로그인을 하면 우측 상단에 Push Message 건수와 로그인/로그아웃 링크가 나온다 

    + 중앙에는 내가 질문한 것에 대한 답변과 다른 질문/답변들 그리고 Following 한 재능 기부자들의 글목록이 나온다

    + 글 목록은 핀터레스트 스타일 이다 


  - Question & Answer

    + 내가 질문한 것과 답변 받은 것을 확인 할 수 있다 

    + 질문내역을 클릭 하면 질문에 대한 답변을 볼 수 있다. 

    + 답변에 대한 점수를 줄 수 있고, 답변이 틀리면 마이너스 점수도 줄 수 있다 


  - Following 재능 기부자들의 글 보기

    + 팔로어한 사람별의 최근 글을 볼 수 있다. 내가 관심갖는 글만 보기 

    + 보는 방식은 Q&A화면과 유사하다 

    + 연재글에 역시 질문과 답을 달 수 있다 

 

  - Concern Area

    + 관심영역은 현재 커뮤니티 서비스 관리화면에서 목록을 만들 수 있다. (마치 워드프레스의 관리화면처럼-향후 개발)

    + 특정 관심 영역을 들어가면 해당 영역에서 활동하는 모든 글들을 볼 수 있다.

    + 검색하여 글을 찾을 수 있다 


  - 구루구루

    + 이곳은 명예의 전당이다 

    + 가장 점수가 높은 재능 기부자를 상위에 놓고 점수가 높은 순으로 글목록이 나열 된다 

    + 그리고 이번달의 글과 질문 답변에 대해 볼 수 있다 



다음의 비디오를 참조 했으면 좋겠다. Community Planet은 바로 이 정신이다. 


posted by 윤영식
2013. 12. 15. 13:38 AngularJS/Start MEAN Stack

개발자들이 가장 힘들어 하는 부분은 UI 개발이라 생각한다. 더 나아가 UX(User eXperience)를 어떻게 UI속에 녹여내어 편의성과 제품의 가치에 맞는 감성을 넣을 수 있을지도 고민해 보아야 한다. 막상 클라이언트 개발을 시작하였지만 마땅히 디자이너의 도움 없이도 완성도 있는 서비스를 만들고 싶다면 CSS Framework을 사용해 보자. 


요즘 추세는 크게 두가지 측면으로 움직이는 것 같다. 하나는 웹의 모양과 색감등 보여지는 부분을 담당하는 CSS를 프레임워크화하여 일관된 UX를 전달해주고 있고 이를 공개하고 있다. 이중 가장 유명한 것이 트위터 부트스트랩이다. 버전 3.0 에서는 반응형 웹 디자인(RWD, Responsive Web Design)이 기본으로 적용되어 모바일 스마트기기의 해상도에 따라 화면이 최적화되어 표현될 수 있도록 하는 기능을 제공한다. 둘째는 플랫디자인이다. 구글이나 애플에서 주도를 하고 있고 간결하면서 사용성을 늘린 디자인으로 보인다. 기본 트위터 부트스트랩을 확장하여 플랫디자인 CSS를 적용한 다양한 CSS 프레임워크를 제공하고 있다. 


따라서 트위터 부트스트랩을 기본으로 하여 확장한 CSS 프레임워크를 사용하게 되면 '반응형 웹 디자인' 과 '플랫디자인'을 동시에 충족하여 적용할 수 있게 된다. 두가지 요소에 대해서는 이곳의 주제와 약간 벗어나므로 좀 더 전문적인 서적을 통해 개념과 상세 기능을 익히기 바란다. 여기서는 선택한 CSS 프레임워크를 어떻게 적용하고 활용하는지 중점적으로 보도록 하겠다. 



1. 반응형 웹 디자인 고려

  - Fluid Layout : 화면 사이즈에 따라 배치된 요소들의 정렬을 재 배치하는 것이다. 화면이 작아진다고 하여 좌우 스크롤이 생기는 것이 아니라 요소가 밑으로 흘러들어 간다. 이를 위하여 부트 스트랩은 Grid 12 컬럼으로 요소 배치 및 플루이드 정렬을 할 수있다 

  - Responsive Menu : 부트 스트랩은 상단에 메뉴를 놓고 해상도에 따라 메뉴의 형태를 자동 변경해 준다 

서비스 개발이 위의 두가지를 먼저 유념하여 사용하도록 한다. 기본적인 사용법은 트위터 부트스랩의 홈페이지를 참조하자 



2. 플랫 디자인에 대한 고려

애플은 iOS7에서 플랫디자인을 선보이고, 구글은 전사 서비스를 플랫디자인으로 도배하고 있습니다. 프러덕트 디자이너가 Visual 단계에서 바라본 플랫디자인의 요소 5가지를(참조) 유념하여 개발자가 UI와 UX를 플랫 디자인으로 진행할 때 고려해야할 사항에 대해 미리 알아두면 좋을 것 같습니다.

     + Use of simple elements 

        심플한 아이콘 : 사각, 원형의 단순 모형에 배치, 컬러, 형태로 표현 -> 사용자가 UI 를 쉽게 인식하고 사용토록 만든다 

     + Absence of depth 

        배제된 효과 : 그림자, 입체감, 그라데이션등의 현실감있게 하는 3D적 효과를 배제하여 직관적으로 만든다  

     + Typography

        타이포그래피 : 화려한 폰트를 배제하고 간결한 폰트를 사용한다. 예) 산세리프 

     + Color

        단순한 칼라 : 2~3가지 또는 5~6가지의 색상만을 사용하여 간결하고 과감하고 표현한다. 핑크그린블루가 대세  

     + Minimalism

        미니멀리즘 : 미니멀리즘과 부합 - 간단하고 심플하게 표현으로 다양한 웹, 모바일에 적응이 쉽다  



3. 서비스 디자인으로 나아가야 한다 

  서비스을 만들 때 개발자는 어떻게 접근을 하고 있을까요? 아마도 대부분 디자이나 웹 퍼블리셔가 만들어준 화면에 코딩을 하는 정도를 경험했으리라 봅니다. 하지만 자신의 웹앱 서비스를 만들기 시작한다면 단지 디자인이 문제가 아니라 UX라는 사용자 경험을 고려하여 사람과 컴퓨터의 상호작용을 생각해 보아야 하고, 우리가 만들려는 프로덕트의 가치를 서비스에 어떻게 녹여 내야 하는지 연구하고 새롭게 창조하는 과정을 거쳐향 합니다. 이를 줄이면 서비스 디자인이라고 말하며 서비스 개발전 우리가 사용하는 도구와 프레임워크들이 가치를 창출하기 위하여 어떻게 쓰여야하는지 큰 뷰에서 설명을 해주고 있습니다. 인식의 전환을 통하여 중요한 가치를 찾고 반응형 웹 디자인이나 플랫 디자인이 왜 나왔는지 이해하여 보는 것도 재미날 것같습니다. 한국디자인진흥원의 윤성원님이 이야기하는 서비스 디자인 개념 자료를 한번 보시면 쉽게 이해 되리라 생각합니다. 


  - 서비스 디자인 이란 무엇인가? - 개념 이해


posted by 윤영식
2013. 12. 5. 13:34 AngularJS/Start MEAN Stack

이전 블로그에서 MEAN Stack에 대한 의미를 알아보았습니다. Angular.js 의 Why, How, What을 살펴보도록 하겠습니다.


1. Angular.js 가 주고자 하는 가치는 무엇인가?

  앵귤러는 모던 애플리케이션을 개발하기 위한 프레임워크입니다. 복잡하고 단순 반복적인 작업을 대폭 줄여줌으로써 신속한 개발을 가능하게 해줍니다. 이를 통해 보다 빨리 고객의 피드백을 받고 개선할 수 있는 에자일한 개발 진행이 가능해 집니다. 보다 적은 작업을 통해 보다 더 많은 행복을 주는 프레임워크입니다. 


  1989년 HTML을 시작으로 2005년 Ajax가 나오고 이후 jQuery를 통하여 DOM 을 조작을 통한 웹서비스 개발 시대를 거쳐 지속적인 브라우져 기술의 성숙과 JavaScript 해석기의 성능향상으로 이제는 자바스크립트를 통해 클라이언트에서도 서버와 같은 MVC 패턴 방식의 애플리케이션 개발이 가능해 졌습니다. 만일 jQuery만을 통해 웹앱을 개발한다고 생각하면 HTML 페이지마다 들어가는 서버코드와 자바스크립트들의 복잡한 코드에 머리를 쥐어 짤지도 모릅니다. 

  저는 13년을 넘게 자바언어로 서버만을 개발했었습니다. 가끔 클라이언트단의 jQuery 코드를 볼 때마다 저 영역으로는 절대 들어가지 말자라고 생각했습니다. 왜일까요? 그것은 서버처럼 잘 정비된 애플리케이션 프레임워크도 없으며, 서버코드와 자바스크립트를 HTML 사이사이에 끼워 넣으며 스파게티같은 코드를 짜야 하기 때문이었습니다. 

  그러나 2010년 이후부터 상황은 바뀌었습니다. Backbone.js가 이러한 복잡함을 해결하고자 초기 프레임워크로 나왔고, 이후 Ember.js 그리고 Angular.js 에서 Meteor.js 까지 모던 웹앱을 개발할 수 있는 프레임워크가 나왔습니다. 앵귤러는 어떻길래 이들 프레임워크 중 단연 선풍적인 인기를 누리고 있는 걸까요?


  - .js F/W  트랜드 



- .js 에서 dot(점)을 빼고 Backbonejs 와 Angularjs 트랜드 


  Backbonejs 와 Angularjs(노란색) 의 트랜드 변화는 급상승 중



2. Angular.js 가 어떻길래 개발자들이 열광하는가?

  앵귤러의 아버지인 미스코님(Misko Hevery)의 소개 동영상을 잠시 감상해 봅시다 


  요즘 SI시에 서버개발을 위하여 Spring Framework와 iBatis(myBatis)를 사용하는 것이 기본적인 관례처럼 되었는데요. 이들의 기능을 잠깐 생각해 볼까요. 

  - DI (Dependency Injection) 을 통하여 코드간의 결합도를 줄여주고, 테스트 코드의 작성을 쉽게 해줍니다. 

  - MVC 패턴를 통하여 기본적인 서버 개발의 틀을 가이드 해줍니다.

  - 다양한 라이브러리와 툴의 결합으로 개발 생산성을 높여줍니다.


 이를 앵귤러 입장에서 생각해 보면 정확히 위와 같은 요구사항을 충족해 주고 있습니다. 

  - DI를 지원합니다. 모듈단위의 개발로 코드를 간결하게 유지하고 테스트를 쉽게 해줍니다.

  - MV* 패턴을 통하여 역할을 나누고 아키텍쳐 Layered 개발을 가능하게 해줍니다. 당연히 유지 보수가 쉬워지겠죠

  - jQuery의 Plugins 포함한 기존의 다양한 라이브러리를 재 사용할 수 있습니다. 

  - 서버의 Maven, Ant 와 같은 관련된 라이브러리 의존성 관리 및 빌드 자동화 툴과 결합하여 개발 생산성을 높일 수 있습니다. 


  하지만 앵귤러를 시작하기 전에 하나의 선입견을 버리고 새로운 인식으로 접근을 해야 합니다. 자바스크립트는 이제 브라우져에서 화면의 단순 조작을 통한 효과에 쓰이는 언어가 아닌 진정한 엔터프라이즈급 애플리케이션부터 모바일 웹앱까지 모던한 애플리케이션을 만드는데 가장 많이 쓰이고 있는 개발언어가 되었다는 것입니다. 구글은 크롬앱을 코르도바(폰갭)를 통하여 안드로이드 및 iOS에서 구동하는 툴킷을 2014년 초에 출시할 계획입니다. 크롬앱은 자바스크립트로 개발합니다. 이는 본격적으로 네이티브 모바일 앱과 자바스크립트 모바일 웹앱이 함께 공존할 수 있는 시기가 왔다는 것을 암시합니다. 


 앵귤러(Angular.js)는 화면을 조작하는 라이브러리나 화면을 조작하는 프레임워크가 아니 모던 애플리케이션을 개발하는 프론트엔드 프레임워크입니다. 최근에는 이를 SPA(Single Page Application) 개발이라 부릅니다. Adobe의 Flex 기술을 통하여 클라이언트단에 엔터프라이즈 애플리케이션을 개발하는 RIA(Rich Internet Application) 가 선풍적인 인기를 누렸음을 잘 알것입니다. 인터넷이 되는 스마트 기기 에서 Adobe Flash의 공식 미지원 발표에 있은 후 이제 RIA의 용어는 점점 잊혀져 가고 있습니다. 하지만 사람들의 요구는 UX에 점점 더 목말라하고 있습니다. 단순한 웹서비스로는 이에 대응하기 힘들며 PC시대의 RIA를 표방한 Adobe의 Flex 프레임워크처럼 현재의 PC와 Mobile을 통합하는 진정한 RIA와 같은 자바스크립트 진영의 개발 프레임워크가 바로 앵귤러(Angular.js) 입니다. 무엇을 제공하 길래 앵귤러는 SPA 프레임워크의 큰형님이 되고 있는 걸까요?


 SPA vs RIA 비교 



3. Angular.js에는 무엇이 있는가?

  앵귤러는 클라이언트의 애플리케이션을 견고하게 만들 수 있는 방법을 제시하고 있습니다. 클라이언트의 생명은 바로 UX에 있다고 생각합니다. 훌륭한 사용자 경험을 주기 위하여 앵귤러 사용한다면 빠르게 개발하고 사용자의 피드백을 받아 개선해 갈 수 있습니다.


  견고한 싱글 페이지 웹 애플리케이션(SPA)을 만들기 위하여 앵귤러는 다음과 같은 기능을 제공합니다. 

  - 모듈 단위 개발을 통하여 글로벌 영역을 오염시키지 않고 모듈 단위 개발을 가능하게 해줍니다. 따라서 대규모의 애플리케이션 확장이 가능해 집니다. 


  - 양방향 데이터 바인딩을 통하여 View(HTML) 와 Controller(자바스크립트) 사이에 데이터에 대한 양방향 동기화를 자동으로 해줍니다. 즉, View 에서 데이터를 입력하면 Controller 단의 데이터를 업데이트 해주고, 그 반대도 가능해 집니다. 기존 jQuery에서는 데이터의 동기화를 위하여 Event와 Listener를 등록하는 코드를 모두 개발해야 했다면 앵귤러에서는 이런 코드를 찾아 볼 수 없습니다. Flex의 [Bindable]을 생각하면 됩니다. 


  - 앵귤러를 보통 MVW 프레임워크라 부릅니다. W는 Whatever의 의미로 Controller, Service등 무엇이든 올 수 있다는 것입니다. 역시 가장 중요한 것은 Model과 View의 연동을 양방향으로 함으로써 UX를 보다 쉽고 빠르게 개발토록 하는데 있습니다. 


  - Controller는 View HTML에서 발생한 이벤트에 따라 Model 변경을 제어하고, Service는 백앤드 서비스를 데이터 I/O 통신을 담당합니다. 각 필요한 기능은 DI(Dependency Injection)을 통하여 펑션의 파라미터 주입방식으로 인스턴스를 받아서 사용합니다. 


  - SPA 의 Single Page라는 의미는 최초에 index.html 을 서버로부터 받은 후 부분적인 화면의 변경을 위하여 부분 HTML(Partial HTML)만을 받아서 index.html의 특정 영역의 DOM 객체 변경을 통해 View의 일부분을 바꿔줍니다. 이후 서버로 부터 받는 것은 View의 컨텐츠 데이터인 JSON 이나 XML 입니다. 이렇게 화면의 부분 변경을 위하여 Routing 기능을 제공합니다. 


  - jQuery + PHP, jQuery + JSP 코드가 들어간 HTML 이 기존의 코딩 방식이라면 Javascript + HTML 만 존재하는 것이 앵귤러의 코딩 방식이다. 또한 반복해서 사용하는 HTML 또는 자바스크립트 코드조각이나 위젯/플러그인을 앵귤러만의 컴포넌트로 만들어서 HTML tag, attribute, class, comment 등의 방식으로 HTML에 포함시킬 수 있는 Directives(지시자) 기능을 제공합니다. Flex의 MXML에 차트관련 컴포넌트를 <Line> 태그로 표현하듯이, Angular.js의 Directive(컴포넌트)를 만들면 HTML안에 <Line> 과 같은 태그를 포함시킬 수 있습니다. 즉, Directive는 HTML 태그로 표기할 수 있는 컴포넌트 모듈을 만들 수 있고 Callback 펑션 기능을 내부로 숨기고 재사용할 수 있는 것이다. 


  - 백앤드로 Node.js를 사용한다면 Flex의 Messaging 기술과 같은 Push 방식의 구현을 Socket.io 모듈을 통하여 보다 더 쉽게 구현 할 수 있다. 


이제 자바스크립트 코딩이 가능하다면 싱글 페이지 애플리케이션(SPA) 에 도전해 봅시다.



<참조>

  - 스타트업을 위한 서비스 개발 기술 - MEAN Stack

  - 웹 애플리케이션 견고하게 만들기

  - 프론트 엔드 웹앱 프레임워크 (SlideShare)

posted by 윤영식
2013. 11. 25. 09:36 AngularJS/Start MEAN Stack

MEAN Stack을 가지고 서비스를 만드는데 사용을 하려면 어떤 순서로 익혀야 할까? 각각의 기술을 따로 배우는 것은 어차피 전문서적들이 있기 때문에 여기서 다시 상세히 설명하는 것은 의미가 없을 것 같고, 서비스를 기획, 개발, 런칭까지의 과정을 알아보자. 모든 과정의 내용은 웹 서비스쪽에 공개되어 공유될 것이다 




Sprint-1) Full Stack 개발자 되기

  - MEAN 개념과 개요

    + AngularJS

    + NodeJS + ExpressJS

    + MongoDB + Redis(Advanced)

  - MEAN 이외에 알아야 할 것들

    + JavaScript 

    + Bootstrap (or Zurb Foundation

    + NodeJS Modules

    + Mongoose



Sprint-1) 개발 준비 단계

  - 로컬 개발 환경 만들기 

    + Vagrant + Linux 환경 구성하기

    + 개발환경에 Node.js, MongoDB 설치하기 

    + Trello 에서 Scrum 개발하기 

  - MEAN Stack 개발도구 개발환경에 설치하고 익히기 

    + Yeoman : 클라이언트 라이브러리관리 및 배포 Grunt, Bower, Yo 설치하기 

    + NPM : 서버 라이브러리 관리

  - Git & GitHub 저장소 만들기 

    + Git Branch 전략 

    + GitHub 사용방법

  - 클라우드 환경에 배포하여 테스트 하기

    + Cloud Development(Nitrous.io) 를 통한 협업개발 및 테스트하기

    + Heroku같은 PaaS에 배포하여 테스트하기 

 


Sprint-1) MEANK Stack 개발 환경 구성하기

  - SRS 작성하기 

    + Software Request Requirement란 무엇인가?

    + Trello 에 소프트웨어 요구사항 정의서 작성하기 

    + Balsamiq 도구 사용하여 Wireframe & Mockup 그리기 화면 그리기

  - AngularJS Scaffolding 코드 사용하기 

    + angular-generator 알아보기 

    + angular 기본골격 만들기

    + NodeJS로 테스트 하기 

  - MEAN Stack 테스트 전략 

    + AngularJS에서의 테스트 방법 : Karma Framework

    + NodeJS에서의 테스트 방법 : Mocha 그외 



Sprint-2) 서비스 프로토타입 개발하기

  - 가장 중요한 화면 설계하기

    + 클라이언트 화면 Mockup 구체화 하기 

    + NodeJs에서 처리 할 RESTful API 설계하기 

    + MongoDB 에 저장 할 Document Schema 설계하기

  - MongoDB Store 개발 및 테스트

    + Dynamic Schema, Embedded vs Reference 에 대한 이해 

    + Mongoose 기반으로 Schema 만들기

    + Test Framework 이용한 Model 단위 테스트하기 

  - Node.js 기반 RESTful API 개발 및 테스트

    + RESTful API 코드 뼈대 만들기 in NodeJS

    + Test Framework 이용한 서비스 단위 테스트하기

    + RESTful Client 테스트 도구 또는 curl 로 호출 테스트하기

  - Angular.js 기반 화면 개발 및 테스트

    + AngularJS와 Bootstrap 이용하여 화면 구성하기

    + AngularJS 애플리케이션 구조 개발 및 RESTful API 서비스 개발 

    + Test Framework 이용한 화면 단위 테스트하기 

  - Heroku 같은 PaaS에 올려서 통합 테스트 하기 



Sprint-3) 서비스 개발 반복하기

  - 반복하기 

    + sprint-01에서 반복하였던 과정을 지속하기 

  - 메인 및 로그인 화면 만들기 

    + Bootstrap의 다양한 도구 활용

    + OAuth 기능 로그인 하기  

  - 채팅기능 만들기 

    + 채팅 Mockup 만들기

    + NodeJS Socket.io API 구현

    + MongoDB 채팅 Schema 설계

    + AngularJS Socket.io 멀티 케스팅 구현 

    + Bootstrap 통한 화면 구현 및 AngularJS 연동

 - Heroku 같은 PaaS에 올려서 프로토타입 테스트 하기 



Sprint-3) 서비스 런칭하기

  - 서비스 일일 빌드시스템 구성하기 

    + Travis (또는 Jenkins)기반 자동 배포 기능 만들기 

    + Travis 에서 빌드된 것을 클라우드시스템(PaaS) 자동 배포하기 

  - 런칭 페이지 만들기 

    + GitHub Pages를 이용한 런칭 만드는 방법

    + Bootstrap을 이용하여 런칭 페이지만들어 GitHub에 반영하기 

  - 모니터링 하기

    + 클라이언트 AngularJS Log 모니터링 하기
    + NodeJS Log 모니터링 하기 

    + MEAN Stack 운영 및 장애 모니터링 전략 



Next Project) MEAN Stack Advanced

  - 채팅기능 고도화하기 

    + SNS 서비스 아키텍쳐들 알아보기  

    + 대용량 데이터를 처리를 위한 아키텍쳐 설계하기

  - NodeJS + Redis + MongoDB 연동하기 

    + Redis 알아보기 

    + NodeJS-Redis 연동하기 

  - 정제된 데이터 RDBMS와 연동하기

    + NodeJS - MySQL 연동하기 

    + MongoDB의 MapReduce를 이용하여 데이터 마이닝하기 

    + NodeJS에서 MongoDB 마이닝 데이터 MySQL에 넣기 

  - SNS 서비스 운영 고도화

    + NodeJS와 Nginx 연동하기 

    + NodeJS를 Proxy 서버로 만들어 Clustering 하기

    + MongoDB Sharding 구조 만들기 

    + Redis Master/Slave구조 만들기 

  - 유지보수 전략

    + 테스트 환경과 프로덕션 환경의 재구성

    + NodeJS Scale-out 확장 전략

    + MongoDB Sharding 및 Data Backup 전략 

    + Redis Scale-out 전략 

  - 빅데이터의 활용

    + 개인화를 위한 데이터 활용 방안

    + MongoDB Integration Framework을 이용한 MapReducing


전체 서비스를 만들고 런칭하기까지 참 많은 것들을 익히고 개발해야 하지만, 누군가에게 의미있는 가치를 줄 수 있는 SNS(Social Network Service)를 할 수 있다는 것에 만족을 느낀다. 


posted by 윤영식
2013. 11. 20. 10:58 AngularJS/Start MEAN Stack

갑작이 떠오른 아이디어를 모바일 서비스로 빠르게 만들고 싶다. 클라이언트/서버/스토어 이런 것을 해줄 사람은 없다. 나 홀로 만들어 보고 프로토타입핑해서 스타팅해보고 싶을 때 MEAN Stack을 사용하자 




1. 스프링같은 "WebApp Framework" 에 대한 고민

  클라이언트단의 Android 또는 iOS 네이티브 코드는 익히는데 시간은 없고, 다양한 스마트 기기에 대응을 했으면 한다. 그리고 JavaScript를 어느 정도 할 수 있다. 이때 생각할 수 있는 대안이 "웹앱 사이트" 또는 "모바일 웹앱"을 만드는 것이다. 그러나 예전 jQuery 코드를 생각해 보자. 조금만 복잡해 지면 쉽게 스파게티가 되고, 테스트 코드는 엄두도 못내며 결국 유지보수의 골칫덩어리로 남게 된다. 

그렇다면 자바에서 Spring Framework과 같이 DI가 되면서 MVC처럼 모듈화 개발을 가능하게 해주는 클라이언트단 프레임워크는 없을까? 처음엔 Backbone.js를 살펴 보았고, 예로 Trello와 같은 서비스에서 사용을 하고 있다. 하지만 코드의 길이가 길어지고 불필요한 중복코드의 남발이 야기되어 좀 더 간결하면서 예전 Flex Framework과 같은 것은 없을지 고민해 보았다. HTML을 확장해서 사용해도 자동 해석을 하여 브라우져가 이해할 수 있는 HTML로 바꾸어 주면 되는 그런 프레임워크 말이다. 역시 구글 형님들도 똑같은 고민을 하고 프로젝트에 적용하고 놀라운 효과를 본 새로운 프레임워크를 공식 런칭하게 되니 이름하여 AngularJS





2. 새로운 "I/O Machine" 에 대한 고민

  서비스에서 요하는 기능중 가장 중요한 부분은 Push 기능이 아닐까 싶다. 실시간으로 변화하는 정보를 수많은 유저에게 바로 Push 서비스해줄 수 있는 서버가 필요하다. 하지만 Push를 위하여 별도의 Port를 사용하긴 싫고 웹앱이므로 요청하였던 HTTP Port를 사용하고 싶다면 어떤 것을 선택해야 할까?

서버단이 Java라면 vert.x도 고려해 볼 만하다. 하지만 아직 레퍼런스와 문서가 부족하다. 하지만 여기에 우리에게 구세주와 같은 세로운 I/O 머신이 나왔으니 V8엔진을 기반으로 돌아가는 Node.js이다. Node.js를 사용하는 가장 큰 목적중 하나가 Socket.io와 같은 이벤트 기반의 Push 모듈들이 아닐까 생각이든다. Node.js는 모듈기반으로 필요한 것들을 추가하여 사용할 수 있다. 이를 위하여 NPM(Node Package Manager)이 존재하고 방대한 모듈과 레퍼런스들이 존재한다. 또한 Chrome에 적용된 V8 자바스크립트 해석기를 달고 있으므로 모든 코딩은 JavaScript 만으로 이루어진다





3. 스키마에 자유로운 "Store"에 대한 고민 

  서비스를 빠르게 만들고 싶은데 개발을 진행하다 보면 자주 테이블이 변경되고 이에 맞추어 서버코드들도 수정을 해주거나 새롭게 테이블을 생성해 주는 번거로운 작업들을 거친다. 따라서 스키마가 자유로운 데이터베이스를 원한다. 그리고 서비스이므로 사용자의 데이터를 많이 쌓아 두고 싶다. 이를 통해 향후 마케팅 용으로 사용하려 한다. 이왕이면 클라이언트/서버가 모두 자바스크립트이므로 데이터베이스 핸들링도 자바스크립트 이면 좋겠다. 이를 위해 새롭게 나온 NoSQL 이 있으니 이름하여 MongoDB. JSON형태로 데이터를 저장하고 정규화 없이 도큐먼트 형태로 저장을 할 수 있어서 스키마의 제약이 없다. 또한 테라급의 데이터 정도는 MongoDB를 Sharding구조로 만들어 MapReduce를 이용하여 처리할 수 있다. 그리고 Node.js 처럼 V8 해석기를 탑재하여 데이터 핸들링을 JavaScript로 할 수 있다. 마치 Oracle의 SqlPlus에서 Sql을 수행하듯 MongoDB가 정의한 형식의 쿼리를 수행할 수 있다. 





4. Node.js를 기업용 WAS(Web Application Server)로 만들기  

  초창기 Java를 떠올려 보자. 처음엔 Applet에 열광하여 업무를 애플릿으로 만든적이 있다. 그때 서버단은 Apache + PHP정도 였을 것이다. 그리고 SUN에서 J2EE 스팩을 발표하면서 Tomcat, JBoss, WebSphere, WebLogic 같은 WAS가 나왔으며, 이 또한 C계열의 TP-Monitor처럼 Java기반 I/O Machine 이 나오게 된 것이다. Java진영의 WAS는 나름의 공통된 스팩을 기반으로 웹을 이끌어 갈 수 있는 새로운 I/O 머신으로 떠올랐고, 기업에서 요구하는 Transaction 처리에 대한 스팩도 제안하여 많이 사용하게 되었다. 최근에는 Tomcat같은 Servlet 엔진만 갖춘 WAS에 Spring Framework를 사용하면 충분히 기업용 Transaction을 처리하는 업무를 개발할 수 있는 환경까지 오게 되었다. 그러나 Java의 WAS가 web1.0의 중심 머신이었다면 Node.js기반의 WAS가 web2.0의 중심 머신으로 되기 위한 조건은 무엇일까?

  결국 Node.js위에 올라가는 Java의 J2EE 스팩과 같은 견고한 스팩이 필요하다. 현재까지는 아직 그러한 스팩이 나오진 않으나 Java의 Servlet 스팩에 비견할 만한 프레임워크가 나왔으니 그것이 Express.js 이다. 이제 web2.0의 I/O 머신은 Node.js가 될 것이고, 여기에 웹에 대한 표준 스팩구현체는-사실 Node.js를 위한 공통된 스팩은 없다- 아니지만 가장 많이 사용하는 Express.js를 통하여 Node.js가 Tomcat과 같은 기능을 보유하게 되었다.




Angular.js -> Node.js + Express.js -> MongoDB 를 사용하게 되면 

  - JavaScript로 모두 개발을 한다. 클라이언트/서버 분리하여 개발할 필요가 없다. 그냥 FullStack JavaScript개발을 하면 된다

  - 클라이언트 개발이 MV* 개발이 되어 서버 개발에 익숙한 개발자가 쉽게 접근할 수 있다 

  - 서버에서 Push 기능을 쉽게 적용할 수 있으므로 다양한 서비스의 응용이 가능하다 

  - 자바의 서블릿 엔진 구현체인 Tomcat과 같이 충실한 RESTful I/O 머신을 쉽게 구현할 수 있다

  - 서버의 멀티 쓰레드 문제에서 해방될 수 있다. Asynch의 세상에서 놀자 

  - 데이터 스키마를 미리 정의하지 않고 에자일하게 개발을 진행할 수 있다

  - 큰 규모의 데이터를 저장하고 처리 할 수 있다  



그러나 우리에게 남은 과제는 이것들을 어떻게 배우고 사용하는지 잘 모른다는 것이다. 각자를 심도 있게 배우는 것은 별도의 가이드북을 따로 보길 바란다. 대신 여기서는 실전 서비스를 어떻게 하면 빠르게 만들 수 있을지 노하우를 적어보려한다. 낮에는 회사에서 업무를 보면서 향후 스타트업을 준비하는 개발자 또는 사람들에게 좀 더 이로운 가치를 전달해 주고 싶은 개발자가 3개월안에 개발할 수 있는 서비스를 이 책을 통해 런칭할 수 있는 것이 가능해 질 수 있길 바란다. 그래서 책을 쓰면서 동시에 작은 서비스를 만들어 보려 한다. 책은 가이드이고 그물일 뿐이다. 실제 돌아가는 서비스가 물고기이다. 즉, 실제 서비스를 낚지 못하면 이 책의 가치도 없다고 생각한다. 실제로 이것을 증명해 보이고 싶다. 



Getting Start MEAN

          


posted by 윤영식
2013. 9. 4. 14:05 AngularJS/Start MEAN Stack

Mobile 서비스 개발을 위하여 JavaScript 기반의 기술 스택을 선택하여 사용하다 보니 1년사이에 MongoDB, Express.js, Angular.js, Node.js를 공부하고 솔루션에 적용하게 되었다. 그간 해당 기술들을 추상화한 프레임워크들을 사용하여 개발을 진행해 왔다. 그러나 가끔 풀리지 않는 문제에 대한 근원적인 해결책을 찾는데는 다시 처음부터 추상화 내역에 대한 이해가 바탕이 되지 않으면 풀리지 않았다. 


즉, 기본 스택을 추상화한 스택사용시에는 추상화 영역에 대한 사용경험과 전반적인 이해가 없이는 오히려 시간공수만 더 늘어나는 격이 된다. 예로 Express와 MongoDB를 좀 더 수월하게 사용키위하여 Trains, Sails Framework들을 사용해 보았고 나름의 장점은 있으나 왠지 남의 옷을 입은듯한 느낌이랄까? 문제 발생시 빠른 대응이 어려웠다 (개인적으론 Trains Framework의 Node.js단의 IoC 방식을 좋아한다)


따라서 소규모 팀으로 개발하면서 하나의 가치를 빠른 시간안에 전달하고자 한다면 기본 Stack기반으로 진행하면서 필요한 시점에 스스로 추상화 모듈을 만들어 사용하는 방법이 좋지 않을까 생각한다. (Don't Invent Wheel 이니 초기엔 쓸만한 것을 GitHub에서 찾아 응용하고 없으면 개발하자) 그런 의미에서 mean.io 에서 기본 Stack에 충실하면서 현재 사용되는 최신 개발 기술들 - Jade, Bootstrap, Mongoose, Bower, Grunt 같은 - 도 함께 접목되어 적당히 어려운 상태이므로 개발시 집중(Flow)을 가능케 하는 구조를 가지고 있다. (너무 어려우면 흥미를 읽고, 너무 쉬우면 긴장감이 떨어진다. 적당히 어려우면 흥미와 긴장감을 주어 개발시 집중을 가능케 한다) 



1. 설치

  - 홈페이지에서 zip 파일 다운로드 

  - 프로젝트로 이동해서 

$ cd mean


// 사전에 node.js 설치 

// Node, npm 설치 shell

$ npm install .

 

// 사전에 mongodb 설치하고 start

$ grunt



2. 접속 

  - http://localhost:3000/

    + Sign Up 할 수 있다

    + 테스트 글을 CRUD 할 수 있다 

    


  - MongoDB 

> show collections

articles

sessions

system.indexes

users


해당 스택을 통해 SPA(Single Page Application) 개발에 대한 Lean(신속한) 스타트업을 시도하자.

 


<참조>

  - mean.io

posted by 윤영식
prev 1 next