App libros Angular 7 + Bootstrap 4 + NodeJS

Instalación

Código fuente disponible aquí.

Servidor

cd servidor
npm install
node app.js

Cliente

cd cliente
npm install
ng serve -o

Peticiones web desde Angular 7

Archivo services/books.service.ts

...

import { HttpClient } from '@angular/common/http';

...

export class BooksService {
  list:any[];

  constructor(public http:HttpClient) { }

  get() {
    this.http.get("http://localhost:8080/libros/").subscribe((data:any) => {
      this.list = data;
      console.log(this.list);
    });    
  }

  add(book:any) {
    this.http.post("http://localhost:8080/libros/", book).subscribe((data:any) => {
      this.get();
    });
  }
}

Archivo pages/home/home.component.ts

...

import { BooksService } from '../../services/books.service';

...

export class HomeComponent implements OnInit {
  constructor(public books:BooksService) { }

  ngOnInit() {
    this.books.get();
  }
}

Archivo pages/home/list.component.ts

...

import { BooksService } from '../../services/books.service';

...

export class ListComponent implements OnInit {
  search:string = '';

  constructor(public books:BooksService) { }

  ngOnInit() {
    this.books.get();
  }
}

Archivo pages/add/add.component.ts

...

import { BooksService } from '../../services/books.service';

...

export class AddComponent implements OnInit {
  book:any = {id:0, titulo:'', autor:'', precio:0, img:'', url:''};

  constructor(public books:BooksService) { }

  ngOnInit() { }

  add() {
    console.log(this.book);
    this.books.add(this.book);
  }
}

App libros Angular 7 + Bootstrap 4

Filtro de libros

<input type="text" class="form-control" [(ngModel)]="search">
...
<a class="card" *ngFor="let book of books.list | includes:search"></a>
ng generate pipe pipes/includes

includes.pipe.ts

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'includes'
})
export class IncludesPipe implements PipeTransform {

  transform(books:any, text:string): boolean {
    if (!text.length) return books;
    return books.filter((book:any) => (book.title.includes(text) || book.author.includes(text)));
  }

}

Guardando los libros

ng generate service services/books

books.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})

export class BooksService {
  list:any[] = [
    { id: 1, title:'Cuentos para ser escuchados', author:'Fernando Ruiz Rico', price:3.99, img:'https://images-na.ssl-images-amazon.com/images/I/41Xs%2BVAP-3L._SX331_BO1,204,203,200_.jpg', url:'https://www.amazon.es/dp/8461700511' },
    { id: 1, title:'Cuentos para ser compartidos', author:'Fernando Ruiz Rico', price:3.99, img:'https://images-na.ssl-images-amazon.com/images/I/41eWDLASNJL._SX331_BO1,204,203,200_.jpg', url:'https://www.amazon.es/dp/8409041294' },
    { id: 1, title:'La pandilla digital contra el profesor analógico', author:'Fernando Ruiz Rico', price:3.99, img:'https://images-na.ssl-images-amazon.com/images/I/518Kv-ZvfUL._SX331_BO1,204,203,200_.jpg', url:'https://www.amazon.es/dp/1719898235' }
  ];

  constructor() {
    var storage = localStorage.getItem('books-list');
    if (storage) this.list = JSON.parse(storage);
  }

  add(book:any) {
    this.list.unshift(book);
    this.save();
  }

  delete(item:number) {
    this.list.splice(item, 1);
    this.save();
  }  

  save() {
    localStorage.setItem('books-list', JSON.stringify(this.list));
  }
}

El resultado

Pulsa aquí.

Angular 7 + Bootstrap 4

Installation

ng new mybootstrapweb
cd mybootstrapweb
npm i bootstrap

src/styles.css

@import '~bootstrap/dist/css/bootstrap.min.css';

Compile and test

ng serve -o

Header / Footer

ng g c template/header
ng g c template/footer

template/header.component.html

<header class="navbar bg-primary">
  <span class="navbar-brand text-light" routerLink="/">Profile</span>
  <span class="ml-auto">
    <button class="btn btn-info mr-2" routerLink="/about">About</button>
    <button class="btn btn-info" routerLink="/contact">Contact</button>
  </span>
</header>

template/footer.component.html

<footer class="navbar fixed-bottom bg-danger">
  <span class="m-auto text-light">Copyright © 2019 - Fernando</span>
</footer>

Home / About / Contact

ng g c pages/home
ng g c pages/about
ng g c pages/contact

pages/home.component.html

<div class="container text-center"> 
  <h1>Fernando Ruiz</h1> 
  <img class="img-fluid" src="../../../assets/fernando.jpeg"> 
</div>

pages/about.component.html

<div class="container">
  <h1>About Me</h1>
  <p>I am passionate about Microsoft .NET technology and likes to share knowledge with the .NET developer's community.</p>
  <p>I am a contributor in Microsoft and the ASP.NET developer community.</p>
    
  <p>MVP Award Winner | Community Author | S/W Developer & Programmer | Blogger | Community Award Winner | Most Valuable Blogger(MVB).</p>
</div>

pages/contact.component.html

<div class="container ">
  <h1>Contact Me</h1>
  Please mail me on the below-mentioned mail-id: [email protected]
</div>

All together

app.component.html

<app-header></app-header>
<router-outlet></router-outlet>
<app-footer></app-footer>

app-routing.module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
    
import {HomeComponent} from './pages/home/home.component';
import {AboutComponent} from './pages/about/about.component';
import {ContactComponent} from './pages/contact/contact.component';

const routes: Routes = [
  {path: '',component: HomeComponent},
  {path: 'about',component: AboutComponent},
  {path: 'contact',component: ContactComponent},
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Angular 7

Installation

npm install -g @angular/cli
ng new myweb
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS   [ http://sass-lang.com   ]

Angular 7 Components

cd miweb
ng serve -o
/app.component.html
/app.component.scss
/app.component.ts
> ng generate component nav
// output

> ng g c about
// output

> ng g c contact
// output

> ng g c home
// output

Angular 7 Templating

app.component.html

<app-nav></app-nav>

<section>
  <router-outlet></router-outlet>
</section>

nav.component.html

<header>
  <div class="container">
    <a routerLink="/" class="logo">My Web</a>
    <nav>
      <ul>
        <li><a routerLink="/">Home</a></li>
        <li><a routerLink="/about">About</a></li>
        <li><a routerLink="/contact">Contact us</a></li>
      </ul>
    </nav>
  </div>
</header>
<!-- From: -->
<a routerLink="/" class="logo">My Web</a>

<!-- To: -->
<a routerLink="/" class="logo">{{ appTitle }}</a>

nav.component.ts

export class NavComponent implements OnInit {

  appTitle: string = 'My Web Site';
  // OR (either will work)
  appTitle = 'My Web Site';

  constructor() { }

  ngOnInit() {
  }

}

/src/styles.scss

@import url('https://fonts.googleapis.com/css?family=Montserrat:400,700');

body, html {
    height: 100%;
    margin: 0 auto;
}

body {
    font-family: 'Montserrat';
    font-size: 18px;
}

a {
    text-decoration: none;
}

.container {
    width: 80%;
    margin: 0 auto;
    padding: 1.3em;
    display: grid;
    grid-template-columns: 30% auto;

    a {
        color: white;
    }
}

section {
    width: 80%;
    margin: 0 auto;
    padding: 2em;
}

nav/component.scss

header {
    background: #7700FF;

    .logo {
        font-weight: bold;
    }

    nav {
        justify-self: right;
    
        ul {
            list-style-type: none;
            margin: 0; padding: 0;

            li {
                float: left;

                a {
                    padding: 1.5em;
                    text-transform: uppercase;
                    font-size: .8em;

                    &:hover {
                        background: #8E2BFF;
                    }
                }
            }
        }
    }
}

Angular 7 Routing

/src/app/app-routing.module.ts

// Other imports removed for brevity

import { HomeComponent } from './home/home.component';
import { AboutComponent } from './about/about.component';
import { ContactComponent } from './contact/contact.component';

const routes: Routes = [
  { path: '', component: HomeComponent },
  { path: 'about', component: AboutComponent },
  { path: 'contact', component: ContactComponent },
];

// Other code removed for brevity

Angular 7 Event Binding

/src/app/home/home.component.html 

<h1>Home</h1>

<button (click)="firstClick()">Click me</button>

home.component.ts

export class HomeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

  firstClick() {
    console.log('clicked');
  }

}
(focus)="myMethod()"
(blur)="myMethod()" 
(submit)="myMethod()"  
(scroll)="myMethod()"

(cut)="myMethod()"
(copy)="myMethod()"
(paste)="myMethod()"

(keydown)="myMethod()"
(keypress)="myMethod()"
(keyup)="myMethod()"

(mouseenter)="myMethod()"
(mousedown)="myMethod()"
(mouseup)="myMethod()"

(click)="myMethod()"
(dblclick)="myMethod()"

(drag)="myMethod()"
(dragover)="myMethod()"
(drop)="myMethod()"

Angular 7 Class & Style Binding

home.component.html

<h1 [class.gray]="h1Style">Home</h1>

home.component.ts

  h1Style: boolean = false;

  constructor() { }

  ngOnInit() {
  }

  firstClick() {
    this.h1Style = true;
  }

home.component.scss

.gray {
    color: gray;
}

home.component.html

<h1 [ngClass]="{
  'gray': h1Style,
  'large': !h1Style
}">Home</h1>

home.component.scss

.large {
    font-size: 4em;
}

home.component.html

<h1 [style.color]="h1Style ? 'gray': 'black'">Home</h1>

<h1 [ngStyle]="{
  'color': h1Style ? 'gray' : 'black',
  'font-size': !h1Style ? '1em' : '4em'
}">Home</h1>

Angular 7 Services

> ng generate service data

/src/app/data.service.ts

// Other code removed for brevity

export class DataService {

  constructor() { }

  firstClick() {
    console.log('clicked');
  }
}

/src/app/home/home.component.ts

import { Component, OnInit } from '@angular/core';
import { DataService } from '../data.service';

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

  constructor(private data: DataService) { }

  ngOnInit() {
  }

  firstClick() {
    this.data.firstClick();
  }

}

Angular 7 HTTP Client

/src/app/app.module.ts

// Other imports
import { HttpClientModule } from '@angular/common/http';

...

  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,    // <-- Right here
  ],

/src/app/data.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';  // Import it up here

@Injectable({
  providedIn: 'root'
})
export class DataService {

  constructor(private http: HttpClient) { }

  getUsers() {
    return this.http.get('https://reqres.in/api/users');
  }
}

home.component.ts

export class HomeComponent implements OnInit {

  users: Object;

  constructor(private data: DataService) { }

  ngOnInit() {
    this.data.getUsers().subscribe(data => {
        this.users = data
        console.log(this.users);
      }
    );
  }

}

home.component.html

<h1>Users</h1>

<ul *ngIf="users">
  <li *ngFor="let user of users.data">
    <img [src]="user.avatar">
    <p>{{ user.first_name }} {{ user.last_name }}</p>
  </li>
</ul>

home.component.scss

ul {
    list-style-type: none;
    margin: 0;padding: 0;

    li {
        background: rgb(238, 238, 238);
        padding: 2em;
        border-radius: 4px;
        margin-bottom: 7px;
        display: grid;
        grid-template-columns: 60px auto;

        p {
            font-weight: bold;
            margin-left: 20px;
        }

        img {
            border-radius: 50%;
            width: 100%;
        }
    }
}

Angular 7 Forms

app.module.ts

// other imports
import { ReactiveFormsModule } from '@angular/forms';

// other code
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule  // <- Add here
  ],

 contact.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

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

export class ContactComponent implements OnInit {

  messageForm: FormGroup;
  submitted = false;
  success = false;

  constructor(private formBuilder: FormBuilder) { }

  ngOnInit() {
    this.messageForm = this.formBuilder.group({
      name: ['', Validators.required],
      message: ['', Validators.required]
    });
  }

  onSubmit() {
    this.submitted = true;

    if (this.messageForm.invalid) {
        return;
    }

    this.success = true;
  }
}

contact.component.html

<h1>Contact us</h1>

<form [formGroup]="messageForm" (ngSubmit)="onSubmit()">

    <h5 *ngIf="success">Your form is valid!</h5>

    <label>
      Name:
      <input type="text" formControlName="name">
      <div *ngIf="submitted && messageForm.controls.name.errors" class="error">
        <div *ngIf="messageForm.controls.name.errors.required">Your name is required</div>
      </div>
    </label>
  
    <label>
      Message:
      <textarea formControlName="message"></textarea>
      <div *ngIf="submitted && messageForm.controls.message.errors" class="error">
        <div *ngIf="messageForm.controls.message.errors.required">A message is required</div>
      </div>
    </label>

    <input type="submit" value="Send message" class="cta">
  
  </form>

  <div *ngIf="submitted" class="results">
    <strong>Name:</strong> 
    <span>{{ messageForm.controls.name.value }}</span>

    <strong>Message:</strong> 
    <span>{{ messageForm.controls.message.value }}</span>
  </div>

contact.component.scss

label {
    display: block;
    
    input, textarea {
        display: block;
        width: 50%;
        margin-bottom: 20px;
        padding: 1em;
    }

    .error {
        margin-top: -20px;
        background: yellow;
        padding: .5em;
        display: inline-block;
        font-size: .9em;
        margin-bottom: 20px;
    }
}

.cta {
    background: #7700FF;
    border: none;
    color: white;

    text-transform: uppercase;
    border-radius: 4px;
    padding: 1em;
    cursor: pointer;
    font-family: 'Montserrat';
}

.results {
    margin-top: 50px;

    strong {
        display: block;
    }
    span {
        margin-bottom: 20px;
        display: block;
    }
}

Angular 1

AngularJS es un Framework desarrollado con JavaScript

<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>

AngularJS amplía la funcionalidad en el código HTML

  • La directiva ng-app crea un elemento que contendrá el código web desarrollado con AngularJS.
  • La directiva ng-model enlaza el valor de controles HTML (input, select, textarea) con datos de la página web.
  • La directiva ng-bind enlaza datos de la página web para que puedan ser visualizados en la vista HTML.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>

<div ng-app="">
  <p>Name: <input type="text" ng-model="name"></p>
  <p ng-bind="name"></p>
</div>

</body>
</html>

Enlaces de datos

  • Los enlaces de datos nos permiten unir expresiones de AngularJS con los datos introducidos en cualquier formulario.
  • En el siguiente ejemplo el campo {{ firstName }} está unido con ng-model="firstName".
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
  <div ng-app="" ng-init="firstName='John'">

    <p>Name: <input type="text" ng-model="firstName"></p>
    <p>You wrote: {{ firstName }}</p>

  </div>
</body>
</html>
  • A continuación podemos observar rápidamente cómo dos campos de texto se enlazan utilizando dos directivas de tipo ng-model y se utilizan los valores introducidos para realizar una multiplicación en tiempo real:
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
  <div ng-app="" ng-init="quantity=1;price=5">

    Quantity: <input type="number" ng-model="quantity">
    Costs:    <input type="number" ng-model="price">

    Total in dollar: {{ quantity * price }}

  </div>
</body>
</html>

Controladores

  • La directiva ng-controller nos permite definir el controlador del contenido web.
  • Un controlador es un objeto JavaScript, creado a partir de un objeto JavaScript estándar.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
  <div ng-app="myApp" ng-controller="myCtrl">

    First Name: <input type="text" ng-model="firstName"><br>
    Last Name: <input type="text" ng-model="lastName"><br>
    <br>
    Full Name: {{firstName + " " + lastName}}

  </div>

  <script>
    var app = angular.module('myApp', []);
    app.controller('myCtrl', function($scope) {
      $scope.firstName = "John";
      $scope.lastName = "Doe";
    });
  </script>
</body>
</html>

Filtros

  • Filtro de moneda:
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>
  <div ng-app="myApp" ng-controller="costCtrl">
    <h1>Price: {{ price | currency }}</h1>
    <h1>Price: {{ price | currency:"USD$" }}</h1> 
    <h1>Price: {{ price | currency:"USD$":0 }}</h1>                  
  </div>
  <script>
    var app = angular.module('myApp', []);
    app.controller('costCtrl', function ($scope) {
      $scope.price = 58.25;
    });
  </script>
  <p>The currency filter formats a number to a currency format.</p>
</body>
</html>
  • Bucles y filtros
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>
<body>

<div ng-app="myApp" ng-controller="namesCtrl">
  <p>Type a letter in the input field:</p>
  <p><input type="text" ng-model="test"></p>

  <ul>
    <li ng-repeat="x in names | filter:test">{{ x }}</li>
  </ul>
</div>

<script>
  angular.module('myApp', []).controller('namesCtrl', function($scope) {
    $scope.names = ['Jani','Carl','Margareth','Hege','Joe','Gustav','Birgit','Mary','Kai'];
  });
</script>

<p>The list will only consists of names matching the filter.</p>
</body>
</html>

Validación de formularios

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script>  
<body>

<h2>Validation Example</h2>

<form ng-app="myApp" ng-controller="validateCtrl" name="myForm" novalidate>

  <p>Username:<br>
  <input type="text" name="user" ng-model="user" required>
  <span style="color:red" ng-show="myForm.user.$dirty && myForm.user.$invalid">
  <span ng-show="myForm.user.$error.required">Username is required.</span>
  </span>
  </p>

  <p>Email:<br>
  <input type="email" name="email" ng-model="email" required>
  <span style="color:red" ng-show="myForm.email.$dirty && myForm.email.$invalid">
  <span ng-show="myForm.email.$error.required">Email is required. 
  </span>
  <span ng-show="myForm.email.$error.email">Invalid email address.</span>
  </span>
  </p>

  <p>
  <input type="submit"
  ng-disabled="myForm.user.$dirty && myForm.user.$invalid ||  
  myForm.email.$dirty && myForm.email.$invalid">
  </p>

</form>

<script>
  var app = angular.module('myApp', []);
  app.controller('validateCtrl', function($scope) {
    $scope.user = 'John Doe';
    $scope.email = '[email protected]';
  });
</script>

</body>
</html>