Welcome to Full Stack Reddit Clone with Spring boot and Angular – Part 14. In Part 13, we completed the Login and Signup functionality and saw how to handle Refresh Tokens in our Angular Application.
In this article, we will improve the aesthetics of our home page, and start creating Subreddits and Posts. Let’s start coding.
If you are a visual learner like me, you can check out the video version of this tutorial below:
Download Source Code
Source code for Angular application – https://github.com/SaiUpadhyayula/angular-reddit-clone
Source code for Spring boot backend –https://github.com/SaiUpadhyayula/spring-reddit-clone
Refactoring the Home Component
The home page component is a work-in-progress, you can see right now it looks very ugly, we have to style it to make it look decent.
First let’s have a look at how the final version of our Home Page looks like.
As highlighted within the red box, we are going to divide the elements inside our home component into multiple components, so that we can reuse them if necessary.
- Post Title Component
- Vote button Component
- Sidebar Component
- Subreddit Sidebar Component
Let’s start by creating these components
Post Tile Component
post-title.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { PostModel } from '../post-model';
import { faComments } from '@fortawesome/free-solid-svg-icons';
@Component({
selector: 'app-post-tile',
templateUrl: './post-tile.component.html',
styleUrls: ['./post-tile.component.css']
})
export class PostTileComponent implements OnInit {
@Input() data: Array<PostModel>;
faComments = faComments;
constructor() { }
ngOnInit(): void {
}
}
<strong>post-tile.component.html</strong>
<div class="row post" *ngFor="let post of data">
<div class="col-md-1">
<app-vote-button [post]="post"></app-vote-button>
</div>
<div class="col-md-11">
<span class="subreddit-info">
<span class="subreddit-text"><a class="posturl" routerLink="">{{post.subredditName}}</a></span>
<span> . Posted by <a class="username" routerLink="/user/{{post.userName}}">{{post.userName}}</a></span>
<span> . {{post.duration}}</span>
</span>
<hr />
<div class="post-title">
<a class="postname" href="{{post.url}}">{{post.postName}}</a>
</div>
<div>
<p class="post-text">{{post.description}}</p>
</div>
<hr />
<span>
<a class="btnCommments" role="button">
<fa-icon [icon]="faComments"></fa-icon>
Comments({{post.commentNum}})
</a>
<button class="login" (click)="goToPost(post.id)">
Read Post
</button>
</span>
</div>
</div>
post-tile.component.css
.post{
--post-line-color: #ccc;
border: 1px solid #ccc;
margin-top: 10px;
margin-bottom: 10px;
overflow: hidden;
background-color: rgba(255,255,255,0.8);
color: #878A8C;
position: relative;
border-radius: 4px;
padding:5px;
}
.btnCommments{
border-radius: 2px solid;
color: #878A8C;
opacity: 1;
}
.btnCommments:hover{
opacity: 0.65;
cursor: pointer;
}
.post-title{
font-size: 28px;
font-weight: bold;
opacity: 1;
}
.posturl, .postname{
color: black;
}
.subreddit-text{
font-weight: bold;
}
.post-text{
margin-top: 10px;
}
Vote Button Component
vote-button.component.ts
import { Component, OnInit, Input } from '@angular/core';
import { PostModel } from '../post-model';
import { faArrowUp, faArrowDown } from '@fortawesome/free-solid-svg-icons';
@Component({
selector: 'app-vote-button',
templateUrl: './vote-button.component.html',
styleUrls: ['./vote-button.component.css']
})
export class VoteButtonComponent implements OnInit {
@Input() post: PostModel;
faArrowUp = faArrowUp;
faArrowDown = faArrowDown;
constructor() {
}
ngOnInit() {
}
}
vote-button.component.html
<div class="d-flex flex-column votebox">
<div class="p-2">
<fa-icon (click)="upvotePost()" class="upvote" [icon]="faArrowUp" [style.color]="upvoteColor"></fa-icon>
</div>
<div class="p-2 votecount">{{post.voteCount}}</div>
<div class="p-2">
<fa-icon (click)="downvotePost()" class="downvote" [icon]="faArrowDown" [style.color]="downvoteColor"></fa-icon>
</div>
</div>
vote-button.component.css
.votebox {
display: flex;
text-align: center;
flex-direction: column;
font-size: 1em;
}
.votesection{
margin:0px;
background-color: #ffffff;
}
.upvote,.downvote {
cursor: pointer;
border-radius: 2px solid;
}
.upvote:hover {
color: green;
}
.downvote:hover {
color: red;
}
.votecount{
font-weight: bold;
}
Inside the VotebuttonComponent
and vote-button.component.html
we can see new tags called fa-icon
and CSS classes faArrowUp
and faArrowDown
. These are coming from Fort Awesome. Let’s add the below dependencies to our package.json file and run npm install to download these dependencies to our project.
package.json
{
"name": "angular-reddit-clone",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~9.1.0",
"@angular/common": "~9.1.0",
"@angular/compiler": "~9.1.0",
"@angular/core": "~9.1.0",
"@angular/forms": "~9.1.0",
"@angular/platform-browser": "~9.1.0",
"@angular/platform-browser-dynamic": "~9.1.0",
"@angular/router": "~9.1.0",
"bootstrap": "^4.4.1",
"rxjs": "~6.5.5",
"tslib": "^1.11.1",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.901.0",
"@angular/cli": "~9.1.0",
"@angular/compiler-cli": "~9.1.0",
"@angular/language-service": "~9.1.0",
"@types/node": "~13.11.0",
"@types/jasmine": "~3.5.10",
"@types/jasminewd2": "~2.0.8",
"codelyzer": "^5.2.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.1",
"karma": "~4.4.1",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.1",
"karma-jasmine": "~3.1.1",
"karma-jasmine-html-reporter": "^1.5.3",
"protractor": "~5.4.3",
"ts-node": "~8.8.1",
"tslint": "~6.1.1",
"typescript": "~3.8.3",
"ngx-webstorage": "5.0.0",
"ngx-toastr": "12.0.1",
"@fortawesome/angular-fontawesome": "^0.6.1",
"@fortawesome/fontawesome-svg-core": "^1.2.28",
"@fortawesome/free-solid-svg-icons": "^5.13.0"
}
}
Side Bar Component
side-bar.component.ts
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-side-bar',
templateUrl: './side-bar.component.html',
styleUrls: ['./side-bar.component.css']
})
export class SideBarComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
side-bar.component.html
<div class="sidebar">
<img src="https://www.redditstatic.com/desktop2x/img/id-cards/home-banner@2x.png">
<div style="text-align: center; font-size: 1em">Welcome to Spring Reddit Clone home page. Come here to
check in with your favorite subreddits.</div>
<div style="text-align: center">
<button class="btnCreatePost" (click)="goToCreatePost()">Create Post</button>
</div>
<div style="text-align: center">
<button class="btnCreateSubreddit" (click)="goToCreateSubreddit()">Create Subreddit</button>
</div>
</div>
side-bar.component.css
.sidebar{
width: 312px;
height: 242px;
--post-line-color: #ccc;
border: 1px solid #ccc;
margin-bottom: 10px;
overflow: hidden;
background-color: rgba(255,255,255,0.8);
color: #878A8C;
position: relative;
border-radius: 4px;
padding:5px;
margin-top: 10px;
margin-bottom: 10px;
}
.btnCreatePost, .btnCreateSubreddit{
background-color: #0079D3;
border-color: #0079D3;
color: aliceblue;
fill: #0079D3;
border: 1px solid;
border-radius: 4px;
text-align: center;
letter-spacing: 1px;
text-decoration: none;
font-size: 12px;
font-weight: 700;
letter-spacing: .5px;
line-height: 24px;
text-transform: uppercase;
padding: 3px 16px;
opacity: 1;
width: 288px;
height: 34px;
}
.btnCreateSubreddit{
margin-top: 5px;
color: #0079D3;
background-color: transparent;
}
.sidebar>ul>li{
font-size: 16px;
font-weight: 500;
line-height: 20px;
color: #1c1c1c;
}
Subreddit Side Bar Component
subreddit-side-bar.component.html
<div class="sidebar-view-subreddit">
<div style="color: black; font-weight: bold">Browse Subreddits</div>
<hr />
<span *ngFor="let subreddit of subreddits">
<span class="subreddit-text"><a href="/view-subreddit/{{subreddit.id}}">{{subreddit.name}}</a></span>
<hr />
</span>
<div style="text-align: center" *ngIf="displayViewAll">
<a style="font-weight: bold" routerLink="/subreddits">View All</a>
</div>
</div>
<strong>subreddit-side-bar.component.css</strong>
.sidebar-view-subreddit {
width: 312px;
height: 242px;
--post-line-color: #ccc;
border: 1px solid #ccc;
margin-bottom: 10px;
overflow: hidden;
background-color: rgba(255, 255, 255, 0.8);
color: #878A8C;
position: relative;
border-radius: 4px;
padding: 5px;
margin-top: 10px;
margin-bottom: 10px;
}
.sidebar-view-subreddit {
height: 280px;
}
Let’s update our styles.css file with below css so that our home page component does not overlap with the header component.
<strong>styles.css</strong>
/* You can add global styles to this file, and also import other style files */
html{
padding: 0;
height: 100%;
}
body{
margin-top: 63px;
height: 100%;
}
input.ng-invalid.ng-touched {
border: 1px solid red;
}
.login,
.sign-up {
background-color: #0079D3;
border-color: #0079D3;
color: aliceblue;
fill: #0079D3;
border: 1px solid;
border-radius: 4px;
text-align: center;
letter-spacing: 1px;
text-decoration: none;
font-size: 12px;
font-weight: 700;
letter-spacing: .5px;
line-height: 24px;
text-transform: uppercase;
padding: 3px 16px;
opacity: 1;
}
Home Component
This is how our home component html looks like:
home-component.html
<div class="reddit-body">
<div class="container">
<div class="row">
<hr />
<div class="col-md-9">
<app-post-tile [data]="posts$"></app-post-tile>
</div>
<div class="col-md-3">
<app-side-bar></app-side-bar>
<app-subreddit-side-bar></app-subreddit-side-bar>
</div>
</div>
</div>
</div>
home-component.ts
import { Component, OnInit } from '@angular/core';
import { PostModel } from '../shared/post-model';
import { PostService } from '../shared/post.service';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
posts$: Array<PostModel> = [];
constructor(private postService: PostService) {
this.postService.getAllPosts().subscribe(post => {
this.posts$ = post;
});
}
ngOnInit(): void {
}
}
After making the above changes the home page should look something like below:
Display Subreddits
As you can see in the above image, the Browse Subreddits section is empty, let’s read all the subreddits and display them inside the section.
For that, I am going to create a Service called SubredditService
by typing ng g s subreddit/subreddit
in the terminal.
The above command should create subreddit.service.ts
and subreddit.service.spec.ts
files inside subreddit folder.
As we have done before, let’s check our REST API Documentation and under the documentation for Subreddit Controller
The getAllSubreddits() API call returns 4 fields description, id, name and numberOfPosts lets create a model class to hold this information.
subreddit-model.ts
export class SubredditModel {
id?: number;
name: string;
description: string;
postCount?: number;
}
subreddit-service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { SubredditModel } from './subreddit-model';
@Injectable({
providedIn: 'root'
})
export class SubredditService {
constructor(private http: HttpClient) { }
getAllSubreddits(): Observable<Array<SubredditModel>> {
return this.http.get<Array<SubredditModel>>('http://localhost:8080/api/subreddit/');
}
}
We are reading all the Subreddits from our Backend through the getAllSubreddits()
inside SubredditService
, let’s wireup this call to SubredditSideBarComponent
subreddit-side-bar.component.ts
import { Component, OnInit } from '@angular/core';
import { SubredditService } from 'src/app/subreddit/subreddit.service';
import { SubredditModel } from 'src/app/subreddit/subreddit-model';
@Component({
selector: 'app-subreddit-side-bar',
templateUrl: './subreddit-side-bar.component.html',
styleUrls: ['./subreddit-side-bar.component.css']
})
export class SubredditSideBarComponent implements OnInit {
subreddits: Array<SubredditModel> = [];
constructor(private subredditService: SubredditService) { }
ngOnInit(): void {
this.subredditService.getAllSubreddits().subscribe(data => {
this.subreddits = data;
});
}
}
As our changes to the component are already synced with subreddit-side-bar.component.html
, we should be able to see the Subreddits inside the Sidebar section.
So everything seems good, but we have to limit the number of Subreddits we can display in the Subreddit Sidebar section, let’s add some logic to display only the first 3 subreddits inside the section, and if there are more than 3 entries we will show a View All button.
subreddit-side-bar.component.ts
import { Component, OnInit } from '@angular/core';
import { SubredditService } from 'src/app/subreddit/subreddit.service';
import { SubredditModel } from 'src/app/subreddit/subreddit-model';
@Component({
selector: 'app-subreddit-side-bar',
templateUrl: './subreddit-side-bar.component.html',
styleUrls: ['./subreddit-side-bar.component.css']
})
export class SubredditSideBarComponent implements OnInit {
subreddits: Array<SubredditModel> = [];
displayViewAll: boolean;
constructor(private subredditService: SubredditService) {
this.subredditService.getAllSubreddits().subscribe(data => {
if (data.length >= 4) {
this.subreddits = data.splice(0, 3);
this.displayViewAll = true;
} else {
this.subreddits = data;
}
});
}
ngOnInit(): void {
}
}
We introduced a flag called displayViewAll to limit the number of subreddits displayed in the sidebar.
If the number of subreddits >= 4 then we set the value of displayViewAll to true.
import { Component, OnInit } from '@angular/core';
import { SubredditService } from 'src/app/subreddit/subreddit.service';
import { SubredditModel } from 'src/app/subreddit/subreddit-model';
@Component({
selector: 'app-subreddit-side-bar',
templateUrl: './subreddit-side-bar.component.html',
styleUrls: ['./subreddit-side-bar.component.css']
})
export class SubredditSideBarComponent implements OnInit {
subreddits: Array<SubredditModel> = [];
displayViewAll: boolean;
constructor(private subredditService: SubredditService) {
this.subredditService.getAllSubreddits().subscribe(data => {
if (data.length > 3) {
this.subreddits = data.splice(0, 3);
this.displayViewAll = true;
} else {
this.subreddits = data;
}
});
}
ngOnInit(): void {
}
}
This is how our final version of the home page looks like.
Conclusion
So we came to end of this article, in the next post we will see how to create Subreddits and Posts in our application.
I hope these articles are helpful to you, please don’t forget to share this blog posts with your friends or colleagues who may find this helpful.
I will see you in the next article, until then Happy Coding Techies 🙂
Icons made by Flat Icons and Freepik from www.flaticon.com