News App using the LWC and Apex REST Callouts
What you will learn in this project
- How to create an LWC Component
- How to set Remote Site Setting
- Creation of Lightning App Page
- How to Generate API for news API
- Apex class to call Rest API(news API)
- How to use apex imperative call in LWC
- How to use getter to generate dynamic classes
- Display data on LWC
- Will create card and Modal in the same component.(it's always recommended to create separately)
- Deploy your component
Final Output
Video Tutorial
Steps and code
- Go to the following URL and generate the API KEY
https://newsapi.org/
- Add the below URL to the Remote site Settings
https://newsapi.org/
- Go to setup => Quick Find => Lightning App Builder => create New Lightning App Page(News App using LWC)
- 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>
- 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
- newsComponent class
- newsComponent lwc component
Final Output
Now place your component to the page you have created. You will see the following output