Here is a short working example on how we can easily sort todos saved inside an array:
The initial HTML markup: <input id="myInput" type="text" /><button id="Add"> Add new LI </button> <ul class="todoList"></ul>
<script>
// initial array to hold the todos const todos = [];
// we have helper function for doing the actual sorting const doSort = (todos) => { return todos
// .map(todo => todo.toLowerCase()) // make all the items lowercase .sort((a, b) => {
// compare 2 words letter by letter if (a.value > b.value) { return 1; } // when the first letter is after the second if (a.value < b.value) { return -1; } // when the second letter is before the second return 0; // if both letters are the same }) }
// attach event listener to the Add button
document.querySelector('#Add').addEventListener('click', () => {
// get what's inside the input const data = document.querySelector('#myInput');
// push the new todo into the todos array todos.push(data.value);
// create additional helper array with object values and indexes var mapped = todos.map( (el, i) => ({ index: i, value: el.toLowerCase() }) );
// sort the todos const sortedTodos = doSort(mapped)
// restore the originals from the todos array .map(el => todos[el.index]);
// display the sorted totos todoList.innerHTML = sortedTodos.map(todo => '<li>' + todo + '</li>').join('');
// clear the value of the input data.value = ''; });
//get reference to the todoList const todoList = document.querySelector('.todoList');
Here is how to create a simple todo-list in Angular. We will be also using extensively RxJs BehaviorSubject as well as Observables. If you want more information on the techniques used, I advise you to practice with the full Angular for beginners course.
First install angular with: sudo npm i -g @angular/cli
Then create a new todo project: ng new todo
Inside the todo directory create 2 new components and service using the Angular CLI: ng g c todos ng g c form ng g s services/todos
Next, modify the following files: app.component.html
// to include the new component tags <div class="container"> <h1>{{appTitle}}</h1> <app-todos [limit]="3"></app-todos> <app-form></app-form> </div>
... app.module.ts import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { TodosComponent } from './todos/todos.component';
// import httpclientmodule to be able to perform http requests import { HttpClientModule } from '@angular/common/http';
// import forms module to be able to use 2-way [(binding)] import { FormsModule } from '@angular/forms'; import { FormComponent } from './form/form.component';
@NgModule({ declarations: [ AppComponent, TodosComponent, FormComponent ],
// and place it (FormsModule) inside modules to be visible by all derived services and componenets imports: [ BrowserModule, HttpClientModule, FormsModule // add into imports the formsmodule to be accessible from the components ], providers: [], bootstrap: [AppComponent] }) export class AppModule { }
... form.component.html <div> <input type="text" placeholder="Add todo..."
<!-- bind the title input field of the form to the model this.title --> [(ngModel)]="title"
<!-- allow with enter to be able to run addTodo() --> (keydown.enter)="addTodo()" /> <button (click)="addTodo()">Add todo</button> </div>
... form.component.ts
import { Component } from '@angular/core';
// import both the interface Todo as well as the TodoService import { Todo, TodosService } from '../services/todos.service';
export class FormComponent implements OnInit {
// title will be bound to the title input of the form title: string = '';
// use dependency injection to inject the TodosService constructor(private todosService: TodosService) { }
addTodo() {
// construct todo object and add it using the service const todo: Todo = { id: Date.now(), title: this.title, complete: false, } this.todosService.addTodo(todo); } }
...
todos.component.ts import { Component, OnInit, Input } from '@angular/core'; import { TodosService, Todo } from '../services/todos.service'; import { Observable } from 'rxjs'; @Component({ selector: 'app-todos', templateUrl: './todos.component.html', styleUrls: ['./todos.component.scss'] }) export class TodosComponent implements OnInit { constructor(private todosService: TodosService) { } private fetchData$: Observable<boolean>; private todos$: Observable<Todo[]>;
// receive the limit from the parent(appcomponent) via input @Input() limit: number;
ngOnInit() {
// initialize the both observables this.fetchData$ = this.todosService.getTodos(this.limit); this.todos$ = this.todosService.todos$; }
// call the service onChange(id: number) { this.todosService.onToggle(id); }
// call the service removeTodo(id: number) { this.todosService.removeTodo(id); }
}
...
todos.component.html
<!-- mainly to fetch the http json data & setup this.todos --> <div *ngIf="(fetchData$ | async ); else errorFetch"></div>
<!-- get the observable data and spread it on the page --> <ul *ngIf="(todos$ | async ) as todos; else loading"> <li *ngFor="let todo of todos; let i = index"> <span [class.done]="todo.complete"> <input type="checkbox" [checked]="todo.complete" (change)="onChange(todo.id)">
<!-- bind [checked] to the state complete of todo pass todo.id on(change) event,
and run onChange function from the .ts file
--> {{i + 1}} {{todo.title}} </span> <small>{{todo.date | date}}</small>
<!-- use pipe date to format the data: you can see other pipes from API/datepipe on angular website --> <button class="remove" (click)="removeTodo(todo.id)">×</button> </li> </ul>
<ng-template #loading> Loading, please wait ...</ng-template> <ng-template #errorFetch> There is an error while loading the data, please try again ...</ng-template>
...
todos.service.ts import { Injectable } from '@angular/core'; import { Observable, BehaviorSubject, of } from 'rxjs'; import { tap, map, catchError } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http';
// create interface with the structure of a simple todo item export interface Todo { id: number title: string complete: boolean } @Injectable({ providedIn: 'root' }) export class TodosService {
constructor(private http: HttpClient) { } public todos: Todo[] = []; private subject = new BehaviorSubject<Todo[]>([]); // to be able to retain the last emitted version ! public todos$ = this.subject.asObservable();
getTodos(limit: number): Observable<boolean> {
return this.http.get<Todo[]>(`https://jsonplaceholder.typicode.com/todos?_limit=${limit}`)
// just return observable of true or false .pipe( map((fetchedTodos: Todo[]) => {
// setup the local this.todos member this.todos = fetchedTodos;
// propagade the todos to subscribers via subject this.subject.next(this.todos); return true; }), catchError(err => { alert(err.message); return of(false); }) ); }
onToggle(id: number) {
// get the todo index by the provided ID const idx = this.todos.findIndex(todo => todo.id === id); this.todos[idx].complete = !this.todos[idx].complete; }