News App using the LWC and Apex REST Callouts

What you will learn in this project

  1. How to create an LWC Component
  2. How to set Remote Site Setting
  3. Creation of Lightning App Page
  4. How to Generate API for news API
  5. Apex class to call Rest API(news API)
  6. How to use apex imperative call in LWC
  7. How to use getter to generate dynamic classes
  8. Display data on LWC
  9. Will create card and Modal in the same component.(it's always recommended to create separately)
  10. Deploy your component

Final Output

Fig: LWC News App Demo
Fig: LWC News App Demo


Video Tutorial


Steps and code

  1. Go to the following URL and generate the API KEY
https://newsapi.org/

Fig: newsapi API key generate flow
Fig: newsapi API key generate flow

  1. Add the below URL to the Remote site Settings
https://newsapi.org/

Fig: Remote Site Setting flow
Fig: Remote Site Setting flow

  1. Go to setup => Quick Find => Lightning App Builder => create New Lightning App Page(News App using LWC)
  2. Create the an LWC component newsComponent and add the following code to the respective files.
newsComponent.js
import { LightningElement, track } from 'lwc';
import retriveNews from "@salesforce/apex/newsController.retriveNews";
export default class NewsComponent extends LightningElement {
    @track result = []
    @track selectedNews={};
    @track isModalOpen = false;
    /***modalBackdropClass method set the classes based on the isModalOpen Value ***/
    get modalClass(){
        return `slds-modal ${this.isModalOpen ? "slds-fade-in-open" :""}`
    }

    /***modalBackdropClass method set the class based on the isModalOpen Value ***/
    get modalBackdropClass(){
        return this.isModalOpen ? "slds-backdrop slds-backdrop_open" : "slds-backdrop"
    }
    connectedCallback(){
        this.fetchNews();
    }
/****fetchNews method gets called on page load, and within this, we are calling the retriveNews apex method using apex imperative approach****/
    fetchNews(){
        retriveNews().then(response=>{
            console.log(response);
            this.formatNewsData(response.articles)
        }).catch(error=>{
            console.error(error);
        })
    }
/****formatNewsData method structuring the response we are getting from the apex method by adding the id, name and date  ****/
    formatNewsData(res){
        this.result = res.map((item, index)=>{
            let id = `new_${index+1}`;
            let date = new Date(item.publishedAt).toDateString()
            let name = item.source.name;
            return { ...item, id: id, name: name, date: date}
        })

    }
    /****showModal method fetch the id of the news card that has been clicked and filter particular news based on the id ****/
    showModal(event){
        let id = event.target.dataset.item;
        this.result.forEach(item=>{
            if(item.id === id){
                this.selectedNews ={...item}
            }
        })
        this.isModalOpen = true;
    }
    /****closeModal method close the modal ****/
    closeModal(){
        this.isModalOpen = false;
    }
}
newsComponent.html
<template>
  <div class="container">
    <div class="slds-grid slds-gutters slds-wrap justify-center">
      <template for:each={result} for:item="item">
        <div class="card" key={item.id}>
          <img class="card-image" src={item.urlToImage} />
          <div class="card-text">
            <span class="date">{item.date}</span>
            <h2>{item.title}</h2>
          </div>
          <div class="card-stats" onclick={showModal} data-item={item.id}>Read More</div>
        </div>
      </template>
    </div>
  </div>
  <!---Modal Section -->
  <section  role="dialog" tabindex="-1" aria-labelledby="modal-heading-01"
    aria-modal="true" aria-describedby="modal-content-id-1" class={modalClass}>
    <div class="slds-modal__container">
      <header class="slds-modal__header">
        <button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse"  title="Close" onclick={closeModal}>
          <lightning-icon icon-name="utility:close" alternative-text="Close Modal" variant="inverse" size="small"></lightning-icon>
        </button>
        <h2 id="modal-heading-01" class="slds-modal__title slds-hyphenate">{selectedNews.title}</h2>
      </header>
      <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
        <img src={selectedNews.urlToImage} />
        <div>
          <div>Source - {selectedNews.name}</div>
          <div>{selectedNews.date}</div>
        </div>
        <p class="content">{selectedNews.content}
          <a href={selectedNews.url} target="_blank">Go to source</a>
        </p>
        <template if:true={selectedNews.author}>
          <div class="footer">
            <div>Author - {selectedNews.author}</div>
          </div>
        </template>
      </div>
    </div>
  </section>
  <!---this Div will show black background when modal gets opened -->
  <div class={modalBackdropClass}></div>
</template>
newsComponent.css
.container {
  display: flex;
  align-items: center;
  justify-content: center;
  background: -webkit-linear-gradient(
    350deg,
    rgb(21, 22, 24) 0%,
    rgb(34, 35, 40) 80%
  );
}
.slds-grid.justify-center {
  justify-content: center;
}
.card {
  display: grid;
  grid-template-columns: 300px;
  grid-template-rows: 210px 210px 50px;
  grid-template-areas: "image" "text" "stats";

  border-radius: 18px;
  background: white;
  box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.9);
  font-family: roboto;
  text-align: center;

  transition: 0.5s ease;
  margin: 30px;
}
.card-image {
  grid-area: image;
  border-top-left-radius: 15px;
  border-top-right-radius: 15px;
  background-size: cover;
  max-height: 170px;
  width: 100%;
  min-height: 170px;
}

.card-text {
  grid-area: text;
}
.card-text .date {
  color: rgb(255, 7, 110);
  font-size: 13px;
}
.card-text p {
  color: grey;
  font-size: 15px;
  font-weight: 300;
}
.card-text h2 {
  font-size: 22px;
  margin: 0rem 1rem 1rem 1rem;
}
.card-stats {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  border-bottom-left-radius: 15px;
  border-bottom-right-radius: 15px;
  background: #ff076e;
  padding: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  color: #fff;
  cursor: pointer;
  font-size: 22px;
  font-weight: 500;
}
.card-stats .border {
  border-left: 1px solid rgb(172, 26, 87);
  border-right: 1px solid rgb(172, 26, 87);
}

.card:hover {
  transform: scale(1.15);
  box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.6);
}

.content {
  font-size: 20px;
}
.slds-modal__title {
  font-size: 24px;
  font-weight: 700;
}
.slds-modal__header {
  font-size: 24px;
  font-weight: 700;
  background: #ff076e;
  color: #fff;
}
.footer {
  display: flex;
  background: rgb(255, 77, 7);
  color: #fff;
  padding: 1rem;
  font-size: 1.3rem;
}
newsComponent.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>48.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>
  1. Create the newsComponent Apex class and add the following code to the respective files.

Replace YOU_NEWS_API text with your API key in the below `newsComponent.cls`

newsComponent.cls
public with sharing class newsController {
    @AuraEnabled
    public static Map<String, Object> retriveNews(){
        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setEndpoint('http://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=YOU_NEWS_API');
        httpRequest.setMethod('GET');

        Map<String, Object> newsJsonData = new Map<String, Object>();
        String strResponse = null;

        try{
            Http http = new Http();
            HttpResponse httpResponse = http.send(httpRequest);
            if(httpResponse.getStatusCode() == 200){
                strResponse = httpResponse.getBody();
            } else {
                throw new CalloutException(httpResponse.getBody());
            }

        } catch(Exception ex){
            throw ex;
        }

        if(!String.isBlank(strResponse)){
            newsJsonData = (Map<String, Object>)JSON.deserializeUntyped(strResponse);
        }
        if(!newsJsonData.isEmpty()){
            return newsJsonData;
        } else {
            return null;
        }
    }
}

Deployment

Deploy your component to the salesforce org in the following order

  1. newsComponent class
  2. newsComponent lwc component

Final Output

Now place your component to the page you have created. You will see the following output

Fig: LWC News App Demo
Fig: LWC News App Demo


Site developed by Nikhil Karkra © 2021 | Icons made by Freepik