Fermer

mai 22, 2018

Authentification Firebase et Angular avec Auth0: Partie 2 –


Cet article a été publié à l'origine sur le blog Auth0.com et est republié ici avec sa permission.

Dans cette série de didacticiels en deux parties, nous apprendrons comment créer une application qui sécurise une extrémité arrière de nœud et une extrémité avant angulaire avec authentification Auth0 . Notre serveur et notre application authentifieront également une base de données Cloud Firestore Firebase avec des jetons personnalisés afin que les utilisateurs puissent laisser des commentaires en temps réel de manière sécurisée après s'être connectés avec Auth0. Le code de l'application angulaire peut être trouvé sur le repo GitHub angulaire-firebase et l'API Node se trouve dans le repo firebase-auth0-nodeserver .

La première partie de notre tutorial, Authentification Firebase et Angular avec Auth0: Part 1 couvert:

  • intro et configuration pour Auth0 et Firebase
  • implémentant une API Node sécurisée qui frappe les jetons Firebase personnalisés et fournit des données pour notre application [19659006] Architecture d'application angulaire avec modules et chargement différé
  • Authentification angulaire avec Auth0 avec service et route guard
  • Composants angulaires partagés et service API.

Authentification de Firebase et Angular avec Auth0: Partie 2

Partie 2 de notre tutoriel couvrira:

  1. Affichage des chiens: Async et NgIfElse
  2. Détails du chien avec les paramètres de l'itinéraire
  3. Commentaire Catégorie du modèle
  4. Firebase Firestore Cloud et les règles [19659006] Commentaires Composant
  5. Formulaire de commentaire Component
  6. Temps réel Commentaires
  7. Conclusion

ceci:

 Angulaire Firebase application avec des jetons personnalisés Auth0

Reprenons là où nous nous étions arrêtés à la fin de Authentification Firebase et Angular avec Auth0: Partie 1 .

Affichage des chiens: Async et NgIfElse

Implémentons la page d'accueil de notre application – la liste des chiens. Nous avons créé l'échafaudage pour ce composant lorsque nous avons configuré l'architecture de notre application Angular

Remarque importante: Assurez-vous que votre API Node.js est en cours d'exécution. Si vous avez besoin d'un rappel sur l'API, reportez-vous à Comment authentifier Firebase et Angular avec Auth0: Partie 1 – API nœud .

Dogs Component Class

Ouvrez le composant dogs. ts fichier de classe maintenant et implémenter ce code:

 // src / app / dogs / dogs / dogs.component.ts
import {Component, OnInit} à partir de '@ angular / core';
importer {Title} à partir de '@ angular / platform-browser';
importer {ApiService} à partir de '../../core/api.service';
importer {Dog} à partir de './../../core/dog';
import {Observable} à partir de 'rxjs / Observable';
importer {tap, catchError} à partir de 'rxjs / operators';

@Composant({
  sélecteur: 'app-dogs',
  templateUrl: './dogs.component.html'
})
export classe DogsComponent implémente OnInit {
  pageTitle = 'Chiens populaires';
  dogsList $: Observable ;
  chargement = vrai;
  erreur: booléen;

  constructeur(
    titre privé: Titre,
    api privée: ApiService
  ) {
    this.dogsList $ = api.getDogs $ (). pipe (
      appuyez sur (val => this._onNext (val)),
      catchError ((err, caught) => this._onError (err, catch))
    )
  }

  ngOnInit () {
    this.title.setTitle (this.pageTitle);
  }

  private _onNext (val: Dog []) {
    this.loading = faux;
  }

  private _onError (err, caught): Observable  {
    this.loading = faux;
    this.error = true;
    return Observable.throw ('Une erreur est survenue lors de l'extraction des données de chiens.');
  }

}

Après nos importations, nous allons mettre en place quelques propriétés locales:

  • pageTitle : pour mettre nos pages

    et
  • dogsList $ : l'observable retourné par notre requête HTTP API pour récupérer les données de listage des chiens
  • loading : pour afficher une icône de chargement alors que la requête API est en cours
  • error : pour afficher une erreur si quelque chose se passe mal récupérant des données de l'API.

Nous allons utiliser le tuyau asynchrone déclaratif pour répondre à dogsList $ observable retourné par notre API GET demande. Avec le pipe async, nous n'avons pas besoin de vous abonner ou de vous désinscrire dans notre classe DogsComponent : le processus d'abonnement sera géré automatiquement! Nous devons simplement mettre en place notre observable.

Nous rendrons Titre et ApiService disponibles à notre classe en les transmettant au constructeur, puis nous établirons notre dogsList $ observable. Nous utiliserons les opérateurs RxJS tap (précédemment connu sous le nom d'opérateur do ) et catchError pour appeler les fonctions du gestionnaire. L'opérateur tap exécute les effets secondaires mais n'affecte pas les données émises, il est donc idéal pour la définition d'autres propriétés. La fonction _onNext () définira en chargeant à false (puisque les données ont été émises avec succès). La fonction _onError () définira en chargeant et l'erreur correctement et lancera une erreur. Comme mentionné précédemment, nous n'avons pas besoin de s'abonner ou se désinscrire de dogsList $ observable parce que le tuyau asynchrone (que nous ajouterons dans le modèle)

Lors de l'initialisation de notre composant, nous allons utiliser ngOnInit () pour espionner le hook de cycle de vie OnInit à définir le document . [19659036] Voilà pour notre classe de composants Chiens!

Modèle de composants pour chiens

Passons au modèle à dogs.component.html :


{{pageTitle}}

Ces ont été les 10 races de chiens les plus populaires aux États-Unis en 2016 classés par l'American Kennel Club (AKC).   

# {{dog.rank}}: {{dog.breed}}

En savoir plus           

        
      
    
  

Il y a deux choses dans ce modèle que nous allons examiner de plus près:

 ...

  
  


...     
      ...

Ce code fait des choses très utiles de manière déclarative. Explorons

Tout d'abord, nous avons un élément avec une variable de référence de modèle ( #noDogs ). L'élément n'est jamais rendu directement. Il est destiné à être utilisé avec des directives structurelles (telles que NgIf). Dans ce cas, nous avons créé une vue incorporée avec qui contient à la fois les composants de chargement et d'erreur. Chacun de ces composants sera rendu basé sur une condition. La vue incorporée noDogs elle-même ne sera restituée que si on lui demande de le faire.

Alors, comment (et quand) devons-nous dire cette vue?

La suivante <div * ngIf = ". .. est en fait un NgIfElse utilisant le préfixe asterisk comme sucre syntaxique .Nous utilisons aussi le tuyau async avec notre dogsList $ observable et réglage une variable pour que nous puissions référencer les valeurs émises dans notre template ( comme dogsList ) Si quelque chose ne va pas avec les dogsList $ observable, nous avons un else noDogs ] instruction qui indique au modèle de rendre la vue .Ceci serait vrai avant que les données aient été extraites avec succès de l'API, ou si une erreur a été levée par l'observable.

Si dogsList $ | async a émis avec succès une valeur, la div va afficher et nous pouvons itérer sur notre valeur dogsList (qui devrait être un tableau de Dog s, comme spécifié dans notre classe de composants) utilisant la directive structurelle NgForOf ( * ngFor ) pour afficher les informations de chaque chien.

Comme vous pouvez le voir dans le HTML restant, chaque chien sera affiché avec une image, un rang, une race, et un lien vers leur page de détail individuelle, que nous créerons ensuite.

Voir le composant Chiens dans le navigateur en accédant à la page d'accueil de votre application à l'adresse http: // localhost: 4200 . L'application Angular devrait demander à l'API d'aller chercher la liste des chiens et de les afficher!

Note: Nous avons aussi inclus le composant . Puisque nous avons généré ce composant mais n'avons pas encore implémenté sa fonctionnalité, il devrait apparaître dans l'interface utilisateur sous la forme "Comments works!"

Pour tester la gestion des erreurs, vous pouvez arrêter le serveur API ( Ctrl + c dans l'invite de commande ou le terminal du serveur). Ensuite, essayez de recharger la page. Le composant d'erreur doit s'afficher puisque l'API ne peut pas être atteinte, et nous devrions voir les erreurs appropriées dans la console du navigateur:

 Application angulaire avec l'API Node.js montrant l'erreur de données

Détails du chien avec paramètres de route

] Ensuite, nous allons implémenter notre composant Dog. Ce composant routé sert de page de détails pour chaque chien. Nous avons déjà mis en place notre architecture de module Dog avec routage et chargement paresseux dans la première partie de ce tutoriel.

Rappel: Vous pouvez vous souvenir de la partie 1 que la page des détails du chien est protégée par la AuthGuard garde de route . Cela signifie que le visiteur doit être authentifié pour accéder à la page. En outre, l'appel API nécessite un jeton d'accès pour renvoyer des données.

Dog Component Class

Ouvrez le fichier de classe dog.component.ts et ajoutez:

 // src / app / chien / chien / dog.component.ts
importer {Component, OnInit, OnDestroy} à partir de '@ angular / core';
importer {ActivatedRoute} à partir de '@ angular / router';
importer {Title} à partir de '@ angular / platform-browser';
importer {ApiService} à partir de '../../core/api.service';
importer {DogDetail} à partir de './../../core/dog-detail';
import {Abonnement} à partir de 'rxjs / Subscription';
import {Observable} à partir de 'rxjs / Observable';
importer {tap, catchError} à partir de 'rxjs / operators';

@Composant({
  sélecteur: 'app-dog',
  templateUrl: './dog.component.html',
  styles: [`
    .dog-photo {
      background-repeat: no-repeat;
      background-position: 50% 50%;
      background-size: cover;
      min-height: 250px;
      width: 100%;
    }
  `]
})
export class DogComponent implémente OnInit, OnDestroy {
  paramSub: Abonnement;
  chien $: Observable ;
  chargement = vrai;
  erreur: booléen;

  constructeur(
    route privée: ActivatedRoute,
    api privée: ApiService,
    titre privé: Titre
  ) {}

  ngOnInit () {
    this.paramSub = this.route.params
      .souscrire(
        params => {
          this.dog $ = this.api.getDogByRank $ (params.rank) .pipe (
            appuyez sur (val => this._onNext (val)),
            catchError ((err, caught) => this._onError (err, catch))
          )
        }
      )
  }

  private _onNext (val: DogDetail) {
    this.loading = faux;
  }

  private _onError (err, caught): Observable  {
    this.loading = faux;
    this.error = true;
    return Observable.throw ('Une erreur est survenue lors de la récupération des données détaillées de ce chien.');
  }

  getPageTitle (dog: DogDetail): string {
    const pageTitle = `# $ {dog.rank}: $ {dog.breed}`;
    this.title.setTitle (pageTitre);
    return pageTitre;
  }

  getImgStyle (url: string) {
    retourne `url ($ {url})`;
  }

  ngOnDestroy () {
    this.paramSub.unsubscribe ();
  }

}

Ce composant est très similaire à notre composant de liste Dogs avec juste quelques différences clés.

Nous allons importer les dépendances nécessaires et utiliser en privé le service ApiService et Title dans notre classe.

Le composant Détails du chien repose sur un paramètre d'itinéraire pour déterminer quel chien nous devons extraire des données. Le paramètre route correspond au rang du chien désiré dans la liste des dix chiens les plus populaires, comme:

 # URL pour chien # 2:
http: // localhost: 4200 / chien / 2

Pour accéder à ce paramètre dans la classe component, nous devons importer l'interface ActivatedRoute la transmettre au constructeur, et souscrire aux paramètres de la route activée observable .

Nous pouvons ensuite passer le paramètre rank à notre méthode de service API getDogByRank $ () . Nous devrions aussi nous désabonner des paramètres d'itinéraire observables lorsque le composant est détruit . Notre chien $ observable peut utiliser tap et catchError des gestionnaires similaires à notre liste de chiens.

Nous aurons aussi besoin de quelques méthodes pour aider notre

La méthode getPageTitle () utilise les données API pour générer un titre de page qui inclut le rang et la race du chien.

La méthode getImgStyle () utilise l'API données pour renvoyer une valeur CSS d'arrière-plan.

Modèle de composant de chien

Utilisons maintenant ces méthodes dans notre modèle dog.component.html :

 

   
   


  

{{getPageTitle (chien)}}

  • Groupe: {{dog.group}}
  • Personnalité: {{dog.personality}}
  • Niveau d'énergie: {{dog.energy}}
  

Dans l'ensemble, ce modèle ressemble et fonctionne de la même façon que notre modèle de composant listing Dogs, sauf que nous ne faisons pas d'itération sur un tableau. Au lieu de cela, nous montrons des informations pour un seul chien, et le titre de la page est généré dynamiquement au lieu de statique. Nous utiliserons les données du chien émis par de l'observable (de dog $ async comme chien ) pour afficher les détails à l'aide des classes Bootstrap CSS . Le composant devrait ressembler à ceci dans le navigateur quand fini:

 Application angulaire avec le tuyau asynchrone et l'authentification - détail de chien

Pour arriver à la page de détail d'un chien, un utilisateur non authentifié sera invité par le AuthGuard pour se connecter en premier. Une fois authentifiés, ils seront redirigés vers la page de détails demandée. Essayez-le!

Maintenant que nos pages de listes de chiens et de détails sont terminées, il est temps de travailler sur l'ajout de commentaires en temps réel!

La première chose que nous ferons est d'établir la forme de nos commentaires. initialiser de nouvelles instances de commentaire. Implémentons la classe comment.ts dans notre application Angular:

 // src / app / comments / comment.ts
classe d'exportation Commentaire {
  constructeur(
    utilisateur public: chaîne,
    uid public: chaîne,
    image publique: chaîne,
    texte public: chaîne,
    horodatage public: numéro
  ) {}

  // Solution de contournement car Firestore n'accepte pas les instances de classe
  // comme données lors de l'ajout de documents; doit déballer l'instance pour enregistrer.
  // Voir: https://github.com/firebase/firebase-js-sdk/issues/311
  public get getObj (): object {
    résultat de const = {};
    Object.keys (this) .map (key => résultat [key] = this [key]);
    résultat de retour;
  }

}
Contrairement aux modèles  Dog  et  DogDetail notre modèle  Comment  est un  class pas une interface   . Nous finirons par initialiser  Commentaire  instances dans notre composant de formulaire de commentaire, et pour ce faire, une classe est nécessaire. En outre, Firestore n'accepte que les objets JS classiques lors de l'ajout de documents à une collection. Nous devons donc ajouter une méthode à notre classe qui déplie l'instance vers un objet. En revanche, une interface ne fournit qu'une description   d'un objet. Cela suffisait pour  Dog  et  DogDetail mais ne suffirait pas pour  Commenter . 

Une fois rendu, nous voulons que les commentaires ressemblent à ceci: [19659036]  Angulaire Firebase application avec des commentaires

Comme vous pouvez le voir, chaque commentaire a un nom d'utilisateur, une image, un texte de commentaire, et une date et l'heure. Les commentaires ont également besoin d'un identifiant unique, fourni dans les données sous la forme uid . Cet identifiant unique garantit aux utilisateurs un accès approprié pour supprimer leurs propres commentaires, mais pas les commentaires laissés par d'autres personnes.

Maintenant que nous avons une idée de ce à quoi un commentaire devrait ressembler, allons configurer nos règles Firebase Firestore.

Firebase Cloud Firestore et ses règles

Nous utiliserons la base de données Cloud Firestore de Firebase pour stocker les commentaires de notre application. Cloud Firestore est une base de données NoSQL, flexible, évolutive et hébergée dans le cloud qui fournit des fonctionnalités en temps réel. Au moment de la rédaction, Firestore est en version bêta, mais c'est la base de données recommandée pour toutes les nouvelles applications mobiles et Web. Vous pouvez en savoir plus sur en choisissant entre Base de données en temps réel (RTDB) et Cloud Firestore ici .

Rappel: Si vous avez besoin d'un rafraîchissement rapide sur le produit Firebase, relisez Comment authentifier Firebase et Angulaire avec Auth0 - Partie 1: Firebase et Auth0 .

Firestore organise des données comme documents dans collections . Ce modèle de données devrait vous être familier si vous avez de l'expérience avec les bases de données NoSQL axées sur les documents comme MongoDB . Sélectionnez maintenant Cloud Firestore comme base de données maintenant

  1. Connectez-vous au projet Firebase que vous avez créé dans la partie 1 de ce tutoriel
  2. Cliquez sur Database dans le menu latéral.
  3. Dans la liste déroulante située à côté de l'en-tête de la base de données, sélectionnez Cloud Firestore .

Ajouter une collection et Premier document

L'onglet Data sera affiché par défaut. base de données n'a actuellement rien dedans. Ajoutons notre collection et un document pour que nous puissions interroger notre base de données dans Angular et que quelque chose soit retourné.

Cliquez sur + Ajouter Collection . Nommez votre collection commentaires puis cliquez sur le bouton Suivant . Vous serez invité à ajouter votre premier document

 Console Firebase - ajouter un document

Dans le champ Document id cliquez sur Auto-ID . Cela va automatiquement remplir un identifiant pour vous. Ensuite, ajoutez les champs que nous avons créés précédemment dans le modèle comment.ts avec les types appropriés et quelques données d'espace réservé. Nous avons seulement besoin de ce document de départ jusqu'à ce que nous sachions que notre liste se comporte correctement dans notre application Angular, puis nous pouvons le supprimer en utilisant la console Firebase et entrer des commentaires correctement en utilisant un formulaire dans le front end.

avoir un formulaire construit encore, les données de la graine seront utiles. Une fois que vous avez entré les champs et les types corrects, vous pouvez remplir les valeurs comme bon vous semble. Voici une suggestion:

 utilisateur : Test User
Uid : abc-123
photo : https://cdn.auth0.com/avatars/tu.png
text : Ceci est un commentaire de test de la console Firebase.
horodatage : 1514584235257

Note: Un commentaire avec une valeur inventée uid ne sera valide pour aucun utilisateur authentifié réel une fois que nous aurons établi des règles de sécurité Firebase. Le document de départ devra être supprimé en utilisant la console Firebase si nous voulons l'enlever plus tard. Nous ne pourrons pas le supprimer en utilisant les méthodes SDK dans l'application Angular, comme vous le verrez dans les règles ci-dessous.

Une fois que vous avez entré le commentaire de votre faux utilisateur, cliquez sur le bouton Enregistrer . La nouvelle collection et le nouveau document doivent apparaître dans la base de données. Cela fournit des données que nous pouvons interroger dans notre application Angular

Firebase Rules

Ensuite, nous allons configurer la sécurité de notre base de données Firestore. Passez maintenant à l'onglet Rules

Les règles de sécurité Firebase fournissent la validation back-end et la validation . Dans l'API du nœud de notre application, nous avons vérifié que les utilisateurs étaient autorisés à accéder aux points de terminaison à l'aide du middleware d'authentification Auth0 et JWT . Nous avons déjà configuré l'authentification Firebase dans notre application API et Angular, et nous utiliserons la fonction de règles pour autoriser les permissions sur la base de données.

Une règle est une expression évaluée pour déterminer si une requête est autorisée pour effectuer une action souhaitée. - Référence aux règles de sécurité de Cloud Firestore

Ajoutez le code suivant dans l'éditeur de règles de base de données Firebase. Nous y reviendrons plus en détail ci-dessous.

 // Règles de base de données Firebase pour Cloud Firestore
service cloud.firestore {
  match / databases / {base de données} / documents {
    match / comments / {document = **} {
      autoriser read: si vrai;
      Autoriser create: if request.auth! = null
        && request.auth.uid == request.resource.data.uid
        && request.resource.data.text est une chaîne
        && request.resource.data.text.size () <= 200;
      autoriser la suppression: if request.auth! = null
        && request.auth.uid == resource.data.uid;
    }
  }
}

Firestore a des méthodes de demande de règle : a lu et a écrit . La lecture comprend obtenir les opérations et list . L'écriture inclut les opérations create update et delete . Nous allons implémenter lire créer et supprimer règles.

Note: Nous n'allons pas ajouter de fonction d'édition de commentaires dans notre application, donc mise à jour n'est pas incluse. Toutefois, n'hésitez pas à ajouter une règle update si vous souhaitez ajouter cette fonctionnalité par vous-même!

Les règles sont exécutées lorsqu'une requête d'utilisateur correspond à un document chemin. Les chemins peuvent être entièrement nommés, ou ils peuvent utiliser des caractères génériques. Nos règles s'appliquent à tous les documents de la collection comments que nous avons créée

Nous voulons tout le monde pouvoir lire commentaires, aussi bien anonymes qu'authentifiés . Par conséquent, la condition pour autoriser lire est simplement si elle est vraie .

Nous voulons seulement utilisateurs authentifiés pour pouvoir créer nouveau commentaires. Nous allons vérifier que l'utilisateur est connecté et que les données sauvegardées possèdent une propriété uid qui correspond à l'authentification de l'utilisateur uid () request.auth.uid dans les règles Firebase). En outre, nous pouvons faire un peu de validation sur le terrain ici. Nous allons vérifier que les données de la requête ont une propriété text qui est une chaîne de 200 caractères ou moins (nous ajouterons aussi cette validation dans notre application Angular sous peu).

Enfin, nous voulons seulement Les utilisateurs peuvent supprimer leurs propres commentaires . Nous pouvons autoriser la suppression si l'UID de l'utilisateur authentifié correspond à la propriété uid du commentaire existant en utilisant resource.data.uid .

Remarque: Vous pouvez en savoir plus sur la requête et ressources mots-clés dans les documents Firebase

Maintenant que notre base de données est prête, il est temps de retourner à notre application Angular et de mettre en œuvre des commentaires en temps réel! La première chose que nous allons faire est d'afficher les commentaires. Nous voulons que les commentaires soient mis à jour de façon asynchrone en temps réel, explorons comment faire avec notre base de données Cloud Firestore et le angularfire2 SDK .

Nous avons déjà créé l'architecture de notre module Commentaires, commençons par construire sur notre comments.component.ts :

 // src / app / comments / comments / comments.component.ts
import {Component} à partir de '@ angular / core';
importer {AngularFirestore, AngularFirestoreCollection, AngularFirestoreDocument} à partir de 'angularfire2 / firestore';
import {Observable} à partir de 'rxjs / Observable';
importer {map, catchError} depuis 'rxjs / operators';
importer {Commentaire} à partir de './../comment';
importer {AuthService} à partir de '../../auth/auth.service';

@Composant({
  selector: 'app-comments',
  templateUrl: './comments.component.html',
  styleUrls: ['./comments.component.css']
})
export class CommentsComponent {
  private _commentsCollection: AngularFirestoreCollection ;
  commentaires $: Observable ;
  chargement = vrai;
  erreur: booléen;

  constructeur(
    privé afs: AngularFirestore,
    autorisation publique: AuthService
  ) {
    // Recevez les 15 derniers commentaires de Firestore, classés par date et heure
    this._commentsCollection = afs.collection  (
      'commentaires',
      ref => ref.orderBy ('horodatage'). limite (15)
    )
    // Mettre en place observable des commentaires
    this.comments $ = this._commentsCollection.snapshotChanges ()
      .tuyau(
        map (res => this._onNext (res)),
        catchError ((err, caught) => this._onError (err, catch))
      )
  }

  private _onNext (res) {
    this.loading = faux;
    this.error = false;
    // Ajouter Firestore ID aux commentaires
    // L'ID est nécessaire pour supprimer des commentaires spécifiques
    return res.map (action => {
      const data = action.payload.doc.data () comme commentaire;
      const id = action.payload.doc.id;
      return {id, ... données};
    });
  }

  private _onError (err, caught): Observable  {
    this.loading = faux;
    this.error = true;
    return Observable.throw ('Une erreur s'est produite lors de la récupération des commentaires.');
  }

  onPostComment (commentaire: commentaire) {
    // Défaites l'occurrence de Comment à un objet pour Firestore
    // Voir https://github.com/firebase/firebase-js-sdk/issues/311
    const commentObj =  comment.getObj;
    this._commentsCollection.add (commentObj);
  }

  canDeleteComment (uid: string): booléen {
    if (! this.auth.loggedInFirebase ||! this.auth.userProfile) {
      return false;
    }
    return uid === this.auth.userProfile.sub;
  }

  deleteComment (id: string) {
    // Supprimer le commentaire avec l'invite de confirmation en premier
    if (window.confirm ('Êtes-vous sûr de vouloir supprimer votre commentaire?')) {
      const thisDoc: AngularFirestoreDocument  = this.afs.doc  (`comments / $ {id}`);
      thisDoc.delete ();
    }
  }

}

Nous allons d'abord importer les dépendances angularfire2 nécessaires pour utiliser Firestore, les collections et les documents. Nous avons aussi besoin de Observable carte et catchError de RxJS, notre modèle Comment et AuthService . [19659036] Nous allons déclarer les membres ensuite. Le privé _commentsCollection est une collection Firestore contenant des objets ayant la forme de Comment . Le commentaires $ observable est un flux avec des valeurs qui prennent la forme de tableaux de Comment s. Ensuite, nous avons nos propriétés de chargement et erreur .

Après avoir passé AngularFirestore et AuthService à la fonction constructeur, nous devons récupère nos données de collecte auprès de Cloud Firestore. Nous allons utiliser la méthode angularfire2 collection () pour ce faire, en spécifiant Comment comme type, en passant le nom de notre collection ( commentaires ) , ordonnant les résultats par horodatage et limitant aux 15 derniers commentaires

Ensuite, nous allons créer nos commentaires $ observables en utilisant le _commentsCollection . Nous utiliserons les opérateurs map () et catchError () RxJS pour gérer les données et les erreurs émises.

Dans notre gestionnaire privé _onNext () nous 'set chargement et erreur à faux . Nous ajouterons également l'ID de document Firestore à chaque élément des tableaux émis par le flux commentaires $ . Nous avons besoin de ces identifiants pour permettre aux utilisateurs de supprimer des commentaires individuels. Afin d'ajouter l'ID aux valeurs émises, nous utiliserons la méthode snapshotChanges () pour accéder aux métadonnées . Nous pouvons alors mapper (19459037) document id s dans les données renvoyées en utilisant l'opérateur de propagation .

Note: Vous remarquerez peut-être que nous n'avons pas mis erreur à faux dans la méthode de succès dans nos chiens ou chiens observables, mais nous le faisons ici. Le flux de commentaires émet une valeur à chaque fois qu'un utilisateur de ajoute un commentaire en temps réel. Par conséquent, nous devrons peut-être réinitialiser le statut d'erreur de manière asynchrone en réponse.

Le gestionnaire privé _onError () doit être très familier avec nos autres composants. Il définit chargement et erreur propriétés et génère une erreur.

La méthode onPostComment () sera exécutée lorsque l'utilisateur soumet un commentaire en utilisant le formulaire de commentaire composant (que nous allons construire prochainement). La charge utile onPostComment () contiendra une instance Comment contenant les données de commentaire de l'utilisateur, qui doivent ensuite être dépliées sur un objet normal pour être enregistrées dans Firestore. Nous allons enregistrer l'objet commentaire non déroulé à l'aide de la méthode angular firestore add () .

La méthode canDeleteComment () vérifie si l'utilisateur actuel est le propriétaire d'un commentaire donné. S'ils ont créé le commentaire, ils peuvent également le supprimer. Cette méthode vérifie que la propriété userProfile.sub de l'utilisateur connecté correspond à l'identificateur du commentaire .

La méthode deleteComment () est exécutée lorsque l'utilisateur clique sur l'icône pour supprimer un commentaire. Cette méthode ouvre une boîte de dialogue de confirmation qui confirme l'action et, si elle est confirmée, utilise l'argument id pour supprimer le document de commentaire correct de la collection Firestore. (C'est pourquoi nous avons besoin d'ajouter le document id s à nos données lorsque nous avons mappé les valeurs émises par nos commentaires $ observables.)

Note: Rappelons que nos règles Firestore empêchent également utilisateurs de supprimer des commentaires qu'ils n'ont pas créés. Nous devrions toujours nous assurer que les droits d'accès sont appliqués le les deux front-end et back-end pour une sécurité adéquate.

Maintenant, mettons nos fonctionnalités de classe pour fonctionner dans l'interface utilisateur. Ouvrez le fichier comments.component.html et ajoutez:

 
  

Commentaires

Chargement des commentaires ...     

  • {{comment.user}} {{comment.timestamp | date: 'court'}} & times;

S'il vous plaît connectez-vous pour laisser un commentaire.       

    
  

Nous utiliserons principalement des classes Bootstrap pour styliser nos commentaires, avec un peu de CSS personnalisé que nous ajouterons ensuite. Notre modèle de commentaires, comme nos modèles de chien et de composant chien, a un et utilise le tuyau asynchrone avec NgIfElse pour afficher l'interface utilisateur appropriée

La liste des commentaires devrait afficher l'image du commentaire (l'utilisateur avatar de son auteur), le nom de l'utilisateur et l'horodatage formaté avec le [DatePipe] . Nous passerons l'uid du commentaire à la méthode canDeleteComment () pour déterminer si un lien de suppression doit être affiché. We’ll then display the comment textusing property binding to innerHTML.

Finally, we’ll create elements to show the comment form or a message instructing users to log in if they wish to leave a comment.

Note: Our will use event binding to emit an event called postComment when a user submits a comment. The CommentsComponent class listens for that event and handles it with the onPostComment() method that we created, using the $event payload to save the submitted comment to the Firestore database. We’ll hook up the (postComment) event when we create the form in the next section.

Finally, open the comments.component.css file and let’s add a few styles to our comments list:

/* src/app/comments/comments/comments.component.css */
.avatar {
  affichage: inline-block;
  height: 30px;
}
.comment-text {
  background: #eee;
  position: relative;
}
.comment-text::before {
  border-bottom: 10px solid #eee;
  border-left: 6px solid transparent;
  border-right: 6px solid transparent;
  content: '';
  display: block;
  height: 1px;
  position: absolute;
    top: -10px; left: 9px;
  width: 1px;
}

Now that we have a listing of comments that updates in realtime, we need to be able to add new comments in our front end.

Open the comment-form.component.ts file and let’s get started:

// src/app/comments/comment-form/comment-form.component.ts
import { Component, OnInit, Output, EventEmitter } from '@angular/core';
import { Comment } from './../../comment';
import { AuthService } from '../../../auth/auth.service';

@Composant({
  selector: 'app-comment-form',
  templateUrl: './comment-form.component.html'
})
export class CommentFormComponent implements OnInit {
  @Output() postComment = new EventEmitter();
  commentForm: Comment;

  constructor(private auth: AuthService) { }

  ngOnInit() {
    this._newComment();
  }

  private _newComment() {
    this.commentForm = new Comment(
      this.auth.userProfile.name,
      this.auth.userProfile.sub,
      this.auth.userProfile.picture,
      '',
      null);
  }

  onSubmit() {
    this.commentForm.timestamp = new Date().getTime();
    this.postComment.emit(this.commentForm);
    this._newComment();
  }

}

As mentioned earlier, we will need to emit an event from this component to the parent CommentsComponentwhich sends the new comment to Firestore. The CommentFormComponent is responsible for constructing the Comment instance with the appropriate information gathered from the authenticated user and their form input and sending that data to the parent. In order to emit the postComment event, we’ll import Output and EventEmitter. We’ll also need our Comment class and AuthService to get user data.

Our comment form component’s members include an Output decorator (postComment) that is an EventEmitter with type of Commentand commentFormwhich will be an instance of Comment to store form data.

In our ngOnInit() method, we’ll create a new Comment instance with the private _newComment() method. This method sets the local commentForm property to a new instance of Comment with the authenticated user’s namesuband picture. The comment text is an empty string and the timestamp is set to null (it will be added when the form is submitted).

The onSubmit() method will be executed when the comment form is submitted in the template. This method adds the timestamp and emits the postComment event with the commentForm data as its payload. It also calls the _newComment() method to reset the comment form.

Open the comment-form.component.html file and add this code:


  
            

The comment form template is quite simple. The form’s only field is a text input, since all other comment data (like name, picture, UID, etc.) is added dynamically in the class. We’ll use a simple template-driven form to implement our comment form.

The

element listens for an (ngOnSubmit) event, which we’ll handle with our onSubmit() method. We’ll also add a template reference variable called #tplForm and set it to ngForm. This way, we can access the form’s properties in the template itself.

The element should have an [(ngModel)] that binds to commentForm.text. This is the property we want to update when a user types in the form field. Recall that we set up our Firestore rules to accept comment text 200 characters or less, so we’ll add this maxlength to our front end, along with a required attribute so that users cannot submit empty comments.

Finally, the to submit the form should be [disabled] if the form is not valid. We can reference the valid property using the tplForm reference variable we added to the

element.

Verify in the browser that the comments show up as expected. The only comment so far should be the seed comment that we added directly in Firebase. When fetched and rendered, our comments list should look like this:

Firebase Firestore comments with Angular

The comment form should show up if the user is authenticated. Log in and try adding a comment.

Users can delete their own comments. A red x should appear next to the comment’s date and time if the user is the owner of a comment. Clicking this delete icon prompts for confirmation and then removes the comment in realtime.

Remember that the seed document we added in Firebase cannot be deleted in the Angular app because its uid property doesn’t match any real user’s data. Let’s delete it manually now.

Open your Firebase console and view your Firestore comments collection. Find the document that contains the seed comment. Using the menu dropdown in the upper right, select Delete document to remove it:

Firebase delete comment

Now, any comments that are added to our database should be able to be deleted by their author in the back end.

When comments are added, they should show up and that’s great, but it doesn’t really demonstrate the true realtime nature of our Firestore database. We could add comments in the UI without a refresh using a traditional server and database as well, simply by updating the view.

Angular form with Firebase Firestore

In order to truly see our realtime database at work, open the app in a second browser and authenticate using a different login. With both browsers in view, add a comment in one browser. It will appear in the second browser at the same time.

realtime commenting with Firestore in Angular

This is what Firebase’s realtime databases can do!

Conclusion

Congratulations! You now have an Angular app that authenticates Firebase with Auth0 and is built on a scalable architecture.

The first part of our tutorial, How to Authenticate Firebase and Angular with Auth0: Part 1covered:

  • intro and setup for Auth0 and Firebase
  • implementing a secure Node API that mints custom Firebase tokens and provides data for our app
  • Angular application architecture with modules and lazy loading
  • Angular authentication with Auth0 with service and route guard
  • shared Angular components and API service.

The second part of our tutorial covered:

  • displaying data with the Async pipe and NgIfElse
  • using route parameters
  • modeling data with a class
  • Firebase Cloud Firestore database and security rules
  • implementing Firestore database in Angular with angularfire2
  • simple template-driven form with component interaction.

Angular Testing Resources

If you’re interested in learning more about testing in Angular, which we did not cover in this tutorial, please check out some of the following resources:

Additional Resources

You can find more resources on Firebase, Auth0, and Angular here:

What’s Next?

Hopefully you learned a lot about building scalable apps with Angular and authenticating Firebase with custom tokens. If you’re looking for ideas to expand on what we’ve built, here are some suggestions:

  • implement an inappropriate language filter for comments
  • implement authorization roles to create an admin user with the rights to delete other peoples’ comments
  • add functionality to support comment editing
  • add comments to individual dog detail pages using additional Firestore collections
  • add testing
  • and much more!




Source link