Créer un blog avec Strapi et Angular
Tags: strapi, angular, webdev, programming
Published: 2022-05-08 21:07:40

Suite à la vidéo de Yoandev ([https://youtu.be/KGHJYoxlGRE](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]( https://strapi.io/blog/build-a-blog-with-angular-js-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
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5i39ecbpx19obn2xuug8.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kyccf6arb5xbulz1jo8s.png)
 

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/76yrs3b7sv4e3c18a81h.png)
 

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](http://localhost:1337/graphql).

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
            }
          }
        }
      }
    }
  } 
}
```

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"
                }
              }
            }
          }
        }
      ]
    }
  }
} 
```

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x4a75edxbaqooqoop3o6.png)
 
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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rugnmefq7qcjb5w1c3wa.png)
 
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
            }
          }
        }
      }
    }
  } 
}

```

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"
                }
              }
            }
          }
        }
      ]
    }
  }
}
```  

### 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
```

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

L'application est maintenant créée, on va maintenant la lancer:
```
cd frontend
ng serve
```

Ouvrez votre navigateur à l'adresse [http://localhost:4200/](http://localhost:4200/)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/83i28bi2s45wglufkdzu.png)
 
Installons maintenant le plugin Apollo / client GraphQL à notre application:
```
ng add apollo-angular
```

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1pttw6xs709m6nq914p6.png)

On va aussi ajouter la bibliothèque de composants CSS "UIKit" :
```
yarn add uikit jquery
```

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"
]
```

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);
}
```

### Nav component

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

```
ng generate c nav --skip-import
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jea6cmshjnkmgccro07k.png)


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;
      });
  }
}
```

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>
```

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"}}}
```
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
],
...
```

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>
```

Ici, on ajoute notre composant "nav" (la balise <app-nav> </app-nav>" et on laisse le router (balise router-outlet) gérer le reste de la page.
 
Contemplez le résultat dans votre navigateur :
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/msuvs4sfe7ow3crarwsl.png)

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 
```
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;
      });
  }
}
```

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>
```

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"}}}
``` 
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
],
...
```

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 }
];
...
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dttbp55xqmq4vjml25kx.png)
 
### 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
```
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;
    });
  }

}
```

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>
```

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 }
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yeqk8nlruyq6xylf5201.png)

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
```
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
],
...
```

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';
...
```
puis modifier dans ngOnInit l'affectation de la variable content:
```
this.content = this.markdownService.compile(this.data?.article.data.attributes.content)
```

Maintenant c'est bon !
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dagx6fuizc7ugmy2vk7v.png)
 

### 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
```
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();
  }

}

```

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>
```

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
],
...
```
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 }
...
```

# 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](https://youtu.be/KGHJYoxlGRE))