2019. 4. 4. 17:17ㆍFrontend/AngularJS
해당 포스트는 Inflearn AngularJS 강좌를 기반으로 작성되었으며, 언급되는 개념 및 실습 과정을 담았습니다.
따라서, 코드는 단계적으로 작성되어 이전 코드와 연결되어 있습니다.
AngularJS
폼
폼 만들기
목표
최종 결과물은 다음과 같다.
사진과 같은 폼을 생성한다
페이지 리플레쉬할 때 자동으로 커서가 가르키도록 한다.
placeholder에 친절한 안내메세지를 작성한다.
텍스트 입력 후 추가 버튼을 눌렀을 때 아래와 같이 새로운 투두가 추가되도록 한다.
새로운 투두는 입력 받은 값 / 현재 시간 / 완료되지 않음으로 표시한다.
input text는 지워주도록 한다.
html 코드 짜기
index.html
1.<html>
2.
3.<head>
4. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
5. <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
6. <link rel="stylesheet" href="style.css">
7.</head>
8.
9.<body ng-app="todo" ng-controller="TodoCtrl">
10.
11. <div class="container">
12. <h1>Todo</h1>
13.
14. <!-- 폼 영역을 추가해준다. -->
15. <form name="todoForm" ng-submit="add(newTodoTitle)">
16. <div class=input-group>
17. <input type="text" class="form-control" ng-model="newTodoTitle" placeholder="새로운 투두를 입력하세요." autofocus>
18. <span class="input-group-btn">
19. <button class="btn btn-success" type="submit">추가</button>
20. </span>
21. </div>
22. </form>
23. <!-- -->
24.
25. <ul class="list-unstyled">
26. <li ng-repeat="todo in todos | filter: statusFilter">
27. <div class="input-group mb-3">
28. <div class="input-group-prepend">
29. <div class="input-group-text">
30. <input type="checkbox" ng-model="todo.completed">
31. </div>
32. </div>
33. <input type="text" ng-model="todo.title">
34. <div class="input-group-append">
35. <button class="btn btn-danger" type="button" ng-click="remove(todo)">삭제</button>
36. </div>
37. </div>
38. <date>{{ todo.createdAt | date : 'yyyy-MM-dd HH:mm:ss'}}</date>
39. </li>
40. </ul>
41.
42. <button class="btn btn-primary" ng-click="statusFilter = {completed : true}">Completed</button>
43. <button class="btn btn-primary" ng-click="statusFilter = {completed : false}">Active</button>
44. <button class="btn btn-primary" ng-click="statusFilter = {}">All</button>
45. </div>
46.
47. <script src="script.js"></script>
48.</body>
49.
50.</html>
목표 체크리스트와 비교해서 코드를 뜯어보자.
1. <form name="todoForm" ng-submit="add(newTodoTitle)">
2. <!-- bootstrap 오른쪽 버튼 모양 지정-->
3. <div class=input-group>
4. <input type="text" class="form-control" ng-model="newTodoTitle" placeholder="새로운 투두를 입력하세요." autofocus>
5. <span class="input-group-btn">
6. <button class="btn btn-success" type="submit">추가</button>
7. </span>
8. </div>
9. </form>
부트스트랩을 가져다가 적용 시켰다. (이전 포스트 코드 참고)
class="form-control"
을 설정하면 화면에 맞게 width가 변경된다.calss="btn-success"
로 초록색 성공 버튼 생성
autofocus
를 설정한다.
placeholder="새로운 투두를 입력하세요."
form name="todoForm"
이름은 그냥 지정했다. 지금 기능 동작에는 없어도 무방ng-model="newTodoTitle"
를 input에 설정해 값을 담도록 한다.ng-submit
을 실행하면todo
모듈의TodoCtrl
컨트롤러안에add
함수를 동작시킨다.그리고 매개변수로 input에서 받은 값을 담은
newTodoTitle
을 넣어보낸다.
<form name="todoForm">
이름을 지정했다는걸 기억하자.
현재는 없어도 무방하짐만 뒤에 검증할 때 쓸 예정이다.
js 코드 짜기
1.var app = angular.module('todo', []);
2.
3.app.controller('TodoCtrl', function($scope) {
4. $scope.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.
22. $scope.remove = function(todo) {
23. // todos 배열에서 todo index를 찾는다.
24. var idx = $scope.todos.findIndex(function (item) {
25. return item.id == todo.id;
26. });
27.
28. // idx가 유효할 때 삭제해준다.
29. if (idx > -1) {
30. $scope.todos.splice(idx, 1)
31. }
32. };
33.
34. $scope.add = function(newTodoTitle) {
35. // create new todo
36. var newTodo = {
37. title: newTodoTitle,
38. completed: false,
39. createdAt: Date.now()
40. };
41.
42. // push into todos
43. $scope.todos.push(newTodo);
44.
45. // input field를 비워준다.
46. $scope.newTodoTitle = null;
47. }
48.});
null
로 지정
그럼 성공!
폼 검증
목표
입력 중인 상태에서 입력 값에 대한 검증 결과를 표시한다.
입력 중이고 3글자 미만일 때 유효하지 않다는 UI 표시
바로 밑에 경고 메세지를 표시한다.
입력창 부분에 빨간색 테두리를 표시한다.
입력 중이고 3글자 이상일 때 유효하다는 UI 표시
경고 메세지를 안보이게 한다.
입력창 부분에 초록색 테두리를 표시한다.
알아두어야 할 것
현재 html의 구조는 이렇게 되어 있다.
1.<body ng-app="todo" ng-controller="TodoCtrl">
2....
3. <form name="todoForm" ng-submit="add(newTodoTitle)">
4. ...
5. </form>
6.</body>
<form>
태그에 name="todoForm"
을 지정한다.
= todo
모듈의 TodoCtrl
컨트롤러 $scope
에 toddForm
이라는 변수가 추가되었다.
그럼 이 toddForm
변수엔 어떤 데이터가 할당될까?
궁금하니깐, 출력해보자.
1.<!-- todoForm 변수를 json 형식으로 출력해라 -->
2.<pre>{{ todoForm | json }}</pre>
$
로 시작하는 변수명 : 앵귤러에서 제공하는 미리 정의된 키워드들
$error
: 에러가 있을 때 메세지가 표시됨$dirty
: 폼의input
에 무언가 입력되면true
로 변한다.$pristine
:$dirty
의 반대$valid
: 폼이 유효할 때true
/ 아닐 때false
(default는true
)$invalid
:$valid
의 반대$submitted
: 제출되었는지에 대한 여부
여기서 우리는 $dirty
와 $valid
를 사용해서 form
의 데이터 검증을 실시할 것이다.
검증 조건 추가하기
<input>
태그에 minlength=3
으로 최소 길이 제한 조건을 지정한다.
앞으로 해당 조건에 맞지 않을 경우
$invalid = true
가 될 것
경고창 띄우기
경고창을 띄울 영역을
<div>
로 생성해준다.ng-show
디렉티브를 활용해 입력 중이고 유효하지 않을 때만 해당 영역을 보여주게 한다.메세지가 보일 영역을 부트 스트랩 Alert의 노란색 부분을 가져와 삽입해주자.
1. <form name="todoForm" ng-submit="add(newTodoTitle)">
2. <div class=input-group>
3. <!-- 최소 길이를 3으로 제한한다. -->
4. <input type="text" class="form-control" ng-model="newTodoTitle" placeholder="새로운 투두를 입력하세요." autofocus minlength="3">
5. <span class="input-group-btn">
6. <button class="btn btn-success" type="submit">추가</button>
7. </span>
8. </div>
9. <!-- 경고창 부분 -->
10. <div ng-show="todoForm.$dirty && todoForm.$invalid">
11. <!-- 부트스트랩 경고창 부분 -->
12. <div class="alert alert-warning" role="alert">3글자 이상 입력하세요.</div>
13. </div>
14. </form>
테두리 경고색 표시하기
크롬 개발자도구(F12)를 켜서 경고창 부분 코드를 확인하면 다음과 같이 class
에 상황에 맞게 자동으로 속성들이 추가된 것을 확인할 수 있다.
해당 속성들을 활용해서 css
편집을 해준다.
유효하지 않을 때만 분석해보면,
입력중이고(
.ng-dirty
) 유효하지 않을 때(.ng-invalid
)border
값을 설정해주면 된다..ng-invalid.ng-dirty { border: solid 1px red; }
두가지 속성을 붙여쓰는 이유는 같은 depth임을 표시하기 위함
이렇게 하면,
<form>
,<input>
영역에 모두 테두리가 표시된다.
우리는 입력창에만 적용할 것이기 때문에 css를 다듬어준다.
style.css
1..input-group .ng-invalid.ng-dirty {
2. border: solid 1px red;
3.}
4.
5..input-group .ng-valid.ng-dirty {
6. border: solid 1px green;
7.}
그럼 끝!
디렉티브
커스텀한 디렉티브를 만들어 사용해보자.
1.<!-- 기존 코드 -->
2.<h1>Todo</h1>
이랬던 기존 제목 코드 부분을
커스텀 디렉티브 <todo-title>
을 만들어 다음과 같이 변경할 것이다.
index.html
1.<body ng-app="todo" ng-controller="TodoCtrl">
2....
3. <!-- 커스텀 디렉티브 활용 -->
4. <todo-title></todo-title>
5.</body>
todo
모듈 안에 있으니 새로 디렉티브를 정의해보자
특이한 것은 js에서는 카멜케이스를 적용해
todo-title
이 아닌todoTitle
이라고 명시해야 한다.
script.js
1.var app = angular.module('todo', []);
2....
3.app.directive('todoTitle', function() {
4. return { // 하나의 객체를 리턴
5. // 템플릿에 설정한 문자열이 표시된다.
6. template: '<h1>Todo 목록</h1>'
7. };
8.});
이런식으로 커스텀한 디렉티브를 정의해 사용하면 html
코드가 깔끔해질 것이다.
따라서, index.html
코드를 다음과 같은 디렉티브를 만들어 분리한다.
todoItem
: 아이템 부분todoFilters
: 필터 버튼 부분todoForm
: 폼 입력 부분
그런데 문제는 html
코드가 너무 길어서 template
에 모두 입력하기 어렵다!
따라서 html
코드를 따로 파일을 분리해서 작성해두고
templateUrl
속성을 사용해 경로를 지정하여 해당 파일을 불러온다.
index.html
1. <todo-form></todo-form>
2. <!-- 폼 영역을 추가해준다. -->
3. <!--<form name="todoForm" ng-submit="add(newTodoTitle)">-->
4. <!-- <div class=input-group>-->
5. <!-- <input type="text" class="form-control" ng-model="newTodoTitle" placeholder="새로운 투두를 입력하세요." autofocus minlength="3">-->
6. <!-- <span class="input-group-btn">-->
7. <!-- <button class="btn btn-success" type="submit">추가</button>-->
8. <!-- </span>-->
9. <!-- </div>-->
10. <!-- <div ng-show="todoForm.$dirty && todoForm.$invalid">-->
11. <!-- <div class="alert alert-warning" role="alert">3글자 이상 입력하세요.</div>-->
12. <!-- </div>-->
13. <!--</form>-->
todoItem.tpl.html
1.<div class="input-group mb-3">
2. <div class="input-group-prepend">
3. <div class="input-group-text">
4. <input type="checkbox" ng-model="todo.completed">
5. </div>
6. </div>
7. <input type="text" ng-model="todo.title">
8. <div class="input-group-append">
9. <button class="btn btn-danger" type="button" ng-click="remove(todo)">삭제</button>
10. </div>
11.</div>
12.<date>{{ todo.createdAt | date : 'yyyy-MM-dd HH:mm:ss'}}</date>
script.js
1.app.directive('todoItem', function() {
2. return {
3. templateUrl: 'todoItem.tpl.html'
4. }
5.});
js 코드 분리
이처럼 우리는 코드를 기능 별로 분리해서 가독성을 높이는 것이 좋다.
하지만, 우리의 모든 js 코드(directive, app, controller )는 script.js
에 모두 담겨있다.
이를 각각 분리해보자.
Codepen에서는 무슨일인지 utf-8 설정을 해줘도 한글이 되지 않아
여기서부터는 Plunker를 사용해 개발했다.
기능별로 분리
기존 script.js
파일안에 코드를 아래 3개로 분리시킬 것이다.
(script.js
는 없앨 것이다.)
앱 분리 :
app.js
디렉티브 분리 :
directives.js
컨트롤러 분리 :
controller.js
app.js
1.// 기존 app 변수는 필요없다.
2.// 그냥 앵귤러에 todo 모듈을 선언한다.
3.angular.module('todo', []);
디렉티브와 컨트롤러는 기존에 app
변수를 사용해 접근했었다.
이제 파일을 분리하였기 때문에, todo
모듈에 접근하기 위해서는 다음 코드를 통해 접근한다.
1.angular.module('todo')
directives.js
1.angular.module('todo').directive('todoTitle', function() {
2. return {
3. template: '<h1>Todo List</h1>'
4. };
5.});
6.
7.angular.module('todo').directive('todoItem', function() {
8. return {
9. templateUrl: 'todoItem.tpl.html'
10. }
11.});
12.
13.angular.module('todo').directive('todoForm', function() {
14. return {
15. templateUrl: 'todoForm.tpl.html'
16. }
17.});
18.
19.angular.module('todo').directive('todoFilter', function() {
20. return {
21. templateUrl: 'todoFilter.tpl.html'
22. }
23.});
controller.js
1.angular.module('todo').controller('TodoCtrl', function($scope) {
2. $scope.todos = [
3. {
4. title: '요가 수업',
5. completed: false,
6. createdAt: Date.now()
7. },
8. {
9. title: '앵귤러 학습',
10. completed: false,
11. createdAt: Date.now()
12. },
13. {
14. title: '운동하기',
15. completed: true,
16. createdAt: Date.now()
17. }
18. ];
19.
20. $scope.remove = function(todo) {
21. // todos 배열에서 todo index를 찾는다.
22. var idx = $scope.todos.findIndex(function (item) {
23. return item.id == todo.id;
24. });
25.
26. // idx가 유효할 때 삭제해준다.
27. if (idx > -1) {
28. // 배열 객체에서 제공되는 함수인 splice를 이용하면 원하는 위치에 요소를 삭제할 수 있다.
29. // idx 위치부터 1개의 요소를 삭제한다.
30. $scope.todos.splice(idx, 1)
31. }
32. };
33.
34. $scope.add = function(newTodoTitle) {
35. // create new todo
36. var newTodo = {
37. title: newTodoTitle,
38. completed: false,
39. createdAt: Date.now()
40. };
41.
42. // push into todos
43. $scope.todos.push(newTodo);
44.
45. // input field를 비워준다.
46. $scope.newTodoTitle = null;
47. }
48.});
html에 js파일 import
파일을 모두 분리했으니 모든 파일을 불러준다.
index.html
1.<html>
2.<head>
3. <meta charset="utf-8">
4. <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
5. <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
6. <link rel="stylesheet" href="style.css">
7.</head>
8.
9.<body ng-app="todo" ng-controller="TodoCtrl">
10.
11. <div class="container">
12. <!-- 제목 -->
13. <todo-title></todo-title>
14. <!-- 폼 -->
15. <todo-form></todo-form>
16. <!-- 목록 -->
17. <ul class="list-unstyled">
18. <li ng-repeat="todo in todos | filter: statusFilter">
19. <!-- 아이템 -->
20. <todo-item></todo-item>
21. </li>
22. </ul>
23. <!-- 필터 버튼 -->
24. <todo-filter></todo-filter>
25. </div>
26.
27. <!-- javascript 파일 호출 -->
28. <script src="app.js"></script>
29. <script src="directives.js"></script>
30. <script src="controller.js"></script>
31.</body>
32.
33.</html>
이렇게 코드가 잘 정리되었다!