DEV Community

Cover image for Créer un blog avec Strapi et Angular
Guillaume
Guillaume

Posted on

Créer un blog avec Strapi et Angular

Suite à la vidéo de Yoandev (https://youtu.be/KGHJYoxlGRE), voici le petit (grand !) défit du jour : créer un blog avec Strapi et Angular.

Quelques explications:

Alors avant de commencer à coder, on va regarder pourquoi Strapi et pourquoi Angular.

Strapi est ce qu'on appelle un "headless CMS".
En bref, vous pouvez créer vos propres "type de contenu" (comme dans Drupal par exemple) et les exposer via une API.
C'est vraiment pratique, et ça a beaucoup d'avantages:

  • on s'évite la customisation de la partie "front" qui est souvent un enfer avec les CMS traditionnels
  • on découple la partie backend (Strapi) et la partie "front" (ici Angular).
  • on va disposer d'une API, donc potentiellement, vous pouvez aller la consommer avec une appli mobile ou un autre front.
  • si jamais vous n'êtes pas satisfait d'Angular, rien ne vous empêche de la refactorer avec du VueJS, du React, ou ... du Symfony comme Yoan ;)

Heu c'est quoi un "type de contenu"

Hé ben c'est un objet, une "Entity" en Symfony, ou un Model dans une version plus générique.

Dans notre exemple, notre type de contenu va être "Article".
Il va contenir:

  • un titre
  • un contenu
  • une image
  • une date de publication

Vous avez tout compris ?
Alors on y va pour le code !

Note : cet article est basé sur un article du blog de Strapi : Build a blog with Angular, Strapi and Apollo

Pré-requis

Pas grand chose si ce n'est node et npm (et yarn ici)

Personnellement, j'utilise NVM (https://github.com/nvm-sh/nvm) pour éviter les conflits de version de Node suivant les projets.
Je vous donnerai les commandes à faire tout au long du tutoriel.

Backend

Installation de Strapi

On va créer un répertoire pour la totalité du projet (backend + frontend) et rentrer à l'intérieur.

mkdir blog-strapi && cd blog-strapi

Puis on va créer notre application Strapi:

yarn create strapi-app backend --quickstart --no-run

Le flag "--no-run" va éviter de la démarrer tout de suite après l'installation.

Premier problème soulevé ici avec la version de node de ma distribution Linux.

Ma solution: entrer dans le répertoire de l'application Strapi, supprimer le répertoire node_modules, puis créer un fichier .nvmrc pour "statifier" la version de Node utilisée pour le projet (ici 16+) et enfin réinstaller les dépendances (un simple yarn suffit).

En bref:

cd backend
rm -rf node_modules/
echo "16" > .nvmrc
yarn
Enter fullscreen mode Exit fullscreen mode

Toujours dans le même répertoire "backend" (notre application Strapi), installons le plugin graphql avec

yarn strapi install graphql

Quand l'installation du plugin est finie, lançons notre application:
yarn strapi dev

Normalement, la fenêtre de navigateur va s'ouvrir sur l'interface d'administration de Strapi, et vous demander de créer un compte admin.

Création de notre type de contenu "Article".

Dans le menu de gauche, sélectionnez "Content-type Builder" puis le lien "Create new collection type".

Donnez lui le nom "Article", tel que:

Image description

Puis ajoutez (bouton "Add another field") les champs suivants:

  • title de type "Text" (required)
  • content with type Rich Text (required)
  • image with type Media (Single image) and (required)

Note 1 : pour mettre un champ "required", allez dans l'autre onglet "advanced settings"

Image description

Note 2 : le champ "published_at" est automatique, donc n'est pas à créer.

On enregistre avec "Save" et puis on se lâche, on va créer nos premiers articles dans le backend, via le "Content Manager".

Après la rédaction de quelques articles, on se dit que ça serait bien de tester si l'API nous les renvoie !

Aucun problème, tout est prévu !

Ah oui, sauf qu'avant, il faut régler la partie "permissions".
L'API n'est pas accessible par défaut. Il faut, pour chaque type de contenu, déterminer qui a droit de quoi faire.

Allez dans le menu de gauche "Settings", puis dans "USERS & PERMISSIONS PLUGIN" > Roles.

Nous allons modifier le rôle "public" (avec le crayon) et ajouter les droits comme suit:

Image description

Maintenant que les permissions sont réglées, on va pouvoir tester notre API.

Rendez-vous sur l'interface de test de GraphQL avec votre navigateur ici.

Je vous laisse regarder ce qu'est graphQL, mais en bref, comparé à REST, cela va vous permettre de "choisir" les champs qui seront renvoyés par l'API. C'est comme REST mais en mieux, même si le langage de requêtage n'est pas forcément trivial.

Note: l'interface de test GraphQL est très bien faite : elle dispose notamment d'une complétion automatique bien pratique !

Sur l'interface, créez une requête telle que:

query Articles {
    articles {
    data {
      id, 
      attributes {
        title, 
        content, 
        image {
          data {
            attributes{
              url
            }
          }
        }
      }
    }
  } 
}
Enter fullscreen mode Exit fullscreen mode

Après execution, vous obtiendrez une réponse telle que :

{
  "data": {
    "articles": {
      "data": [
        {
          "id": "1",
          "attributes": {
            "title": "Premier post",
            "content": "Ah enfin ce premier post !\nLe premier d'une longue série ;)\n\nEt puis je vais rappatrier ici tous les articles produits sur d'autres plateformes ;)\n\nA très bientôt !",
            "image": {
              "data": {
                "attributes": {
                  "url": "/uploads/wanderlabs_T_Ap9_Hue_Sl_KQ_unsplash_1_5e82873dce.jpg"
                }
              }
            }
          }
        },
        {
          "id": "2",
          "attributes": {
            "title": "Créer un blog avec Strapi",
            "content": "D'avoir, allez voir la vidéo de Yoandev ;)",
            "image": {
              "data": {
                "attributes": {
                  "url": "/uploads/photo_1499750310107_5fef28a66643_ixlib_rb_1_2_2f258ec988.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80"
                }
              }
            }
          }
        }
      ]
    }
  }
} 
Enter fullscreen mode Exit fullscreen mode

Catégories

Chaque Article va disposer d'une "catégorie".

Créez une nouvelle collection nommée "Categorie" avec un seul champ "name" de type "Text" et la sauvegarder.

On va maintenant lier nos 2 types de contenu "Article" et "Categorie" : une catégorie est liée à 1 ou plusieurs articles.

Ainsi, toujours dans le "Content-Type Builder", sélectionnez "Article" puis ajouter un champ de type "Relation" comme suit:

Image description

N'oubliez pas de cliquer sur "finish" dans la modale, puis sur "Save" pour enregistrer les modifications de notre type de contenu Article.

Enfin, il faut régler la partie "droits" sur notre nouveau type "Category".
Comme pour Article, on va dans le menu de gauche "Settings", puis dans "USERS & PERMISSIONS PLUGIN" > Roles > public, et modifier l'entrée Categorie comme suit :

Image description

Maintenant que vous avez vos catégories, allez dans le Content-Manager, créez des catégories et associez chacun de vos articles à une catégorie.

Vous pouvez de nouveau tester votre API avec le client GraphQL avec une requête telle que:

query Articles {
    articles {
    data {
      id, 
      attributes {
        title, 
        categorie {
          data {
            id, 
            attributes {
              name
            }
          }
        },
        content, 
        image {
          data {
            attributes{
              url
            }
          }
        }
      }
    }
  } 
}

Enter fullscreen mode Exit fullscreen mode

Vous obtiendrez alors un résultat de ce type:

{
  "data": {
    "articles": {
      "data": [
        {
          "id": "1",
          "attributes": {
            "title": "Premier post",
            "categorie": {
              "data": {
                "id": "3",
                "attributes": {
                  "name": "blog"
                }
              }
            },
            "content": "Ah enfin ce premier post !\nLe premier d'une longue série ;)\n\nEt puis je vais rappatrier ici tous les articles produits sur d'autres plateformes ;)\n\nA très bientôt !",
            "image": {
              "data": {
                "attributes": {
                  "url": "/uploads/wanderlabs_T_Ap9_Hue_Sl_KQ_unsplash_1_5e82873dce.jpg"
                }
              }
            }
          }
        },
        {
          "id": "2",
          "attributes": {
            "title": "Créer un blog avec Strapi",
            "categorie": {
              "data": {
                "id": "2",
                "attributes": {
                  "name": "strapi"
                }
              }
            },
            "content": "D'avoir, allez voir la vidéo de Yoandev ;)",
            "image": {
              "data": {
                "attributes": {
                  "url": "/uploads/photo_1499750310107_5fef28a66643_ixlib_rb_1_2_2f258ec988.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80"
                }
              }
            }
          }
        }
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Résumé du backend

Maintenant, on a un beau backend avec Strapi qui nous fournit une belle API GraphQL pour délivrer des articles avec des catégories.

 Frontend

Dans notre console, on va laisser Strapi démarré, et on va revenir dans le répertoire de base du projet

cd blog-strapi

 Création de l'application Angular

D'abord, on va installer le CLI Angular :

sudo npm install -g @angular/cli
Enter fullscreen mode Exit fullscreen mode

Puis créons notre application frontend (acceptez l'ajout de "Angular Routing":

ng new frontend
Enter fullscreen mode Exit fullscreen mode

L'application est maintenant créée, on va maintenant la lancer:

cd frontend
ng serve
Enter fullscreen mode Exit fullscreen mode

Ouvrez votre navigateur à l'adresse http://localhost:4200/

Image description

Installons maintenant le plugin Apollo / client GraphQL à notre application:

ng add apollo-angular
Enter fullscreen mode Exit fullscreen mode

Image description

On va aussi ajouter la bibliothèque de composants CSS "UIKit" :

yarn add uikit jquery
Enter fullscreen mode Exit fullscreen mode

Ajoutons maintenant les dépendances javascript du kit à notre code.
Pour cela, ouvrez le fichier angular.json et trouvez la clé
projects > frontend > architect > build > options > scripts qui doit être un tableau vide et configurez-le tel que:

"scripts": [
    "node_modules/jquery/dist/jquery.min.js",
    "node_modules/uikit/dist/js/uikit.min.js",
    "node_modules/uikit/dist/js/uikit-icons.min.js"
]
Enter fullscreen mode Exit fullscreen mode

On va aussi ajouter la partie CSS à notre projet:
Modifiez le fichier src/style.css qui doit être vide comme suit:

/* You can add global styles to this file, and also import other style files */
@import "../node_modules/uikit/dist/css/uikit.min.css";
@import "../node_modules/uikit/dist/css/uikit.css";
@import "../node_modules/uikit/dist/css/uikit-core.css";
@import url("https://fonts.googleapis.com/css?family=Staatliches");

a {
  text-decoration: none;
}

h1 {
  font-family: Staatliches;
  font-size: 120px;
}

#category {
  font-family: Staatliches;
  font-weight: 500;
}

#title {
  letter-spacing: 0.4px;
  font-size: 22px;
  font-size: 1.375rem;
  line-height: 1.13636;
}

#banner {
  margin: 20px;
  height: 800px;
}

#editor {
  font-size: 16px;
  font-size: 1rem;
  line-height: 1.75;
}

.uk-navbar-container {
  background: #fff !important;
  font-family: Staatliches;
}

img:hover {
  opacity: 1;
  transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
}
Enter fullscreen mode Exit fullscreen mode

Nav component

On va créer notre premier composant, la barre de navigation :

ng generate c nav --skip-import
Enter fullscreen mode Exit fullscreen mode

4 fichiers sont ainsi créés, mais on va uniquement travailler avec les fichiers .html and .ts .
Les fichiers .html sont vos templates (la partie visible) et les .ts sont le "moteur" de votre composant, ce qu'il fait, comment il se comporte, etc.

Image description

Maintenant, on va modifier notre composant "nav" pour aller chercher les catégories et les afficher en haut à droite de notre barre de navigation.

Ouvrez le fichier nav/nav.component.ts et modifiez votre code comme suit:

import { Component, OnInit } from '@angular/core';
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";

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

export class NavComponent implements OnInit {

  data: any = {};
  loading = true;
  errors: any;

  constructor(private apollo: Apollo) {}

  ngOnInit(): void {
    this.apollo.watchQuery({
        query: gql`
          query Categories {
            categories {
              data {
                id, 
                attributes {
                  name
                }
              }
            }
          }
        `
      })
      .valueChanges.subscribe((result: any) => {
        this.data = result?.data?.categories;
        this.loading = result.loading;
        this.errors = result.error;
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

Ici, on va chercher nos catégories avec Apollo lors de l'initialisation du composant avec une requête graphql.

La variable data va contenir nos catégories.

Maintenant, modifions le template pour les afficher !

Ouvrez nav/nav.component.html et modifiez le code comme suit:

<nav class="uk-navbar-container" uk-navbar>
    <div class="uk-navbar-left">
        <ul class="uk-navbar-nav">
        <li class="uk-active"><a href="#">Strapi blog</a></li>
        </ul>
    </div>

    <div class="uk-navbar-right">
        <ul *ngIf="data" class="uk-navbar-nav">
            <li *ngFor="let category of data.data" class="uk-active">
                <a routerLink="/category/{{ category.id }}" routerLinkActive="active" class="uk-link-reset">
                {{ category.attributes.name }}
                </a>
            </li>
        </ul>
    </div>
</nav>
Enter fullscreen mode Exit fullscreen mode

Dans cette vue, on peut bien accéder à notre variable "data" (qui contient les catégories) et sa variable "data" qui contient le tableau des catégories.
On fait une boucle ngFor dessus pour afficher une à une nos catégories et créer un lien avec.

Note : "data.data" n'est pas très lisible, mais cela est dû à la forme du JSON qui est renvoyé de l'API de Strapi.
Voici un exemple de ce que vous renvoie l'API:

{"data":{"categories":{"data":[{"id":"2","attributes":{"name":"strapi","__typename":"Categorie"},"__typename":"CategorieEntity"},{"id":"3","attributes":{"name":"blog","__typename":"Categorie"},"__typename":"CategorieEntity"}],"__typename":"CategorieEntityResponseCollection"}}}
Enter fullscreen mode Exit fullscreen mode

La variable "data" dans notre .ts contient bien "result" (l'ensemble du résultat), ".data" pour accéder au premier élément "data" du résultat (qui contient alors "categories":{"data":[...]}....
Dans la vue, on est donc obligé de prendre "data.data" pour avoir le tableau.

Encore 2 petites modifications et on est bon.

La première, c'est de déclarer notre module "NavComponent" dans la liste des modules.

Pour cela, modifiez app.modules.ts comme suit:

...
import { NavComponent } from "./nav/nav.component";
...

declarations: [
  AppComponent,
  NavComponent
],
...
Enter fullscreen mode Exit fullscreen mode

On importe simplement le module, et on l'ajoute dans le tableau "declarations".

Enfin, on va modifier notre template de base de l'application pour ajouter notre barre de navigation.

Pour cela, ouvrez le fichier app.component.html, supprimez tout son contenu, et ajoutez simplement le code suivant:

<app-nav></app-nav>
<router-outlet></router-outlet>
Enter fullscreen mode Exit fullscreen mode

Ici, on ajoute notre composant "nav" (la balise " et on laisse le router (balise router-outlet) gérer le reste de la page.

Contemplez le résultat dans votre navigateur :
Image description

Mes 2 catégories sont bien Strapi et Blog. Victoire !

 Articles component

On va appliquer le même raisonnement à notre 2ème composant, "ArticlesComponent", qui va lister tous les articles.

  • Création du composant
  • Modification de la logique du composant dans le .ts
  • Modification de la vue du composant
  • Référencement du composant dans la liste des modules
  • Modification du routage de l'application

Création du composant

A la racine du projet, en ligne de commande, créons notre composant:

ng generate c articles/articles --skip-import 
Enter fullscreen mode Exit fullscreen mode

Notez ici que j'ai ajouté "articles/" devant le nom du composant. ça a Pour effet de créer un répertoire englobant pour tous nos futurs composants qui traiteront d'articles (comme le prochain qui affichera un article). Je trouve que c'est une bonne pratique, surtout dans les grosses applications, sinon on s'y perd vite....

Comme pour la barre de navigation, modifiez le .ts du composant src/app/articles/articles/articles.components.ts comme suit:

import { Component, OnInit } from '@angular/core';
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";

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

  data: any = {};
  loading = true;
  errors: any;
  leftArticlesCount: any;
  leftArticles?: any[];
  rightArticles?: any[];

  constructor(private apollo: Apollo) { }

  ngOnInit(): void {
    this.apollo.watchQuery({
      query: gql`
        query Articles {
          articles {
            data {
              id, 
              attributes {
                title, 
                categorie {
                  data {
                    id, 
                    attributes {
                      name
                    }
                  }
                },
                content, 
                image {
                  data {
                    attributes{
                      url
                    }
                  }
                }
              }
            }
          }
        }` 
      })
      .valueChanges.subscribe((result: any) => {
        this.data = result?.data?.articles;
        this.leftArticlesCount = Math.ceil(this.data?.data.length / 5);
        this.leftArticles = this.data?.data.slice(0, this.leftArticlesCount);

        this.rightArticles = this.data?.data.slice(
          this.leftArticlesCount,
          this.data?.data.length
        );
        this.loading = result.loading;
        this.errors = result.error;
      });
  }
}
Enter fullscreen mode Exit fullscreen mode

Ici, pas grand chose de nouveau puisqu'on a le même principe que la barre de navigation:

  • une requête graphql
  • un traitement des résultats avec la consitution de 2 tableaux : 1 pour la partie gauche (les grandes images) et 1 pour la partie droite

Modifions maintenant le template associé src/app/articles/articles/articles.component.html :

<div class="uk-section">
    <div class="uk-container uk-container-large">
      <h1>Strapi blog</h1>

      <div class="uk-child-width-1-2" uk-grid>
        <div>
          <a
            routerLink="/article/{{ article.id }}"
            routerLinkActive="active"
            *ngFor="let article of leftArticles"
            class="uk-link-reset"
          >
            <div class="uk-card uk-card-muted">
              <div *ngIf="article.attributes.image" class="uk-card-media-top">
                <img
                  src="http://localhost:1337{{ article.attributes.image.data.attributes.url }}"
                  alt=""
                  height="100"
                />
              </div>
              <div class="uk-card-body">
                <p
                  id="category"
                  *ngIf="article.attributes.categorie"
                  class="uk-text-uppercase"
                >
                  {{ article.attributes.categorie.data.attributes.name }}
                </p>
                <p id="title" class="uk-text-large">{{ article.attributes.title }}</p>
              </div>
            </div>
          </a>
        </div>
        <div>
          <div class="uk-child-width-1-2@m uk-grid-match" uk-grid>
            <a
              routerLink="/article/{{ article.id }}"
              routerLinkActive="active"
              *ngFor="let article of rightArticles"
              class="uk-link-reset"
            >
              <div class="uk-card uk-card-muted">
                <div *ngIf="article.attributes.image" class="uk-card-media-top">
                  <img
                    src="http://localhost:1337{{ article.attributes.image.data.attributes.url }}"
                    alt=""
                    height="100"
                  />
                </div>
                <div class="uk-card-body">
                  <p id="category" *ngIf="article.attributes.categorie" class="uk-text-uppercase">
                    {{ article.attributes.categorie.data.attributes.name }}
                  </p>
                  <p id="title" class="uk-text-large">{{ article.attributes.title }}</p>
                </div>
              </div>
            </a>
          </div>
        </div>
      </div>
    </div>
  </div>
Enter fullscreen mode Exit fullscreen mode

Afin de contruire et retrouver les chemins, vous pouvez vous référer au JSON produit par l'API tel que:

{"data":{"articles":{"data":[{"id":"1","attributes":{"title":"Premier post","categorie":{"data":{"id":"3","attributes":{"name":"blog","__typename":"Categorie"},"__typename":"CategorieEntity"},"__typename":"CategorieEntityResponse"},"content":"Ah enfin ce premier post !\nLe premier d'une longue série ;)\n\nEt puis je vais rappatrier ici tous les articles produits sur d'autres plateformes ;)\n\nA très bientôt !","image":{"data":{"attributes":{"url":"/uploads/wanderlabs_T_Ap9_Hue_Sl_KQ_unsplash_1_5e82873dce.jpg","__typename":"UploadFile"},"__typename":"UploadFileEntity"},"__typename":"UploadFileEntityResponse"},"__typename":"Article"},"__typename":"ArticleEntity"},{"id":"2","attributes":{"title":"Créer un blog avec Strapi","categorie":{"data":{"id":"2","attributes":{"name":"strapi","__typename":"Categorie"},"__typename":"CategorieEntity"},"__typename":"CategorieEntityResponse"},"content":"D'avoir, allez voir la vidéo de Yoandev ;)","image":{"data":{"attributes":{"url":"/uploads/photo_1499750310107_5fef28a66643_ixlib_rb_1_2_2f258ec988.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80","__typename":"UploadFile"},"__typename":"UploadFileEntity"},"__typename":"UploadFileEntityResponse"},"__typename":"Article"},"__typename":"ArticleEntity"}],"__typename":"ArticleEntityResponseCollection"}}}
Enter fullscreen mode Exit fullscreen mode

Référençons maintenant notre composant dans les modules.
Pour cela, modifiez app.modules.ts comme suit:

...
import { ArticlesComponent } from "./articles/articles/articles.component"
...

declarations: [
  AppComponent,
  NavComponent,
  ArticlesComponent
],
...
Enter fullscreen mode Exit fullscreen mode

Modifions maintenant le "Router" de notre application, dans le fichier app-routing.module.ts :

...
import { ArticlesComponent } from "./articles/articles/articles.component"

const routes: Routes = [
  { path: "", component: ArticlesComponent }
];
...
Enter fullscreen mode Exit fullscreen mode

On importe notre composant, et on déclare une route.
Ici, on dit que notre ArticlesComponent sera la page d'accueil puisque le "path" est vide.

Constatez le résultat dans votre navigateur:

Image description

Article component

On a listé nos articles, mais maintenant, que se passe-t-il si je clique que un article ? rien !

Réglons ce problème en créant un "ArticleComponent" pour afficher un article :

ng generate c articles/article --skip-import
Enter fullscreen mode Exit fullscreen mode

Là encore, j'ajoute "articles/" pour mettre notre composant dans le répertoire des composants "articles".

Encore une fois, commençons par modifier notre partie "comportement" du composant avec son fichier src/app/articles/article/article.component.ts :

import { Component, OnInit } from '@angular/core';
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";
import { ActivatedRoute } from "@angular/router";

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

  data: any = {};
  image: any;
  title: any;
  content: any;
  loading = true;
  errors: any;

  constructor(private apollo: Apollo, private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.apollo.watchQuery({
      query: gql`
        query Articles($id: ID!) {
          article(id: $id) {
            data {
              id,
              attributes {
                title, 
                content, 
                categorie {
                  data {
                    id, 
                    attributes {
                      name
                    }
                  }
                },
                image {
                  data {
                    attributes{
                      url
                    }
                  }
                }
              }
            }
          }
        }` 
      ,
      variables: {
        id: this.route.snapshot.paramMap.get("id")
      }
    })
    .valueChanges.subscribe(result => {
      this.data = result.data;
      this.image = this.data?.article.data.attributes.image?.data?.attributes?.url
      this.title = this.data?.article.data.attributes.title
      this.content = this.data?.article.data.attributes.content
      this.loading = result.loading;
      this.errors = result.errors;
    });
  }

}
Enter fullscreen mode Exit fullscreen mode

Puis, modifions notre template src/app/articles/article/article.component.html :

<div id="banner"
  class="uk-height-small uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding"
  [style.background-image]="
    'url(http://localhost:1337' + image + ')'
  "
  uk-img
>
  <h1>{{ title }}</h1>
</div>

<div class="uk-section">
  <div class="uk-container uk-container-small">
    <p>
        {{ content }}
    </p>
    <p></p>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Modifiez aussi app.module.ts pour ajouter le module ArticleComponent.
Puis ajoutez la route suivante dans le fichier app-routing.module.ts

{ path: "articles/:id", component: ArticleComponent }
Enter fullscreen mode Exit fullscreen mode

Vous pouvez maintenant cliquer dans la page d'accueil sur un article pour afficher notre nouvelle page.

Si ça marche bien, il y a un petit soucis dans l'affichage.
En effet, le code html saisi dans Strapi est rendu tel quel.
Dans le backoffice de Strapi, vous saisissez votre contenu riche (le champs "content") en markdown.

L'affichage est donc rendu avec les marqueurs Markdown.
Voici un exemple:

Image description

Ici, on voit bien que la phrase "essai de texte riche", que j'ai mis en gras dans Strapi, donc encadré avec 2 étoiles de chaque côté, n'est pas rendu en gras mais avec les marqueurs.

Pour "convertir" le code Markdown en code HTML, on va utiliser une librairie Javascript : ngx-markdown.

Installons et configurons cette librairie:

yarn add ngx-markdown
Enter fullscreen mode Exit fullscreen mode

Ajoutez maintenant le module dans app.modules.ts, dans les imports tel que:

...
import { MarkdownModule } from "ngx-markdown";
...

imports: [
  MarkdownModule.forRoot(),
  RouterModule.forRoot(appRoutes, { enableTracing: true }),
  BrowserModule,
  AppRoutingModule,
  GraphQLModule,
  HttpClientModule
],
...
Enter fullscreen mode Exit fullscreen mode

On va utiliser le plugin pour transformer notre variable "content" du markdown vers du HTML.
Dans article.component.ts ajoutez un import:

...
import { MarkdownService } from 'ngx-markdown';
...
Enter fullscreen mode Exit fullscreen mode

puis modifier dans ngOnInit l'affectation de la variable content:

this.content = this.markdownService.compile(this.data?.article.data.attributes.content)
Enter fullscreen mode Exit fullscreen mode

Maintenant c'est bon !
Image description

Categorie component

Il nous reste un dernier composant à construire, celui pour afficher les articles d'une catégorie, celui qui affichera les articles liés à une catégorie (les liens du haut de la barre de navigation).

Créons le :

ng generate c category --skip-import
Enter fullscreen mode Exit fullscreen mode

On fait comme d'habitude, on modifier le fichier de classe src/app/category/category.component.ts tel que:

import { Component, OnInit } from '@angular/core';
import { Apollo } from "apollo-angular";
import gql from "graphql-tag";
import { ActivatedRoute, ParamMap } from "@angular/router";

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

  data: any = {};
  category: any = {};
  loading = true;
  errors: any;
  leftArticlesCount: any;
  leftArticles?: any[];
  rightArticles?: any[];
  id: any;
  queryCategorie: any;

  constructor(private apollo: Apollo, private route: ActivatedRoute) { }

  ngOnInit(): void {
    this.route.paramMap.subscribe((params: ParamMap) => {
      this.id = params.get("id");
      this.queryCategorie = this.apollo.watchQuery({
        query: gql`
          query Categorie($id: ID!) {
            categorie(id: $id) {
              data {
                id,
                attributes {
                  name,
                  articles {
                    data {
                      id,
                      attributes {
                        title,
                        content,
                        createdAt,
                        image {
                          data {
                            attributes{
                              url
                            }
                          }
                        }

                      }
                    }
                  }
                }
              }
            }
          }
        ` 
        ,
        variables: {
          id: this.id
        }
      })
      .valueChanges.subscribe(result => {
        this.data = result.data;

        this.category = this.data.categorie.data.attributes.name
        this.leftArticlesCount = Math.ceil(this.data?.categorie.data.attributes.articles.data.length / 5);

        this.leftArticles = this.data?.categorie.data.attributes.articles.data.slice(0, this.leftArticlesCount);
        this.rightArticles = this.data?.categorie.data.attributes.articles.data.slice(
          this.leftArticlesCount,
          this.data?.categorie.data.attributes.articles.data.length
        );

        this.loading = result.loading;
        this.errors = result.errors;
      });
    });  
  }

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

}

Enter fullscreen mode Exit fullscreen mode

Rien d'exceptionnel ici :

  • on crée une requête graphql
  • on lance la requête à la création du composant, et on construit les 2 listes (leftArticles, rightArticles)

Puis, modifions la partie HTML:

<div class="uk-section">
    <div class="uk-container uk-container-large">
      <h1>{{ category }}</h1>

      <div class="uk-child-width-1-2" uk-grid>
        <div>
          <a
            routerLink="/articles/{{ article.id }}"
            routerLinkActive="active"
            *ngFor="let article of leftArticles"
            class="uk-link-reset"
          >
            <div class="uk-card uk-card-muted">
              <div *ngIf="article.attributes.image" class="uk-card-media-top">
                <img
                  src="http://localhost:1337{{ article.attributes.image.data.attributes.url }}"
                  alt=""
                  height="100"
                />
              </div>
              <div class="uk-card-body">
                <p
                  id="category"
                  class="uk-text-uppercase"
                >
                  {{ category }}
                </p>
                <p id="title" class="uk-text-large">{{ article.attributes.title }}</p>
              </div>
            </div>
          </a>
        </div>
        <div>
          <div class="uk-child-width-1-2@m uk-grid-match" uk-grid>
            <a
              routerLink="/articles/{{ article.id }}"
              routerLinkActive="active"
              *ngFor="let article of rightArticles"
              class="uk-link-reset"
            >
              <div class="uk-card uk-card-muted">
                <div *ngIf="article.attributes.image" class="uk-card-media-top">
                  <img
                    src="http://localhost:1337{{ article.attributes.image.data.attributes.url }}"
                    alt=""
                    height="100"
                  />
                </div>
                <div class="uk-card-body">
                  <p
                    id="category"
                    *ngIf="article.category"
                    class="uk-text-uppercase"
                  >
                    {{ article.category.name }}
                  </p>
                  <p id="title" class="uk-text-large">{{ article.title }}</p>
                </div>
              </div>
            </a>
          </div>
        </div>
      </div>
    </div>
  </div>
Enter fullscreen mode Exit fullscreen mode

Référençons maintenant notre composant dans les modules.
Pour cela, modifiez app.modules.ts comme suit:

...
import { ArticlesComponent } from "./articles/articles/articles.component"
...

declarations: [
  AppComponent,
  NavComponent,
  ArticlesComponent,
  ArticleComponent,
  CategoryComponent
],
...
Enter fullscreen mode Exit fullscreen mode

Enfin, ajoutez la route suivante dans le fichier app-routing.module.ts (avec son import)

...
import { ArticleComponent } from "./articles/article/article.component"
...
{ path: "category/:id", component: CategoryComponent }
...
Enter fullscreen mode Exit fullscreen mode

Conclusion

Loin d'être un spécialiste d'Angular, et de GraphQL, on arrive néanmoins à créer un client facilement pour notre API Strapi.

La prise en main de Strapi est en revanche vraiment facile !
Réellement impressionné par la dernière version, vite installé.

Merci à Yoandev pour l'inspiration de cet article.
Retrouvez sa chaine et sa vidéo ici : (https://youtu.be/KGHJYoxlGRE)

Discussion (0)