[AngularJS] AngularJS 학습 (2)

2019. 4. 4. 17:17Frontend/AngularJS

반응형
AngularJS

해당 포스트는 Inflearn AngularJS 강좌를 기반으로 작성되었으며, 언급되는 개념 및 실습 과정을 담았습니다.
따라서, 코드는 단계적으로 작성되어 이전 코드와 연결되어 있습니다.

> AngularJS 학습(1) 포스트 보러가기

AngularJS

폼 만들기

목표

최종 결과물은 다음과 같다.

  1. 사진과 같은 폼을 생성한다

    • 페이지 리플레쉬할 때 자동으로 커서가 가르키도록 한다.

    • placeholder에 친절한 안내메세지를 작성한다.

Alt text

  1. 텍스트 입력 후 추가 버튼을 눌렀을 때 아래와 같이 새로운 투두가 추가되도록 한다.

    • 새로운 투두는 입력 받은 값 / 현재 시간 / 완료되지 않음으로 표시한다.

    • input text는 지워주도록 한다.

Alt 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에 친절한 안내메세지를 작성한다.
  • 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.});
새로운 투두는 입력 받은 값 / 현재 시간 / 완료되지 않음으로 표시한다.
input text는 지워주도록 한다.
  • 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 컨트롤러 $scopetoddForm이라는 변수가 추가되었다.

그럼 이 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가 될 것

경고창 띄우기

  1. 경고창을 띄울 영역을 <div>로 생성해준다.

  2. ng-show 디렉티브를 활용해 입력 중이고 유효하지 않을 때만 해당 영역을 보여주게 한다.

  3. 메세지가 보일 영역을 부트 스트랩 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는 없앨 것이다.)

  1. 앱 분리 : app.js

  2. 디렉티브 분리 : directives.js

  3. 컨트롤러 분리 : 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>

이렇게 코드가 잘 정리되었다!

반응형