diff --git a/angular.json b/angular.json
index 93b2559..f730ce7 100644
--- a/angular.json
+++ b/angular.json
@@ -53,7 +53,13 @@
"development": {
"optimization": false,
"extractLicenses": false,
- "sourceMap": true
+ "sourceMap": true,
+ "fileReplacements": [
+ {
+ "replace": "src/environments/environment.ts",
+ "with": "src/environments/environment.development.ts"
+ }
+ ]
}
},
"defaultConfiguration": "production"
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 36093e1..12b5d24 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,336 +1 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Hello, {{ title }}
-
Congratulations! Your app is running. 🎉
-
-
-
-
- @for (item of [
- { title: 'Explore the Docs', link: 'https://angular.dev' },
- { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
- { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
- { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
- { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
- ]; track item.title) {
-
- {{ item.title }}
-
-
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 9d39c38..9292758 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -9,5 +9,4 @@ import { RouterOutlet } from '@angular/router';
styleUrl: './app.component.scss'
})
export class AppComponent {
- title = 'todo-app-angular';
}
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index 6c6ef60..91081fa 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,8 +1,8 @@
-import { ApplicationConfig } from '@angular/core';
-import { provideRouter } from '@angular/router';
+import { ApplicationConfig } from '@angular/core'
+import { provideRouter, withComponentInputBinding } from '@angular/router'
-import { routes } from './app.routes';
+import { routes } from './app.routes'
export const appConfig: ApplicationConfig = {
- providers: [provideRouter(routes)]
-};
+ providers: [provideRouter(routes, withComponentInputBinding())],
+}
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
index dc39edb..2306edb 100644
--- a/src/app/app.routes.ts
+++ b/src/app/app.routes.ts
@@ -1,3 +1,39 @@
-import { Routes } from '@angular/router';
+import { Routes } from '@angular/router'
+import { HomeComponent } from './pages/home/home.component'
+import { TodoPageComponent } from './pages/todo-page/todo-page.component'
+import { TodoCreateComponent } from './pages/todo-create/todo-create.component'
+import { TodoEditComponent } from './pages/todo-edit/todo-edit.component'
+import { PageNotFoundComponent } from './pages/page-not-found/page-not-found.component'
+import { TodoDetailComponent } from './pages/todo-detail/todo-detail.component'
-export const routes: Routes = [];
+export const routes: Routes = [
+ {
+ path: '',
+ component: HomeComponent,
+ title: 'Home',
+ },
+ {
+ path: 'list',
+ component: TodoPageComponent,
+ title: 'TODO',
+ },
+ {
+ path: 'todo/:id',
+ component: TodoDetailComponent,
+ title: 'TODO Detail',
+ },
+ {
+ path: 'create',
+ component: TodoCreateComponent,
+ title: 'Add TODO',
+ },
+ {
+ path: 'edit/:id',
+ component: TodoEditComponent,
+ title: 'Edit TODO',
+ },
+ {
+ path: '**',
+ component: PageNotFoundComponent,
+ },
+]
diff --git a/src/app/components/todo/todo-item/todo-item.component.html b/src/app/components/todo/todo-item/todo-item.component.html
new file mode 100644
index 0000000..d5a699b
--- /dev/null
+++ b/src/app/components/todo/todo-item/todo-item.component.html
@@ -0,0 +1,3 @@
+
+ {{ todo.title }}
+
diff --git a/src/app/components/todo/todo-item/todo-item.component.scss b/src/app/components/todo/todo-item/todo-item.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/todo/todo-item/todo-item.component.spec.ts b/src/app/components/todo/todo-item/todo-item.component.spec.ts
new file mode 100644
index 0000000..5521c5e
--- /dev/null
+++ b/src/app/components/todo/todo-item/todo-item.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TodoItemComponent } from './todo-item.component';
+
+describe('TodoItemComponent', () => {
+ let component: TodoItemComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TodoItemComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TodoItemComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/todo/todo-item/todo-item.component.ts b/src/app/components/todo/todo-item/todo-item.component.ts
new file mode 100644
index 0000000..2ff5cc5
--- /dev/null
+++ b/src/app/components/todo/todo-item/todo-item.component.ts
@@ -0,0 +1,15 @@
+import { Component, Input } from '@angular/core'
+import { Todo } from '../../../models'
+import { RouterLink } from '@angular/router'
+
+@Component({
+ selector: 'app-todo-item',
+ standalone: true,
+ imports: [RouterLink],
+ templateUrl: './todo-item.component.html',
+ styleUrl: './todo-item.component.scss',
+})
+export class TodoItemComponent {
+ @Input()
+ todo!: Todo
+}
diff --git a/src/app/components/todo/todo-list/todo-list.component.html b/src/app/components/todo/todo-list/todo-list.component.html
new file mode 100644
index 0000000..33e5835
--- /dev/null
+++ b/src/app/components/todo/todo-list/todo-list.component.html
@@ -0,0 +1,3 @@
+
diff --git a/src/app/components/todo/todo-list/todo-list.component.scss b/src/app/components/todo/todo-list/todo-list.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/components/todo/todo-list/todo-list.component.spec.ts b/src/app/components/todo/todo-list/todo-list.component.spec.ts
new file mode 100644
index 0000000..6ca0ced
--- /dev/null
+++ b/src/app/components/todo/todo-list/todo-list.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TodoListComponent } from './todo-list.component';
+
+describe('TodoListComponent', () => {
+ let component: TodoListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TodoListComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TodoListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/todo/todo-list/todo-list.component.ts b/src/app/components/todo/todo-list/todo-list.component.ts
new file mode 100644
index 0000000..7c41221
--- /dev/null
+++ b/src/app/components/todo/todo-list/todo-list.component.ts
@@ -0,0 +1,16 @@
+import { Component, Input } from '@angular/core'
+import { Todo } from '../../../models'
+import { TodoItemComponent } from '../todo-item/todo-item.component'
+import { CommonModule } from '@angular/common'
+
+@Component({
+ selector: 'app-todo-list',
+ standalone: true,
+ imports: [TodoItemComponent, CommonModule],
+ templateUrl: './todo-list.component.html',
+ styleUrl: './todo-list.component.scss',
+})
+export class TodoListComponent {
+ @Input()
+ todoList!: Todo[]
+}
diff --git a/src/app/models.ts b/src/app/models.ts
new file mode 100644
index 0000000..289af12
--- /dev/null
+++ b/src/app/models.ts
@@ -0,0 +1,29 @@
+export type TodoState = 0 | 1 | 2
+
+export type Todo = {
+ id: number
+ category: TodoCategory | null
+ title: string
+ body: string
+ state: TodoState
+}
+
+export type TodoCreate = {
+ categoryId: number
+ title: string
+ body: string
+}
+
+export type TodoUpdate = {
+ id: number
+ categoryId: number
+ title: string
+ body: string
+ state: TodoState
+}
+
+export type TodoCategory = {
+ id: number
+ name: string
+ color: string
+}
diff --git a/src/app/pages/home/home.component.html b/src/app/pages/home/home.component.html
new file mode 100644
index 0000000..216679e
--- /dev/null
+++ b/src/app/pages/home/home.component.html
@@ -0,0 +1,3 @@
+
diff --git a/src/app/pages/home/home.component.scss b/src/app/pages/home/home.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/home/home.component.spec.ts b/src/app/pages/home/home.component.spec.ts
new file mode 100644
index 0000000..60c47c4
--- /dev/null
+++ b/src/app/pages/home/home.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HomeComponent } from './home.component';
+
+describe('HomeComponent', () => {
+ let component: HomeComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [HomeComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(HomeComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/home/home.component.ts b/src/app/pages/home/home.component.ts
new file mode 100644
index 0000000..deb69c4
--- /dev/null
+++ b/src/app/pages/home/home.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-home',
+ standalone: true,
+ imports: [],
+ templateUrl: './home.component.html',
+ styleUrl: './home.component.scss'
+})
+export class HomeComponent {
+
+}
diff --git a/src/app/pages/page-not-found/page-not-found.component.html b/src/app/pages/page-not-found/page-not-found.component.html
new file mode 100644
index 0000000..a50502a
--- /dev/null
+++ b/src/app/pages/page-not-found/page-not-found.component.html
@@ -0,0 +1 @@
+Page Not Found.
diff --git a/src/app/pages/page-not-found/page-not-found.component.scss b/src/app/pages/page-not-found/page-not-found.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/page-not-found/page-not-found.component.spec.ts b/src/app/pages/page-not-found/page-not-found.component.spec.ts
new file mode 100644
index 0000000..19ef971
--- /dev/null
+++ b/src/app/pages/page-not-found/page-not-found.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { PageNotFoundComponent } from './page-not-found.component';
+
+describe('PageNotFoundComponent', () => {
+ let component: PageNotFoundComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [PageNotFoundComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(PageNotFoundComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/page-not-found/page-not-found.component.ts b/src/app/pages/page-not-found/page-not-found.component.ts
new file mode 100644
index 0000000..11ff605
--- /dev/null
+++ b/src/app/pages/page-not-found/page-not-found.component.ts
@@ -0,0 +1,12 @@
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'app-page-not-found',
+ standalone: true,
+ imports: [],
+ templateUrl: './page-not-found.component.html',
+ styleUrl: './page-not-found.component.scss'
+})
+export class PageNotFoundComponent {
+
+}
diff --git a/src/app/pages/todo-create/todo-create.component.html b/src/app/pages/todo-create/todo-create.component.html
new file mode 100644
index 0000000..9d16359
--- /dev/null
+++ b/src/app/pages/todo-create/todo-create.component.html
@@ -0,0 +1,27 @@
+
diff --git a/src/app/pages/todo-create/todo-create.component.scss b/src/app/pages/todo-create/todo-create.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/todo-create/todo-create.component.spec.ts b/src/app/pages/todo-create/todo-create.component.spec.ts
new file mode 100644
index 0000000..93210df
--- /dev/null
+++ b/src/app/pages/todo-create/todo-create.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TodoCreateComponent } from './todo-create.component';
+
+describe('TodoCreateComponent', () => {
+ let component: TodoCreateComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TodoCreateComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TodoCreateComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/todo-create/todo-create.component.ts b/src/app/pages/todo-create/todo-create.component.ts
new file mode 100644
index 0000000..78961b2
--- /dev/null
+++ b/src/app/pages/todo-create/todo-create.component.ts
@@ -0,0 +1,80 @@
+import { Component } from '@angular/core'
+import { TodoCategory, TodoCreate } from '../../models'
+import { CommonModule } from '@angular/common'
+import {
+ FormControl,
+ FormGroup,
+ ReactiveFormsModule,
+ Validators,
+} from '@angular/forms'
+import { TodoService } from '../../services/todo-service'
+import { TodoCategoryService } from '../../services/todo-category-service'
+import { Router } from '@angular/router'
+import { toInt } from '../../utils/converter'
+
+@Component({
+ selector: 'app-todo-create',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule],
+ templateUrl: './todo-create.component.html',
+ styleUrl: './todo-create.component.scss',
+})
+export class TodoCreateComponent {
+ public readonly formGroup = new FormGroup({
+ title: new FormControl('', Validators.required),
+ body: new FormControl('', Validators.required),
+ category: new FormControl(''),
+ })
+
+ public categoryListPromise: Promise | null = null
+
+ public constructor(
+ private readonly router: Router,
+ private readonly todoService: TodoService,
+ private readonly todoCategoryService: TodoCategoryService
+ ) {}
+
+ public ngOnInit() {
+ this.categoryListPromise = this.todoCategoryService.getAll()
+ }
+
+ // form getters
+
+ get titleForm() {
+ return this.formGroup.controls.title
+ }
+
+ get bodyForm() {
+ return this.formGroup.controls.body
+ }
+
+ get categoryForm() {
+ return this.formGroup.controls.category
+ }
+
+ // event handlers
+
+ public async onCreateClicked() {
+ const formData = this.getFormData()
+
+ if (formData == null) {
+ return
+ }
+
+ await this.todoService.create(formData)
+
+ this.router.navigate(['/list'])
+ }
+
+ private getFormData(): TodoCreate | null {
+ if (this.formGroup.invalid) {
+ return null
+ }
+
+ return {
+ categoryId: toInt(this.formGroup.value.category!),
+ title: this.formGroup.value.title!,
+ body: this.formGroup.value.body!,
+ }
+ }
+}
diff --git a/src/app/pages/todo-detail/todo-detail.component.html b/src/app/pages/todo-detail/todo-detail.component.html
new file mode 100644
index 0000000..520a58b
--- /dev/null
+++ b/src/app/pages/todo-detail/todo-detail.component.html
@@ -0,0 +1,15 @@
+
+ {{ todo.title }}
+ {{ todo.body }}
+ カテゴリ
+ {{ todo.category?.name }}
+ ステータス
+ {{ todo.state }}
+ 操作
+
+
diff --git a/src/app/pages/todo-detail/todo-detail.component.scss b/src/app/pages/todo-detail/todo-detail.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/todo-detail/todo-detail.component.spec.ts b/src/app/pages/todo-detail/todo-detail.component.spec.ts
new file mode 100644
index 0000000..87a420a
--- /dev/null
+++ b/src/app/pages/todo-detail/todo-detail.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TodoDetailComponent } from './todo-detail.component';
+
+describe('TodoDetailComponent', () => {
+ let component: TodoDetailComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TodoDetailComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TodoDetailComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/todo-detail/todo-detail.component.ts b/src/app/pages/todo-detail/todo-detail.component.ts
new file mode 100644
index 0000000..7bcdba4
--- /dev/null
+++ b/src/app/pages/todo-detail/todo-detail.component.ts
@@ -0,0 +1,32 @@
+import { Component, Input } from '@angular/core'
+import { Todo } from '../../models'
+import { Router, RouterLink } from '@angular/router'
+import { CommonModule } from '@angular/common'
+import { TodoService } from '../../services/todo-service'
+import { toInt } from '../../utils/converter'
+
+@Component({
+ selector: 'app-todo-detail',
+ standalone: true,
+ imports: [RouterLink, CommonModule],
+ templateUrl: './todo-detail.component.html',
+ styleUrl: './todo-detail.component.scss',
+})
+export class TodoDetailComponent {
+ public todoPromise: Promise | null = null
+
+ constructor(
+ private readonly router: Router,
+ private readonly todoService: TodoService
+ ) {}
+
+ @Input()
+ set id(todoId: string) {
+ this.todoPromise = this.todoService.getTodo(toInt(todoId))
+ }
+
+ public async onRemoveClicked(id: number) {
+ await this.todoService.remove(id)
+ this.router.navigate(['/list'])
+ }
+}
diff --git a/src/app/pages/todo-edit/todo-edit.component.html b/src/app/pages/todo-edit/todo-edit.component.html
new file mode 100644
index 0000000..f895e1b
--- /dev/null
+++ b/src/app/pages/todo-edit/todo-edit.component.html
@@ -0,0 +1,34 @@
+
diff --git a/src/app/pages/todo-edit/todo-edit.component.scss b/src/app/pages/todo-edit/todo-edit.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/todo-edit/todo-edit.component.spec.ts b/src/app/pages/todo-edit/todo-edit.component.spec.ts
new file mode 100644
index 0000000..24fdfe4
--- /dev/null
+++ b/src/app/pages/todo-edit/todo-edit.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TodoEditComponent } from './todo-edit.component';
+
+describe('TodoEditComponent', () => {
+ let component: TodoEditComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TodoEditComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TodoEditComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/todo-edit/todo-edit.component.ts b/src/app/pages/todo-edit/todo-edit.component.ts
new file mode 100644
index 0000000..b3d06ae
--- /dev/null
+++ b/src/app/pages/todo-edit/todo-edit.component.ts
@@ -0,0 +1,111 @@
+import { Component, Input } from '@angular/core'
+import {
+ FormControl,
+ FormGroup,
+ ReactiveFormsModule,
+ Validators,
+} from '@angular/forms'
+import { TodoCategory, TodoState, TodoUpdate } from '../../models'
+import { CommonModule } from '@angular/common'
+import { TodoService } from '../../services/todo-service'
+import { toInt } from '../../utils/converter'
+import { TodoCategoryService } from '../../services/todo-category-service'
+import { Router } from '@angular/router'
+
+@Component({
+ selector: 'app-todo-edit',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule],
+ templateUrl: './todo-edit.component.html',
+ styleUrl: './todo-edit.component.scss',
+})
+export class TodoEditComponent {
+ public readonly formGroup = new FormGroup({
+ id: new FormControl(null, Validators.required),
+ title: new FormControl('', Validators.required),
+ body: new FormControl('', Validators.required),
+ category: new FormControl(''),
+ state: new FormControl('', [Validators.min(0), Validators.max(2)]),
+ })
+
+ public categoryListPromise: Promise | null = null
+
+ constructor(
+ private readonly router: Router,
+ private readonly todoService: TodoService,
+ private readonly todoCategoryService: TodoCategoryService
+ ) {}
+
+ public ngOnInit() {
+ this.categoryListPromise = this.todoCategoryService.getAll()
+ }
+
+ // path parameters
+
+ @Input()
+ set id(todoId: string) {
+ this._loadTodo(toInt(todoId))
+ }
+
+ // form controls
+
+ private get formId() {
+ return this.formGroup.controls.id
+ }
+
+ get formTitle() {
+ return this.formGroup.controls.title
+ }
+
+ get formBody() {
+ return this.formGroup.controls.body
+ }
+
+ get formCategory() {
+ return this.formGroup.controls.category
+ }
+
+ get formState() {
+ return this.formGroup.controls.state
+ }
+
+ private async _loadTodo(todoId: number) {
+ const todo = await this.todoService.getTodo(todoId)
+
+ this.formId.setValue(todo.id)
+ this.formTitle.setValue(todo.title)
+ this.formBody.setValue(todo.body)
+ this.formCategory.setValue(todo.category?.id.toString() ?? '')
+ this.formState.setValue(todo.state.toString())
+ }
+
+ private getFormData(): TodoUpdate | null {
+ if (this.formGroup.invalid) {
+ return null
+ }
+
+ return {
+ id: this.formId.value!,
+ categoryId: toInt(this.formCategory.value!),
+ title: this.formTitle.value!,
+ body: this.formBody.value!,
+ state: toInt(
+ this.formState.value!
+ ) as TodoState /* Validator を通ってるなら 0-2 の数値であることが確定している */,
+ }
+ }
+
+ // event handlers
+
+ public async onEditClicked() {
+ const formData = this.getFormData()
+
+ if (formData == null) {
+ return
+ }
+
+ await this.todoService.update(formData)
+
+ this.router.navigate(['/list'])
+ }
+}
diff --git a/src/app/pages/todo-page/todo-page.component.html b/src/app/pages/todo-page/todo-page.component.html
new file mode 100644
index 0000000..9cd890c
--- /dev/null
+++ b/src/app/pages/todo-page/todo-page.component.html
@@ -0,0 +1,2 @@
+
+追加
diff --git a/src/app/pages/todo-page/todo-page.component.scss b/src/app/pages/todo-page/todo-page.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/pages/todo-page/todo-page.component.spec.ts b/src/app/pages/todo-page/todo-page.component.spec.ts
new file mode 100644
index 0000000..31a9f10
--- /dev/null
+++ b/src/app/pages/todo-page/todo-page.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { TodoPageComponent } from './todo-page.component';
+
+describe('TodoPageComponent', () => {
+ let component: TodoPageComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [TodoPageComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(TodoPageComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/todo-page/todo-page.component.ts b/src/app/pages/todo-page/todo-page.component.ts
new file mode 100644
index 0000000..72f7981
--- /dev/null
+++ b/src/app/pages/todo-page/todo-page.component.ts
@@ -0,0 +1,23 @@
+import { Component } from '@angular/core'
+import { Todo } from '../../models'
+import { TodoListComponent } from '../../components/todo/todo-list/todo-list.component'
+import { RouterLink } from '@angular/router'
+import { TodoService } from '../../services/todo-service'
+import { CommonModule } from '@angular/common'
+
+@Component({
+ selector: 'app-todo-page',
+ standalone: true,
+ imports: [TodoListComponent, RouterLink, CommonModule],
+ templateUrl: './todo-page.component.html',
+ styleUrl: './todo-page.component.scss',
+})
+export class TodoPageComponent {
+ public todoListPromise: Promise | null = null
+
+ public constructor(private readonly todoService: TodoService) {}
+
+ public ngOnInit() {
+ this.todoListPromise = this.todoService.getAll()
+ }
+}
diff --git a/src/app/services/todo-category-service.ts b/src/app/services/todo-category-service.ts
new file mode 100644
index 0000000..cd42b1f
--- /dev/null
+++ b/src/app/services/todo-category-service.ts
@@ -0,0 +1,16 @@
+import { Injectable } from '@angular/core'
+import { TodoCategory } from '../models'
+import { environment } from '../../environments/environment'
+
+@Injectable({
+ providedIn: 'root',
+})
+export class TodoCategoryService {
+ public async getAll(): Promise {
+ const response = await fetch(`${environment.apiUrl}/todo-category/list`, {
+ method: 'GET',
+ })
+ const todoCategoryList = await response.json()
+ return todoCategoryList
+ }
+}
diff --git a/src/app/services/todo-service.ts b/src/app/services/todo-service.ts
new file mode 100644
index 0000000..a513bb9
--- /dev/null
+++ b/src/app/services/todo-service.ts
@@ -0,0 +1,50 @@
+import { Injectable } from '@angular/core'
+import { Todo, TodoCreate, TodoUpdate } from '../models'
+import { environment } from '../../environments/environment'
+
+@Injectable({
+ providedIn: 'root',
+})
+export class TodoService {
+ public async getTodo(todoId: number): Promise {
+ const response = await fetch(`${environment.apiUrl}/todo/${todoId}`, {
+ method: 'GET',
+ })
+ const todo = await response.json()
+ return todo
+ }
+
+ public async getAll(): Promise {
+ const response = await fetch(`${environment.apiUrl}/todo/list`, {
+ method: 'GET',
+ })
+ const todoList = await response.json()
+ return todoList
+ }
+
+ public async create(todo: TodoCreate) {
+ await fetch(`${environment.apiUrl}/todo`, {
+ method: 'POST',
+ body: JSON.stringify(todo),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ }
+
+ public async update(todo: TodoUpdate) {
+ await fetch(`${environment.apiUrl}/todo`, {
+ method: 'PUT',
+ body: JSON.stringify(todo),
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ }
+
+ public async remove(id: number) {
+ await fetch(`${environment.apiUrl}/todo/${id}`, {
+ method: 'DELETE',
+ })
+ }
+}
diff --git a/src/app/utils/converter.ts b/src/app/utils/converter.ts
new file mode 100644
index 0000000..28a18f0
--- /dev/null
+++ b/src/app/utils/converter.ts
@@ -0,0 +1,5 @@
+export function toInt(s: string): number {
+ const v = parseInt(s, 10)
+ if (Number.isNaN(v)) throw new Error(`Invalid value: ${s}`)
+ return v
+}
diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts
new file mode 100644
index 0000000..3c5b84e
--- /dev/null
+++ b/src/environments/environment.development.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ apiUrl: 'http://localhost:9000',
+}
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
new file mode 100644
index 0000000..ce513b5
--- /dev/null
+++ b/src/environments/environment.ts
@@ -0,0 +1,3 @@
+export const environment = {
+ apiUrl: 'https://api.todo-app',
+}