April 8, 2020

Full Stack Spring and Angular - Part 14

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.

Home Page with Component Information - Full Stack Spring and Angular - Part 14

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

Create all components - Full Stack Spring and Angular - Part 14

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

About the author 

Sai Upadhyayula

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Subscribe now to get the latest updates!