Skip to content
Angular Essentials
GitHub

Creating todo component

Initial setup

  • Create a new Angular app with routing enabled and style as scss, so you can follow along.
  • Install NgRx.
ng add @ngrx/store@latest
  • Install Angular Material to give our app a nice look. Configuration should be straight forward. If you are stuck follow here.
ng add @angular/material

Generate todo component

ng g c forms/todo --skip-tests=true

Generate todo module

ng g m forms/todo

Register TodoComponent in TodoModule instead of AppModule and import TodoModule in AppModule as below. This is just to keep our setup clean.

@NgModule({
  declarations: [TodoComponent],
  imports: [
    CommonModule
  ]
})
export class TodoModule {
}
@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({}, {}),
    BrowserAnimationsModule,
    TodoModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Don’t worry about StoreModule.forRoot({}, {}) for now.

Todo model

// todo.model.ts
export interface Todo {
  id: number;
  description: string;
  done: boolean;
}

Todo component

// todo.component.ts
@Component({
  selector: 'app-todo',
  templateUrl: './todo.component.html',
  styleUrls: ['./todo.component.scss']
})
export class TodoComponent implements OnInit {

  todos: Todo[] = [];
  todoDescriptionFormControl = new FormControl('', [Validators.required]);
  todoIdFormControl = new FormControl(null, [Validators.required]);

  constructor() {
  }

  ngOnInit(): void {
    this.todos = [
      {id: 1, description: 'Buy milk', done: true},
      {id: 2, description: 'Learn RxJS', done: false},
      {id: 3, description: 'Learn Angular', done: true},
      {id: 4, description: 'Learn NgRx', done: false},
      {id: 5, description: 'Learn Angular animation', done: true},
    ];
  }

  undoOrCompleteTodo(item: Todo) {
    this.todos = this.todos.map(todo => todo.id === item.id ? {...todo, done: !todo.done} : todo);
  }

  deleteTodo(id: number) {
    const todo = this.todos.find(todo => todo.id === id);
    if (todo) {
      this.todos.splice(this.todos.indexOf(todo), 1);
    }
  }

  addTodo(): void {
    if (this.todoIdFormControl.value && this.todoIdFormControl.value >= 0 && !this.todos.find(t => t.id === this.todoIdFormControl.value)) {
      const todo: Todo = {
        id: this.todoIdFormControl.value,
        description: this.todoDescriptionFormControl.value ?? '',
        done: false
      }
      this.todos.push(todo);
    }
  }
}

Todo template

The fancy html tags like mat-form-field, mat-label are provided by Angular Material which we need to import from @angular/material.

<!--todo.component.html-->
<div class="centre-xy card-wrapper">
  <div>
    <mat-form-field appearance="fill">
      <mat-label>Todo Description</mat-label>
      <textarea matInput [formControl]="todoDescriptionFormControl"></textarea>
      <mat-error *ngIf="todoDescriptionFormControl.hasError('required')">
        Please enter todo description
      </mat-error>
    </mat-form-field>
    <mat-form-field appearance="fill" class="ml-10">
      <mat-label>Todo Id</mat-label>
      <input matInput type="number" [formControl]="todoIdFormControl">
      <mat-error *ngIf="todoIdFormControl.hasError('required')">
        Please enter todo id
      </mat-error>
    </mat-form-field>
    <button [disabled]="!todoDescriptionFormControl.valid || !todoIdFormControl.valid"
            (click)="addTodo()"
            mat-raised-button
            color="primary"
            class="ml-10">Add Todo
    </button>
  </div>
  <mat-card class="card centre-xy" *ngFor="let todo of todos">
    <mat-card-title class="card-title inline"
                    [ngClass]="todo.done ? 'green' : 'pink'">{{todo.description}}</mat-card-title>
    <div class="todo-action">
      <button *ngIf="todo.done" mat-button class="red" (click)="undoOrCompleteTodo(todo)">Undo</button>
      <button *ngIf="!todo.done" mat-button class="green" (click)="undoOrCompleteTodo(todo)">Complete</button>
      <button mat-button class="coffee" (click)="deleteTodo(todo.id)">Delete</button>
    </div>
  </mat-card>
</div>

Todo stylesheet

// todo.component.scss
.todo-action {
  float: right;
  display: flex;
  flex-direction: row;
}

Todo module

// todo.module.ts
@NgModule({
  declarations: [TodoComponent],
  exports: [
    TodoComponent
  ],
  imports: [
    CommonModule,
    MatCardModule,
    MatButtonModule,
    MatInputModule,
    ReactiveFormsModule
  ]
})
export class TodoModule {
}

App styles

If you want to get consistent with my style.

// styles.scss
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;500&display=swap');

* {
  font-family: 'Quicksand', sans-serif;
}

body {
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  background: #020203;
  color: #ffffff;
}

.card {
  background: #161616 !important;
  margin: 20px;
  width: 70%;
}

.card-title {
  color: #CED4DA;
  font-size: 1rem !important;
}

.inline {
  display: inline !important;
}

.right {
  float: right !important;
}

.centre-xy {
  display: flex;
  justify-content: center;
  align-items: center;
}

.card-wrapper {
  display: flex;
  flex-direction: column;
  margin: 1.2rem;
}

.green {
  color: #037971;
}

.pink {
  color: #e31b6d
}

.red {
  color: #ec5453;
}

.coffee {
  color: #6f1a07;
}

.ml-10 {
  margin-left: 10px !important;
}

Enable routing for todo

// app-routing.module.ts
const routes: Routes = [
  {path: '', redirectTo: 'todo', pathMatch: 'full'},
  {path: 'todo', component: TodoComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule {
}
// app.module.ts
@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    AppRoutingModule,
    StoreModule.forRoot({}, {}),
    BrowserAnimationsModule,
    TodoModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}
<!--app.component.html-->
<router-outlet></router-outlet>

Note: If you don't get same style as me, you may need to remove mat-typography class from index.html.

Verify changes

At this step in your todo component you should be able to perform delete, undo, and complete operations.