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

Publication

Category

Recent Post

2013. 9. 25. 16:52 My Services/Smart Visualization

AngularJS는 구글이 후원하고 있고 2013년 구글I/O에서도 2개의 세션에서 소개가 될 정도로 관심이 높은 차기 웹 클라이언트단의 애플리케이션 개발을 위한 프레임워크이다. MVC -Model View Controller - 기반으로 역할을 나누고, SoC (separate of concer)이라는 관심의 분리를 위하여 모듈에 대한 DI (Dependency Injection)을 사용한다. 




1. AngularJS 어떻게 배워야 하지?

  - AngularJS에 대한 개념적 이해와 익히는 방법을 알아보자 

  - 전반적으로 다시 한번 복습해 보고

  - 이젠 AngularJS만의 길을 가보자 



2. AngularJS 스케폴딩 둘러보기 

  - SmartVisualization/app 폴더는 다음과 같다 

    + index.html : 최초 호출 페이지

    + bower_components : bower에 의해서 설치된 client에서 사용하는 library들. 

       bower 를 통해 install 하면 JavaScript 링크가 index.html에 자동 삽입된다 

    + scripts : angular javascript 파일 폴더들 

    + views : angular의 template 파일은 .html 확장자이다. 보통 partial 파일이라고 한다

       


  - index.html 가 메인 페이지이다 

    + ng-app 를 통하여 AngularJS 애플리케이션 명칭을 정의

    + ng-view 는 views 폴더의 partial html이 표현되는 영역이다. DOM을 보게 되면 해당 영역만 변경이 이루어 진다 

    + app.js 가 AngularJS의 메인 수행 파일이고 일반적으로 partial view에 대한 routing을 정의한다 

    + controller/main.js 은 AngularJS의 컨트롤러 파일이다. partial html 파일이 여러개 있더라도 동일한 controller를 사용할 수 있다. 

<!doctype html>

<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->

<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->

<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->

<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->

  <head>

    <meta charset="utf-8">

    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <title></title>

    <meta name="description" content="">

    <meta name="viewport" content="width=device-width">

    <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->


        <!-- build:css(.tmp) styles/main.css , 부트스트랩의 RWD까지 적용되어 있다 -->

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

        <link rel="stylesheet" href="bower_components/bootstrap-sass/bootstrap-responsive-2.3.2.css">

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

        <!-- endbuild -->

</head>

  <!-- yo angular:app SmartVisualization 명령수행으로 App 접미어를 붙여서 프로젝트 명칭을 만들었다 --> 

  <body ng-app="SmartVisualizationApp">

    <!--[if lt IE 7]>

      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>

    <![endif]-->


    <!--[if lt IE 9]>

      <script src="bower_components/es5-shim/es5-shim.js"></script>

      <script src="bower_components/json3/lib/json3.min.js"></script>

    <![endif]-->


    <!-- Add your site or application content here : ng-view 영역이 실제 partial html 파일로 대체되는 영역 -->

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


    <!-- angular.js 자체에 light jquery를 내장하고 있지만 정식버전 jquery를 사용하고 싶다면 

          하기와 같이 angular 위에 놓으면 된다 -->

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

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


        <!-- build:js scripts/plugins.js, 사용하지 않는 부트스트랩 자바스크립트는 제거한다 -->

        <script src="bower_components/bootstrap-sass/js/bootstrap-affix.js"></script>

        <script src="bower_components/bootstrap-sass/js/bootstrap-alert.js"></script>

        .. 중략 ..

        <script src="bower_components/bootstrap-sass/js/bootstrap-scrollspy.js"></script>

        <script src="bower_components/bootstrap-sass/js/bootstrap-collapse.js"></script>

        <script src="bower_components/bootstrap-sass/js/bootstrap-tab.js"></script>

        <!-- endbuild -->


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

        <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>

        <!-- endbuild -->


        <!-- build:js({.tmp,app}) scripts/scripts.js, 업무적으로 추가되는 파일은 이곳에 넣는다

              단, yo 명령으로 controller 또는 service를 생성하면 자동으로 파일명이 추가된다  -->

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

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

        <!-- endbuild -->

</body>

</html>


  - AngularJS 메인 애플리케이션 파일인 app.js 을 보자 

    + module : ng-app에서 정의한 SmartVisualizationApp 명칭이 들어가고 

      [] 에는 별도로 파일을 구분하였다면 의존관계에 있는 모듈을 정의 한다 

    + templateUrl : index.html 위치에서 partial html 상대위치를 정의한다 

    + controller : main.html 안의 model 제어를 위한 controller명칭을 정의한다 (controller/main.js 소스에 정의된 컨토롤러 명칭)

    + redirectTo : http://localhost:9000/ 을 넣으면 무조건 views/main.html이 보여지도록 그외의 url에 대한 redirect를 정의한다 

'use strict';


angular.module('SmartVisualizationApp', [])

  .config(function ($routeProvider) {

    $routeProvider

      .when('/', {

        templateUrl: 'views/main.html',

        controller: 'MainCtrl'

      })

      .otherwise({

        redirectTo: '/'

      });

  });


  - main.html 은 partial html 파일이다   

    + 이곳에 이동평균에 관련된 view가 정의될 것이다 

<div class="hero-unit">

  <h1>'Allo, 'Allo!</h1>

  <p>You now have</p>

  <ul>

      <li ng-repeat="thing in awesomeThings">{{thing}}</li>

  </ul>

  <p>installed.</p>

  <h3>Enjoy coding! - Yeoman</h3>

</div>



3. index.html 메인 뷰페이지 RWD 수행

  - RWDResponsive Web Design의 약어로 반응형 웹 디자인이라고 한다. 

    RWD의 목적은 사용자에게 최적의 뷰 경험을 제공하는데 있다. 

    웹 서비스를 만들 때 중요한 요소로 검색엔진 최적화, 웹 접근성, 반응형 웹 디자인을 필수로 고려해야 한다. (참조

  - RWD는 시각화 표현에 대한 부분이고 웹에서는 CSS가 이를 담당하며, 가장 유명한 CSS Framework으로 Twitter Bootstrap이 있다

    하지만 Bootstrap 이외에 GroundWorks, Zurb Foundation, Amazing Web Library 들도 있으니 구미에 맞는 것을 선택한다.

  - Bootstrap을 사용하게 되면 몇가지 유용한 도구와 서비스들이 있다. 

    여기서는 Bootstrap 기반으로 화면의 Layout을 만들어 주는 Bootply를 사용한다 

 


  - 화면에서 가운데 메뉴 "Drag-and-Drop"을 선택하면 팝업창에서 3가지 Layout 옵션이 나온다. 여기선 "Simple"을 선택한다

  

  

  - Editing 화면에서 내역을 수정하고 상단의 "Run"을 클릭하면 변경 내역을 다양한 기기 해상도로 테스트해 볼 수 있다 

    + yo angular는 bootstrap 2.3* 기반이니 주의한다. Bootply는 boostrap v3.*도 지원한다 (default v2.3*)

  


  - Bootply의 html 내역중 메뉴 관련 내용을 copy하여 index.html에 추가한다

    ng-view 부분에 main.html 내역이 표시될 것이다 

// index.html 추가 부분

<body ng-app="SmartVisualizationApp">

    <!--[if lt IE 7]>

      <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>

    <![endif]-->


    <!--[if lt IE 9]>

      <script src="bower_components/es5-shim/es5-shim.js"></script>

      <script src="bower_components/json3/lib/json3.min.js"></script>

    <![endif]-->


    <div class="navbar navbar-inverse navbar-fixed-top">

      <div class="navbar-inner">

        <div class="container">

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

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

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

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

          </button>

          <a class="brand" href="#">Smart Visualization</a>

          <div class="nav-collapse collapse">

            <ul class="nav">

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

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

            </ul>

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

        </div>

      </div>

    </div>


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

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


  - main.html 전체 내역을 다음과 같이 바꾼다. bootstrap의 container css class로 감싸준다. 

    app.js에서 루트 '/' 호출은 /views/main.html 로 라우팅되므로 index.html의 ng-view에서 main.html 보여지게 된다 

<div class="container">

  <h1>Smart Visualization Service</h1>

  <p>

    Job Less, Feel More - 인위적인 작업을 적게 하면 더 행복해 질 수 있다고 믿습니다.<br> 

    단순 파일 업로드만으로 데이터 통계를 미려한 차트로 표현해 드립니다.<br>

    .csv 파일에 시간과 수치값만 있다면 SVG Chart로 통계 변환 수치를 보여드립니다

  </p>

  

</div> <!-- /container -->


  - 변경 내역을 저장하고 확인해 보자 

$ grunt server


// 브라우져 사이즈를 줄여보면 RWD 경험을 제공한다 



<참조>

  - 견고한 Web Applicatoin을 만드는 방법 with AngularJS

  - Think in AngularJS Way : 접근 방법에 대한 조언

  - Bootstrap Layout 전문 서비스 Bootply

  - Boostrap기반의 유용한 도구와 서비스들

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 윤영식
prev 1 2 next