
Angular.js를 사용하는 ionic 기반의 웹앱을 구성하는 프로젝트를 다루기 위해서는 프레임워크를 어느정도 이해하여야 코딩에 손을 댈 수 있다. 자바스트립트와 html 문법은 잘 안다는 가정하에 이 프레임워크를 이해하여야 한다. 아직까지 Angular.js가 정말로 유용한 프레임워크인지는 의문이 간다. Angular.js version 2가 나오면서 더욱 더 복잡해졌기 때문이다. 하지만 일단 version 1 에 대한 프로젝트를 커버하기 위해 기본적인 사항을 알아본다.
Angular.js는 구글이 만든 MV*(Model - View - Whatever) 자바스크립트 프레임워크로 모델과 뷰가 분리되어 동적인 웹 화면을 구성하기에 적합한 구조를 가지고 있다.

AngularJS의 기본구조를 나타낸 그림으로 AngularJS가 어떻게 로딩되고 시작되는지를 나타내주는 그림으로 순서는 다음과 같다.
<body ng-app>
  <span>Insert your name:</span>
  <input type="text" ng-model="user.name" />
  <h3>Echo: {{user.name}}</h3>
</body>See the Pen NdYzMX by youngdeok (@medusakiller) on CodePen.
동작방식은 ng-model 디렉티브로 input이 양방향 바인딩(two-way binding) 되어 데이터를 표현한다. 그러면 user.name는 어디에 저장되는지가 궁금할텐데 user.name은 $scope에 저장되어 있다. input에 입력할 때마다 스코프의 user.name 객체가 갱신되고 Angular.js의 {{ ... }} 인터폴레이션으로 모델을 출력할 수 있다. 그래서 HTML에서 user.name의 값을 볼 수 있는 것이다. input에 입력할 때 user.name이 스코프에 저장되고 인터폴레이션으로 HTML에서 그 값을 보게 된다.

템플릿에서 $scope에 user.name을 설정하면 컨트롤러에서도 user.name에 접근할 수 있다.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
  $scope.message = 'World';
});
<body ng-app="app" ng-controller="MainCtrl">
  Hello, {{ message }}
</body>See the Pen angular.js ex02 by youngdeok (@medusakiller) on CodePen.
app 에 컨트롤러를 생성하여 코드내에서 message 에 대한 내용을 받는다. 
body태그에 포함된 ng-app="app" ng-controller="MainCtrl" 는 directive 라고 한다.  ng-app은 body 요소가 Anuglar 어플리케이션에 포함되어 있다고 알려준다. 즉, body 요소내의 모든 것을 Angular 어플리케이션이 관리하도록 한다. ng-controller는 요소의 스코프를 MainCtrl이라는 컨트롤러에 할당하겠다는 것이다.
// app.js
 var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
  $scope.greet = function() {
    $scope.message = "Hello, " + $scope.user.name;
  }
});
<body ng-app="app" ng-controller="MainCtrl">
  What's your name?:
  <input type="text" ng-model="user.name" />
  <button ng-click="greet()">Click here!</button>
  <h3>{{ message }}</h3>
</body>See the Pen Angular.js ex03 by youngdeok (@medusakiller) on CodePen.
컨트롤러를 보면 $scope에 함수를 연결하는 방법을 알 수 있다. $scope에 연결한 함수에서 인풋에 입력된 값인 $scope.user.name의 문자열을 이어붙혀서 $scope에 message로 추가한다.
그리고 HTML에서 버튼을 생성하고 ng-click 디렉티브를 사용했다. 간단히 얘기하자면 ng-click 디렉티브는 해당 요소를 클릭할 수 있게 만들어서 클릴할 때마다 할당한 함수를 실행하는데 이 예제에서는 greet()를 실행한다.
<body>
  <div id="chart"></div>
</body>이 코드에서 id가 어떤 역할을 하는지 살펴보려면 구현된 자바스크립트 파일중에 하나를 봐야 한다.
// charts.js
$('#chart').pieChart({ ... });이 부분을 찾아내면 위의 HTML이 파이차트를 담는 곳이라는 것을 알 수 있다. 여기서 문제점은 페이지에 사용한 모든 자바스크립트 파일을 보지 않으면 페이지가 무엇을 하는지 정확히 알 수 없다는 것이다.
이제 Angular 어플리케이션의 코드를 보자.
<body>
  <pie-chart width="400" height="400" data="data"></pie-chart>
</body>파이차트를 추가한다는 간다한 사실뿐만 아니라 크기가 어느정도 이고 어떤 데이터가 할당되었는지를 알 수 있다.
이미 ng-app, ng-controller, ng-click, ng-model 과 같은 ng 접두사를 사용하는 다수의 내장 디렉티브가 존재한다. 
<button ng-click="show = !show">Show</button>
  <div ng-show="show">
    I am only visible when show is true.
  </div>show는 표현식이 true일때만(예제에서는 바인딩한 값) 해당 요소를 보여준다. 이 예제에서 ng-click을 사용한 방법을 주의깊게 보길 바란다. 이 예제에서는 컨트롤러에 함수를 생성할 필요가 없으므로(컨트롤러 자체도 필요없다!) 디렉티브의 인자를 표현식으로 작성해서 show의 값을 토글했다. show는 undefined로 시작되고 첫 클릭시 true가 된다. ng-show의 반대인 ng-hide도 있다.
배열목록을 출력해 보자.
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
  $scope.developers = [
      {
        name: "Youngdeok", country: "Korea"
      },
      {
        name: "Dave", country: "Canada"
      },
      {
        name: "Wesley", country: "USA"
      },
      {
        name: "Krzysztof", country: "Poland"
      }
    ];
});
<body ng-app="app" ng-controller="MainCtrl">
 <ul>
   <li ng-repeat="person in developers">
     {{person.name}} from {{person.country}}
   </li>
 </ul>
</body>
See the Pen angularjs ex04 by youngdeok (@medusakiller) on CodePen.
HTML에서 ng-repeat 디렉티브를 사용했다. ng-repeat는 컬렉션의 아이템마다 새로운 템플릿을 생성할 것이다. 예제에서는 4개의 아이템이 있으므로 ng-repeat가 이 코드 부분을 4번 생성할 것이다.
각 반복된 <li>는 자신만의 스코프를 가지며 person은 developers의 요소에 접근한다.
이처럼 각 디렉티브는 역할이 있으며 자신만의 디렉티브도 만들어낼 수 있다.
 <body ng-app="app" ng-controller="MainCtrl">
  What's your name?:
  <input type="text" ng-model="user.name" />
  <button ng-click="greet()">Click here!</button>
  <h3>{{ message }}</h3>
</body>이 코드에서 페이지가 로딩 되었을 때 input에 포커스를 주고 싶다면 다음과 같은 디렉티브를 만들어낼 수 있다.
// focus.js
app.directive('focus', function() {
  return {
    link: function(scope, element, attrs) {
      element[0].focus();
    }
  };
});디렉티브는 객체를 반환해야 하고 이 반환 객체에 몇가지 속성을 정의할 수 있지만 이 예제에서는 속성을 사용하지 않았다. 디렉티브는 link 함수를 반환할 수 있는데 이 link함수 안에 템플릿 로직의 대부분을 작성하고 여기서 DOM 리스너를 등록하거나 DOM을 갱신하는 등의 작업을 할 수 있다.
이 link함수는 3개의 파라미터를 받는다.(실제로는 4개지만 이는 약간 고급에 해당한다.) 3개의 파라미터는 scope와 디렉티브를 사용한 element와 element의 속성인 attr이다. 여기서 HTML 요소에 click 이벤트나 mouseenter 이벤트를 바인드할 수 있다.
이 예제에서는 첫번째 요소(인풋)을 가져와서 focus함수를 호출했다. element가 어떻게 동작하는지 궁금하면 공식문서 Element API를 참고해라.
이제 남은 작업을 이 디렉티브를 사용하는 것 뿐이다. 포커스를 주고자 하는 요소에 이 디렉티브 이름을 추가하면 된다.
 <body ng-app="app" ng-controller="MainCtrl">
  What's your name?:
  <input type="text" focus ng-model="user.name" />
  <button ng-click="greet()">Click here!</button>
  <h3>{{ message }}</h3>
</body>그러면 HTML을 렌더링하는 디렉티브는 어떻게 작성해야 할까? 다음 예제를 보자.
// hello.js
app.directive('hello', function() {
  return {
    restrict: "E",
    replace: true,
    template: "<div>Hello readers, thank you for coming</div>"
  }
});true로 설정하면 해당 요소는 새로운 템플릿으로 대체될 것이다.따라서 hello 디렉티브는 다음과 같이 사용 가능하다.
<hello></hello>이 코드는 template의 코드인 <div>Hello readers, thank you for coming</div> 같이 변경된다. html을 별도의 파일로 관리하려면 templateUrl에 파일을 지정할 수도 있다. 이때 로드할 html의 상대위치를 정의한다.
  <body>
    <div ng-controller="Ctrl">
      <my-example></my-example>
    </div>
  </body>
// app.js
angular.module('app', [])  
  .controller('Ctrl', function($scope) {
    $scope.person = {
      name: 'Youngdeok',
      address: 'Incheon'
    };
  })
  .directive('myExample', function() {
    return {
      templateUrl: 'my-example.html'
    };
});
<!-- my-example.html -->
<span> Name: {{person.name}} </br> Address: {{person.address}} </span>
디렉티브를 사용한 HTML의 태그에 template 또는 templateUrl에 포함된 태그 내용을 추가할지 교체할지 설정한다. true로 설정할 경우 HTML의 디렉티브를 사용한 태그를 template 또는 templateUrl에 작성된 내용으로 교체된다.
// app.js
angular.module('app', [])  
  .controller('Ctrl', function($scope) {
    $scope.person = {
      name: 'Youngdeok',
      address: 'Incheon'
    };
  })
  .directive('myExample', function() {
    return {
      restrict: 'E',
      template: '<div>Hello AngularJS</div>',
      replace: true
    };
});
그 밖에 transclude 를 사용하면 원본 내용을 포함하는 형태로 교체할 수 있다. 원본은 template나 templateUrl에 <span ng-transclude> 를 사용한 곳에 들어가게 된다. 
기본적인 표현식을 파이프를 사용해 필터를 적용할 수 있다. 예를 들어 ng-repeat에서 개발자 목록을 출력을 이름으로 정렬 한다면,
<ul>
  <li ng-repeat="person in developers | orderBy:'name'">
    {{ person.name }} from {{ person.country }}
  </li>
</ul>와 같이 orderBy를 사용한다. 만일 -name을 사용하면 역순으로 정렬될 것이다.
이정도로도 유용하다고 생각할 것이지만 좀 더 괜찮은 예제를 보자. 4명의 개발자가 아니라 300명의 개발자가 있고 이를 (name, country등으로) 필터링하기를 원한다고 해보자. 이를 위해서 컬렉터를 필터링하는 방법을 정하고 필터링되지 않은 컬렉션을 필터링한 컬렉션으로 교체하려고 할텐데 더 간단한 방법이 있다.
<body ng-app="app" ng-controller="MainCtrl">
  Search: <input ng-model="search" type="text" />
  <ul>
    <li ng-repeat="person in developers | filter:search">
      {{ person.name }} from {{ person.country }}
    </li>
  </ul>
</body>input에 할당된 search는 filter에 전달되어 필요한 정보에 따라 필터링 한다. 좀더 정밀하게 name을 골라내려면,
<body ng-app="app" ng-controller="MainCtrl">
  Search: <input ng-model="search.name" type="text" />
   <ul>
     <li ng-repeat="person in developers | filter:search">
       {{ person.name }} from {{ person.country }}
     </li>
   </ul>
</body>search.name을 사용해 바인딩하여 이름을 전달 하는 것이다.
자신만의 필터를 직접 생성하는 방법을 알아본다. 첫글자를 대문자를 필터를 어떻게 작성하는지 보자.
app.filter('capitalize', function() {
  return function(input, param) {
    return input.substring(0,1).toUpperCase()+input.substring(1);
  }
});이 예제에서는 인풋의 첫글자를 대문자로 바꿨다. 이제 이 필터를 사용해 보자.
<span>{{ "this is some text" | capitalize }}</span>
서비스는 어플리케이션의 어떤 기능을 제공하는 싱글톤 클래스다. 어플리케이션 로직을 컨트롤러에 분산시키는 대신 다른 서비스에 로직을 둘 수 있다.
Angular에는 HTTP 요청을 관리하는 $http나 Promise에 대한 $q와 같은 많은 내장 서비스가 있다. 간단한 서비스를 생성해 이해해 보자.
서비스를 사용하는 가장 일반적인 경우는 컨트롤러간에 정보를 공유해야하는 경우이다. 모든 컨트롤러는 자신만의 스코프를 가지므로 다른 컨트롤러의 스코프를 바인딩할 수 없다. 이 문제를 해결하기 위해 서비스를 사용하면 한 곳에서 데이터를 가지고 있고 필요한 어디서나 이 데이터를 사용하도록 할 수 있다. 우선 문제의 상황을 보기 위해 서비스가 없는 다음 예제를 살펴보자.
<div ng-controller="MainCtrl">
  MainCtrl:
  <input type="text" ng-model="user.name">
</div>
<div ng-controller="SecondCtrl">
  SecondCtrl:
  <input type="text" ng-model="user.name">
</div>
// controllers.js
app.controller('MainCtrl', function($scope) {
});
app.controller('SecondCtrl', function($scope) {
});위 처럼 두개의 분리된 컨트롤러에서 user.name을 다루려고 할때 안쪽에서 이름을 쓰면 다른쪽에서 똑같이 반영되도록 하고 싶을때 이코드는 제대로 동작하지 않는다. 왜냐면 스코프가 분리되어 있기 때문이다.

이 문제를 해결하기 위해 두 컨트롤러에서 사용할 수 있도록 사용자 이름을 가진 서비스를 생성할 것이다.
// user_information.js
app.factory('UserInformation', function() {
  var user = {
    name: "Angular.js"
  };
  return user;
});서비스를 생성하기 위해 app 모듈의 factory 함수를 사용했다. 서비스를 생성하는 좀더 고급적인 방법도 있다.(service와 provider 함수를 사용하지만 이는 다른 글에서 설명하겠다.) 서비스를 생성하는 여러가지 방법이 있지만 이 예제에서는 미리 정의해 놓은 이름으로 private 사용자 객체를 생성해서 반환했다. 이 서비스는 컨트롤러에서 다음과 같이 사용한다.
// controllers.js
app.controller('MainCtrl', function($scope, UserInformation) {
  $scope.user = UserInformation;
});
app.controller('SecondCtrl', function($scope, UserInformation) {
  $scope.user = UserInformation;
});이 예제는 다음과 같이 된다.

이 예제는 의도대로 잘 동작한다. MainCtrl와 SecondCtrl 양쪽의 $scope.user는 UserInformation를 사용하고 서비스가 싱글톤이기 때문에 한 컨트롤러에서 UserInformation의 값을 바꾸면 다른 쪽에서도 바뀐다.
여기서 UserInformation 파라미터가 어디서 왔는지 궁금할 것이다. Angular는 서비스를 필요로 하는 곳에 서비스를 주입하는 의존성 주입을 사용한다. 의존성 주입이 동작하는 방식을 설명하는 것은 이 글의 주제를 벗어나지만 간단히 말하자면 서비스를 생성하면 어느 컨트롤러나 디렉티브, 다른 서비스에도 이 서비스를 주입할 수 있다. 주입하는 방법은 그냥 파라미터에 서비스의 이름을 전달하면 된다. 아마 이 의존성 주입이 $scope 파라미터를 사용한 것과 같은 것인지 궁금할텐데 $scope는 다른 컨트롤러에 주입되기는 하지만 실제로 서비스는 아닌 예외사항 중 하나이다.
"If you would thoroughly know anything, teach it to other."
- Tryon Edwards -