We will be developing a News Application using the Angular framework. This interactive app will allow users to search for news articles and filter them by category.
With a clean and responsive design, it will dynamically display articles, providing an engaging and user-friendly experience. The app will use Angular Material components for a modern and visually appealing interface.
Project Preview
Project PreviewPrerequisites
Approach
- Create a responsive layout with Angular Material components for consistent styling.
- Implement a search bar to filter news articles based on user input.
- Use Angular’s Reactive Forms to handle search queries and category selection.
- Display news articles dynamically and ensure a user-friendly interface with clear navigation.
Steps to Create News app using Angular
Step 1: Install Angular CLI
If you haven’t installed Angular CLI yet, install it using the following command
npm install -g @angular/cli
Step 2: Create a New Angular Project
ng new news-app --no-standalone
cd news-app
Step 3: Create Standalone Component
Create a standalone component. You can generate a standalone component using the Angular CLI:
ng generate component news-app
Step 4: Install Packages
Execute the below command to install additional packages.
npm install @angular/material @angular/cdk @fortawesome/angular-fontawesome @fortawesome/fontawesome-free @fortawesome/free-solid-svg-icons
Dependencies
"dependencies": {
"@angular/animations": "^18.2.0",
"@angular/cdk": "^18.2.0",
"@angular/common": "^18.2.0",
"@angular/compiler": "^18.2.0",
"@angular/core": "^18.2.0",
"@angular/forms": "^18.2.0",
"@angular/material": "^18.2.0",
"@angular/platform-browser": "^18.2.0",
"@angular/platform-browser-dynamic": "^18.2.0",
"@angular/router": "^18.2.0",
"@fortawesome/angular-fontawesome": "^0.15.0",
"@fortawesome/fontawesome-free": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.10"
}
Folder Structure
Folder StructureExample: Create the required files as seen in the folder structure and add the following codes.
App Component
Below is the App Component demonstrating the selector of news-app in HTML file and all the necessary imports in app.module.ts file.
HTML
<!--src/app/app-component.html-->
<div class="app-container">
<app-news-app></app-news-app>
</div>
JavaScript
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NewsAppComponent } from './news-app/news-app.component';
const routes: Routes = [
{ path: '', component: NewsAppComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
JavaScript
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'news-angular-app';
}
JavaScript
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { provideHttpClient, withInterceptorsFromDi }
from '@angular/common/http';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule }
from '@angular/material/button-toggle';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule }
from '@angular/platform-browser/animations';
import { MatToolbarModule } from '@angular/material/toolbar';
import { AppComponent } from './app.component';
import { NewsAppComponent } from './news-app/news-app.component';
@NgModule({
declarations: [
AppComponent,
NewsAppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatButtonToggleModule,
MatFormFieldModule,
MatInputModule,
MatToolbarModule
],
providers: [provideHttpClient(withInterceptorsFromDi())],
bootstrap: [AppComponent]
})
export class AppModule { }
News-App Component
Below is the NewsApp Component having the UI for New App in HTML file followed by its CSS in news-app.component.css and all the logical implementation in news-app.component.ts.
HTML
<!--app/news-app/news-app.component.html-->
<div class="news-app-container">
<mat-toolbar class="navbar">
<span class="navbar-title">GeeksforGeeks</span>
</mat-toolbar>
<div class="search-container">
<mat-form-field class="search-field">
<input matInput [formControl]="searchControl" placeholder="Search for news">
</mat-form-field>
</div>
<mat-button-toggle-group class="category-buttons" (change)="onCategoryChange($event.value)">
<mat-button-toggle *ngFor="let category of categories" [value]="category">
{{ category | titlecase }}
</mat-button-toggle>
</mat-button-toggle-group>
<div class="news-list">
<div class="news-item" *ngFor="let article of filteredArticles">
<img [src]="article.urlToImage ? article.urlToImage :
'https://media.geeksforgeeks.org/wp-content/uploads/20240819134655/news.png'" alt="News Image"
class="news-image">
<div class="news-content">
<h4 class="news-title">{{ article.title }}</h4>
<p class="news-description">{{ article.description }}</p>
<a [href]="article.url" target="_blank" class="news-link">Read More...</a>
</div>
</div>
</div>
<button mat-raised-button color="primary" (click)="loadMore()" *ngIf="!isLoading">
Load More
</button>
<p *ngIf="isLoading">Loading...</p>
</div>
CSS
/*src/app/news-app/news-app.component.css*/
.navbar {
background-color: #007bff;
color: white;
padding: 10px 20px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.navbar-title {
font-size: 24px;
font-weight: bold;
margin: 0;
}
.search-container {
display: flex;
justify-content: center;
margin: 20px 0;
}
.search-field {
max-width: 600px;
width: 100%;
}
.mat-form-field {
width: 100%;
border-radius: 4px;
}
.mat-form-field-appearance-fill .mat-form-field-flex {
border: 1px solid #007bff;
border-radius: 4px;
background-color: #f8f9fa;
}
.mat-form-field-appearance-fill .mat-form-field-infix {
border: none;
}
input.mat-input-element {
padding: 12px;
border: none;
border-radius: 4px;
font-size: 16px;
}
input.mat-input-element::placeholder {
color: #666;
opacity: 1;
}
.news-app-container {
text-align: center;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
color: green;
font-size: 36px;
margin-bottom: 10px;
}
.subheader {
font-size: 24px;
margin-bottom: 20px;
}
.category-buttons {
margin-bottom: 20px;
}
.news-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
}
.news-item {
width: 100%;
max-width: 300px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition: transform 0.3s, box-shadow 0.3s;
}
.news-item:hover {
transform: translateY(-10px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.news-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.news-content {
padding: 15px;
text-align: left;
}
.news-title {
font-size: 18px;
margin: 10px 0;
font-weight: bold;
}
.news-description {
font-size: 14px;
margin-bottom: 10px;
color: #666;
}
.news-link {
color: #007bff;
text-decoration: none;
font-weight: bold;
}
.news-link:hover {
text-decoration: underline;
}
button {
margin-top: 20px;
}
JavaScript
//src/app/news-app/news-app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-news-app',
templateUrl: './news-app.component.html',
styleUrls: ['./news-app.component.css']
})
export class NewsAppComponent implements OnInit {
newsArticles: any[] = [];
filteredArticles: any[] = [];
searchControl = new FormControl<string>('');
page = 1;
totalResults = 0;
isLoading = false;
categories = ['general', 'entertainment', 'technology', 'sports', 'business', 'health', 'science'];
selectedCategory = 'general';
private apiKey = 'ecfaf9eaaa8d40a5b5d769210f5ee616'; // Make sure this is your valid API key
constructor(private http: HttpClient) { }
ngOnInit(): void {
this.fetchNews();
this.searchControl.valueChanges.pipe(debounceTime(300)).subscribe(searchTerm => {
this.filterArticles(searchTerm ?? '');
});
}
fetchNews(): void {
this.isLoading = true;
const url = `https://newsapi.org/v2/top-headlines?country=in&category=${this.selectedCategory}
&page=${this.page}&apiKey=${this.apiKey}`;
this.http.get<any>(url).subscribe({
next: (response) => {
this.newsArticles = response.articles;
this.totalResults = response.totalResults;
this.isLoading = false;
this.filterArticles(this.searchControl.value ?? '');
},
error: (err) => {
console.error('API error occurred:', err);
this.isLoading = false;
// Display a user-friendly error message
alert('Something went wrong; please try again later.');
}
});
}
loadMore(): void {
if (this.newsArticles.length < this.totalResults) {
this.page += 1;
this.fetchNews();
}
}
filterArticles(searchTerm: string): void {
if (searchTerm) {
this.filteredArticles = this.newsArticles.filter(article =>
article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.description?.toLowerCase().includes(searchTerm.toLowerCase())
);
} else {
this.filteredArticles = this.newsArticles;
}
}
onCategoryChange(category: string): void {
this.selectedCategory = category;
this.page = 1;
this.fetchNews();
}
}
Complete Code:
HTML
<!--src/app/app-component.html-->
<div class="app-container">
<app-news-app></app-news-app>
</div>
HTML
<!--app/news-app/news-app.component.html-->
<div class="news-app-container">
<mat-toolbar class="navbar">
<span class="navbar-title">GeeksforGeeks</span>
</mat-toolbar>
<div class="search-container">
<mat-form-field class="search-field">
<input matInput [formControl]="searchControl" placeholder="Search for news">
</mat-form-field>
</div>
<mat-button-toggle-group class="category-buttons" (change)="onCategoryChange($event.value)">
<mat-button-toggle *ngFor="let category of categories" [value]="category">
{{ category | titlecase }}
</mat-button-toggle>
</mat-button-toggle-group>
<div class="news-list">
<div class="news-item" *ngFor="let article of filteredArticles">
<img [src]="article.urlToImage ? article.urlToImage :
'https://media.geeksforgeeks.org/wp-content/uploads/20240819134655/news.png'" alt="News Image"
class="news-image">
<div class="news-content">
<h4 class="news-title">{{ article.title }}</h4>
<p class="news-description">{{ article.description }}</p>
<a [href]="article.url" target="_blank" class="news-link">Read More...</a>
</div>
</div>
</div>
<button mat-raised-button color="primary" (click)="loadMore()" *ngIf="!isLoading">
Load More
</button>
<p *ngIf="isLoading">Loading...</p>
</div>
CSS
/*src/app/news-app/news-app.component.css*/
.navbar {
background-color: #007bff;
color: white;
padding: 10px 20px;
display: flex;
justify-content: center;
align-items: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
.navbar-title {
font-size: 24px;
font-weight: bold;
margin: 0;
}
.search-container {
display: flex;
justify-content: center;
margin: 20px 0;
}
.search-field {
max-width: 600px;
width: 100%;
}
.mat-form-field {
width: 100%;
border-radius: 4px;
}
.mat-form-field-appearance-fill .mat-form-field-flex {
border: 1px solid #007bff;
border-radius: 4px;
background-color: #f8f9fa;
}
.mat-form-field-appearance-fill .mat-form-field-infix {
border: none;
}
input.mat-input-element {
padding: 12px;
border: none;
border-radius: 4px;
font-size: 16px;
}
input.mat-input-element::placeholder {
color: #666;
opacity: 1;
}
.news-app-container {
text-align: center;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.header {
color: green;
font-size: 36px;
margin-bottom: 10px;
}
.subheader {
font-size: 24px;
margin-bottom: 20px;
}
.category-buttons {
margin-bottom: 20px;
}
.news-list {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 20px;
}
.news-item {
width: 100%;
max-width: 300px;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
transition: transform 0.3s, box-shadow 0.3s;
}
.news-item:hover {
transform: translateY(-10px);
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3);
}
.news-image {
width: 100%;
height: 200px;
object-fit: cover;
}
.news-content {
padding: 15px;
text-align: left;
}
.news-title {
font-size: 18px;
margin: 10px 0;
font-weight: bold;
}
.news-description {
font-size: 14px;
margin-bottom: 10px;
color: #666;
}
.news-link {
color: #007bff;
text-decoration: none;
font-weight: bold;
}
.news-link:hover {
text-decoration: underline;
}
button {
margin-top: 20px;
}
JavaScript
// src/app/app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { provideHttpClient, withInterceptorsFromDi }
from '@angular/common/http';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule }
from '@angular/material/button-toggle';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { BrowserAnimationsModule }
from '@angular/platform-browser/animations';
import { MatToolbarModule } from '@angular/material/toolbar';
import { AppComponent } from './app.component';
import { NewsAppComponent } from './news-app/news-app.component';
@NgModule({
declarations: [
AppComponent,
NewsAppComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
MatButtonModule,
MatButtonToggleModule,
MatFormFieldModule,
MatInputModule,
MatToolbarModule
],
providers: [provideHttpClient(withInterceptorsFromDi())],
bootstrap: [AppComponent]
})
export class AppModule { }
JavaScript
// src/app/app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'news-angular-app';
}
JavaScript
// src/app/app-routing.module.ts
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { NewsAppComponent } from './news-app/news-app.component';
const routes: Routes = [
{ path: '', component: NewsAppComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
JavaScript
//src/app/news-app/news-app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-news-app',
templateUrl: './news-app.component.html',
styleUrls: ['./news-app.component.css']
})
export class NewsAppComponent implements OnInit {
newsArticles: any[] = [];
filteredArticles: any[] = [];
searchControl = new FormControl<string>('');
page = 1;
totalResults = 0;
isLoading = false;
categories = ['general', 'entertainment', 'technology', 'sports', 'business', 'health', 'science'];
selectedCategory = 'general';
private apiKey = 'ecfaf9eaaa8d40a5b5d769210f5ee616'; // Make sure this is your valid API key
constructor(private http: HttpClient) { }
ngOnInit(): void {
this.fetchNews();
this.searchControl.valueChanges.pipe(debounceTime(300)).subscribe(searchTerm => {
this.filterArticles(searchTerm ?? '');
});
}
fetchNews(): void {
this.isLoading = true;
const url = `https://newsapi.org/v2/top-headlines?country=in&category=${this.selectedCategory}
&page=${this.page}&apiKey=${this.apiKey}`;
this.http.get<any>(url).subscribe({
next: (response) => {
this.newsArticles = response.articles;
this.totalResults = response.totalResults;
this.isLoading = false;
this.filterArticles(this.searchControl.value ?? '');
},
error: (err) => {
console.error('API error occurred:', err);
this.isLoading = false;
// Display a user-friendly error message
alert('Something went wrong; please try again later.');
}
});
}
loadMore(): void {
if (this.newsArticles.length < this.totalResults) {
this.page += 1;
this.fetchNews();
}
}
filterArticles(searchTerm: string): void {
if (searchTerm) {
this.filteredArticles = this.newsArticles.filter(article =>
article.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
article.description?.toLowerCase().includes(searchTerm.toLowerCase())
);
} else {
this.filteredArticles = this.newsArticles;
}
}
onCategoryChange(category: string): void {
this.selectedCategory = category;
this.page = 1;
this.fetchNews();
}
}
Open the terminal, run this command from your root directory to start the application
ng serve --open
Open your browser and navigate to http://localhost:4200
Output
Similar Reads
Movie App Using Angular
We will be creating a Movie Search Engine using Angular. This application allows users to search for movies by entering keywords and fetching and displaying a list of movie results in card format. The search engine initially loads a default set of movies to showcase the functionality. Through this p
5 min read
Quiz App using Angular
We will be creating a Quiz application using the Angular framework. This app will allow users to answer multiple-choice questions and view their results interactively. We'll use Angular Material for a polished UI and implement responsive design to ensure it looks great on all devices. The applicatio
5 min read
Angular ng new
The Angular CLI (ng new) is a command-line interface through which developers can quickly create and get Angular applications. Furthermore, it brings about simplicity in the introductory phases of development by generating all necessary files and folder structures that are required for an Angular pr
3 min read
Meme Generator App using Angular
Angular has become one of the, most popular frameworks for building dynamic web applications. In this project, we will build a simple yet entertaining Meme generator app using Angular. The app will fetch random memes from a public API and display them to the user. This project will help us understan
5 min read
Joke generator App Using Angular
Angular has become one of the most popular frameworks for building dynamic web applications. In this project, we build a simple yet entertaining Joke generator app using Angular. The app will fetch random jokes from a public API and display them to the user. This project will help us understand how
3 min read
Quote Generator App Using Angular
A Quote Generator App is a simple application that displays random quotes to users. It is a great project for practising Angular basics such as components, services, data and API integration.Here we develop a simple Quote Generator App using Angular. This application can able to display a new Quote
6 min read
Dice Rolling App Using Angular
We will be developing a Dice Rolling Application using the Angular framework. This application will showcase how to implement interactive features with smooth animations and a responsive layout. We will use FontAwesome icons to represent dice faces and create an engaging user experience with a visua
3 min read
Weather App Using Angular
We will be creating a weather application using the Angular framework. This app will allow users to check the current weather conditions by entering a city name. It will provide detailed information including temperature, weather description, wind speed, and humidity. With responsive design and inte
8 min read
Calculator App Using Angular
This Calculator app will provide basic arithmetic functionalities such as addition, subtraction, multiplication, and division, with a clean and modern UI. It will be fully responsive and interactive, ensuring a great user experience across different devices. This project is suitable for both beginne
4 min read
Event Calender using Angular
In this article, we will walk through the steps to create a dynamic Event Calendar using Angular 17 standalone components. This calendar will allow users to add events to specific dates and view them interactively. We will utilize Angular's powerful features and best practices to build a clean and e
6 min read