Youtube channel !

Be sure to visit my youtube channel
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

Modernizing old php project with the help of AI

0. Keep docker running in separate outside of VSCODE terminal 1. The importance of GIT for version control - create modernization branch 2. ...