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

Publication

Category

Recent Post

2013. 8. 22. 17:52 Dev Environment/Sublime Text

Sublime Text 2 에서 3 으로 업그레이드를 해보자.


1. 설치 

  - 다운로드 받아서 설치한다



2. Package Control 설정 

  - 수작업으로 해야 한다 (Mac 기준)

  - Git이 필요하다 

  - 우선 Packages로 이동하고 git clone을 통하여 "Package Control"을 다운로드 받는다

// 하기 디렉토리로 이동 

/Library/Application Support/Sublime Text 3/Packages


$ git clone https://github.com/wbond/sublime_package_control.git "Package Control"

Cloning into 'Package Control'...

remote: Counting objects: 2524, done.

remote: Compressing objects: 100% (1005/1005), done.

remote: Total 2524 (delta 1654), reused 2369 (delta 1506)

Receiving objects: 100% (2524/2524), 823.53 KiB | 56.00 KiB/s, done.

Resolving deltas: 100% (1654/1654), done.

  - 터미널에서 sublime text 3 바로 수행할 수 있도록 심볼릭 링크 설정 

// 하기 디렉토리로 이동

/bin


$ sudo ln -s /Applications/Sublime\ Text.app/Contents/SharedSupport/bin/subl subl

$ ls 

lrwxr-xr-x   1 root  wheel       62  8 22 17:42 subl -> /Applications/Sublime Text.app/Contents/SharedSupport/bin/subl



3. 인기 패키지 설치 

  - 많이 사용하는 것들을 Package Control Installer를 이용하여 설치한다 : 인기 10 Top Package

  - 단축키 : command + shift + p  => install 타입핑하면 "Package Control : install package" 문구를 선택함 



<참조>

  - Package Control 설치하기 

posted by 윤영식
2013. 8. 22. 15:36 AngularJS/Concept

AngularJS의 서비스 종류는 여러가지가 있습니다. Factory만 알았다면 Costant, Value, Provider 그리고 서비스의 확장 방법등에 대해 알아보자. "AngularJS Service is an Application Brain"



1. 개념

  - 서비스는 여러 종류가 있다

  - 항시 Singleton 이다. 즉, 하나의 Instance만 존재한다 



2. Constant

  - 공통으로 사용하는 환경값을 지정할 때 상수를 지정한다 

  - 사용자 정의 Directive에서 환경값을 가져다 쓰고 싶을 때 유용하다

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, fooConfig) {

  $scope.fooConfig = fooConfig;

});


app.constant('fooConfig', {

  config1: true,

  config2: "Default config2"

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    config1: {{fooConfig.config1}}

    <br />

    config2: {{fooConfig.config2}}

  </body>

</html>



3. Value

  - Value Service는 Constant Service와 유사하지만 최초 한번은 변경을 할 수 있다. Factory 서비스의 작은 동생뻘이다 

  - Directive에서 사용하기 유용하다 

  - Value를 가지고 계산할 수는 없다 

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, fooConfig) {

  $scope.fooConfig = fooConfig;

  // 최초 한번의 변경이 가능

  angular.extend(fooConfig, {config3: "I have been extended"}); 

});


app.value('fooConfig', {

  config1: true,

  config2: "Default config2 but it can changes"

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    config1: {{fooConfig.config1}}

    <br />

    config2: {{fooConfig.config2}}

    <br />

    config3: {{fooConfig.config3}}

  </body>

</html>



4. Factory

  - 가장 일반적으로 사용하는 서비스 종류이다 

  - 팩토리는 어떤 형태의 데이터타입이라도 리턴할 수 있다. 리턴된 오브젝트를 가지고 작업을 한다 

  - 단, 리턴된 오브젝트의 값을 변경하면 해당 팩토리의 인스턴스를 사용하는 모든 곳에 변경값이 반영된다 

  - Revealing Module Pattern 식으로 작성을 한다 

// script 

app = angular.module("app", []);

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


// revealing module pattern 방식의 팩토리 서비스

app.factory('foo', function() {

  var thisIsPrivate = "Private";

  function getPrivate() {

    return thisIsPrivate;

  }

  return {

    variable: "This is public",

    getPrivate: getPrivate

  };

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>



5. Service 

  - "service" service는 Factory service와 유사사지만 생성자명칭(== 서비스명칭)을 넘기면 자동으로 new를 통하여 생성된다 

    즉, Factory 처럼 오브젝트의 리턴이 필요없다 

  - 하기 예는 완전히 동일하다. 주의 할 것은 factory안에서 또는 service사용시 new을 여러번 하여도 반드시 한번만 인스턴스화 한다

    즉, Singleton 패턴으로 객체 하나만이 생성된다 

// script 

app = angular.module("app", []);


// Service를 통해서 생성 

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.service('foo', function() {

  var thisIsPrivate = "Private";

  this.variable = "This is public";

  this.getPrivate = function() {

    return thisIsPrivate;

  };

});


// Factory를 통해서 생성 : MainCtrl 에서 foo 파라미터 값과 하위 로직을 foo2로 바꿔도 동일 결과 

app.controller('MainCtrl', function($scope, foo2) {

  $scope.foo = foo2;

});


app.factory('foo2', function() {

  return new Foobar();

});


function Foobar() {

  var thisIsPrivate = "Private";

  this.variable = "This is public";

  this.getPrivate = function() {

    return thisIsPrivate;

  };

}


// 또는 펑션을 넘길 수도 있다 : : MainCtrl 에서 foo 파라미터 값과 하위 로직을 foo3로 바꿔도 동일 결과 

app.controller('MainCtrl', function($scope, foo3) {

  $scope.foo = foo3;

});


app.service('foo3', Foobar);


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>

 


6. Provider

  - Provider는 factory의 큰형님 뻘이다 

  - $get fuction을 가지고 있어야 한다. 만일 Provider 명칭이 foo이고 controller에 inject될 때 실제는 $get function의 결과값이 inject되는 것이다. 왜 factory로 사용하면 간편한데 굳이 이렇게 할까?

  - 이유인즉슨, config function을 통해서 provide에 대한 환경설정을 할 수 있기 때문이다 

  - 예를 보자 

    1) thisIsPrivate 변수가 $get 펑션 밖에 위치한다 

    2) 따라서 setPrivate 메소드를 통하여 thisIsPrivate 변수값을 변경할 수 있다 

    3) 이런게 하는 이유는 우리가 필요로 하는 다양한 환경값을 바꾸어서 환경설정을 하고 싶기 때문이다

    4) Provider명칭이 'foo' 이므로 app.config 에서 'fooProvider' 명을 준다 

// script 

app = angular.module("app", []);

app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.provider('foo', function() {

  var thisIsPrivate = "Private";

  return {

    setPrivate: function(newVal) {

      thisIsPrivate = newVal; 

    },

    

    $get: function() {

      function getPrivate() {

        return thisIsPrivate;

      }


      return {

        variable: "This is public",

        getPrivate: getPrivate

      };

    } 

  };

});


app.config(function(fooProvider) {

  fooProvider.setPrivate('New value from config');

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

  </body>

</html>



7. Decorator (보너스1)

  - 기존 서비스에 새로운 펑션을 넣고 싶을 경우 사용한다

  - 예를 보자 

    1) $provide는 모든 서비스들을 내부적으로 생성할 때 사용한다 

    2) $provide.decorator 를 통해서 서비스를 확장할 수 있다. 

        첫번째 인자는 서비스 명칭, 두번째는 콜백으로 서비스 인스턴스인 $delegate을 받는다 

    3) $delegate에 greet 라는 메소드를 확장 정의 한다 

    4) 만일 3rd party 라이브러리 사용시 확장하고 싶을 경우 decorator를 사용한다 

// script 

app = angular.module("app", []);


app.controller('MainCtrl', function($scope, foo) {

  $scope.foo = foo;

});


app.factory('foo', function() {

  var thisIsPrivate = "Private";

  function getPrivate() {

    return thisIsPrivate;

  }

  return {

    variable: "This is public",

    getPrivate: getPrivate

  };

});


app.config(function($provide) {

  $provide.decorator('foo', function($delegate) {

    $delegate.greet = function() {

      return "Hello, I am a new function of 'foo'";

    };

    

    return $delegate;

  });

});


// html 

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body ng-controller="MainCtrl">

    public variable: {{foo.variable}}

    <br />

    private variable (through getter): {{foo.getPrivate()}}

    <br />

    greet: {{foo.greet()}}

  </body>

</html>



8. 새로운 인스턴스 생성하기 (보너스2)

  - 팩토리 메소드를 두어서 호출시 마다 무언가 새로 생성하고 싶을 경우 사용한다 

  - 예를 보자 

    1) Function 1급 class인 Person을 만든다 

    2) MainCtrl, SecondCtrl 각각 getById(1) 을 호출하지만 서로 다른 인스턴스가 전달되었다

    3) "Update It" 버튼을 클릭하면 두번째 열만 "Dave - Canada" 로 바뀜을 확인 할 수 있다 

// script 

app = angular.module('app', []);

app.controller('MainCtrl', function($scope, personService) {

  $scope.aPerson = Person.getById(1);

});

app.controller('SecondCtrl', function($scope, personService) {

  $scope.aPerson = Person.getById(1);

  $scope.updateIt = function() {

    $scope.aPerson.update();

  };

});


// Our class

function Person( json ) {

  angular.extend(this, json);

}


Person.prototype = {

  update: function() {

    // Update it (With real code :P)

    this.name = "Dave";

    this.country = "Canada";

  }

};


Person.getById = function( id ) {

  // Do something to fetch a Person by the id

  return new Person({

    name: "Jesus",

    country: "Spain"

  });

};


// Our factory

app.factory('personService', function() {

  return {

    getById: Person.getById

  };

});


// html

<!DOCTYPE html>

<html ng-app="app">

<head>

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>JS Bin</title>

</head>

  <body>

    <p ng-controller="MainCtrl">{{aPerson.name}} - {{aPerson.country}}</p>

    <div ng-controller="SecondCtrl">

      {{aPerson.name}} - {{aPerson.country}}

      <button ng-click="updateIt()">Update it</button>

    </div>

  </body>

</html>

  - 위의 script 코드를 CoffeeScript로 하면 class 표현을 쉽게 할 수 있다 

app.controller 'MainCtrl', ($scope, personService) -> $scope.aPerson = personService.getById(1) app.controller 'SecondCtrl', ($scope, personService) -> $scope.aPerson = personService.getById(2) $scope.updateIt = () -> $scope.aPerson.update() class Person constructor: (json) -> angular.extend @, json update: () -> @name = "Dave" @country = "Canada" @getById: (id) -> new Person name: "Jesus" country: "Spain" app.factory 'personService', () -> { getById: Person.getById 

}



<참조>

  - 원문 : Understanding Service Types

  - 가장 잘 설명된 글 : http://stackoverflow.com/questions/15666048/angular-js-service-vs-provider-vs-factory (강추)

  - 관련글 : GDG Korea WebTech 고재도님 링크

  - AngularJS Creating Service 매뉴얼

  - 이미지 : AngularJS Large Scale Architecture

  - JavaScript에서 객체를 만드는 3가지 방법

  - $provider를 통한 생성

posted by 윤영식
2013. 8. 21. 18:14 AngularJS/Concept

AngularJS를 사용할 때 가장 햇갈리는 부분이면서 중급으로 가기위하여 반드시 알고 넘어가야 하는 부분이 Scope 오브젝트에서 제공하는 $watch, $apply, $digest 메소드이다. 이들에 대해서 알아보자 



1. Event-Loop와 Angular Way

  - 브라우져는 사용자의 동작에 반응(이벤트)하기 위하여 이벤트 루프를 돌며 이벤트에 반응한다

  - Angular는 "angular context"라는 것을 만들어서 이벤트 루프를 확장한다 



2. $watch 목록

  - UI와 무언가를 바인딩하게 되면 반드시 $watch list(목록)에 $watch를 넣는다 

  - $watch는 모델(model)에 변경이 있는지를 항시 감시하는 역할을 한다

  - user와 pass에 대한 model 변경을 감시하기 위하여 두개의 $watch가 만들어져서 $watch list에 첨부된다 

User: <input type="text" ng-model="user" /> 

Password: <input type="password" ng-model="pass" />

  - $scope에 모델을 두개 만들고, html에서 한개의 model만 사용할 경우는 $watch가 한개만 만들어져서 $watch list에 첨부된다 

// script 

app.controller('MainCtrl', function($scope) { $scope.foo = "Foo"; $scope.world = "World"; });


// html

Hello, {{world}}

  - 배열일 경우 객체의 멤버를 각각 {{}}로 사용했으므로, name 1개, age 1개의 $watch가 만들어 진다. 예로 people 배열이 10개면 

    10 (배열) * 2 (name, age) + 1 (ng-repeat자체) = 총 21개의 $watch가 만들어져서 $watch list에 첨부된다 

// script

app.controller('MainCtrl', function($scope) { $scope.people = [...]; });


// html

<ul>

<li ng-repeat="person in people"> {{person.name}} - {{person.age}} </li> </ul>

  - Directives 만들 때도 바인딩이 있으면 당연히 $watch가 생성된다. 그럼 언제일까? template이 로딩될때 즉, linking 절차일때 필요한 $watcher가 생성된다 



3. $digest loop 

  - 브라우져가 "angular context"에 의하여 관리되어 질 수 있는 이벤트를 받게 될 때, $digest loop 가 작동되어 진다 

  - $digest loop은 두개의 작은 루프로 만들어진다. 하나는 $evalAsync queue를 처리하고, 다른 하나는 $watch list를 처리한다 

  - 처리되어지는 것은 무엇일까? 

    $digest는 $watch list를 루핑돌면서 model 변경을 체크하고, $watch에 등록된 listener handler를 수행한다

  - 이때 dirty-checking이 이루어지는데, 하나가 변경되면 모든 $watch를 루핑돌고 다시 체크해 보고 변화가 없을 때가지 루핑을 돈다

    그러나 무한 루프를 방지하기 위하여 기본적으로 최대 10번의 루핑을 돈다.  

    그리고 나서 $digest loop가 끝났을 때 DOM을 업데이트 한다. (즉, 변경감지시 즉시 DOM 반영이 아닌 Loop끝났을 때 반영함) 

  - 예를 보자. 

    1) $watch를 하나 가진다. ng-click의 경우는 클릭하면 펑션이 수행되므로 변화가 발생하지 않기 때문에 {{ name}}에 대해 1개만 생성

    2) 버튼을 클릭한다

    3) 브라우져는 "angular context"로 들어가서 처리될 이벤트를 받는다 

    4) $digest loop 가 수행되고, 변경을 체크하기 위하여 $watch에게 문의를 한다 

    5) $watch는 $scope.name에 변경이 있으면 변경을 보고한다. 그러면 다시 한번 $digest loop가 강제 수행된다

    6) $digest loop 돌면서 더 이상 변경된 것이 없다

    7) 브라우져는 통제권을 돌려받고 $scope.name 변경값을 반영하기 위하여 DOM을 업데이트 한다 

    

    여기서 중요한 것은 "angular context" 안으로 들어간 모든 이벤트는 $digest loop를 수행한다 는 것이다. 

    즉, 예로 input에 write하는 매번, 모든 $watch를 돌면서 변경을 체크하는 루프가 수행되는 것이다 

// script

app.controller('MainCtrl', function() { $scope.name = "Foo"; $scope.changeFoo = function() { $scope.name = "Bar"; } });


// html

{{ name }} <button ng-click="changeFoo()">Change the name</button>



4. $apply를 통하여 angular context로 들어가기 

  - 이벤트가 발생할 때 $apply를 호출하게 되면, 이벤트는 "angular context"로 들어가게 된다 

  - 그런데 $apply를 호출하지 않으면 "angular context" 밖에서 이벤트는 수행하게 된다 

  - 기존 ng-click 같은 이미 만들어져 있는 Directive들은 이벤트를 $apply 안에 랩핑한다. 

     또는 ng-model="foo"가 있다면 'f'를 입력하면 $apply("foo='f';")식으로 랩핑하여 호출한다  



5. Angular는 우리를 위해 자동으로 $apply를 호출해 주지 않는다 

  - jQuery의 예를 보면 jQuery 플러그인에서 이벤트에 대해 $apply가 호출되지 않기 때문에 발생한 이벤트는 "angular context"로 못들어가게 되므로 "$digest loop"도 수행되지 않게 된다. 결국 DOM의 변경이 발생하지 않는다 

  - 예를 보자 : <clickable> 앨리먼트를 클릭할 때마다 foo, bar 값이 1씩 증가하는 Directive이다 

     1) 클릭을 해보면 1씩 증가를 할까?  증가 하지 않는다 

     2) click 이벤트는 공통 이벤트이고 $apply로 감싸(랩핑)지지 않았기 때문이다 

     3) 결국 "angular context"에 못들어가니 "$digest loop"가 수행되지 않으니 $watch도 수행되지 않기 때문에 DOM 변경은 없다. 

         단, Click을 하게 되면 값은 1씩 증가한다. 즉 $scope값 변경이 DOM에 반영되지 않는다

app = angular.module('app', []); app.controller('MainCtrl', function($scope) { $scope.foo = 0; $scope.bar = 0; $scope.hello = "Hello"; $scope.setHello = function() { $scope.hello = "World"; }; });

app.directive('clickable', function() { return { restrict: "E", scope: { foo: '=', bar: '=' }, template: '<ul style="background-color: lightblue"><li>{{foo}}</li><li>{{bar}}</li></ul>', link: function(scope, element, attrs) { element.bind('click', function() { scope.foo++; scope.bar++; }); } } });

  - 예제 : http://jsbin.com/opimat/2/edit 를 보자 

    

    1) <clickable> 파란색 영역을 2번 클릭하면 숫자값의 변경에 대해서 DOM에 반영되지 않는다. 즉, 화면은 그대로 0 이다

    2) <button>의 ng-click을 하게되면 setHello()가 호출되고 자동으로 $apply에 감싸서 수행된다

    3) 이벤트가 "angular context"로 들어가면 "$digest loop"가 수행되면서 모든 "$watch list"를 돌면서 변경을 체크한다

    4) 변경된 값이 있다면 DOM을 업데이트 하므로 <clickable>의 foo, bar값이 현재 2번 클릭하여 2이므로 DOM으로 2로 변경한다 

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>

<meta charset=utf-8 />

<title>Directive example</title>

</head>

<body ng-controller="MainCtrl">

  <clickable foo="foo" bar="bar"></clickable>

  <hr />

  

  {{ hello }} <button ng-click="setHello()">Change hello</button>

</body>

</html>


  - 이벤트를 "angular context"로 들어가서 처리하게 해주는 방법-1

    이벤트 안에서 $apply()를 호출한다 

    $apply는 $scope(또는 Directive link의 scope)의 메소드이다. 따라서 $apply를 호출하여 "$digest loop"를 강제로 수행시킨다. 

element.bind('click', function() { scope.foo++; scope.bar++; scope.$apply(); 

});


  - 이벤트를 "angular context"로 들어가서 처리하게 해주는 방법-2

    여기서는 단순 값의 증가이지만 내부적으로 서버요청하다 에러가 발생하면 "방법-1"에서는 "angular context"가 전혀 에러를 알 수 없다

    방법-2처럼 감싸서(Wrapped) 사용해야 에러가 발생하면 "angular context"가 알 수 있도록 대응을 할 수 있다 

element.bind('click', function() { scope.$apply(function() { scope.foo++; scope.bar++; }); 

})


  - jQuery 플로그인을 사용하면 $apply 호출을 통해 "$digest loop"가 수행시켜 DOM 업데이트를 할 수 있다 



6. 우리 것은 $watch를 사용하자 

  - 우리의 모든 바인딩은 DOM 업데이트를 위하여 각각 $watch를 생성한다는 것을 알고 있다. 

     우리가 직접 $watch를 만들고 싶다면 어떨까?

  - 예를 보자

    1) $watch 첫번째 인자는 String, function이 가능하다. 여러개 모델 감시는 ; 로 구분한다 

    2) 두번째 인자는 첫번째 인자의 값이 변경되면 수행될 Handler 이다 

    3) 여기서는 <input>의 값을 변경할 때마다 updated 값이 1씩증가하여 {{updated}}에 값이 반영된다. 즉, DOM이 업데이트 된다

    4) View에서 값이 변경되어 Controller가 수행될 때 $watch를 발견하게 되고 바로 $watch가 수행된다.

// script 

app.controller('MainCtrl', function($scope) { $scope.name = "Angular"; $scope.updated = -1; $scope.$watch('name', function() { $scope.updated++; }); });


// html

<body ng-controller="MainCtrl">

<input ng-model="name" /> Name updated: {{updated}} times. </body>

    5) Controller가 수행되면서 $watch가 무조건 수행되는 것을 막기 위한 방법으로 두번째 인자 펑션의 파라미터 값으로 new, old 값을 받아 비교한다. 즉 최초에는 수행되지 않게 된다

$scope.$watch('name', function(newValue, oldValue) { if (newValue === oldValue) { return; } // AKA first run $scope.updated++

});

   6) 만일 값변경을 체크하는 것이 Object일 경우는 $watch의 3번째 인자값으로 true를 준다. 즉, Object의 비교를 수행도록 한다

app.controller('MainCtrl', function($scope) {

$scope.user = { name: "Fox" }; $scope.updated = 0; $scope.$watch('user', function(newValue, oldValue) { if (newValue === oldValue) { return; } $scope.updated++; }, true); });


주의할 점은 한 페이지에 $digest loop를 돌면서 체크하는 $watch가 2000~3000개 정도 생성되는 경우라면 성능상에 이슈가 있겠지만, 일반적인 경우 dirty-checking 하는 $digest loop은 상당히 빠르다



<참조>

  - 원문 : Watch how the apply runs a digest

  - 불필요한 $watch 제거하여 성능향상 시키기

  - AngularJS Concept 중에서

# Runtime 

The diagram and the example below describe how Angular interacts with the browser's event loop. 


일반적인 세상)

1. The browser's event-loop waits for an event to arrive. An event is a user interaction, timer event, or network event (response from a server). 

2. The event's callback gets executed. This enters the JavaScript context. The callback can modify the DOM structure. 

3. Once the callback executes, the browser leaves the JavaScript context and re-renders the view based on DOM changes. 


앵귤러 세상)

Angular modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and Angular execution context. Only operations which are applied in Angular execution context will benefit from Angular data-binding, exception handling, property watching, etc... You can also use $apply() to enter Angular execution context from JavaScript. Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks. 


1. Enter Angular execution context by calling scope.$apply(stimulusFn). Where stimulusFn is the work you wish to do in Angular execution context. 

2. Angular executes the stimulusFn(), which typically modifies application state. 

3. Angular enters the $digest loop. The loop is made up of two smaller loops which process $evalAsync queue and the $watch list. The $digest loop keeps iterating until the model stabilizes, which means that the $evalAsync queue is empty and the $watch list does not detect any changes. 

4. The $evalAsync queue is used to schedule work which needs to occur outside of current stack frame, but before the browser's view render. This is usually done with setTimeout(0), but the setTimeout(0) approach suffers from slowness and may cause view flickering since the browser renders the view after each event. 

5. The $watch list is a set of expressions which may have changed since last iteration. If a change is detected then the $watch function is called which typically updates the DOM with the new value. 

6. Once the Angular $digest loop finishes the execution leaves the Angular and JavaScript context. This is followed by the browser re-rendering the DOM to reflect any changes. H


예)

ere is the explanation of how the Hello world example achieves the data-binding effect when the user enters text into the text field. 

1. During the compilation phase: 

    1. the ng-model and input directive set up a keydown listener on the <input> control. 

    2. the {{name}}interpolation sets up a $watch to be notified of name changes. 

2. During the runtime phase: 

    1. Pressing an 'X' key causes the browser to emit a keydown event on the input control. 

    2. The input directive captures the change to the input's value and calls $apply("name = 'X';") to update the application model inside the Angular execution context. 

    3. Angular applies the name = 'X'; to the model. 

    4. The $digest loop begins 

    5. The $watch list detects a change on the name property and notifies the {{name}} interpolation, which in turn updates the DOM. 

    6. Angular exits the execution context, which in turn exits the keydown event and with it the JavaScript execution context. 

    7. The browser re-renders the view with update text.


posted by 윤영식
2013. 8. 21. 11:41 AngularJS/Concept

AngularJS에 대해 어떻게 생각하시나요? 그냥 webpage를 만드는 Front-end 단의 JavaScript Framework 정도로 생각하시나요? AngularJS는 클라이언트의 브라우져에서 SPA(Single Page Application)를 구현하기 위한 프레임워크입니다. Webpage를 만드는 것이 아니라 SPA의 A처럼 애플리케이션을 만드는 프레임워크라는 것이지죠. Adobe의 Flex 프레임워크와 대비하여 생각해도 되지 않을까 합니다. AngularJS로 개발한다면 새로운 관점에서 바라보아야 합니다. Angular Way의 길에 들어오세요



1. 페이지를 디자인 하지 말고 DOM(Document Object Model) 조작을 통해 페이지를 변경하라 

  - "나는 몇개의 DOM 조각을 가지고 있다 이것을 가지고 뭔가 동작하는 것을 만들고 싶다"라는 생각에서 AngularJS는 출발한다 

  - 완성하고 싶은 것이 있으면 애플리케이션 아키텍쳐 디자인을 한 다음 마지막에 화면 디자인을 한다 



2. AngularJS 와 함께 jQuery 에 대해서 말하지 마라 

  - AngularJS를 사용하면 jQuery 를 바탕으로 개발하는 것은 배제하길 바란다. Angular Way를 가라~~

  - jQuery 플러그인을 AngularJS에서 사용하기 위해 $apply를 남발하여 코드를 꼬아 놓지 말자. jQuery 플러그인 대부분은 약간의 코드조각으로 AngularJS에서 재작성되어 질 수 있다 (Directive로 만들어서 사용하기)



3. 아키텍쳐라는 단어를 항상 생각하라

  - SPA (Sinlge Page Application)는 application 이다. webpage가 아니다. 서버사이드 개발과 똑같이 생각하라

  - 따라서 모듈화 하고 확장성 있고 테스트 가능하게 개발해야 한다 


3-1. 화면은 공식적으로 표현되는 것 

  - jQuery 경우를 보자 

<ul class="main-menu">
    <li class="active">
        <a href="#/home">Home</a>
    </li>
    <li>
        <a href="#/menu1">Menu 1</a>
        <ul>
            <li><a href="#/sm1">Submenu 1</a></li>
            <li><a href="#/sm2">Submenu 2</a></li>
            <li><a href="#/sm3">Submenu 3</a></li>
        </ul>
    </li>
    <li>
        <a href="#/home">Menu 2</a>
    </li> 

</ul>


// JavaScript 

$('.main-menu').dropdownMenu();

  - 위의 경우를 단순한 AngularJS의 Directive로 표현한다. 이렇게 하면 jQuery처럼 HTML과 Javascript를 분리할 필요가 없고 해당 <ul> HTML 태그가 dropdown-menu임을 바로 알 수 있다.  

<ul class="main-menu" dropdown-menu>
    ... 

</ul>

  - 애플리케이션 개발시 반은 jQuery 반은 AngularJS 스타일로 - half jQuery, half AngularJS - 절대로 절대로 개발하지 말라

  - 디자인을 하지 말고, Markup을 해라. 즉, 아키텍쳐링을 하고 그 다음에 디자인을 하라. 


3-2. Data Binding

  - DOM을 조작(첨부,삭제)하는데 있어서 Two Way Binding을 적극적으로 사용하라 

  - jQuery 의 경우를 보자 : append하고 delete 하는 조작들을 넣어야 한다. 

$.ajax({
  url: '/myEndpoint.json',
  success: function ( data, status ) {
    $('ul#log').append('<li>Data Received!</li>');
  

});

<ul class="messages" id="log"</ul>

  - AngularJS 를 보자 : $scope를 통하여 controller와 view 간의 데이터가 삭제 되거나 첨부되는 것을 보다 쉽고 깔끔하게 처리해 준다

// controller 코드
$http( '/myEndpoint.json' ).then( function ( response ) {
    $scope.log.push( { msg: 'Data Received!' } ); 

});


// html 코드 

<div class="messages">
    <div class="alert" ng-repeat="entry in log">
        {{ entry.msg }}
    </div> 

</div>


3-3. 모델객체와 화면과 분리되어 있다

  - $scope를 통하여 view와 controller간 모델역할을 수행한다 

  - 이 방법은 view와 완전 독립적이어서 separation of concern을 유지하고 테스트 가능하게 만들준다


3-4. 관심의 분리 (SoC, separation of concern)

  - 아키텍쳐링을 하여 개발하면 관심 영역별로 분리하여 유지할 수 있다

  - 화면(view)은 공식적인 표현되는 것이고, 모델(model)은 데이터를 대표하며, 업무를 수행하는 서비스를 갖게된다 

  - 이것은 3-5에서 이야기하는 DI와도 연관이 있다 


3-5. 의존성 주입 (DI, Dependency Injection)

  - 코드안의 하드코딩된 부분을 제거하고 의존관계에 있는 모듈을 다양한 방법으로 전달해 준다

  - AngularJS에서는 펑션의 파라미터로 넘겨준다. 이를 통해 테스트시에 서버를 직접 호출할 필요없이 필요한 목서비스(mock service)를 전달하여 테스트를 할 수도 있다 



4. 항시 TDD 하라 

  - Test Driven Development 를 수행하라

  - jQuery로 테스트 할 때는 항시 테스트 화면을 만들어 해야 제대로 된 테스트 수행이 가능해진다. 그러나 AngularJS는 그렇지 않다

  - 내부에 사용되는 기능을 모듈로 분리해 개발하고 DOM조작에 대해서 테스트하고 애플리케이션과 통합할 수가 있기 때문이다 

  - 그것은 관심의 분리(SoC) 방식으로 개발하기 때문에 TDD가 AngularJS에서 가능한 것이다 

  - 예를 보자 

// HTML : when-active라는 AngularJS Directive(지시자)

<a href="/hello" when-active>Hello</a>

// Test Script : Karma를 보자. DOM을 조작하여 화면을 이동시킨 후 true, false인지 체크한다 

it( 'should add "active" when the route changes', inject(function() {
    var elm = $compile( '<a href="/hello" when-active>Hello</a>' )( $scope );

    $location.path('/not-matching');
    expect( elm.hasClass('active') ).toBeFalsey();

    $location.path( '/hello' );
    expect( elm.hasClass('active') ).toBeTruthy();
}));


// Script : 정상적으로 라우팅이 되면 active class 첨부, 아니면 삭제 

.directive( 'whenActive', function ( $location ) {
    return {
        scope: true,
        link: function ( scope, element, attrs ) {
            scope.$on( '$routeChangeSuccess', function () {
                if ( $location.path() == element.attr( 'href' ) ) {
                    element.addClass( 'active' );
                }
                else {
                    element.removeClass( 'active' );
                }
            });
        }
    };
});



5. Directive(지시자)는 패키징된 jQuery가 아니다 

  - Directive안에서 DOM 조작을 하라. 하지만 jQuery 스타일은 배제하라 

  - Directive는 template을 가지고 있는 widget과 유사하다. 이를 통해 SoC를 보여주고 있고, 테스트를 가능하게 한다 

  - jQuery 스타일의 버튼토글 코딩을 보자 : AngularJS Directive안에 아래와 같이 jQuery 스타일의 DOM 조작을 수행하지 말라

.directive( 'myDirective', function () {
    return {
        template: '<a class="btn">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            var on = false;

            $(element).click( function () {
                if ( on ) {
                    $(element).removeClass( 'active' );
                }
                else {
                    $(element).addClass( 'active' );
                }

                on = !on;
            });
        }
    }; 

});

  - Angular Way 방식의 버튼코글 : ng-show, ng-class, ng-binding 과 같은 Directive를 통해 DOM 조작을 수행하라  

.directive( 'myDirective', function () {
    return {
        scope: true,
        template: '<a class="btn" ng-class="{active: on}" ng-click="toggle()">Toggle me!</a>',
        link: function ( scope, element, attrs ) {
            scope.on = false;
            scope.toggle = function () {
                scope.on = !$scope.on;
            };
        }
    }; });

  - Directive는 jQuery같은 펑션의 집합이 아니다. Directive는 HTML의 확장이다. 이는 HTML안에 우리가 수행하고 싶은 무언가를 넣고 싶은데 못 할 경우에 Directive를 만들어야 하는 것이다. 그래서 HTML의 일부로서 사용하는 것이다. 



Angular Way에서 jQuery 방식을 사용하지 말고 포함조차 시키지 말아라. 


- View  === AngularJS의 Directive가 포함된 HTML이다 

- Model === $scope를 통하여 Controller에서 Two way binding을 자동 수행한다 

- View와 Controller를 통하여 표현과 행위를 분리한다 (SoC)

- AngularJS 애플리케이션 접근법

   Step 1) Model을 먼저 생각하고 Service를 만든다

   Step 2) Model이 표현될 View에 대해 생각해 보고 template을 만들고, 필요하면 자동 모델 바인딩되는 Directive도 만든다.

   Step 3) 각 View 를 Controller에 연결한다 (ng-view and routing, ng-controller)



<참조>

  - 원문 : Think in AngularJS

  - AngularJS의 상속에 대한 이해 (그림포함)

posted by 윤영식
2013. 8. 20. 17:32 AngularJS/Prototyping

iCheck 플러그인은 HTML input의 type이 Checkbox나 Radio 의 모양을 UX 입장에서 직관적으로 만들어 준다. AngularJS하에서 이런 JQuery Plugin을 사용하려면 Directive(지시자) 화하여 사용해야 하는데 어떻게 만드는지 알아보자 




1. Directive 만드는 형식 

  - 사용하려는 HTML 안에서 

<div directiveName ></div>

<script type="text/javascript" src="pluginName.js"></script>


  - AngularJS Directive(지시자) 만들기

    + pluginActivationFunction은 플러그인의 호출하는 펑션이다. 

    + scope.$eval 은 플러그인의 환경값을 넣어주면 된다 

App.directive('directiveName', function() {

    return {

        restrict: 'A',

        link: function(scope, element, attrs) {

            $(element).'pluginActivationFunction'(scope.$eval(attrs.directiveName));

        }

    };

}); 



2. iCheck Directive 만들기 

  - ng-app 모듈에 Directive를 만든다 

    + Radio 박스 선택에 따른 부가화면을 show하거나 hidden 하는 처리를 한다  

    + Attribute로 ngIcheck 지시자를 만들었다. (HTML에서는 ng-icheck 로 사용된다)

    + ifChecked의 iCheck 이벤트를 받았다. iCheck의 Radio를 사용하면서 AngularJS의 Radio 이벤트를 받을 수 없게 되었기 때문이다 

    + Radio의 attribute로 value="file" or "direct" 라는 두개의 Radio 선택 옵션을 주었다. 

    + AngularJS Context안에서 $scope의 값 변경을 인식하기 위하여 $scope.$apply()를 iCheck.on listener의 핸들러 펑션에 호출함.

    + $(element).iCheck(<환경값>); 의 plugin 초기화 펑션을 호출해 주었다 

.directive('ngIcheck', function() {

   return {

       restrict: 'A',

       controller: function($scope, $element) { 

        $element.on('ifChecked', function(event){

 if(event.target.value === 'file') {

  $scope.isFile = true;

  $scope.isDirect = false;

  $scope.$apply();

 } else if(event.target.value === 'direct') {

  $scope.isDirect = true;

  $scope.isFile = false;

  $scope.$apply();

 } else {

  $scope.isFile = false;

  $scope.isDirect = false;

  $scope.$apply();

 }

});

       },

       link: function(scope, element, attrs) {

           $(element).iCheck({

        checkboxClass: 'icheckbox_square-blue', 

        radioClass: 'iradio_square-blue', 

        increaseArea: '20%' 

       });

       }

   };

});


// 중요)

// iCheck를 AngularJS의 Directive로 하지 않고 사용하려면 HTML하단에 해당 script를 넣어준다

// 이렇게 하면 Radio 버튼의 이벤트가 AngularJS Context안에서 작동하지 않기 때문에 AngularJS의 기능을 사용할 수 없다 

// 결국 이렇게 사용하지 않기 위해선 JQuery Plugin을 AngularJS Directive로 만들어 AngularJS Context에서 돌아가게 해주는 것이다

<script>

$(document).ready(function(){

  $('input').iCheck({

    checkboxClass: 'icheckbox_square-blue',

    radioClass: 'iradio_square-blue',

    increaseArea: '20%'

  });

});

</script>


  - HTML에서 사용하기 

    + <input type="radio" name="iCheck" .../> : 여기까지는 iCheck에서 사용함 

    + <input ... value="file" ng-icheck> : AngularJS에서 사용. ng-icheck Attribute를 만다면 AngularJS가 컴파일하고 link하고 이벤트 반응에 대하여 controller에서 처리한다 

    + Radio의 선택에 따라서 ng-show="isFile", ng-show="isDirect"에 따라서 해당 div가 show, hidden 되어 진다 

  <table id="maForm" class="table table-striped table-bordered">

      <thead>

        <tr>

          <th width="25%"><i class="icon-check-sign icon-small"></i> Options</th>

          <th width="75%" class="not-for-mobile"><i class="icon-edit icon-small"></i> Insert Data</th>

        </tr>

      </thead>

      <tbody>

        <tr>

          <td>

            <label>Select Input Method</label> <br>

            <table class="table table-condensed">

              <tr class="active">

                  <td><input type="radio" name="iCheck" value="file" ng-icheck /></td>

                  <td>Upload File</td>

              </tr>

              <tr class="active">

                  <td><input type="radio" name="iCheck" value="direct" ng-icheck /></td>

                  <td>Input Direct</td>

              </tr>

            </table>

          </td>

          <td>

          <!-- input direct data -->

          <div ng-show="isDirect">

  <textarea ng-model="ma.direct" class="form-control" rows="12"></textarea>

  </div>

  <!-- upload file such as .csv -->

  <div ng-show="isFile">

                  <input id="fileupload" ng-model="ma.file" type="file" name="files[]" multiple>

  </div>

          </td>

        </tr>

        <tr>

      <td colspan="2">

  <i class="icon-bar-chart icon-small"></i> Moving Average Chart<br>

  <img src="http://www.placehold.it/880x300/EFEFEF/AAAAAA&amp;text=no+image">

      </td>

        </tr>

      </tbody>

    </table>


  - 결과 : 선택하는 옵션 박스가 iCheck 플러그인을 통하여 좀 더 직관적으로 나오게 되었다

    + 단순한 Radio 옵션

    

    + 변경된 iCheck Radio 옵션

    



<참조> 

  - Correct way to integrate JQuery Plugins in AngularJS 

  - How to convert a jQuery plugin to AngularJS Directive : 유튜브 동영상

  - Directive의 $watch 와 $apply 완벽 이해 : $apply, $digest, $watch 에 대한 상세 설명

  - Thinking in AngularJS : Angular Way 

  - SlideJS Plugins을 Directive로 만드는 유사한 예제

'AngularJS > Prototyping' 카테고리의 다른 글

[AngularJS] Twitter Search 만들기  (0) 2013.04.05
[AngularJS] Todo 목록 만들기  (0) 2013.04.05
[AngularJS] Hello World 만들기  (0) 2013.04.04
posted by 윤영식