Angular/Tutorials/OpenClassRooms M Créez une application complète avec Angular et Firebase Une-Bibliothèque : Différence entre versions

De WikiSys
Aller à : navigation, rechercher
m (Houtisse a déplacé la page Angular/Tutorials/OpenClassRooms M Créez une application complète avec Angular et Firebase Ma-Bibliothèque vers [[Angular/Tutorials/OpenClassRooms M Créez une application complète avec Angular et Firebase Une-Bibli...)
(Authentification)
(16 révisions intermédiaires par le même utilisateur non affichées)
Ligne 24 : Ligne 24 :
 
Pour cette application, je vous conseille d'utiliser le CLI pour la création des components.   
 
Pour cette application, je vous conseille d'utiliser le CLI pour la création des components.   
  
 +
su
 
  cd ~/Angular
 
  cd ~/Angular
  mkdir une-bibliotheque
+
  npm install -g npm@latest    (npm est le gestionnaire de paquets officiel de Node.js)
 +
ng new une-bibliotheque --style=scss --skip-tests=true
 +
 
 +
==== vidéo : 2:30 ====
  
 
L'arborescence sera la suivante :
 
L'arborescence sera la suivante :
Ligne 40 : Ligne 44 :
  
 
Les services ainsi créés ne sont pas automatiquement mis dans l'array  '''providers'''  d''''AppModule''' , donc ajoutez-les maintenant.   
 
Les services ainsi créés ne sont pas automatiquement mis dans l'array  '''providers'''  d''''AppModule''' , donc ajoutez-les maintenant.   
 +
 +
npm install --save bootstrap@3.3.7
 +
 +
à vérifier dans '''package.json'''
 +
"bootstrap": "^3.3.7",
 +
==== vidéo : 4:30 ====
 +
 +
Avant de lancer  '''ng serve''' , utilisez NPM pour ajouter '''Bootstrap''' à votre projet, et ajoutez-le à l'array styles de  '''.angular-cli.json'''  :
 +
 +
npm install bootstrap@3.3.7 --save
 +
 +
;angular-cli.json:
 +
<pre>
 +
"styles": [
 +
        "../node_modules/bootstrap/dist/css/bootstrap.css",
 +
        "styles.css"
 +
  ],
 +
</pre>
 +
==== vidéo : 5:30 ====
 +
 +
  npm install rxjs-compat --save
 +
  
 
Pendant que vous travaillez sur  '''AppModule''' , ajoutez également  '''FormsModule''' ,  '''ReactiveFormsModule'''  et  '''HttpClientModule'''  :
 
Pendant que vous travaillez sur  '''AppModule''' , ajoutez également  '''FormsModule''' ,  '''ReactiveFormsModule'''  et  '''HttpClientModule'''  :
  
;app.modules.ts:
+
;app.module.ts:
 
<pre>
 
<pre>
import { }  
+
import { BrowserModule } from '@angular/platform-browser';
 +
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
 +
import { HttpClient, HttpClientModule } from '@angular/common/http';
 +
 
 +
import { AuthGuardService } from './services/auth-guard.service';
 +
import { AuthService } from './services/auth.service';
 +
import { BooksService } from './services/books.service';
 +
 
 
imports: [
 
imports: [
 
     BrowserModule,
 
     BrowserModule,
Ligne 52 : Ligne 85 :
 
     HttpClientModule
 
     HttpClientModule
 
   ],
 
   ],
 
+
providers: [
providers: [AuthService, BooksService, AuthGuardService],
+
    AuthService,  
 +
    BooksService,  
 +
    AuthGuardService
 +
  ],
 
</pre>
 
</pre>
  
 
N'oubliez pas d'ajouter les imports en haut du fichier !
 
N'oubliez pas d'ajouter les imports en haut du fichier !
  
Intégrez dès maintenant le '''routing''' sans '''guard''' afin de pouvoir accéder à toutes les sections de l'application pendant le développement :
+
Intégrez dès maintenant le '''routing''' sans '''guard''' afin de pouvoir accéder à toutes les sections de l'application pendant le ''développement'' :
 
+
;app.module.ts:
 
<pre>
 
<pre>
 +
import { Routes, RouterModule } from '@angular/router';
 +
 
const appRoutes: Routes = [
 
const appRoutes: Routes = [
 
   { path: 'auth/signup', component: SignupComponent },
 
   { path: 'auth/signup', component: SignupComponent },
Ligne 78 : Ligne 116 :
 
</pre>
 
</pre>
  
Générez également un dossier appelé  models  et créez-y le fichier  book.model.ts  :
+
==== vidéo : 8:00 ====
 +
 
 +
Générez également un dossier appelé  '''models''' et créez-y le fichier  '''book.model.ts''' :
 +
;book.model.ts:
 
<pre>
 
<pre>
 
export class Book {
 
export class Book {
Ligne 87 : Ligne 128 :
 
}
 
}
 
</pre>
 
</pre>
Avant de lancer  '''ng serve''' , utilisez NPM pour ajouter '''Bootstrap''' à votre projet, et ajoutez-le à l'array styles de  '''.angular-cli.json'''  :
+
==== vidéo : 8:50 ====
 
+
npm install bootstrap@3.3.7 --save
+
 
+
;angular-cli.json:
+
<pre>
+
"styles": [
+
        "../node_modules/bootstrap/dist/css/bootstrap.css",
+
        "styles.css"
+
  ],
+
</pre>
+
 
+
 
Enfin, préparez  '''HeaderComponent'''  avec un menu de navigation, avec les  '''routerLink'''  et '''AppComponent''' qui l'intègre avec le  '''router-outlet'''  :
 
Enfin, préparez  '''HeaderComponent'''  avec un menu de navigation, avec les  '''routerLink'''  et '''AppComponent''' qui l'intègre avec le  '''router-outlet'''  :
  
;header.componennt.ts:
+
;header.componennt.html:
 
<pre>
 
<pre>
 
<nav class="navbar navbar-default">
 
<nav class="navbar navbar-default">
Ligne 115 : Ligne 145 :
 
       </li>
 
       </li>
 
       <li routerLinkActive="active">
 
       <li routerLinkActive="active">
         <a routerLink="auth/signin">Connexion</a>
+
         <a routerLink="auth/signin">Se connecter</a>
 
       </li>
 
       </li>
 
     </ul>
 
     </ul>
 
   </div>
 
   </div>
 
</nav>
 
</nav>
 +
</pre>
 +
 +
==== vidéo : 10:30 ====
 +
 +
;app.component.html:
 +
<pre>
 
<app-header></app-header>
 
<app-header></app-header>
 
<div class="container">
 
<div class="container">
  <router-outlet></router-outlet>
+
    <router-outlet></router-outlet>  <!-- reception des routes de l'application -->  
 
</div>
 
</div>
 
</pre>
 
</pre>
  
 +
==== vidéo : 10:40 ====
 
La structure globale de l'application est maintenant prête !
 
La structure globale de l'application est maintenant prête !
 +
 +
cd une-bibliotheque
 +
ng serve --open
 +
 +
==== vidéo : 11:00 ====
 +
git commit -m "vidéo : 11:00 première connexion"
 +
 +
===Intégrez Firebase à votre application===
 +
D'abord, installez Firebase avec NPM :
 +
 +
npm install firebase --save- console output -
 +
 +
npm ERR! code E404
 +
npm ERR! 404 Not Found - GET https://registry.npmjs.org/- - Not found
 +
npm ERR! 404
 +
npm ERR! 404  '-@latest' is not in the npm registry.
 +
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
 +
npm ERR! 404
 +
npm ERR! 404 Note that you can also install from a
 +
npm ERR! 404 tarball, folder, http url, or git url.
 +
 +
npm install firebase --save
 +
npm audit fix --force
 +
 +
Pour cette application, vous allez créer un nouveau projet sur Firebase.  Une fois l'application créée, la console Firebase vous propose le choix suivant (sous la rubrique Overview) :
 +
 +
sudo npm install -g firebase-tools
 +
 +
Choisissez "Ajouter Firebase à votre application Web" et copiez-collez la configuration dans le constructeur de votre  '''AppComponent'''  (en ajoutant  ''import * as firebase from 'firebase''';  en haut du fichier, mettant à disposition la méthode  '''initializeApp()''' ) :
 +
Copiez et collez ces scripts en bas de votre balise <body>, et ce, avant d'utiliser les services Firebase :
 +
<pre>
 +
<!-- The core Firebase JS SDK is always required and must be listed first -->
 +
<script src="https://www.gstatic.com/firebasejs/6.1.1/firebase-app.js"></script>
 +
 +
<!-- TODO: Add SDKs for Firebase products that you want to use
 +
    https://firebase.google.com/docs/web/setup#config-web-app -->
 +
</pre>
 +
 +
;app.component.ts:
 +
<pre>
 +
import { Component } from '@angular/core';
 +
import * as firebase from 'firebase';
 +
 +
@Component({
 +
  selector: 'app-root',
 +
  templateUrl: './app.component.html',
 +
  styleUrls: ['./app.component.css']
 +
})
 +
 +
export class AppComponent {
 +
  constructor() {
 +
    const config = {
 +
    apiKey: "AIzaSyBRvorDmAKFK9vneHnRsom9mDW398uUxEg",
 +
    authDomain: "une-bibliotheque-90c91.firebaseapp.com",
 +
    databaseURL: "https://une-bibliotheque-90c91.firebaseio.com",
 +
    projectId: "une-bibliotheque-90c91",
 +
    storageBucket: "une-bibliotheque-90c91.appspot.com",
 +
    messagingSenderId: "1012969104430",
 +
    appId: "1:1012969104430:web:815509d2f2949a33"
 +
    };
 +
    firebase.initializeApp(config);
 +
  }
 +
}
 +
</pre>
 +
 +
Votre application Angular est maintenant liée à votre projet Firebase, et vous pourrez maintenant intégrer tous les services dont vous aurez besoin.
 +
 +
==== vidéo 12:30 ====
 +
 +
===Authentification ===
 +
Votre application utilisera l'authentification par '''adresse mail''' et '''mot de passe''' proposée par '''Firebase'''. 
 +
 +
Pour cela, il faut d'abord l'activer dans la console Firebase :
 +
 +
 +
L'authentification Firebase emploie un système de token : un jeton d'authentification est stocké dans le navigateur, et est envoyé avec chaque requête nécessitant l'authentification.
 +
 +
Dans  AuthService , vous allez créer trois méthodes :
 +
 +
    une méthode permettant de créer un nouvel utilisateur ;
 +
 +
    une méthode permettant de connecter un utilisateur existant ;
 +
 +
    une méthode permettant la déconnexion de l'utilisateur.
 +
 +
Puisque les opérations de création, de connexion et de déconnexion sont asynchrones, c'est-à-dire qu'elles n'ont pas un résultat instantané, les méthodes que vous allez créer pour les gérer retourneront des Promise, ce qui permettra également de gérer les situations d'erreur.
 +
 +
Importez Firebase dans  AuthService  :
 +
 +
import { Injectable } from '@angular/core';
 +
 +
 +
import * as firebase from 'firebase';
 +
 +
 +
@Injectable()
 +
 +
export class AuthService {
 +
 +
Ensuite, créez la méthode  createNewUser()  pour créer un nouvel utilisateur, qui prendra comme argument une adresse mail et un mot de passe, et qui retournera une Promise qui résoudra si la création réussit, et sera rejetée avec le message d'erreur si elle ne réussit pas :
 +
 +
createNewUser(email: string, password: string) {
 +
 +
    return new Promise(
 +
 +
      (resolve, reject) => {
 +
 +
        firebase.auth().createUserWithEmailAndPassword(email, password).then(
 +
 +
          () => {
 +
 +
            resolve();
 +
 +
          },
 +
 +
          (error) => {
 +
 +
            reject(error);
 +
 +
          }
 +
 +
        );
 +
 +
      }
 +
 +
    );
 +
 +
}
 +
 +
Toutes les méthodes liées à l'authentification Firebase se trouvent dans  firebase.auth().
 +
 +
Créez également  signInUser() , méthode très similaire, qui s'occupera de connecter un utilisateur déjà existant :
 +
 +
signInUser(email: string, password: string) {
 +
 +
    return new Promise(
 +
 +
      (resolve, reject) => {
 +
 +
        firebase.auth().signInWithEmailAndPassword(email, password).then(
 +
 +
          () => {
 +
 +
            resolve();
 +
 +
          },
 +
 +
          (error) => {
 +
 +
            reject(error);
 +
 +
          }
 +
 +
        );
 +
 +
      }
 +
 +
    );
 +
 +
}
 +
 +
Créez une méthode simple  signOutUser()  :
 +
 +
signOutUser() {
 +
 +
    firebase.auth().signOut();
 +
 +
}
 +
 +
Ainsi, vous avez les trois fonctions dont vous avez besoin pour intégrer l'authentification dans l'application !
 +
 +
Vous pouvez ainsi créer  SignupComponent  et  SigninComponent , intégrer l'authentification dans  HeaderComponent  afin de montrer les bons liens, et implémenter  AuthGuard  pour protéger la route  /books  et toutes ses sous-routes.
 +
 +
Commencez par  SignupComponent  afin de pouvoir enregistrer un utilisateur :
 +
 +
import { Component, OnInit } from '@angular/core';
 +
 +
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 +
 +
import { AuthService } from '../../services/auth.service';
 +
 +
import { Router } from '@angular/router';
 +
 +
 +
@Component({
 +
 +
  selector: 'app-signup',
 +
 +
  templateUrl: './signup.component.html',
 +
 +
  styleUrls: ['./signup.component.css']
 +
 +
})
 +
 +
export class SignupComponent implements OnInit {
 +
 +
 +
  signupForm: FormGroup;
 +
 +
  errorMessage: string;
 +
 +
 +
  constructor(private formBuilder: FormBuilder,
 +
 +
              private authService: AuthService,
 +
 +
              private router: Router) { }
 +
 +
 +
  ngOnInit() {
 +
 +
    this.initForm();
 +
 +
  }
 +
 +
 +
  initForm() {
 +
 +
    this.signupForm = this.formBuilder.group({
 +
 +
      email: ['', [Validators.required, Validators.email]],
 +
 +
      password: ['', [Validators.required, Validators.pattern(/[0-9a-zA-Z]{6,}/)]]
 +
 +
    });
 +
 +
  }
 +
 +
 +
  onSubmit() {
 +
 +
    const email = this.signupForm.get('email').value;
 +
 +
    const password = this.signupForm.get('password').value;
 +
 +
   
 +
 +
    this.authService.createNewUser(email, password).then(
 +
 +
      () => {
 +
 +
        this.router.navigate(['/books']);
 +
 +
      },
 +
 +
      (error) => {
 +
 +
        this.errorMessage = error;
 +
 +
      }
 +
 +
    );
 +
 +
  }
 +
 +
}
 +
 +
Dans ce component :
 +
 +
    vous générez le formulaire selon la méthode réactive
 +
 +
        les deux champs,  email  et  password , sont requis — le champ  email  utilise  Validators.email  pour obliger un string sous format d'adresse email ; le champ  password  emploie  Validators.pattern  pour obliger au moins 6 caractères alphanumériques, ce qui correspond au minimum requis par Firebase ;
 +
 +
    vous gérez la soumission du formulaire, envoyant les valeurs rentrées par l'utilisateur à la méthode  createNewUser()
 +
 +
        si la création fonctionne, vous redirigez l'utilisateur vers  /books ;
 +
 +
        si elle ne fonctionne pas, vous affichez le message d'erreur renvoyé par Firebase.
 +
 +
Ci-dessous, vous trouverez le template correspondant :
 +
 +
<div class="row">
 +
 +
  <div class="col-sm-8 col-sm-offset-2">
 +
 +
    <h2>Créer un compte</h2>
 +
 +
    <form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
 +
 +
      <div class="form-group">
 +
 +
        <label for="email">Adresse mail</label>
 +
 +
        <input type="text"
 +
 +
              id="email"
 +
 +
              class="form-control"
 +
 +
              formControlName="email">
 +
 +
      </div>
 +
 +
      <div class="form-group">
 +
 +
        <label for="password">Mot de passe</label>
 +
 +
        <input type="password"
 +
 +
              id="password"
 +
 +
              class="form-control"
 +
 +
              formControlName="password">
 +
 +
      </div>
 +
 +
      <button class="btn btn-primary"
 +
 +
              type="submit"
 +
 +
              [disabled]="signupForm.invalid">Créer mon compte</button>
 +
 +
    </form>
 +
 +
    <p class="text-danger">{{ errorMessage }}</p>
 +
 +
  </div>
 +
 +
</div>
 +
 +
Il s'agit d'un formulaire selon la méthode réactive comme vous l'avez vu dans le chapitre correspondant.  Il y a, en supplément, le paragraphe contenant l'éventuel message d'erreur rendu par Firebase.
 +
 +
Vous pouvez créer un template presque identique pour  SignInComponent  pour la connexion d'un utilisateur déjà existant.  Il vous suffit de renommer  signupForm  en  signinForm  et d'appeler la méthode  signInUser()  plutôt que  createNewUser() .
 +
 +
Ensuite, vous allez modifier  HeaderComponent  pour afficher de manière contextuelle les liens de connexion, de création d'utilisateur et de déconnexion :
 +
 +
import { Component, OnInit } from '@angular/core';
 +
 +
import { AuthService } from '../services/auth.service';
 +
 +
import * as firebase from 'firebase';
 +
 +
 +
@Component({
 +
 +
  selector: 'app-header',
 +
 +
  templateUrl: './header.component.html',
 +
 +
  styleUrls: ['./header.component.css']
 +
 +
})
 +
 +
export class HeaderComponent implements OnInit {
 +
 +
 +
  isAuth: boolean;
 +
 +
 +
  constructor(private authService: AuthService) { }
 +
 +
 +
  ngOnInit() {
 +
 +
    firebase.auth().onAuthStateChanged(
 +
 +
      (user) => {
 +
 +
        if(user) {
 +
 +
          this.isAuth = true;
 +
 +
        } else {
 +
 +
          this.isAuth = false;
 +
 +
        }
 +
 +
      }
 +
 +
    );
 +
 +
  }
 +
 +
 +
  onSignOut() {
 +
 +
    this.authService.signOutUser();
 +
 +
  }
 +
 +
 +
}
 +
 +
<nav class="navbar navbar-default">
 +
 +
  <div class="container-fluid">
 +
 +
    <ul class="nav navbar-nav">
 +
 +
      <li routerLinkActive="active">
 +
 +
        <a routerLink="books">Livres</a>
 +
 +
      </li>
 +
 +
    </ul>
 +
 +
    <ul class="nav navbar-nav navbar-right">
 +
 +
      <li routerLinkActive="active" *ngIf="!isAuth">
 +
 +
        <a routerLink="auth/signup">Créer un compte</a>
 +
 +
      </li>
 +
 +
      <li routerLinkActive="active" *ngIf="!isAuth">
 +
 +
        <a routerLink="auth/signin">Connexion</a>
 +
 +
      </li>
 +
 +
      <li>
 +
 +
        <a (click)="onSignOut()"
 +
 +
          style="cursor:pointer"
 +
 +
          *ngIf="isAuth">Déconnexion</a>
 +
 +
      </li>
 +
 +
    </ul>
 +
 +
  </div>
 +
 +
</nav>
 +
 +
Ici, vous utilisez  onAuthStateChanged() , qui permet d'observer l'état de l'authentification de l'utilisateur : à chaque changement d'état, la fonction que vous passez en argument est exécutée.  Si l'utilisateur est bien authentifié,  onAuthStateChanged()  reçoit l'objet de type  firebase.User  correspondant à l'utilisateur.  Vous pouvez ainsi baser la valeur de la variable locale  isAuth  selon l'état d'authentification de l'utilisateur, et afficher les liens correspondant à cet état.
 +
 +
Il ne vous reste plus qu'à créer  AuthGuardService  et l'appliquer aux routes concernées.  Puisque la vérification de l'authentification est asynchrone, votre service retournera une Promise :
 +
 +
import { Injectable } from '@angular/core';
 +
 +
import { CanActivate, Router } from '@angular/router';
 +
 +
import { Observable } from 'rxjs/Observable';
 +
 +
import * as firebase from 'firebase';
 +
 +
 +
@Injectable()
 +
 +
export class AuthGuardService implements CanActivate {
 +
 +
 +
  constructor(private router: Router) { }
 +
 +
 +
  canActivate(): Observable<boolean> | Promise<boolean> | boolean {
 +
 +
    return new Promise(
 +
 +
      (resolve, reject) => {
 +
 +
        firebase.auth().onAuthStateChanged(
 +
 +
          (user) => {
 +
 +
            if(user) {
 +
 +
              resolve(true);
 +
 +
            } else {
 +
 +
              this.router.navigate(['/auth', 'signin']);
 +
 +
              resolve(false);
 +
 +
            }
 +
 +
          }
 +
 +
        );
 +
 +
      }
 +
 +
    );
 +
 +
  }
 +
 +
}
 +
 +
const appRoutes: Routes = [
 +
 +
  { path: 'auth/signup', component: SignupComponent },
 +
 +
  { path: 'auth/signin', component: SigninComponent },
 +
 +
  { path: 'books', canActivate: [AuthGuardService], component: BookListComponent },
 +
 +
  { path: 'books/new', canActivate: [AuthGuardService], component: BookFormComponent },
 +
 +
  { path: 'books/view/:id', canActivate: [AuthGuardService], component: SingleBookComponent }
 +
 +
];
 +
 +
Ah, mais qu'a-t-on oublié ?  Le routing ne prend en compte ni le path vide, ni le path wildcard !  Ajoutez ces routes dès maintenant pour éviter toute erreur :
 +
 +
const appRoutes: Routes = [
 +
 +
  { path: 'auth/signup', component: SignupComponent },
 +
 +
  { path: 'auth/signin', component: SigninComponent },
 +
 +
  { path: 'books', canActivate: [AuthGuardService], component: BookListComponent },
 +
 +
  { path: 'books/new', canActivate: [AuthGuardService], component: BookFormComponent },
 +
 +
  { path: 'books/view/:id', canActivate: [AuthGuardService], component: SingleBookComponent },
 +
 +
  { path: '', redirectTo: 'books', pathMatch: 'full' },
 +
 +
  { path: '**', redirectTo: 'books' }
 +
 +
];
 +
 +
Ainsi, votre application comporte un système d'authentification complet, permettant l'inscription et la connexion/déconnexion des utilisateurs, et qui protège les routes concernées.  Vous pouvez maintenant ajouter les fonctionnalités à votre application en sachant que les accès à la base de données et au stockage, qui nécessitent l'authentification, fonctionneront correctement.
 +
 +
===Base de données===

Version du 11 juin 2019 à 19:27

Créez une application complète avec Angular et Firebase

Pour cette section, vous allez créer une nouvelle application et appliquer des connaissances que vous avez apprises tout au long du cours Angular, ainsi que quelques fonctionnalités que vous n'avez pas encore rencontrées. Vous allez créer une application simple qui recense les livres que vous avez chez vous, dans votre bibliothèque. Vous pourrez ajouter une photo de chaque livre. L'utilisateur devra être authentifié pour utiliser l'application.

Malgré sa popularité, j'ai choisi de ne pas intégrer AngularFire dans ce cours.

Si vous souhaitez en savoir plus, vous trouverez plus d'informations sur la page GitHub d'AngularFire. Vous emploierez l'API JavaScript mise à disposition directement par Firebase.

Pensez à la structure de l'application

Prenez le temps de réfléchir à la construction de l'application. Quels seront les components dont vous aurez besoin ? Les services ? Les modèles de données ?

L'application nécessite l'authentification. Il faudra donc un component pour la création d'un nouvel utilisateur, et un autre pour s'authentifier, avec un service gérant les interactions avec le backend.

Les livres pourront être consultés sous forme d'une liste complète, puis individuellement. Il faut également pouvoir ajouter et supprimer des livres. Il faudra donc un component pour la liste complète, un autre pour la vue individuelle et un dernier comportant un formulaire pour la création/modification. Il faudra un service pour gérer toutes les fonctionnalités liées à ces components, y compris les interactions avec le serveur.

Vous créerez également un component séparé pour la barre de navigation afin d'y intégrer une logique séparée.

Pour les modèles de données, il y aura un modèle pour les livres, comportant simplement le titre, le nom de l'auteur et la photo, qui sera facultative.

Il faudra également ajouter du routing à cette application, permettant l'accès aux différentes parties, avec une guard pour toutes les routes sauf l'authentification, empêchant les utilisateurs non authentifiés d'accéder à la bibliothèque.

Allez, c'est parti !

Structurez l'application

Pour cette application, je vous conseille d'utiliser le CLI pour la création des components.

su
cd ~/Angular
npm install -g npm@latest    (npm est le gestionnaire de paquets officiel de Node.js)
ng new une-bibliotheque --style=scss --skip-tests=true

vidéo : 2:30

L'arborescence sera la suivante :

ng g c auth/signup
ng g c auth/signin
ng g c book-list
ng g c book-list/single-book
ng g c book-list/book-form
ng g c header
ng g s services/auth
ng g s services/books
ng g s services/auth-guard

Les services ainsi créés ne sont pas automatiquement mis dans l'array providers d'AppModule , donc ajoutez-les maintenant.

npm install --save bootstrap@3.3.7 

à vérifier dans package.json

"bootstrap": "^3.3.7",

vidéo : 4:30

Avant de lancer ng serve , utilisez NPM pour ajouter Bootstrap à votre projet, et ajoutez-le à l'array styles de .angular-cli.json  :

npm install bootstrap@3.3.7 --save
angular-cli.json
"styles": [
        "../node_modules/bootstrap/dist/css/bootstrap.css",
        "styles.css"
  ],

vidéo : 5:30

 npm install rxjs-compat --save 


Pendant que vous travaillez sur AppModule , ajoutez également FormsModule , ReactiveFormsModule et HttpClientModule  :

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClient, HttpClientModule } from '@angular/common/http';

import { AuthGuardService } from './services/auth-guard.service';
import { AuthService } from './services/auth.service';
import { BooksService } from './services/books.service';

imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule
  ],
providers: [
     AuthService, 
     BooksService, 
     AuthGuardService
   ],

N'oubliez pas d'ajouter les imports en haut du fichier !

Intégrez dès maintenant le routing sans guard afin de pouvoir accéder à toutes les sections de l'application pendant le développement :

app.module.ts
import { Routes, RouterModule } from '@angular/router';

const appRoutes: Routes = [
  { path: 'auth/signup', component: SignupComponent },
  { path: 'auth/signin', component: SigninComponent },
  { path: 'books', component: BookListComponent },
  { path: 'books/new', component: BookFormComponent },
  { path: 'books/view/:id', component: SingleBookComponent }
];

imports: [
    BrowserModule,
    FormsModule,
    ReactiveFormsModule,
    HttpClientModule,
    RouterModule.forRoot(appRoutes)
],

vidéo : 8:00

Générez également un dossier appelé models et créez-y le fichier book.model.ts  :

book.model.ts
export class Book {
  photo: string;
  synopsis: string;
  constructor(public title: string, public author: string) {
  }
}

vidéo : 8:50

Enfin, préparez HeaderComponent avec un menu de navigation, avec les routerLink et AppComponent qui l'intègre avec le router-outlet  :

header.componennt.html
<nav class="navbar navbar-default">
  <div class="container-fluid">
    <ul class="nav navbar-nav">
      <li routerLinkActive="active">
        <a routerLink="books">Livres</a>
      </li>
    </ul>
    <ul class="nav navbar-nav navbar-right">
      <li routerLinkActive="active">
        <a routerLink="auth/signup">Créer un compte</a>
      </li>
      <li routerLinkActive="active">
        <a routerLink="auth/signin">Se connecter</a>
      </li>
    </ul>
  </div>
</nav>

vidéo : 10:30

app.component.html
<app-header></app-header>
<div class="container">
    <router-outlet></router-outlet>  <!-- reception des routes de l'application --> 
</div>

vidéo : 10:40

La structure globale de l'application est maintenant prête !

cd une-bibliotheque
ng serve --open

vidéo : 11:00

git commit -m "vidéo : 11:00 première connexion"

Intégrez Firebase à votre application

D'abord, installez Firebase avec NPM :

npm install firebase --save- console output -
npm ERR! code E404
npm ERR! 404 Not Found - GET https://registry.npmjs.org/- - Not found
npm ERR! 404 
npm ERR! 404  '-@latest' is not in the npm registry.
npm ERR! 404 You should bug the author to publish it (or use the name yourself!)
npm ERR! 404 
npm ERR! 404 Note that you can also install from a
npm ERR! 404 tarball, folder, http url, or git url.
npm install firebase --save
npm audit fix --force

Pour cette application, vous allez créer un nouveau projet sur Firebase. Une fois l'application créée, la console Firebase vous propose le choix suivant (sous la rubrique Overview) :

sudo npm install -g firebase-tools

Choisissez "Ajouter Firebase à votre application Web" et copiez-collez la configuration dans le constructeur de votre AppComponent' (en ajoutant import * as firebase from 'firebase; en haut du fichier, mettant à disposition la méthode initializeApp() ) : Copiez et collez ces scripts en bas de votre balise <body>, et ce, avant d'utiliser les services Firebase :

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.1.1/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#config-web-app -->
app.component.ts
import { Component } from '@angular/core';
import * as firebase from 'firebase';

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

export class AppComponent {
  constructor() {
    const config = {
    apiKey: "AIzaSyBRvorDmAKFK9vneHnRsom9mDW398uUxEg",
    authDomain: "une-bibliotheque-90c91.firebaseapp.com",
    databaseURL: "https://une-bibliotheque-90c91.firebaseio.com",
    projectId: "une-bibliotheque-90c91",
    storageBucket: "une-bibliotheque-90c91.appspot.com",
    messagingSenderId: "1012969104430",
    appId: "1:1012969104430:web:815509d2f2949a33"
    };
    firebase.initializeApp(config);
  }
}

Votre application Angular est maintenant liée à votre projet Firebase, et vous pourrez maintenant intégrer tous les services dont vous aurez besoin.

vidéo 12:30

Authentification

Votre application utilisera l'authentification par adresse mail et mot de passe proposée par Firebase.

Pour cela, il faut d'abord l'activer dans la console Firebase :


L'authentification Firebase emploie un système de token : un jeton d'authentification est stocké dans le navigateur, et est envoyé avec chaque requête nécessitant l'authentification.

Dans AuthService , vous allez créer trois méthodes :

   une méthode permettant de créer un nouvel utilisateur ;
   une méthode permettant de connecter un utilisateur existant ;
   une méthode permettant la déconnexion de l'utilisateur.

Puisque les opérations de création, de connexion et de déconnexion sont asynchrones, c'est-à-dire qu'elles n'ont pas un résultat instantané, les méthodes que vous allez créer pour les gérer retourneront des Promise, ce qui permettra également de gérer les situations d'erreur.

Importez Firebase dans AuthService  :

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


import * as firebase from 'firebase';


@Injectable()

export class AuthService {

Ensuite, créez la méthode createNewUser() pour créer un nouvel utilisateur, qui prendra comme argument une adresse mail et un mot de passe, et qui retournera une Promise qui résoudra si la création réussit, et sera rejetée avec le message d'erreur si elle ne réussit pas :

createNewUser(email: string, password: string) {

   return new Promise(
     (resolve, reject) => {
       firebase.auth().createUserWithEmailAndPassword(email, password).then(
         () => {
           resolve();
         },
         (error) => {
           reject(error);
         }
       );
     }
   );

}

Toutes les méthodes liées à l'authentification Firebase se trouvent dans firebase.auth().

Créez également signInUser() , méthode très similaire, qui s'occupera de connecter un utilisateur déjà existant :

signInUser(email: string, password: string) {

   return new Promise(
     (resolve, reject) => {
       firebase.auth().signInWithEmailAndPassword(email, password).then(
         () => {
           resolve();
         },
         (error) => {
           reject(error);
         }
       );
     }
   );

}

Créez une méthode simple signOutUser()  :

signOutUser() {

   firebase.auth().signOut();

}

Ainsi, vous avez les trois fonctions dont vous avez besoin pour intégrer l'authentification dans l'application !

Vous pouvez ainsi créer SignupComponent et SigninComponent , intégrer l'authentification dans HeaderComponent afin de montrer les bons liens, et implémenter AuthGuard pour protéger la route /books et toutes ses sous-routes.

Commencez par SignupComponent afin de pouvoir enregistrer un utilisateur :

import { Component, OnInit } from '@angular/core';

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

import { AuthService } from '../../services/auth.service';

import { Router } from '@angular/router';


@Component({

 selector: 'app-signup',
 templateUrl: './signup.component.html',
 styleUrls: ['./signup.component.css']

})

export class SignupComponent implements OnInit {


 signupForm: FormGroup;
 errorMessage: string;


 constructor(private formBuilder: FormBuilder,
             private authService: AuthService,
             private router: Router) { }


 ngOnInit() {
   this.initForm();
 }


 initForm() {
   this.signupForm = this.formBuilder.group({
     email: [, [Validators.required, Validators.email]],
     password: [, [Validators.required, Validators.pattern(/[0-9a-zA-Z]{6,}/)]]
   });
 }


 onSubmit() {
   const email = this.signupForm.get('email').value;
   const password = this.signupForm.get('password').value;


   this.authService.createNewUser(email, password).then(
     () => {
       this.router.navigate(['/books']);
     },
     (error) => {
       this.errorMessage = error;
     }
   );
 }

}

Dans ce component :

   vous générez le formulaire selon la méthode réactive
       les deux champs,  email  et  password , sont requis — le champ  email  utilise  Validators.email  pour obliger un string sous format d'adresse email ; le champ  password  emploie  Validators.pattern  pour obliger au moins 6 caractères alphanumériques, ce qui correspond au minimum requis par Firebase ;
   vous gérez la soumission du formulaire, envoyant les valeurs rentrées par l'utilisateur à la méthode  createNewUser()
       si la création fonctionne, vous redirigez l'utilisateur vers  /books ;
       si elle ne fonctionne pas, vous affichez le message d'erreur renvoyé par Firebase.

Ci-dessous, vous trouverez le template correspondant :

Créer un compte

   <form [formGroup]="signupForm" (ngSubmit)="onSubmit()">
       <label for="email">Adresse mail</label>
       <input type="text"
              id="email"
              class="form-control"
              formControlName="email">
       <label for="password">Mot de passe</label>
       <input type="password"
              id="password"
              class="form-control"
              formControlName="password">
     <button class="btn btn-primary"
             type="submit"
             [disabled]="signupForm.invalid">Créer mon compte</button>
   </form>

Modèle:ErrorMessage

Il s'agit d'un formulaire selon la méthode réactive comme vous l'avez vu dans le chapitre correspondant. Il y a, en supplément, le paragraphe contenant l'éventuel message d'erreur rendu par Firebase.

Vous pouvez créer un template presque identique pour SignInComponent pour la connexion d'un utilisateur déjà existant. Il vous suffit de renommer signupForm en signinForm et d'appeler la méthode signInUser() plutôt que createNewUser() .

Ensuite, vous allez modifier HeaderComponent pour afficher de manière contextuelle les liens de connexion, de création d'utilisateur et de déconnexion :

import { Component, OnInit } from '@angular/core';

import { AuthService } from '../services/auth.service';

import * as firebase from 'firebase';


@Component({

 selector: 'app-header',
 templateUrl: './header.component.html',
 styleUrls: ['./header.component.css']

})

export class HeaderComponent implements OnInit {


 isAuth: boolean;


 constructor(private authService: AuthService) { }


 ngOnInit() {
   firebase.auth().onAuthStateChanged(
     (user) => {
       if(user) {
         this.isAuth = true;
       } else {
         this.isAuth = false;
       }
     }
   );
 }


 onSignOut() {
   this.authService.signOutUser();
 }


}

<nav class="navbar navbar-default">

</nav>

Ici, vous utilisez onAuthStateChanged() , qui permet d'observer l'état de l'authentification de l'utilisateur : à chaque changement d'état, la fonction que vous passez en argument est exécutée. Si l'utilisateur est bien authentifié, onAuthStateChanged() reçoit l'objet de type firebase.User correspondant à l'utilisateur. Vous pouvez ainsi baser la valeur de la variable locale isAuth selon l'état d'authentification de l'utilisateur, et afficher les liens correspondant à cet état.

Il ne vous reste plus qu'à créer AuthGuardService et l'appliquer aux routes concernées. Puisque la vérification de l'authentification est asynchrone, votre service retournera une Promise :

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

import { CanActivate, Router } from '@angular/router';

import { Observable } from 'rxjs/Observable';

import * as firebase from 'firebase';


@Injectable()

export class AuthGuardService implements CanActivate {


 constructor(private router: Router) { }


 canActivate(): Observable<boolean> | Promise<boolean> | boolean {
   return new Promise(
     (resolve, reject) => {
       firebase.auth().onAuthStateChanged(
         (user) => {
           if(user) {
             resolve(true);
           } else {
             this.router.navigate(['/auth', 'signin']);
             resolve(false);
           }
         }
       );
     }
   );
 }

}

const appRoutes: Routes = [

 { path: 'auth/signup', component: SignupComponent },
 { path: 'auth/signin', component: SigninComponent },
 { path: 'books', canActivate: [AuthGuardService], component: BookListComponent },
 { path: 'books/new', canActivate: [AuthGuardService], component: BookFormComponent },
 { path: 'books/view/:id', canActivate: [AuthGuardService], component: SingleBookComponent }

];

Ah, mais qu'a-t-on oublié ? Le routing ne prend en compte ni le path vide, ni le path wildcard ! Ajoutez ces routes dès maintenant pour éviter toute erreur :

const appRoutes: Routes = [

 { path: 'auth/signup', component: SignupComponent },
 { path: 'auth/signin', component: SigninComponent },
 { path: 'books', canActivate: [AuthGuardService], component: BookListComponent },
 { path: 'books/new', canActivate: [AuthGuardService], component: BookFormComponent },
 { path: 'books/view/:id', canActivate: [AuthGuardService], component: SingleBookComponent },
 { path: , redirectTo: 'books', pathMatch: 'full' },
 { path: '**', redirectTo: 'books' }

];

Ainsi, votre application comporte un système d'authentification complet, permettant l'inscription et la connexion/déconnexion des utilisateurs, et qui protège les routes concernées. Vous pouvez maintenant ajouter les fonctionnalités à votre application en sachant que les accès à la base de données et au stockage, qui nécessitent l'authentification, fonctionneront correctement.

Base de données