This tutorial is outdated since it is using Strapi v3.
This month, we published two tutorials on how to create a blog with Nuxt.js and how to create a blog with Next.js with Strapi. But as the saying goes, good things come in threes!
So we decided to also create a tutorial for Angular developers on how to create a blog using Strapi and Apollo, and we hope you'll enjoy using it.
If you are familiar with our blog you must have seen that we've released a series of tutorials on how to make blogs using Strapi with a lot of frontend frameworks: Gatsby Old, Gatsby new, React, Next.js, Vue or Nuxt
Angular is a TypeScript-based open-source web application framework led by the Angular Team at Google and by a community of individuals and corporations. Angular is a complete rewrite from the same team that built AngularJS.
The goal here is to be able to create a blog website using Strapi as the backend, Angular for the frontend, and Apollo for requesting the Strapi API with GraphQL.
The source code is available on GitHub.
To follow this tutorial, you'll need to have Strapi and Angular installed on your computer, but don't worry, we are going to install these together!
This tutorial uses Strapi v3.0.0-beta.17.8.
You need to have node v.12 installed and that's all.
Create a blog-strapi folder and get inside!
mkdir blog-strapi && cd blog-strapi
So that's the easy part. Since the beta.9, we have an awesome package create strapi-app that allows you to create a Strapi project in seconds without installing Strapi globally so let's try it out.
Note: for this tutorial, we will use
yarn
as your package manager.
yarn create strapi-app backend --quickstart --no-run
.
This single command line will create all you need for your back-end. Make sure to add the --no-run
flag as it will prevent your app from automatically starting the server because SPOILER ALERT: we need to install some awesome Strapi plugins.
Now that you know that we need to install some plugins to enhance your app, let's install one of our most popular. The graphql
plugin:
yarn strapi install graphql
Once the installation is completed, you can finally start your Strapi server strapi dev
and create your first Administrator.
Don't forget that Strapi is running on http://localhost:1337.
Nice! Now that Strapi is ready, we are going to create your Angular application.
Well, the easiest part has been completed, let's get our hands dirty developing our blog!
Angular setup
Install the Angular CLI by running the following command:
npm install -g @angular/cli
Create an Angular frontend
project, and accept to add Angular routing
:
ng new frontend
Now your project is all set, you can dive in it and run the server:
1cd frontend
2ng serve
As you might want people to read your blog or to make it "cute & pretty" we will use a popular CSS framework for styling: UiKit and also Apollo to query Strapi with GraphQL:
Dependencies setup
Make sure you are in the frontend folder before running the following commands:
Apollo setup
Install apollo-angular
by running the following command:
ng add apollo-angular
Go in src/app/graphql.modules.ts
and modify the uri
const:
const uri = 'http://localhost:1337/graphql'
;
This way, Apollo will call this address, which is the GraphQL route of your Strapi API.
UIkit setup
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
Install UIkit by running the following command:
yarn add uikit
Open your angular.json
file and add the following code in the first scripts
array. It should look like this:
1"scripts": [
2 "node_modules/jquery/dist/jquery.min.js",
3 "node_modules/uikit/dist/js/uikit.min.js",
4 "node_modules/uikit/dist/js/uikit-icons.min.js"
5]
src/style.css
and paste the following code:1/* You can add global styles to this file, and also import other style files */
2@import "../node_modules/uikit/dist/css/uikit.min.css";
3@import "../node_modules/uikit/dist/css/uikit.css";
4@import "../node_modules/uikit/dist/css/uikit-core.css";
5@import url("https://fonts.googleapis.com/css?family=Staatliches");
6
7a {
8 text-decoration: none;
9}
10
11h1 {
12 font-family: Staatliches;
13 font-size: 120px;
14}
15
16#category {
17 font-family: Staatliches;
18 font-weight: 500;
19}
20
21#title {
22 letter-spacing: 0.4px;
23 font-size: 22px;
24 font-size: 1.375rem;
25 line-height: 1.13636;
26}
27
28#banner {
29 margin: 20px;
30 height: 800px;
31}
32
33#editor {
34 font-size: 16px;
35 font-size: 1rem;
36 line-height: 1.75;
37}
38
39.uk-navbar-container {
40 background: #fff !important;
41 font-family: Staatliches;
42}
43
44img:hover {
45 opacity: 1;
46 transition: opacity 0.25s cubic-bezier(0.39, 0.575, 0.565, 1);
47}
Note: You're importing UIkit style and a beautiful font that you'll use for this tutorial.
Finally! We are now going to create the data structure of our article by creating an Article
content type.
Content Type Builder
link in the sidebar.Create new content-type
and call it article
.Now you'll be asked to create all the fields for your content-type:
title
with type Text (required)content
with type Rich Text (required)image
with type Media (Single image) and (required)published_at
with type date (required)Press Save! Here you go, your first content type has been created. Now you may want to create your first article, but we have one thing to do before that: Grant access to the article content type.
public
role.find
and findone
routes and save.Awesome! You should be ready to create your first article right now and fetch it on the GraphQL Playground.
Here's an example:
Great! Now you may want to reach the moment when you can actually fetch your articles through the API!
Isn't that cool! You can also play with the GraphQL Playground.
You may want to assign a category to your article (news, trends, opinion). You are going to do this by creating another content type in Strapi.
category
content type with the following fieldname
with type TextPress save!
Category has many Articles
like below:public
role. And check the category find
and findone
routes and save.Now you'll be able to select a category for your article in the right sidebox.
Now that we are good with Strapi let's work on the frontend part!
Let's create our first component and see what happens.
Create a nav
component by running the following command:
ng generate c nav --skip-import
Four files have been created, but we'll only work with the .html
and .ts
files. In fact your .html
file is the view of your component and your .ts
file defines what your component will basically do.
I'm going to explain this component in detail, but it will be the same procedures for the other ones.
First of all, you want to fetch categories you've created with Strapi. To do so, you'll create a graphql request and use it in your component.
app/apollo/queries/category
directoryapp/apollo/queries/category/categories.js
file and paste the following code inside:1import gql from "graphql-tag";
2
3const CATEGORIES_QUERY = gql`
4 query Categories {
5 categories {
6 id
7 name
8 }
9 }
10`;
11
12export default CATEGORIES_QUERY;
Here, you are simply fetching the id and the name of every category.
Now lets import this query and the necessary packages:
nav/nav.component.ts
file:1import { Apollo } from "apollo-angular";
2import gql from "graphql-tag";
3import CATEGORIES_QUERY from "../apollo/queries/category/categories";
4import { Subscription } from "rxjs";
1export class NavComponent implements OnInit {
2 data: any = {};
3 loading = true;
4 errors: any;
5
6 private queryCategories: Subscription;
7
8 constructor(private apollo: Apollo) {}
9
10 ngOnInit() {
11 this.queryCategories = this.apollo
12 .watchQuery({
13 query: CATEGORIES_QUERY
14 })
15 .valueChanges.subscribe(result => {
16 this.data = result.data;
17 this.loading = result.loading;
18 this.errors = result.errors;
19 });
20 }
21 ngOnDestroy() {
22 this.queryCategories.unsubscribe();
23 }
24}
What you're doing here is fetching all your categories with Apollo on the component initialization using the query you made. Let's write our Navbar!
nav/nav.component.html
and paste the following code:1<nav class="uk-navbar-container" uk-navbar>
2 <div class="uk-navbar-left">
3 <ul class="uk-navbar-nav">
4 <li class="uk-active"><a href="#">Strapi blog</a></li>
5 </ul>
6 </div>
7
8 <div class="uk-navbar-right">
9 <ul *ngIf="data" class="uk-navbar-nav">
10 <li *ngFor="let category of data.categories" class="uk-active">
11 <a
12 routerLink="/category/{{ category.id }}"
13 routerLinkActive="active"
14 class="uk-link-reset"
15 >
16 {{ category.name }}
17 </a>
18 </li>
19 </ul>
20 </div>
21</nav>
Inside the view, we can access our categories with data.categories
. We can then iterate on it in order to display them all *ngFor="let category of data.categories"
.
Perfect! Now we need to two things!
app.modules.ts
file and import your newly created nav component by importing it and adding it to the declarations
array like this:1...
2import { NavComponent } from "./nav/nav.component";
3...
4
5declarations: [
6 AppComponent,
7 NavComponent
8],
9...
app.component.html
file and declare your nav component by pasting the following code:1<app-nav></app-nav>
2
3<router-outlet></router-outlet>
This way, your nav component will always be displayed on your application
Note The current code is not suited to display a lot of categories as you may encounter a UI issue. Since this blog post is supposed to be short, I will let you improve the code to maybe add a lazy load or something.
For now, the links are not working, you'll work on it later on the tutorial ;)
Note It will now be the same exact logic for every other component:
.ts
file.html
fileapp.module.ts
This component will display all your articles and will be displayed on the main page.
Create an articles
component by running the following command:
ng generate c articles --skip-import
Create a app/apollo/queries/article
directory.
app/apollo/queries/article/articles.js
file and paste the following code inside:1import gql from "graphql-tag";
2
3const ARTICLES_QUERY = gql`
4 query Articles {
5 articles {
6 id
7 title
8 category {
9 id
10 name
11 }
12 image {
13 url
14 }
15 }
16 }
17`;
18
19export default ARTICLES_QUERY;
articles/articles.component.ts
file and paste the following code:1import { Component, OnInit } from "@angular/core";
2import { Apollo } from "apollo-angular";
3import gql from "graphql-tag";
4import ARTICLES_QUERY from "../apollo/queries/article/articles";
5import { Subscription } from "rxjs";
6
7@Component({
8 selector: "app-articles",
9 templateUrl: "./articles.component.html",
10 styleUrls: ["./articles.component.css"]
11})
12export class ArticlesComponent implements OnInit {
13 data: any = {};
14 loading = true;
15 errors: any;
16 leftArticlesCount: any;
17 leftArticles: any[];
18 rightArticles: any[];
19
20 private queryArticles: Subscription;
21
22 constructor(private apollo: Apollo) {}
23
24 ngOnInit() {
25 this.queryArticles = this.apollo
26 .watchQuery({
27 query: ARTICLES_QUERY
28 })
29 .valueChanges.subscribe(result => {
30 this.data = result.data;
31 this.leftArticlesCount = Math.ceil(this.data.articles.length / 5);
32 this.leftArticles = this.data.articles.slice(0, this.leftArticlesCount);
33 this.rightArticles = this.data.articles.slice(
34 this.leftArticlesCount,
35 this.data.articles.length
36 );
37 this.loading = result.loading;
38 this.errors = result.errors;
39 });
40 }
41
42 ngOnDestroy() {
43 this.queryArticles.unsubscribe();
44 }
45}
What you are doing here is fetching your articles thanks to the query you wrote, then you separate them in two arrays. In fact, leftArticles
will contain articles that will be displayed on the left side and rightArticles
on the right side.
articles/articles.component.html
and paste the following code:1<div class="uk-section">
2 <div class="uk-container uk-container-large">
3 <h1>Strapi blog</h1>
4
5 <div class="uk-child-width-1-2" uk-grid>
6 <div>
7 <a
8 routerLink="/article/{{ article.id }}"
9 routerLinkActive="active"
10 *ngFor="let article of leftArticles"
11 class="uk-link-reset"
12 >
13 <div class="uk-card uk-card-muted">
14 <div *ngIf="article.image" class="uk-card-media-top">
15 <img
16 src="http://localhost:1337{{ article.image.url }}"
17 alt=""
18 height="100"
19 />
20 </div>
21 <div class="uk-card-body">
22 <p
23 id="category"
24 *ngIf="article.category"
25 class="uk-text-uppercase"
26 >
27 {{ article.category.name }}
28 </p>
29 <p id="title" class="uk-text-large">{{ article.title }}</p>
30 </div>
31 </div>
32 </a>
33 </div>
34 <div>
35 <div class="uk-child-width-1-2@m uk-grid-match" uk-grid>
36 <a
37 routerLink="/article/{{ article.id }}"
38 routerLinkActive="active"
39 *ngFor="let article of rightArticles"
40 class="uk-link-reset"
41 >
42 <div class="uk-card uk-card-muted">
43 <div *ngIf="article.image" class="uk-card-media-top">
44 <img
45 src="http://localhost:1337{{ article.image.url }}"
46 alt=""
47 height="100"
48 />
49 </div>
50 <div class="uk-card-body">
51 <p
52 id="category"
53 *ngIf="article.category"
54 class="uk-text-uppercase"
55 >
56 {{ article.category.name }}
57 </p>
58 <p id="title" class="uk-text-large">{{ article.title }}</p>
59 </div>
60 </div>
61 </a>
62 </div>
63 </div>
64 </div>
65 </div>
66</div>
app.modules.ts
file, import your component and add it to the declarations array like this:1...
2import { ArticlesComponent } from "./articles/articles.component";
3...
4
5declarations: [
6 AppComponent,
7 ArticlesComponent,
8 NavComponent,
9],
It's time to use the router from angular. You want to display this ArticlesComponent
on your main page
RouterModule
, Routes
from Angular and define the following appRoutes
:1import { RouterModule, Routes } from "@angular/router";
2...
3
4const appRoutes: Routes = [
5 { path: "", component: ArticlesComponent }
6];
imports
array:1...
2imports: [
3 RouterModule.forRoot(appRoutes, { enableTracing: true }),
4 BrowserModule,
5 AppRoutingModule,
6 GraphQLModule,
7 HttpClientModule
8],
9...
Great! You can now list every article on your main page.
Nothing happens if you click on any article, let's change that!
Create an article component by running the following command:
ng generate c article --skip-import
app/apollo/queries/article/article.js
file and paste the following code inside:1import gql from "graphql-tag";
2
3const ARTICLE_QUERY = gql`
4 query Articles($id: ID!) {
5 article(id: $id) {
6 id
7 title
8 content
9 image {
10 url
11 }
12 category {
13 id
14 name
15 }
16 published_at
17 }
18 }
19`;
20
21export default ARTICLE_QUERY;
article/article.component.ts
and paste the following code:1import { Component, OnInit } from "@angular/core";
2import { Apollo } from "apollo-angular";
3import gql from "graphql-tag";
4import ARTICLE_QUERY from "../apollo/queries/article/article";
5import { ActivatedRoute } from "@angular/router";
6import { Subscription } from "rxjs";
7
8@Component({
9 selector: "app-article",
10 templateUrl: "./article.component.html",
11 styleUrls: ["./article.component.css"]
12})
13export class ArticleComponent implements OnInit {
14 data: any = {};
15 loading = true;
16 errors: any;
17
18 private queryArticle: Subscription;
19
20 constructor(private apollo: Apollo, private route: ActivatedRoute) {}
21
22 ngOnInit() {
23 this.queryArticle = this.apollo
24 .watchQuery({
25 query: ARTICLE_QUERY,
26 variables: {
27 id: this.route.snapshot.paramMap.get("id")
28 }
29 })
30 .valueChanges.subscribe(result => {
31 this.data = result.data;
32 this.loading = result.loading;
33 this.errors = result.errors;
34 });
35 }
36 ngOnDestroy() {
37 this.queryArticle.unsubscribe();
38 }
39}
The content of our articles are not in Markdown, you'll need to install a dependency to display the content of your articles in Markdown.
Install ngx-markdown
by running the following command:
yarn add ngx-markdown
Open your app.modules.ts
, import add the MarkdownModule to your imports
array:
1...
2import { MarkdownModule } from "ngx-markdown";
3...
4
5imports: [
6 MarkdownModule.forRoot(),
7 RouterModule.forRoot(appRoutes, { enableTracing: true }),
8 BrowserModule,
9 AppRoutingModule,
10 GraphQLModule,
11 HttpClientModule
12],
13...
Awesome! Let's move on to the next step.
article/article.component.html
and paste the following code:1<div
2 id="banner"
3 class="uk-height-small uk-flex uk-flex-center uk-flex-middle uk-background-cover uk-light uk-padding"
4 [style.background-image]="
5 'url(http://localhost:1337' + data.article.image.url + ')'
6 "
7 uk-img
8>
9 <h1>{{ data.article.title }}</h1>
10</div>
11
12<div class="uk-section">
13 <div class="uk-container uk-container-small">
14 <p>
15 <markdown ngPreserveWhitespaces>
16 {{ data.article.content }}
17 </markdown>
18 </p>
19 <p></p>
20 </div>
21</div>
app.modules.ts
file, import your component, add it to the declarations array and add another route for this component:1...
2import { ArticleComponent } from "./article/article.component";
3...
4
5const appRoutes: Routes = [
6 { path: "", component: ArticlesComponent },
7 { path: "article/:id", component: ArticleComponent },
8];
9...
10
11declarations: [
12 AppComponent,
13 NavComponent,
14 ArticlesComponent,
15 ArticleComponent
16],
17...
Awesome you can now visit any article!
With this component, you'll be able to fetch articles depending on the selected category you are visiting.
Create a category
component by running the following command:
ng generate c category --skip-import
Create a app/apollo/queries/category/articles.js
file containing the following:
1import gql from "graphql-tag";
2
3const CATEGORY_ARTICLES_QUERY = gql`
4 query Category($id: ID!) {
5 category(id: $id) {
6 id
7 name
8 articles {
9 id
10 title
11 content
12 image {
13 url
14 }
15 category {
16 id
17 name
18 }
19 }
20 }
21 }
22`;
23
24export default CATEGORY_ARTICLES_QUERY;
category/category.component.ts
file and paste the following code:1import { Component, OnInit } from "@angular/core";
2import { Apollo } from "apollo-angular";
3import gql from "graphql-tag";
4import CATEGORY_ARTICLES_QUERY from "../apollo/queries/category/articles";
5import { ActivatedRoute, ParamMap } from "@angular/router";
6import { Subscription } from "rxjs";
7
8@Component({
9 selector: "app-category",
10 templateUrl: "./category.component.html",
11 styleUrls: ["./category.component.css"]
12})
13export class CategoryComponent implements OnInit {
14 data: any = {};
15 category: any = {};
16 loading = true;
17 errors: any;
18 leftArticlesCount: any;
19 leftArticles: any[];
20 rightArticles: any[];
21 id: any;
22
23 private queryCategoriesArticles: Subscription;
24
25 constructor(private apollo: Apollo, private route: ActivatedRoute) {}
26
27 ngOnInit() {
28 this.route.paramMap.subscribe((params: ParamMap) => {
29 this.id = params.get("id");
30 this.queryCategoriesArticles = this.apollo
31 .watchQuery({
32 query: CATEGORY_ARTICLES_QUERY,
33 variables: {
34 id: this.id
35 }
36 })
37 .valueChanges.subscribe(result => {
38 this.data = result.data
39 this.category = this.data.category.name
40 console.log(this.data)
41 this.leftArticlesCount = Math.ceil(this.data.category.articles.length / 5);
42 this.leftArticles = this.data.category.articles.slice(0, this.leftArticlesCount);
43 this.rightArticles = this.data.category.articles.slice(
44 this.leftArticlesCount,
45 this.data.category.articles.length
46 );
47 this.loading = result.loading;
48 this.errors = result.errors;
49 });
50 });
51 }
52 ngOnDestroy() {
53 this.queryCategoriesArticles.unsubscribe();
54 }
55}
Here you are doing the same thing that you did in your ArticlesComponent
, but you are fetching articles depending on the selected category.
category/category.component.html
file and paste the following code:1<div class="uk-section">
2 <div class="uk-container uk-container-large">
3 <h1>{{ category }}</h1>
4
5 <div class="uk-child-width-1-2" uk-grid>
6 <div>
7 <a
8 routerLink="/article/{{ article.id }}"
9 routerLinkActive="active"
10 *ngFor="let article of leftArticles"
11 class="uk-link-reset"
12 >
13 <div class="uk-card uk-card-muted">
14 <div *ngIf="article.image" class="uk-card-media-top">
15 <img
16 src="http://localhost:1337{{ article.image.url }}"
17 alt=""
18 height="100"
19 />
20 </div>
21 <div class="uk-card-body">
22 <p
23 id="category"
24 *ngIf="article.category"
25 class="uk-text-uppercase"
26 >
27 {{ article.category.name }}
28 </p>
29 <p id="title" class="uk-text-large">{{ article.title }}</p>
30 </div>
31 </div>
32 </a>
33 </div>
34 <div>
35 <div class="uk-child-width-1-2@m uk-grid-match" uk-grid>
36 <a
37 routerLink="/article/{{ article.id }}"
38 routerLinkActive="active"
39 *ngFor="let article of rightArticles"
40 class="uk-link-reset"
41 >
42 <div class="uk-card uk-card-muted">
43 <div *ngIf="article.image" class="uk-card-media-top">
44 <img
45 src="http://localhost:1337{{ article.image.url }}"
46 alt=""
47 height="100"
48 />
49 </div>
50 <div class="uk-card-body">
51 <p
52 id="category"
53 *ngIf="article.category"
54 class="uk-text-uppercase"
55 >
56 {{ article.category.name }}
57 </p>
58 <p id="title" class="uk-text-large">{{ article.title }}</p>
59 </div>
60 </div>
61 </a>
62 </div>
63 </div>
64 </div>
65 </div>
66</div>
app.modules.ts
file, import your component, add it to the declarations array and add another route for this component:1...
2import { CategoryComponent } from "./category/category.component";
3...
4
5const appRoutes: Routes = [
6 { path: "", component: ArticlesComponent },
7 { path: "article/:id", component: ArticleComponent },
8 { path: "category/:id", component: CategoryComponent }
9];
10...
11
12declarations: [
13 AppComponent,
14 ArticlesComponent,
15 ArticleComponent,
16 NavComponent,
17 CategoryComponent
18],
19
20...
Awesome! You can now navigate through categories :)
Huge congrats, you successfully achieved this tutorial. I hope you enjoyed it!
Still hungry?
Feel free to add additional features, adapt this project to your own needs, and give your feedback in the comments section.
If you want to deploy your application, check our deployment documentation.
Contribute and collaborate on educational content for the Strapi Community.
Can't wait to see your contribution!
Please note: Since we initially published this blog post, we released new versions of Strapi and tutorials may be outdated. Sorry for the inconvenience if it's the case. Please help us by reporting it here.
Get started with Strapi by creating a project using a starter or trying our live demo. Also, consult our forum if you have any questions. We will be there to help you.
See you soon!Maxime started to code in 2015 and quickly joined the Growth team of Strapi. He particularly likes to create useful content for the awesome Strapi community. Send him a meme on Twitter to make his day: @MaxCastres