재우니의 블로그

 

 

안녕하세요! Angular 16을 사용하여 Standalone Components와 라우팅을 활용한 CRUD 기능의 To-do 리스트 애플리케이션을 구축하면서, 각 단계별로 코드의 원리와 작동 방식을 상세히 설명해 드리겠습니다.

 

이 안내서는 주니어 개발자를 대상으로 하며, 각 부분의 개념과 원리를 이해하는 데 도움이 되도록 구성하였습니다.


목차

  1. 프로젝트 생성 및 초기 설정
  2. 컴포넌트 및 서비스 생성
  3. 라우팅 및 부트스트랩 설정
  4. AppComponent 구현
  5. TodoService 구현
  6. TodoListComponent 구현
  7. TodoAddEditComponent 구현
  8. 애플리케이션 실행 및 테스트
  9. 결론

1단계: 프로젝트 생성 및 초기 설정

1.1. Angular CLI 설치

Angular CLI(Command Line Interface)는 Angular 애플리케이션을 생성하고 관리하는 데 사용되는 도구입니다. 먼저, 전역으로 설치합니다.

npm install -g @angular/cli

1.2. 새로운 Angular 프로젝트 생성

ng new 명령어를 사용하여 새로운 프로젝트를 생성합니다.

ng new todo-app --standalone --routing
  • todo-app: 프로젝트 이름입니다.
  • --standalone: Standalone Components 모드를 활성화합니다.
  • --routing: 라우팅 기능을 포함합니다.

이 명령어를 실행하면 Angular는 프로젝트를 생성하고 필요한 파일들을 설정해줍니다.

1.3. 프로젝트 디렉토리로 이동

cd todo-app

2단계: 컴포넌트 및 서비스 생성

2.1. 컴포넌트(Component)란?

컴포넌트는 Angular 애플리케이션의 기본 구성 요소로, 화면에 표시되는 뷰와 그 동작을 정의합니다.

2.2. 서비스(Service)란?

서비스는 컴포넌트 간에 공통된 기능이나 데이터를 공유하기 위해 사용됩니다. 예를 들어, HTTP 요청을 처리하거나, 공통 로직을 관리합니다.

2.3. 컴포넌트 및 서비스 생성 명령어

# To-do 목록 컴포넌트 생성
ng generate component components/todo-list --standalone

# To-do 추가/수정 컴포넌트 생성
ng generate component components/todo-add-edit --standalone

# To-do 서비스 생성
ng generate service services/todo
  • ng generate component: 새로운 컴포넌트를 생성합니다.
  • --standalone: Standalone Component로 생성합니다.
  • ng generate service: 새로운 서비스를 생성합니다.

3단계: 라우팅 및 부트스트랩 설정

3.1. 라우팅(Routing)이란?

라우팅은 URL 경로에 따라 적절한 컴포넌트를 로드하여 화면을 전환하는 기능입니다. Single Page Application(SPA)에서 중요한 역할을 합니다.

3.2. 부트스트랩(Bootstrap)이란?

부트스트랩은 애플리케이션을 시작하는 과정을 말합니다. Angular에서는 루트 컴포넌트를 지정하여 애플리케이션을 부트스트랩합니다.

3.3. main.ts 파일 설정

// src/main.ts
import { enableProdMode } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter, Routes } from '@angular/router';

import { AppComponent } from './app/app.component';
import { TodoListComponent } from './app/components/todo-list/todo-list.component';
import { TodoAddEditComponent } from './app/components/todo-add-edit/todo-add-edit.component';

const routes: Routes = [
  { path: '', redirectTo: 'todos', pathMatch: 'full' },
  { path: 'todos', component: TodoListComponent },
  { path: 'todos/add', component: TodoAddEditComponent },
  { path: 'todos/edit/:id', component: TodoAddEditComponent },
];

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(),
  ],
}).catch(err => console.error(err));
  • provideRouter(routes): 라우터에 경로를 제공하여 설정합니다.
  • provideHttpClient(): HTTP 클라이언트를 제공하여 서비스에서 HTTP 요청을 할 수 있게 합니다.
  • bootstrapApplication(AppComponent, { ... }): AppComponent를 루트 컴포넌트로 지정하여 애플리케이션을 부트스트랩합니다.

4단계: AppComponent 구현

AppComponent는 애플리케이션의 루트 컴포넌트로, 다른 컴포넌트들이 로드되는 기본 틀을 제공합니다.

// src/app/app.component.ts
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [RouterOutlet],
  template: `<router-outlet></router-outlet>`,
})
export class AppComponent {}
  • selector: 'app-root': 이 컴포넌트의 선택자입니다. index.html에서 <app-root></app-root>로 사용됩니다.
  • template: '<router-outlet></router-outlet>': 라우터 아웃렛을 통해 라우팅된 컴포넌트가 이곳에 표시됩니다.
  • imports: [RouterOutlet]: RouterOutlet을 사용하기 위해 임포트합니다.

5단계: TodoService 구현

TodoService는 To-do 항목을 가져오거나, 추가, 수정, 삭제하는 기능을 제공합니다.

// src/app/services/todo.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

export interface Todo {
  id?: number;
  title: string;
  completed: boolean;
}

@Injectable({
  providedIn: 'root',
})
export class TodoService {
  private apiUrl = 'https://jsonplaceholder.typicode.com/todos';

  constructor(private http: HttpClient) {}

  // To-do 목록 조회 (최대 10개)
  getTodos(): Observable<Todo[]> {
    return this.http.get<Todo[]>(`${this.apiUrl}?_limit=10`);
  }

  // 특정 To-do 조회
  getTodoById(id: number): Observable<Todo> {
    return this.http.get<Todo>(`${this.apiUrl}/${id}`);
  }

  // 새로운 To-do 생성
  createTodo(todo: Todo): Observable<Todo> {
    return this.http.post<Todo>(this.apiUrl, todo);
  }

  // 기존 To-do 수정
  updateTodo(todo: Todo): Observable<Todo> {
    return this.http.put<Todo>(`${this.apiUrl}/${todo.id}`, todo);
  }

  // To-do 삭제
  deleteTodo(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`);
  }
}

코드 설명

  • @Injectable({ providedIn: 'root' }): 이 서비스가 애플리케이션 전역에서 사용될 수 있도록 제공됩니다.
  • HttpClient: HTTP 요청을 보내기 위해 Angular에서 제공하는 클라이언트입니다.
  • Observable<T>: RxJS의 Observable로, 비동기 데이터 스트림을 처리합니다.
  • 각 메서드는 HTTP 요청을 보내고, 서버로부터 응답을 Observable로 반환합니다.

6단계: TodoListComponent 구현

TodoListComponent는 To-do 목록을 표시하고, 추가/수정/삭제 기능을 제공합니다.

// src/app/components/todo-list/todo-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { CommonModule } from '@angular/common';

import { TodoService, Todo } from '../../services/todo.service';

@Component({
  selector: 'app-todo-list',
  standalone: true,
  imports: [CommonModule, RouterModule],
  templateUrl: './todo-list.component.html',
})
export class TodoListComponent implements OnInit {
  todos: Todo[] = [];

  constructor(private todoService: TodoService, private router: Router) {}

  ngOnInit(): void {
    this.loadTodos();
  }

  // To-do 목록 로드
  loadTodos(): void {
    this.todoService.getTodos().subscribe((data) => {
      this.todos = data;
    });
  }

  // To-do 추가 페이지로 이동
  addTodo(): void {
    this.router.navigate(['/todos/add']);
  }

  // To-do 수정 페이지로 이동
  editTodo(id: number): void {
    this.router.navigate(['/todos/edit', id]);
  }

  // To-do 삭제
  deleteTodo(id: number): void {
    this.todoService.deleteTodo(id).subscribe(() => {
      alert('삭제되었습니다.');
      this.loadTodos(); // 목록을 새로 고침
    });
  }
}

코드 설명

  • todos: Todo[] = []: To-do 항목을 저장하는 배열입니다.
  • ngOnInit(): 컴포넌트가 초기화될 때 호출되며, 여기서 To-do 목록을 로드합니다.
  • loadTodos(): TodoService를 사용하여 To-do 목록을 가져옵니다.
  • addTodo(), editTodo(id: number): 라우터를 사용하여 다른 페이지로 이동합니다.
  • deleteTodo(id: number): To-do 항목을 삭제하고, 목록을 새로 고칩니다.

템플릿 파일

<!-- src/app/components/todo-list/todo-list.component.html -->
<div>
  <h1>To-do 리스트</h1>
  <button (click)="addTodo()">추가하기</button>
  <ul>
    <li *ngFor="let todo of todos">
      <input type="checkbox" [checked]="todo.completed" disabled />
      {{ todo.title }}
      <button (click)="editTodo(todo.id!)">수정</button>
      <button (click)="deleteTodo(todo.id!)">삭제</button>
    </li>
  </ul>
</div>

템플릿 설명

  • <ul> 안에서 *ngFor 디렉티브를 사용하여 todos 배열을 반복합니다.
  • todo.completed 값에 따라 체크박스의 상태를 설정합니다.
  • 각 항목마다 "수정", "삭제" 버튼을 제공합니다.

7단계: TodoAddEditComponent 구현

TodoAddEditComponent는 To-do를 추가하거나 수정하는 폼을 제공합니다.

// src/app/components/todo-add-edit/todo-add-edit.component.ts
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';

import { TodoService, Todo } from '../../services/todo.service';

@Component({
  selector: 'app-todo-add-edit',
  standalone: true,
  imports: [CommonModule, FormsModule, RouterModule],
  templateUrl: './todo-add-edit.component.html',
})
export class TodoAddEditComponent implements OnInit {
  todo: Todo = {
    title: '',
    completed: false,
  };
  isEditMode = false;

  constructor(
    private todoService: TodoService,
    private route: ActivatedRoute,
    private router: Router
  ) {}

  ngOnInit(): void {
    const idParam = this.route.snapshot.paramMap.get('id');
    if (idParam) {
      this.isEditMode = true;
      const id = Number(idParam);
      this.todoService.getTodoById(id).subscribe((data) => {
        this.todo = data;
      });
    }
  }

  // To-do 저장 (추가 또는 수정)
  saveTodo(): void {
    if (this.isEditMode) {
      this.todoService.updateTodo(this.todo).subscribe(() => {
        alert('수정되었습니다.');
        this.router.navigate(['/todos']);
      });
    } else {
      this.todoService.createTodo(this.todo).subscribe(() => {
        alert('추가되었습니다.');
        this.router.navigate(['/todos']);
      });
    }
  }

  // 취소하여 목록 페이지로 이동
  cancel(): void {
    this.router.navigate(['/todos']);
  }
}

코드 설명

  • todo: Todo: 폼에서 사용되는 To-do 객체입니다.
  • isEditMode: 현재 모드가 추가인지 수정인지를 나타냅니다.
  • ngOnInit(): URL에서 id 파라미터를 가져와 수정 모드인지 확인합니다.
    • 수정 모드라면 해당 To-do를 로드합니다.
  • saveTodo(): 현재 모드에 따라 createTodo() 또는 updateTodo()를 호출합니다.
  • cancel(): 목록 페이지로 이동합니다.

템플릿 파일

<!-- src/app/components/todo-add-edit/todo-add-edit.component.html -->
<div>
  <h1>To-do {{ isEditMode ? '수정' : '추가' }}</h1>
  <form (ngSubmit)="saveTodo()">
    <label>
      제목:
      <input type="text" [(ngModel)]="todo.title" name="title" required />
    </label>
    <br />
    <label>
      완료 여부:
      <input type="checkbox" [(ngModel)]="todo.completed" name="completed" />
    </label>
    <br />
    <button type="submit">{{ isEditMode ? '수정하기' : '추가하기' }}</button>
    <button type="button" (click)="cancel()">취소</button>
  </form>
</div>

템플릿 설명

  • [(ngModel)]: 양방향 데이터 바인딩을 사용하여 폼 필드와 todo 객체를 연결합니다.
  • name 속성은 Angular의 폼 제어를 위해 필요합니다.
  • 폼 제출 시 saveTodo() 메서드가 호출됩니다.

8단계: 애플리케이션 실행 및 테스트

8.1. 애플리케이션 실행

ng serve
  • 이 명령어는 개발용 서버를 시작하고, 파일 변경 시 자동으로 리로드됩니다.

8.2. 브라우저에서 확인

브라우저에서 http://localhost:4200으로 접속합니다.

  • 기본 경로는 ''이며, 라우팅 설정에서 redirectTo: 'todos'로 설정했으므로 TodoListComponent가 로드됩니다.

8.3. 기능 테스트

  • To-do 목록 조회: To-do 리스트가 표시되는지 확인합니다.
  • To-do 추가: "추가하기" 버튼을 클릭하여 새로운 To-do를 추가합니다.
  • To-do 수정: 목록에서 "수정" 버튼을 클릭하여 To-do를 수정합니다.
  • To-do 삭제: 목록에서 "삭제" 버튼을 클릭하여 To-do를 삭제합니다.

주의 사항

  • jsonplaceholder.typicode.com은 테스트용 API로, 데이터 변경이 실제로 저장되지 않습니다. 새로 고침하면 데이터가 초기화될 수 있습니다.

결론

이번 안내서에서는 Angular 16에서 Standalone Components와 라우팅을 활용하여 CRUD 기능을 갖춘 To-do 리스트 애플리케이션을 구축하는 과정을 상세히 살펴보았습니다.

핵심 포인트

  • Standalone Components: 모듈 없이 컴포넌트를 독립적으로 생성하고 사용하였습니다.
  • 라우팅: provideRouter를 사용하여 경로를 설정하고, 화면 전환을 구현하였습니다.
  • 서비스 활용: TodoService를 통해 컴포넌트 간에 데이터를 공유하고, HTTP 요청을 관리하였습니다.
  • HTTP 클라이언트: provideHttpClient를 통해 HTTP 요청을 수행하였습니다.
  • 양방향 데이터 바인딩: [(ngModel)]을 사용하여 폼과 모델 간의 데이터를 동기화하였습니다.

원리 이해

  • Component와 Service의 역할: 컴포넌트는 뷰와 사용자 상호 작용을 관리하고, 서비스는 비즈니스 로직이나 데이터 처리를 담당합니다.
  • 라우팅을 통한 화면 전환: URL 경로에 따라 다른 컴포넌트를 로드하여 SPA에서의 화면 전환을 구현합니다.
  • HTTP 통신: HttpClient를 사용하여 백엔드 API와 통신하고, Observable을 통해 비동기 데이터를 처리합니다.
  • 양방향 데이터 바인딩: ngModel을 사용하여 사용자 입력과 모델 간의 동기화를 쉽게 처리합니다.

추가 학습 자료