Создание блога с помощью Strapi и Angular

Следуя видео Йоандева (https://youtu.be/KGHJYoxlGRE), вот маленькая (большая!) задача дня: создать блог с помощью Strapi и Angular.

Некоторые объяснения:

Итак, прежде чем мы начнем кодить, давайте разберемся, почему Strapi и почему Angular.

Strapi – это то, что мы называем “безголовой CMS”.
Короче говоря, вы можете создавать свои собственные “типы контента” (как, например, в Drupal) и предоставлять их через API.
Это действительно удобно и имеет много преимуществ:

  • вы избегаете настройки “лицевой” части, что часто является адом при использовании традиционных CMS
  • мы разделяем бэкенд (Strapi) и фронтенд (Angular в данном случае).
  • У нас будет API, так что потенциально вы сможете использовать его с мобильным приложением или другим фронтендом.
  • Если вас не устраивает Angular, ничто не мешает вам рефакторить его с помощью VueJS, React или … Symfony как Йоан 😉

Что такое “тип содержимого”?

Ну, это объект, “Entity” в Symfony, или Модель в более общем варианте.

В нашем примере тип содержимого будет “Статья”.
Она будет содержать:

  • название
  • содержание
  • изображение
  • дата публикации

Все выяснили?
Тогда приступайте к коду!

Примечание: эта статья основана на статье из блога Strapi: Постройте блог с помощью Angular, Strapi и Apollo

Пререквизиты

Не так много, кроме node и npm (и yarn здесь).

Лично я использую NVM (https://github.com/nvm-sh/nvm), чтобы избежать конфликтов версий Node между проектами.
Я буду давать вам команды для выполнения на протяжении всего урока.

Бэкэнд

Установка страпи

Мы создадим каталог для всего проекта (бэкенд + фронтенд) и зайдем внутрь.

mkdir blog-strapi && cd blog-strapi

Затем мы создадим наше приложение Strapi:

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

Флаг “–no-run” предотвратит его запуск сразу после установки.

Первая проблема с версией узла моего дистрибутива Linux.

Мое решение: зайдите в каталог приложения Strapi, удалите каталог node_modules, затем создайте файл .nvmrc для “постановки” версии Node, используемой для проекта (здесь 16+), и, наконец, переустановите зависимости (достаточно простого yarn).

В двух словах:

cd backend
rm -rf node_modules/
echo "16" > .nvmrc
yarn
Войдите в полноэкранный режим Выход из полноэкранного режима

Все в том же каталоге “backend” (наше приложение Strapi), давайте установим плагин graphql с помощью команды

yarn strapi install graphql

Когда установка плагина завершена, давайте запустим наше приложение:

Обычно в окне браузера открывается интерфейс администрирования Strapi и предлагается создать учетную запись администратора.

Создайте наш тип контента “Статья”.

В левом меню выберите “Content-type Builder”, а затем ссылку “Create new collection type”.

Дайте ему имя “Статья”, например:

Затем добавьте (кнопка “Добавить другое поле”) следующие поля:

  • title с типом “Текст” (обязательно)
  • содержимое с типом Rich Text (обязательно)
  • изображение с типом Media (Single image) и (обязательно)

Примечание 1: чтобы добавить “обязательное” поле, перейдите на другую вкладку “расширенные настройки

Примечание 2: поле “published_at” является автоматическим, поэтому вам не нужно его создавать.

Сохраняем с помощью “Save”, а затем переходим к созданию наших первых статей в бэкенде, через “Content Manager”.

После написания нескольких статей мы говорим себе, что было бы неплохо проверить, отправляет ли API их нам обратно!

Нет проблем, все предусмотрено!

О да, за исключением того, что сначала мы должны установить часть “разрешения”.
По умолчанию доступ к API отсутствует. Для каждого типа контента вы должны определить, кто имеет право делать то или иное действие.

Перейдите в левое меню “Настройки”, затем в “USERS & PERMISSIONS PLUGIN” и “Roles”.

Мы изменим роль на “public” (с помощью карандаша) и добавим права следующим образом:

Теперь, когда разрешения установлены, мы можем протестировать наш API.

Перейдите в тестовый интерфейс GraphQL с помощью браузера здесь.

Я дам вам возможность ознакомиться с тем, что такое graphQL, но вкратце, по сравнению с REST, он позволит вам “выбирать и выбирать” поля, которые будут возвращены API. Это похоже на REST, но лучше, даже если язык запросов не всегда тривиален.

Примечание: интерфейс теста GraphQL сделан очень хорошо: в нем есть очень практичная функция автозавершения!

На интерфейсе создайте запрос, например:

query Articles {
    articles {
    data {
      id, 
      attributes {
        title, 
        content, 
        image {
          data {
            attributes{
              url
            }
          }
        }
      }
    }
  } 
}
Войдите в полноэкранный режим Выход из полноэкранного режима

После выполнения вы получите ответ следующего содержания

{
  "data": {
    "articles": {
      "data": [
        {
          "id": "1",
          "attributes": {
            "title": "Premier post",
            "content": "Ah enfin ce premier post !nLe premier d'une longue série ;)nnEt puis je vais rappatrier ici tous les articles produits sur d'autres plateformes ;)nnA 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"
                }
              }
            }
          }
        }
      ]
    }
  }
} 
Войдите в полноэкранный режим Выход из полноэкранного режима

Категории

Каждая статья будет иметь “категорию”.

Создайте новую коллекцию с именем “Categorie” с единственным полем “name” типа “Text” и сохраните ее.

Теперь мы свяжем наши 2 типа контента “Статья” и “Категория”: категория связана с 1 или более статьями.

Итак, по-прежнему в “Content-Type Builder” выберите “Article”, а затем добавьте поле типа “Relation” следующим образом:

Не забудьте нажать на “finish” в модале, а затем на “Save”, чтобы сохранить изменения в нашем типе содержимого Article.

Наконец, нам нужно установить часть “права” на наш новый тип “Категория”.
Что касается статьи, перейдите в левое меню “Настройки”, затем в “USERS & PERMISSIONS PLUGIN” > Roles > public, и измените запись Category следующим образом:

Теперь, когда у вас есть категории, перейдите в Content-Manager, создайте категории и свяжите каждый элемент с категорией.

Вы можете снова протестировать свой API с помощью клиента GraphQL с помощью такого запроса, как:

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

Войдите в полноэкранный режим Выход из полноэкранного режима

Вы получите результат, подобный этому:

{
  "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 ;)nnEt puis je vais rappatrier ici tous les articles produits sur d'autres plateformes ;)nnA 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"
                }
              }
            }
          }
        }
      ]
    }
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Краткое описание бэкенда

Теперь у нас есть хороший бэкенд с Strapi, который предоставляет нам хороший GraphQL API для доставки статей с категориями.

 Frontend

В нашей консоли мы оставим Strapi запущенным, и вернемся в базовый каталог проекта

cd blog-strapi

 Создание приложения Angular

Сначала мы установим Angular CLI:

sudo npm install -g @angular/cli
Войдите в полноэкранный режим Выход из полноэкранного режима

Затем давайте создадим наше фронтенд-приложение (примите дополнение “Angular Routing”:

ng new frontend
Войдите в полноэкранный режим Выход из полноэкранного режима

Приложение создано, теперь запустим его:

cd frontend
ng serve
Войдите в полноэкранный режим Выход из полноэкранного режима

Откройте браузер на сайте http://localhost:4200/.

Давайте теперь установим клиентский плагин Apollo / GraphQL в наше приложение:

ng add apollo-angular
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы также добавим библиотеку компонентов CSS “UIKit”:

yarn add uikit jquery
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь давайте добавим зависимости javascript из набора в наш код.
Для этого откройте файл angular.json и найдите ключ
projects > frontend > architect > build > options > scripts, который должен быть пустым массивом и установить его как:

"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"
]
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы также добавим часть CSS в наш проект:
Отредактируйте файл src/style.css, который должен быть пустым, следующим образом:

/* 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);
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Давайте создадим наш первый компонент – панель навигации:

ng generate c nav --skip-import
Войдите в полноэкранный режим Выход из полноэкранного режима

Создается 4 файла, но мы будем работать только с файлами .html и .ts.
Файлы .html – это ваши шаблоны (видимая часть), а .ts – это “двигатель” вашего компонента, что он делает, как себя ведет и т.д.

Теперь мы изменим наш компонент “nav”, чтобы получить категории и отобразить их в правом верхнем углу нашей навигационной панели.

Откройте файл nav/nav.component.ts и измените код следующим образом:

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;
      });
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Здесь мы получим наши категории с помощью Apollo при инициализации компонента с помощью запроса graphql.

В переменной data будут храниться наши категории.

Теперь давайте изменим шаблон, чтобы отобразить их!

Откройте nav/nav.component.html и измените код следующим образом:

<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>
Войдите в полноэкранный режим Выход из полноэкранного режима

В этом представлении мы вполне можем получить доступ к нашей переменной “data” (которая содержит категории) и ее переменной “data”, которая содержит массив категорий.
Мы зацикливаем ngFor на нем, чтобы отобразить наши категории одну за другой и дать ссылки на них.

Примечание: “data.data” не очень читабелен, но это связано с формой JSON, который возвращается из Strapi API.
Вот пример того, что возвращает API:

{"data":{"categories":{"data":[{"id":"2","attributes":{"name":"strapi","__typename":"Categorie"},"__typename":"CategorieEntity"},{"id":"3","attributes":{"name":"blog","__typename":"Categorie"},"__typename":"CategorieEntity"}],"__typename":"CategorieEntityResponseCollection"}}}
Войдите в полноэкранный режим Выход из полноэкранного режима

Переменная “data” в нашем .ts содержит “result” (весь результат), “.data” для доступа к первому элементу “data” результата (который затем содержит “categories”:{“data”:[…]}….
Таким образом, в представлении мы вынуждены брать “data.data”, чтобы иметь таблицу.

Еще две небольшие модификации, и все готово.

Первый – объявить наш модуль “NavComponent” в списке модулей.

Для этого измените app.modules.ts следующим образом:

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

declarations: [
  AppComponent,
  NavComponent
],
...
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы просто импортируем модуль и добавляем его в массив “declarations”.

Наконец, мы изменим наш базовый шаблон приложения, чтобы добавить навигационную панель.

Для этого откройте файл app.component.html, удалите все его содержимое и просто добавьте следующий код:

<app-nav></app-nav>
<router-outlet></router-outlet>
Войдите в полноэкранный режим Выход из полноэкранного режима

Здесь мы добавляем наш компонент “nav” (тег “) и позволяем маршрутизатору (тег router-outlet) обрабатывать остальную часть страницы.

Проверьте результат в своем браузере:

Мои 2 категории – это действительно Strapi и Blog. Победа!

 Компонент статей

Давайте применим те же рассуждения к нашему второму компоненту, “ArticlesComponent”, который будет перечислять все статьи.

  • Создание компонента
  • Модификация логики компонента в .ts
  • Модификация представления компонента
  • Ссылка на компонент в списке модулей
  • Изменение маршрутизации приложения

Создание компонента

В корне проекта, в командной строке, создадим наш компонент:

ng generate c articles/articles --skip-import 
Войдите в полноэкранный режим Выход из полноэкранного режима

Обратите внимание, что перед именем компонента я добавил “articles/”. Это создаст всеобъемлющий каталог для всех наших будущих компонентов, которые будут работать со статьями (например, следующий компонент, который будет отображать статью). Я думаю, что это хорошая практика, особенно в больших приложениях, иначе вы быстро заблудитесь…..

Что касается навигационной панели, измените .ts компонента src/app/articles/articles.components.ts следующим образом:

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;
      });
  }
}
Войдите в полноэкранный режим Выход из полноэкранного режима

Здесь не так много нового, поскольку мы используем тот же принцип, что и в навигационной панели:

  • запрос в graphql
  • обработка результатов с составлением 2 таблиц: 1 для левой части (большие изображения) и 1 для правой части

Теперь давайте изменим связанный шаблон src/app/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>
Войдите в полноэкранный режим Выход из полноэкранного режима

Чтобы построить и найти пути, вы можете обратиться к JSON, создаваемому API, например:

{"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 ;)nnEt puis je vais rappatrier ici tous les articles produits sur d'autres plateformes ;)nnA 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"}}}
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь давайте обратимся к нашему компоненту в модулях.
Для этого измените app.modules.ts следующим образом:

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

declarations: [
  AppComponent,
  NavComponent,
  ArticlesComponent
],
...
Войдите в полноэкранный режим Выход из полноэкранного режима

Давайте теперь изменим “Router” нашего приложения, в файле app-routing.module.ts :

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

const routes: Routes = [
  { path: "", component: ArticlesComponent }
];
...
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы импортируем наш компонент и объявляем маршрут.
Здесь мы говорим, что наш ArticlesComponent будет домашней страницей, поскольку “path” пуст.

Посмотрите результат в своем браузере:

Компонент статьи

Мы перечислили наши статьи, но что произойдет, если я нажму на статью?

Давайте решим эту проблему, создав “ArticleComponent” для отображения статьи:

ng generate c articles/article --skip-import
Войдите в полноэкранный режим Выход из полноэкранного режима

Опять же, я добавляю “articles/”, чтобы поместить наш компонент в каталог компонентов “articles”.

Опять же, давайте начнем с изменения нашей “поведенческой” части компонента с его файла 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;
    });
  }

}
Войдите в полноэкранный режим Выход из полноэкранного режима

Затем, давайте изменим наш шаблон 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>
Войдите в полноэкранный режим Выход из полноэкранного режима

Также измените app.module.ts, чтобы добавить модуль ArticleComponent.
Затем добавьте следующий маршрут в файл app-routing.module.ts

{ path: "articles/:id", component: ArticleComponent }
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь вы можете нажать на статью на главной странице, чтобы отобразить нашу новую страницу.

Если он работает нормально, значит, в дисплее есть небольшая проблема.
Действительно, html-код, введенный в Strapi, отображается как есть.
В бэк-офисе Strapi вы вводите богатый контент (поле “контент”) в формате markdown.

Затем отображение осуществляется с помощью разметки Markdown.
Вот пример:

Здесь видно, что предложение “rich text essay”, которое я выделил жирным шрифтом в Strapi, поэтому обрамлено 2 звездами с каждой стороны, отображается не жирным шрифтом, а в виде уценки.

Для “преобразования” кода Markdown в HTML-код мы будем использовать библиотеку Javascript: ngx-markdown.

Давайте установим и настроим эту библиотеку:

yarn add ngx-markdown
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь добавьте модуль в app.modules.ts, в imports как:

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

imports: [
  MarkdownModule.forRoot(),
  RouterModule.forRoot(appRoutes, { enableTracing: true }),
  BrowserModule,
  AppRoutingModule,
  GraphQLModule,
  HttpClientModule
],
...
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы будем использовать плагин для преобразования нашей переменной “content” из markdown в HTML.
В article.component.ts добавьте импорт:

...
import { MarkdownService } from 'ngx-markdown';
...
Войдите в полноэкранный режим Выход из полноэкранного режима

затем измените в ngOnInit назначение содержимого переменной:

this.content = this.markdownService.compile(this.data?.article.data.attributes.content)
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь все готово!

Категориальный компонент

Нам осталось создать последний компонент, тот, который будет отображать статьи категории, тот, который будет отображать статьи, связанные с категорией (ссылки в верхней части навигационной панели).

Давайте создадим его:

ng generate c category --skip-import
Войдите в полноэкранный режим Выход из полноэкранного режима

Мы поступаем как обычно, изменяем файл класса src/app/category/category.component.ts таким образом:

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

}

Войдите в полноэкранный режим Выход из полноэкранного режима

Здесь нет ничего исключительного:

  • создаем запрос в graphql
  • мы запускаем запрос при создании компонента и создаем 2 списка (leftArticles, rightArticles)

Затем изменим 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>
Войдите в полноэкранный режим Выход из полноэкранного режима

Теперь давайте обратимся к нашему компоненту в модулях.
Для этого измените app.modules.ts следующим образом:

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

declarations: [
  AppComponent,
  NavComponent,
  ArticlesComponent,
  ArticleComponent,
  CategoryComponent
],
...
Войдите в полноэкранный режим Выход из полноэкранного режима

Наконец, добавьте следующий маршрут в файл app-routing.module.ts (с его импортом)

...
import { ArticleComponent } from "./articles/article/article.component"
...
{ path: "category/:id", component: CategoryComponent }
...
Войдите в полноэкранный режим Выход из полноэкранного режима

Заключение

Не будучи специалистами в Angular и GraphQL, мы, тем не менее, смогли легко создать клиента для нашего Strapi API.

С другой стороны, обращаться с Strapi очень просто!
Очень впечатлен последней версией, быстро установил.

Спасибо Йоандеву за вдохновение для этой статьи.
Посмотрите его канал и видео здесь: (https://youtu.be/KGHJYoxlGRE).

Оцените статью
Procodings.ru
Добавить комментарий