Tuesday, September 24, 2019

Creating a News App Project with Angular 8 and Bootstrap

Hello everyone, in this tutorial we are going to create a news app with Angular 8 and Bootstrap. We will be consuming free edition of the The Guardian Developer API to fetch the news articles. We will be including pagination, search and category based news for the purpose of this tutorial.

Once we are done we will have an app looking something like this.
Creating a News App Project with Angular 8 and Bootstrap : Final App

Get the API-Key

Firstly we will have to register on developer portal of The Guardian in order to obtain the Api Key which is required to consume its resources. Head to their portal and click on Register Developer Key. Follow the steps and you will have your unique Api Key. Head over to their documentation page in order to know about the structure of the response object so that it is easy for you to understand once we start consuming these API's.

Setup Angular Project

Use Angular CLI to generate a new project named news-app. Once created install the latest version of Bootstrap and FontAwesome icons by referring the steps mentioned on the below links:


We will also be using ngx-pagination for implementing client side pagination on the news articles which are fetched. To add that to your project refer to the below link:

Create Components/Models/Services

Creating a News App Project with Angular 8 and Bootstrap - Project Setup

  • Create three components named dashboard, header and footer.
  • Create a folder named shared
  • Create a service named news inside the shared folder
  • Create a class named news.model inside the shared folder
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, PipeTransform } from '@angular/core';
import {FormsModule} from '@angular/forms'
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HeaderComponent } from './header/header.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { NewsService } from './shared/news.service';
import {HttpClientModule} from '@angular/common/http';
import {NgxPaginationModule} from 'ngx-pagination';
import { pipe } from 'rxjs';
import { FooterComponent } from './footer/footer.component';

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    DashboardComponent,
    FooterComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    HttpClientModule,
    NgxPaginationModule
    
  ],
  providers: [NewsService],
  bootstrap: [AppComponent]
})
export class AppModule { }

  • Register the news service as a provider in the app.module.ts class as above.
  • Also import the HttpClientModule, FormsModule here as we will be using these modules as well.

Consuming the API

Firstly, we will create the News Model which we will use to store the articles which are returned from the API. Although there are multiple fields in the api, we will be using only few of them to show a preview of the article.Copy the code below into your news.model.ts file


export class NewsModel {
public sectionId: string;
public webPublicationDate : Date;
public webTitle: string;
public webUrl : string;
public thumbnail : string
public trailText : string
}

Below is the snapshot of the default response of the API, but since we are also showing a thumbnail, we will be using a property called show-fields=all to get additional fields as well. You can know more about these additional fields and properties here. As mentioned above, make sure you have gone through the documentation to know the capabilities of the API so that you can modify you application as per it.
Creating a News App Project with Angular 8 and Bootstrap - Default API response

Add the following code to your news.service.ts file. Here we will be consuming the endpoints using Http and store the result after mapping it into NewsModel type of object.


import { Injectable } from '@angular/core';
import { NewsModel } from './news.model';
import { HttpClient, HttpClientModule, HttpParams } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';  
import { Observable } from 'rxjs';  

@Injectable({
  providedIn: 'root'
})
export class NewsService {
newsArticles : NewsModel[] = [];
temp:NewsModel = new NewsModel;

gaurApiKey = "add your api key here";
gaurUrl = "http://content.guardianapis.com/search?show-fields=all&page-size=20&api-key=";
gaurSectionUrl = "https://content.guardianapis.com/search?show-fields=all&page-size=20&q="

  constructor(private http:HttpClient) { }

  GetAllGaurdian()
  {
    return this.http.get(this.gaurUrl + this.gaurApiKey);
  }

  GetGaurdianSearchResult(section:string)
  {
    return this.http.get(this.gaurSectionUrl + section + "&api-key=" + this.gaurApiKey );
  }

 public CusotomMapper(item) : NewsModel{
          
    this.temp = new NewsModel;
      this.temp.thumbnail = item["fields"].thumbnail;
      this.temp.sectionId= item["sectionId"];
      this.temp.webPublicationDate = item["webPublicationDate"];
      this.temp.webTitle = item["webTitle"];
      this.temp.webUrl = item["webUrl"];
      this.temp.trailText = item["fields"].trailText;
      return this.temp;
  }
}

Firstly we create a newsArticles variable which is array of type NewsModel. We also create a temp variable called temp which we will be using to convert the response into our type.

We then have three variables to store api key, general endpoint url and the url to filter news based on search terms or categories. The query parameter "q" is used to pass on the search term, api-key to pass the unique key etc.

We then have two methods, GetAllGaurdian(), used to get all the news and GetGaurdianSearchResult() which is used to get news based on some search text or filter

We then have a method named custom mapper which takes in the response object and creates a NewsModel type of object based upon the fields which we are using in are app.

Note: Since the response has these fields in hierarchical structure hence we had to write this mapper.

Working on the Components

header.component.html

<nav class="navbar navbar-light bg-success navbar-expand-lg">
    <a class="navbar-brand mt-1" href="#">
        <i class="fa fa-newspaper-o fa-2x text-dark  d-inline-block align-top">
    </i> Ng News</a>
    <button class="navbar-toggler" data-toggle="collapse" data-target="#navbaritem" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
</button>

    <div class="collapse navbar-collapse" id="navbaritem">
        <ul class="navbar-nav mr-auto mt-2 mt-lg-0 lead">
            <li class="nav-item active ">
                <a class="nav-link" href="#" (click)="filterClicked($event)">World</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#" (click)="filterClicked($event)">India</a>
            </li>
            <li class="nav-item ">
                <a class="nav-link" href="#" (click)="filterClicked($event)">Tech</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#" (click)="filterClicked($event)">Sports</a>
            </li>
            <li class="nav-item">
                <a class="nav-link" href="#" (click)="filterClicked($event)">Business</a>
            </li>

        </ul>

        <form class="form-group d-flex ml-auto mb-0" #form="ngForm" (submit)="onSubmit(form)">
            <input type="text" class="form-control mr-2 align-self-center" required placeholder="Search" name="searchText" [ngModel]="searchText" value="">
            <button class="btn btn-outline-warning align-self-center" [disabled]="!form.valid" type="submit">Search</button>
        </form>
    </div>
</nav>

Here we design the navigation bar which contains few links to fetch news based on catgories and a search box as well.

Creating a News App Project with Angular 8 and Bootstrap - NavBar.png

We wont be handling any events in this components, hence all its events like the nav clicks and search are emitted to its parent i.e. app component.

header.component.ts

import { Component,  OnInit, Output, EventEmitter } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html',
  styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
  filterText : string;
@Output() search = new EventEmitter();
@Output() filterButton = new EventEmitter();

  constructor() { }

  ngOnInit() {
  }
  onSubmit(form : NgForm)
  {
    console.log(form);
    this.search.emit(form);
  }

  filterClicked($event)
  {
    this.filterText = $event.target.text;
    this.filterButton.emit(this.filterText);
  }
}

Here the form submit event i.e. raised on the Search button and the <a> click events are emitted using search and filterButtons emitter.

dashboard.component.html

<div class="container-fluid py-5 bg-light">
    <div class="row">
        <div class="col-md-4 col-lg-3 my-3 d-flex align-items-stretch" *ngFor="let item of newsService.newsArticles | paginate: { itemsPerPage: 8, currentPage: p }">
            <div class="card">
                <img [src]="item.thumbnail" alt="" class="card-img-top img-fluid img-thumbnail">
                <div class="card-body">
                    <div class="card-title text-capitalize">
                        <h5>{{item.webTitle}}</h5>
                    </div>
                    <p class="card-text">
                        {{ (item.trailText | slice:0:100 ) +'..' }}
                    </p>
                    <div class="d-flex justify-content-left mt-3">
                        <p class="badge badge-secondary mr-3">{{item.webPublicationDate | date}}</p>
                        <p class="badge badge-secondary ">{{item.sectionId}}</p>
                    </div>

                </div>
                <div class="card-footer bg-warning d-flex justify-content-end">

                    <a [href]="item.webUrl" class="btn btn-success" target="_blank">
                        <i class="fa fa-book mr-1"></i>Read More
                    </a>
                </div>
            </div>
        </div>
    </div>
    <pagination-controls class="text-center" (pageChange)="p = $event"></pagination-controls>
</div>

Dashboard is the main component in terms of UI as here we will have the bootstrap code to show the news articles using cards layout.

dashboard.component.ts

import { Component, OnInit } from '@angular/core';
import { NewsService } from '../shared/news.service';
import { NewsModel } from '../shared/news.model';
import { Form } from '@angular/forms';
import { Pipe, PipeTransform } from '@angular/core';
import { element } from 'protractor';
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  articles : any;
  temp : NewsModel = new NewsModel;
  constructor(private newsService : NewsService) { }

  ngOnInit() {
      this.FetchHeadlines();
           }

  FetchHeadlines()
  {
     this.newsService.GetAllGaurdian()
        .subscribe((result) =>
        {
          this.articles = result;
                  this.articles.response.results.forEach(element => {
                       this.newsService.newsArticles.push(this.newsService.CusotomMapper(element));
          });
        }) 
  }
}

The dashboard component consumes the NewsService using dependency injection in the constructor to fetch all news posts once the app is first loaded.

The response from API is stored in a variable named result and then we store it in a local array variable named articles. Then this array is iterated through and each item in it is passed through the custom mapper we created in news service to generate an object of type NewsModel. Once generated this object is stored in the newsArticle array which is used throughout the application to display the news 

footer.component.html  (optional)

<footer class="footer bg-dark py-5">
    <div class="container">
        <div class="row">
            <div class="col text-center">
                <h1 class="display-5 text-uppercase text-light mb-0">
                    <strong>Ng-News</strong>
                </h1>
                <div class="title-underline bg-primary">
                </div>
                <p class="text-white lead font-italic my-2">Web App built with Angular 8, Bootstrap and Gaurdian API</p>
            </div>
        </div>
    </div>
</footer>

footer.component.ts

No changes are required in the footer component and the CLI generated code fragment work as expected.

app.component.html

<app-header (search)="searchNews($event)" (filterButton)="filterNews($event)"></app-header>
<app-dashboard></app-dashboard>
<app-footer></app-footer>

Events from the Header component are handled using the searchNews() and filterNews() functions defined here.

app.component.ts


import { Component } from '@angular/core';
import { Form } from '@angular/forms';
import { NewsService } from './shared/news.service';
import { NewsModel } from './shared/news.model';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'news-app';
  searchText = "";
  resultSet : any;

  constructor(private newsService : NewsService) { }

  searchNews($event){
   
     this.searchText = $event.form.controls["searchText"].value;

     this.newsService.GetGaurdianSearchResult(this.searchText)
     .subscribe((result) => {
        this.resultSet = result;
        this.newsService.newsArticles = [];
        this.resultSet.response.results.forEach(element => {
                console.log(this.newsService.CusotomMapper(element));
        
        this.newsService.newsArticles.push(this.newsService.CusotomMapper(element))
      })
  
    })
  }

  filterNews(text: string)
  {
    console.log(text);
  
    // this.newsService.GetSearchResult(text)
    this.newsService.GetGaurdianSearchResult(text)
    .subscribe((result) => {
      this.newsService.newsArticles = [];
      this.resultSet = result;
      this.resultSet.response.results.forEach(element => {
           this.newsService.newsArticles.push(this.newsService.CusotomMapper(element));
    })
  
  })

}
}

Here we have two separate function defined for better understanding as the same could be achieved through a single function itself. The searchNews() is called when the user enters a search term and clicks on Search button in the navigation bar. This function in turn calls the GetGaurdianSearchResult() function which returns result based on the search query.

Creating a News App Project with Angular 8 and Bootstrap - Event Emitter


The filterNews() function is called when the user clicks on any of the navigation links we had like India, Business etc. The event sends the inner text of that link which is passed onto the GetGaurdianSearchResult() function.

That it's, we have finished Creating a News App Project with Angular 8 and Bootstrap tutorial. If you have any trouble following the steps of the tutorial, you can compare it with the working source code uploaded here.

No comments:
Write comments