2019. 4. 7. 17:37ㆍFrontend/AngularJS
해당 포스트는 Inflearn AngularJS 강좌를 기반으로 작성되었으며, 언급되는 개념 및 실습 과정을 담았습니다.
따라서, 코드는 단계적으로 작성되어 이전 코드와 연결되어 있습니다.
AngularJS
Service
Controller의 분리
이전 포스트에서 Controller, Directives, app을 분리했었는데 
이번엔 Controller 내부의 코드를 분리하는 작업을 할 것이다.
Controller의 역할
- Data 관리 기능 
- View 관리 기능 
이 두가지를 분리할 것인데, 
Data 관리(조작)하는 부분을 Angular의 Service 기능을 사용해서 분리할 것이다.
Angular Service
구현 방법 3가지 (service, factory, provider) 중 factory 함수 사용할 예정
TODO
서비스 구현 방법 3가지 비교
데이터 정의 분리
우리는 데이터를 controller안에 정의해뒀었다. 
해당 데이터를 따로 관리하기 위해서, 
storageData라는 서비스를 생성하여 get()함수를 사용해 데이터를 가져오도록 바꿔보자.
- services.js파일을 생성하여 서비스파일을 따로 관리해준다.- 서비스 이름을 - todoStorage로 정의하였다.
- storage변수는- todos라는 객체와- get()함수를 포함한다.
- storage를 리턴한다. 외부에서- get()함수를 사용해 데이터에 접근할 수 있다.
 
services.js
1.// 서비스 이름 : todoStorage -> 해당 키 값으로 
2.angular.module('todo').factory('todoStorage', function() {
3.  var storage = {
4.    todos: [
5.      {
6.        title: '요가 수업',
7.        completed: false,
8.        createdAt: Date.now()
9.      },
10.      {
11.        title: '앵귤러 학습',
12.        completed: false,
13.        createdAt: Date.now()
14.      },
15.      {
16.        title: '운동하기',
17.        completed: true,
18.        createdAt: Date.now()
19.      }
20.    ],
21.    get: function() {
22.      return storage.todos;
23.    }
24.  };
25.
 26.  return storage;
27.});
- 기존 - controller에서- todoStorage서비스로부터 데이터를 받아온다.- todoStroage를 매개변수로 받아 접근할 수 있다.
- 서비스에서 생성한 - get()함수를 통해- todos데이터를 받아온다.
 
controller.js
1.angular.module('todo').controller('TodoCtrl', function($scope, todoStorage) {
2.  $scope.todos = todoStorage.get();
3.
 4.  // 여기서부턴 기존 코드와 동일
5.  $scope.remove = function(todo) {
6.      ...
7.  };
8.
 9.  $scope.add = function(newTodoTitle) {
10.      ...
11.  }
12.});
- index.html에서- services.js스크립트를 포함해준다.
index.html
1.<script src="services.js"></script>
그럼 정상 동작한다!!
remove 분리
마찬가지로, controller에 모두 작성해두었던 삭제 기능을 분리해보자.
기존 코드는 다음과 같다. 
controller.js (remove 부분)
1.  $scope.remove = function(todo) {
2.    // todos 배열에서 todo index를 찾는다.
3.    var idx = $scope.todos.findIndex(function (item) {
4.      return item.id == todo.id;
5.    });
6.
 7.    // idx가 유효할 때 삭제해준다.
8.    if (idx > -1) {
9.      // 배열 객체에서 제공되는 함수인 splice를 이용하면 원하는 위치에 요소를 삭제할 수 있다.
10.      // idx 위치부터 1개의 요소를 삭제한다.
11.      $scope.todos.splice(idx, 1)
12.    }
13.  };
모든 부분이 데이터 조작을 담당하고 있으므로 서비스로 모두 옮기자!
- dataStorage서비스의- remove()함수를 생성해준다.
services.js
1.angular.module('todo').factory('todoStorage', function() {
2.  var storage = {
3.    todos: ...,
4.    get: function() {
5.      return storage.todos;
6.    },
7.    // remove 함수를 추가해주었다!
8.    // 기존 코드를 copy&paste 한 경우, 스코프 등을 맞게 수정해야 한다.
9.    remove: function(todo) {
10.      var idx = storage.todos.findIndex(function (item) {
11.        return item.id == todo.id;
12.      });
13.      \
14.      if (idx > -1) {\
15.        storage.todos.splice(idx, 1)
16.      }
17.    }
18.  };
19.
 20.  return storage;
21.});
- controller에서- dataStroage의- remove()를 호출해준다.
controller.js
1.  $scope.remove = function(todo) {
2.    todoStorage.remove(todo);
3.  };
add 분리
마찬가지로, controller에 모두 작성해두었던 추가 기능을 분리해보자.
기존 코드는 다음과 같다.
controller.js (add 부분)
1.  $scope.add = function(newTodoTitle) {
2.    // create new todo
3.    var newTodo =  {
4.      title: newTodoTitle,
5.      completed: false,
6.      createdAt: Date.now()
7.    };
8.
 9.    // push into todos
10.    $scope.todos.push(newTodo);
11.
 12.    // view : input field를 비워준다.
13.    $scope.newTodoTitle = null;
14.  }
값이 추가된 후 입력창을 비워주는 부분(마지막 줄 코드)은 View 관리 기능이므로, controller에 그대로 놔둔다. 
이외의 코드들은 data 관리 부분이므로 서비스로 모두 옮기자!
services.js
1.angular.module('todo').factory('todoStorage', function() {
2.  var storage = {
3.    todos: ...,
4.    get: ...,
5.    remove: ...,
6.    // add 함수를 추가해주었다!
7.    // 기존 코드를 copy&paste 한 경우, 스코프 등을 맞게 수정해야 한다.
8.    add: function(newTodoTitle) {
9.      var newTodo =  {
10.        title: newTodoTitle,
11.        completed: false,
12.        createdAt: Date.now()
13.      };
14.
 15.      storage.todos.push(newTodo);
16.    }
17.  };
18.
 19.  return storage;
20.});
- controller에서- dataStroage의- add()를 호출해준다.
controller.js
1.  $scope.add = function(newTodoTitle) {
2.    todoStorage.add(newTodoTitle);
3.    $scope.newTodoTitle = null;
4.  }
localStorage
우리가 만든 웹서비스를 새로고침하면, 모든 데이터가 다 날아간다! 
데이터를 일정하게 유지하기 위해 DB를 사용하지만, 우리는 웹 개발만 다루고 있으므로 
localStorage를 사용하여 개발할 것!
localStorage?
- 로컬 스토리지와 세션 스토리지는 HTML5에서 추가된 저장소 
- 간단한 key-value 형식의 저장소 
- localStorage라는 변수로 접근할 수 있다.- 저장 : - localStorage.setItem(key, value)
- 가져오기 : - localStorage.getItem(key)
 
- 입력은 무조건 - String타입으로 처리된다.
- 약 최대 5mb의 용량을 가진다. 
- Chrome의 경우 SQLite를 사용한다. 그리고 개발자도구에서 확인할 수 있다. 
개발자도구를 켜보자
데이터가 유지되는 웹 개발
데이터를 조작하는 부분이니깐 service에 localStroage에 값을 저장하고 꺼내오는 함수를 정의하자.
- localStorage 조작 함수 정의 - _:- _은 접근제어자 역할을 하는데,- _가 붙은 variable은- private한 성격을 띈다.
- ||: 논리연산자- OR을 사용해 예외처리 함- expr1 || expr2=- expr1을- true로 변환할 수 있으면- expr1을 반환하고, 그렇지 않으면- expr2를 반환
 
 
services.js
1.angular.module('todo').factory('todoStorage', function() {
2.  // 상수는 대문자로 선언
3.  let TODO_DATA = 'TODO_DATA';
4.
 5.
 6.  var storage = {
7.    // todos는 이제 localStorage에서 꺼내온 값을 보관해 놓는 용도로만 사용한다.
8.    todos: [],
9.    // localStorage로 부터 TODO_DATA로 저장해둔 값을 꺼내옴
10.    _getTodoItem: function() {
11.      return JSON.parse(localStorage.getItem(TODO_DATA)) || [];
12.    },
13.    // localStorage로 부터 TODO_DATA로 값을 저장
14.    _saveTodoItem: function(data) {
15.      localStorage.setItem(TODO_DATA, JSON.stringify(data));
16.    },
17.    ...
18.});
- localStorage 조작 함수를 사용한 다른 기능 코드 재작성 - add,- remove:- _saveTodoItem(data)함수를 사용해 변경된 데이터 값을 저장한다.
- get:- _getTodoItem함수를 사용해 데이터 값을 불러온다.
 
services.js
1.  var storage = {
2.      ...,
3.      get: function() {
4.          // angular.copy를 사용하여 데이터 가져옴
5.          angular.copy(storage._getTodoItem(), storage.todos);
6.          return storage.todos;
7.        },
8.        remove: function(todo) {
9.          var idx = storage.todos.findIndex(function (item) {
10.            return item.id == todo.id;
11.          });
12.
 13.          if (idx > -1) {
14.            storage.todos.splice(idx, 1)
15.          }
16.          // 변동 사항 저장
17.          storage._saveTodoItem(storage.todos);
18.        },
19.        add: function(newTodoTitle) {
20.          var newTodo =  {
21.            title: newTodoTitle,
22.            completed: false,
23.            createdAt: Date.now()
24.          };
25.
 26.          storage.todos.push(newTodo);
27.          // 변동 사항 저장
28.          storage._saveTodoItem(storage.todos);
29.        }
30.      };
31.
 32.      return storage;
33.    });
굳이
angular.copy를 사용하는 이유
digest cycle을 효율적으로 사용하기 위해 - 참고
$digest()는 위의 동작원리에서 설명한 대로 현재까지 생성된 모든$scope variable들의 watcher를 실행하여 값이 변화된 variable의 값을 최신값으로 업데이트해주는 일을 합니다.
$digest()은 Angular Context 외에서 들어오는 데이터를 모델에 업데이트를 할 때 개발자가 인위적으로 업데이트를 해야하며 이 때 사용하는 것
즉,
angular.copy를 사용하게 되면 내부의 코드에 따라 자동으로 업데이트 된다.
이렇게 코드를 수정하면 다음과 같이 데이터가 저장되고, 새로고침을 해도 유지되는 것을 확인할 수 있다.
update 기능 구현
아직 아이템의 체크박스를 선택하거나 텍스트를 변경하였을 때 변동사항이 반영되지 않는다 문제가 존재한다. 
이 문제를 해결해보자.
- todoItem.tpl.html수정하여 특정 이벤트 발생시- update()함수 실행시키기- 체크박스가 클릭되었을 때 - update()를 실행한다.
- text 수정 후 focus out 되었을 때 - update()를 실행한다.
 
todoItem.tpl.html
1.<div class="input-group mb-3">
2.  <div class="input-group-prepend">
3.    <div class="input-group-text">
4.      <!-- ng-click 이벤트를 추가해준다 -->
5.      <input type="checkbox" ng-model="todo.completed" ng-click="update()">
6.    </div>
7.  </div>
8.  <!-- ng-blur 이벤트를 추가해준다 -->
9.  <input type="text" ng-model="todo.title" ng-blur="update()">
10.  <div class="input-group-append">
11.  <button class="btn btn-danger" type="button" ng-click="remove(todo)">삭제</button>
12.  </div>
13.</div>
14.<date>{{ todo.createdAt | date : 'yyyy-MM-dd HH:mm:ss'}}</date>
- update()함수를- controller에 명시해준다.- 위의 - todoItem.tpl.html은- index.html에서- todo-item이라는 디렉티브를 사용해서 내장하고 있다.
- 해당 코드는 - <body ng-app="todo" ng-controller="TodoCtrl">내부에 선언되어있다.
- 즉, - todo모듈의- TodoCtrl컨트롤러에- update()함수를 명시해줘야한다.
 
controllers.js
1.angular.module('todo').controller('TodoCtrl', function($scope, todoStorage) {
2.  ...
3.
 4.  $scope.update = function() {
5.    // 서비스의 함수를 사용해 업데이트 한다.
6.    todoStorage.update();
7.  }
8.});
- todoStorage서비스에- update()함수를 정의한다.- 업데이트 하는 것도 데이터 조작에 속하므로 이곳에 코드를 작성하여 관리한다. 
 
services.js
1.angular.module('todo').factory('todoStorage', function() {
2.  let TODO_DATA = 'TODO_DATA';
3.
 4.  var storage = {
5.    todos: [],
6.    ...,
7.    update: function(todo) {
8.      storage._saveTodoItem(storage.todos);
9.    }
10.  };
11.
 12.  return storage;
13.});
최종적으로 테스트하면 이렇게 데이터가 반영되고 유지되는 것을 확인할 수 있다.
The end & More
해당 부분까지만 강좌는 다루고 있었는데,  
실제 프로젝트를 개발하려면 네트워크 통신과 관련된 더 많은 것을 배워야 할 것 같았다.
TODO
- 내장 서비스 - $http:- ajax통신 지원하며- ng-http를 사용해 구현한다.
- $resource: http 서비스를 랩핑해놓은 것으로,- REST API에 최적화되어 있다.
- $q: 비동기 코드를 쉽게 처리하기 위한- promise코드를 제공한다.
 
- 테스트 : 모듈화하여 쉽게 테스트 코드를 작성할 수 있다. 
- 라우팅 : - ng-route를 사용해- SPA(- ajax를 사용해 브라우저 단에서 라우팅 수행하곤 한다.)를 구현할 수 있다.
YEOMAN
미리 작성된 코드 템플릿을 제공한다. 
해당 코드를 참고해 angular를 어떻게 작성하며 개발하