Frontend/Angular

앵귤러 모듈 컴포넌트 개념 및 Todo 리스트 예제 살펴보기

컴슈터 2024. 3. 22. 07:51

모듈(Module)

모듈

세부 구현이 숨겨지고 공개 API를 이용해 다른 코드에서 재사용 가능한 코드를 말한다. (예를 들어 리모컨)

ES6에서의 모듈

ES6에서는 모듈 개념 + 변수의 스콥이 모듈로 제한된다. 스코프가 모듈 내로 제한된다는 의미는 각각 파일들이 하나의 모듈이 되는 것이며, 각각 파일에서 정의가 된 변수들은 파일 안에서 스콥이 보장된다. 따라서 각 파일에 있는 기능을 쓰려면 export, import 작업을 해야 한다.

앵귤러에서의 모듈

컴포넌트, 파이프, 서비스 등과 같은 앵귤러 애플리케이션의 주요 부분을 기능 단위로 그룹핑하게 해 준다. 

  • 모든 앵귤러 애플리케이션은 하나의 Root Module을 가진다. (ex. app.module.ts)
  • 여러 Feature Module을 가질 수 있다.  
  • 재사용할 수 있는 기능을 외부에 배포하기 위해 사용되기도 한다. (ex. 메테리얼 디자인의 MdButtionModule, 부트스트랩의 ModalModule)

컴포넌트(Component)

앵귤러 애플리케이션은 여러 컴포넌트로 구성되어 있으며, 애플리케이션에서 가장 중요한 구성 요소이다. 여러 html 요소들이 한 컴포넌트에 그룹화되어 있다.

  • 빌딩 블록(레고)
  • HTML 요소들의 그룹
  • 뷰(템플릿)와 로직으로 구성되어 있음!

TODO 리스트 예제 만들기

1. 모듈 생성

모듈명이 todo인 모듈을 생성한다. src/app/todo/todo.module.ts 파일이 생성된다.

ng generate module todo            
# 또는 약어 사용가능
ng g m todo

이어서 todo 모듈 내부에 todos 컴포넌트를 생성한다. 이때 --module 플래그로 특정 모듈을 직접 지정가능한데 todo.module.ts 파일을 직접 지정했다. 또한 todo 컴포넌트는 다른 데서 사용할 수 있다는 의미로 --export 플래그를 추가한다. ( todo 컴포넌트도 export를 하기 때문에 ES6 모듈이 된다) 

ng generate component todo/todos --module todo/todo.module.ts --export
# 또는 약어 사용가능
ng g c todo/todos --module todo/todo.module.ts --export

즉 아래처럼 명령어를 입력하면 총 5개의 파일이 생성된다.

최종적으로 모듈 파일 1개와 컴포넌트 관련 파일 4개가 생성된 것을 확인할 수 있다.

이제 todo 관련 내용으로 변경하기 위해 소스를 변경한다. app.component.ts 파일이 아닌 todos.component.ts 파일 내용으로 보이도록 변경할 것이다.

src/app/app.component.html 파일에서 Todo 셀렉터로 변경한다.

<app-todos></app-todos>

src/app/app.module.ts 파일에서 TodoModule 모듈을 추가한다.

// 생략...
import { TodoModule } from './todo/todo.module'; // Todo 모듈 추가

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    TodoModule // Todo 모듈 추가
  ],
  //...
})
export class AppModule { }

여기까지 진행하면 todo 컴포넌트로 대체되어 보인다.

2. 샘플용 파일 작성

src/app/todo/todos/todos.component.html 파일에서 정적인 html 파일을 작성한다. 이 파일을 기준으로 컴포넌트 분리해 볼 것이다. 

<div class="title">
    <h1>나의 일정</h1>
    <h2>3월 19일</h2>
</div>
<div>
    <div>
        <input type="checkbox">운동하기
    </div>
    <div>
        <input type="checkbox">공부하기
    </div>
</div>
<div>
    <input type="text" placeholder="할 일 추가">
</div>

서버 실행 시 정적인 html 파일 내용이 보인다.

3. 템플릿(Template) 이해하기

앵귤러에서 템플릿은 HTML 코드로서 템플릿을 표현한다. Template 표현식과 Template 문장을 가진다. 

템플릿에서 가장 많이 이루어지는 게 특정 대상을 바인딩하는 것이다. 바인딩(Binding)은 컴포넌트 클래스의 속성과 데이터를 실제 브라우저 도큐먼트 오브젝트인 DOM의 속성과 이벤트와 연결하는 것이다.


바인딩은 크게 단방향, 양방향으로 분류할 수 있다. (아래 사진 화살표 참고) 바인딩의 대상으로는 속성, 이벤트, ngModel, class, style이 있다.

4. 단방향 바인딩 사용하기 (컴포넌트 -> DOM)

src/app/todo/todos/todos.component.ts 파일에서 단방향 바인딩을 사용하기 위해 todos 배열 객체를 생성한다.

// 생략...
export class TodosComponent {

  todos: {
    done: boolean,
    text: string
  }[]

  constructor() {
    this.todos = [
      { done: false, text: '운동하기' },
      { done: true, text: '공부하기' },
    ]
  }
}

src/app/todo/todos/todos.component.html 파일에서 todos 객체를 {{ }} 표현식을 이용하여 바인딩한다.

<div class="title">
    <h1>나의 일정</h1>
    <h2>3월 19일</h2>
</div>
<div>
    <div>
      <input type="checkbox" [checked]="todos[0].done">{{ todos[0].text }}
    </div>
    <div>
      <input type="checkbox" [checked]="todos[1].done">{{ todos[1].text }}
    </div>
</div>
<div>
    <input type="text" placeholder="할 일 추가">
</div>

위 소스의 Todo 목록을 *ngFor 문법을 이용하여 반복문으로 변경한다.

<div class="title">
  <h1>나의 일정</h1>
  <h2>3월 19일</h2>
</div>
<div>
  <div *ngFor="let todo of todos">
    <input type="checkbox" [checked]="todo.done">{{ todo.text }}
  </div>
</div>
<div>
  <input type="text" placeholder="할 일 추가">
</div>

여기까지 진행한다면 결과는 아래처럼 나온다.

5. 단방향 바인딩 사용하기 (DOM -> 컴포넌트)

src/app/todo/todos/todos.component.html 파일에서 반복문 부분에 클릭 이벤트인 toggleTodo 메서드를 설정한다.

  <div *ngFor="let todo of todos" (click)="toggleTodo(todo)">
    <input type="checkbox" [checked]="todo.done">{{ todo.text }}
  </div>

src/app/todo/todos/todos.component.ts 파일에서 toggleTodo 메서드 생성한다.

export class TodosComponent {
  // 생략...

  toggleTodo(todo: any) {
    todo.done = !todo.done
  }
}

6. 양방향 바인딩 사용하기 (ngModel)

src/app/todo/todos/todos.component.html 파일에서 [할 일 추가] 버튼에 ngModel 바인딩 처리 및 추가 버튼을 생성한다.

<div>
  <input type="text" placeholder="할 일 추가" [(ngModel)]="newText">
  <button (click)="addTodo(newText)">추가</button>
</div>

ngModel를 사용하기 위해서는 src/app/todo/todo.module.ts 파일에서 FormModule을 import 해야 한다.

// 생략...
import { FormsModule } from '@angular/forms';

@NgModule({
  // 생략...
  imports: [
    CommonModule,
    FormsModule // NgModule을 사용하기 위해 추가
  ],
})

src/app/todo/todos/todos.component.ts 파일에서 newText 변수와 addTodo 메서드를 생성한다.

// 생략...
export class TodosComponent {

  newText: string = '';

  addTodo(newText: string) {
    this.todos.push({
      done: false,
      text: newText
    });
    this.newText = '';
  }
}

결과는 아래와 같다. 입력란에 일정을 추가하면 새롭게 일정이 추가된다.

파이프

마지막으로 화면에 보이는 하드코딩된 날짜를 바꿔보자. 파이프는 템플릿에서 보이는 데이터를 변환해준다. 앵귤러 1 버전에서는 필터로 제공했다. 참고로 예제에서 {{ todos | json }} 으로 확인하면 

  • 파이프 형식 : {{ express | pipeName: paramValue }} 
  • ex. {{ today | date }}
  • ex. {{ today | date : "yy/mm/dd" }}
  • ex. {{ today | date | uppercase }}

src/app/todo/todos/todos.component.html

<div class="title">
  <h1>나의 일정</h1>
  <h2>{{ today | date : 'M월 d일'}}</h2>
</div>

여기까지의 최종 소스

src/app/todo/todos/todos.component.html

<div class="title">
  <h1>나의 일정</h1>
  <h2>{{ today | date : 'M월 d일'}}</h2>
</div>
<div>
  <div *ngFor="let todo of todos" (click)="toggleTodo(todo)">
    <input type="checkbox" [checked]="todo.done">{{ todo.text }}
  </div>
</div>
<div>
  <input type="text" placeholder="할 일 추가" [(ngModel)]="newText">
  <button (click)="addTodo(newText)">추가</button>
</div>

src/app/todo/todos/todos.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-todos',
  templateUrl: './todos.component.html',
  styleUrl: './todos.component.css'
})
export class TodosComponent {

  newText: string = '';

  todos: {
    done: boolean,
    text: string
  }[]

  constructor() {
    this.todos = [
      { done: false, text: '운동하기' },
      { done: true, text: '공부하기' },
    ]
  }

  toggleTodo(todo: any) {
    todo.done = !todo.done
  }

  addTodo(newText: string) {
    this.todos.push({
      done: false,
      text: newText
    });
    this.newText = '';
  }
}

관련 내용 읽어보기