Showing posts with label angular. Show all posts
Showing posts with label angular. Show all posts

Tuesday, December 31, 2019

Angular 8 - Working with reactive forms

Here is a fully functional component featuring dynamic creation of form elements and filling them with server data. For more information on modern development with Angular you can visit this course.


import { Component, OnInit } from '@angular/core';
import { FormGroup, FormArray, Validators, FormBuilder } from '@angular/forms';
let emailRegex = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$";


// initial server data mockup
let serverData =
{
"users": [
{
"firstName": "server:John",
"lastName": "Bernard",
"email": "john@test.com"
},
{
"firstName": "server:Anna",
"lastName": "Parker",
"email": "anna@test.com"
}
],
"date": 1577788750829
};

@Component({
selector: 'app-root',
styleUrls: ['./app.component.scss'],
template: `
<form [formGroup]="usersForm">
<!-- digg deeper to get the users controls (they are also controls of usersForm-->
<!-- usersForm['controls'].users['controls']; -->
{{ usersForm.get('date').value | date}}
<ng-container *ngFor="let userFromGroup of usersForm.get('users')['controls']; let i = index" >
<div class="formgroup" [formGroup]="userFromGroup"> <!-- binds formgroup from the loop -->
{{i}}
<!-- get values inside the fg object to bind to controls -->
<label>First name:<input type="text" formControlName="firstName"></label>
<label>Last name:<input type="text" formControlName="lastName"></label>
<label>Email:<input type="text" formControlName="email"></label>
<button type="button" class="remove" (click)="removeControl(i)">x</button>
</div>
</ng-container>
</form>
<button type="button" (click)="addNew()">Add new</button>
<button type="button" (click)="setValues()">Set values</button>
<button type="button" (click)="patchValues()">Patch values</button>
<button type="button" (click)="getServerData()">Get from server</button>
{{usersForm.value|json}}
`
})


export class AppComponent implements OnInit {
constructor(private fb: FormBuilder) { }

public usersForm: FormGroup;

ngOnInit() {
this.usersForm = this.fb.group({ // get inside the main form group
users: this.fb.array([ // get inside the array of form control groups
this.fb.group({ // the controls array[0]
firstName: ['user 1', { validators: Validators.required }],
lastName: ['', { validators: Validators.required }],
email: ['', { validators: Validators.pattern(emailRegex) }]
}),
this.fb.group({ // the controls array[1]
firstName: ['user 2', { validators: Validators.required }],
lastName: ['', { validators: Validators.required }],
email: ['', { validators: Validators.pattern(emailRegex) }]
})
]),
date: Date.now()
});
}

removeControl(i) {
let uForm = this.usersForm.get('users') as FormArray;
// we cast to FormArray because of:
// Property 'removeAt' does not exist on type 'AbstractControl
uForm.removeAt(1);
}

patchValues() {
let uForm = this.usersForm.get('users') as FormArray;
uForm.patchValue([
{ email: 'mock@mail.com' }
]);
uForm.controls[0].patchValue(
{ email: 'mock@mail.com' }
);
}

setValues() { // strictly setting data
let formUsers = this.usersForm.get('users') as FormArray;
// foreach of the users
formUsers.setValue([
{
firstName: 'John',
lastName: 'Bernard',
email: 'john@test.com'
},
{
firstName: 'Anna',
lastName: 'Parker',
email: 'anna@test.com'
}
]);
}


getServerData() {
this.usersForm = this.fb.group({
users: this.fb.array([]),
// you can also set initial formgroup inside if you like
/*
users: this.fb.array(
serverData.users.map(u =>
this.fb.group({ // the users from the server
firstName: [u.firstName, { validators: Validators.required }],
lastName: [u.lastName, { validators: Validators.required }],
email: [u.email, { validators: Validators.pattern(emailRegex) }]
})
)),
*/
date: Date.now()
})

let usersArray = this.usersForm.get('users') as FormArray;

serverData.users.map(u => { // for each of the users from the server
usersArray.push( // push new controls into the usersArray
this.fb.group({ // set the controls
firstName: [u.firstName, { validators: Validators.required }],
lastName: [u.lastName, { validators: Validators.required }],
email: [u.email, { validators: Validators.pattern(emailRegex) }]
})
);
});
}

getInfo() {
let uForm = this.usersForm.get('users') as FormArray;
let fcontrol = uForm.controls[0].get('firstName').value;
console.log(fcontrol);
}

addNew() {
let formsArray = this.usersForm.get('users') as FormArray;
let arrayLen = formsArray.length;
let newarrayGroup =
this.fb.group({ // the controls
firstName: ['user 3', { validators: Validators.required }],
lastName: ['', { validators: Validators.required }],
email: ['', { validators: Validators.pattern(emailRegex) }]
});
formsArray.insert(arrayLen, newarrayGroup);
}
}

Congratulations! You can further take a look at more examples in the course!

Thursday, December 19, 2019

To do list in Angular

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';

@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})

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;
}

removeTodo(id: number) {
this.todos = this.todos.filter(todo => { return todo.id !== id }
);

// propagade the updated data back to all observables
this.subject.next(this.todos);
}

addTodo(todo: Todo) {
this.todos = [...this.todos, todo];
this.subject.next(this.todos);
}
}

Now you can run: ng serve
and browse: http://127.0.0.1:4200

Congratulations and enjoy learning Angular.

Subscribe To My Channel for updates