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.