Следуя видео Йоандева (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).