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

Prerequisites
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/cliStep 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-appStep 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

Example: 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.
<!--src/app/app-component.html-->
<div class="app-container">
<app-news-app></app-news-app>
</div>
// 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 { }
// 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';
}
// 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.
<!--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>
/*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;
}
//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=$%7Bthis.selectedCategory%7D
&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:
<!--src/app/app-component.html-->
<div class="app-container">
<app-news-app></app-news-app>
</div>
<!--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>
/*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;
}
// 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 { }
// 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';
}
// 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 { }
//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=$%7Bthis.selectedCategory%7D
&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 --openOpen your browser and navigate to http://localhost:4200