How to build a weather app in Angular
Building a weather application in Angular is a classic project that demonstrates how to handle asynchronous data, service injection, and reactive UI updates.
With over 25 years of software development experience and as the creator of CoreUI, I’ve built numerous data-driven dashboards that require real-time API integration.
The most efficient way to build this in Angular is by leveraging HttpClient for API calls and Signals for state management, which provides a highly performant and reactive user experience.
This guide will show you how to structure your service, manage state, and build a polished UI using CoreUI components.
Create an Angular service to fetch data from a weather API and use signals in your component to update the UI reactively.
Step 1: Create the Weather Service
The first step is to create a service that handles the communication with an external weather API. We use Angular’s HttpClient to perform GET requests.
import { Injectable, inject } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
@Injectable({
providedIn: 'root'
})
export class WeatherService {
private http = inject(HttpClient)
private apiKey = 'YOUR_API_KEY_HERE'
private apiUrl = 'https://api.openweathermap.org/data/2.5/weather'
getWeather(city: string): Observable<any> {
const url = `${this.apiUrl}?q=${city}&appid=${this.apiKey}&units=metric`
return this.http.get(url)
}
}
This service utilizes the inject function, which is the modern way to handle dependency injection in Angular. The getWeather method constructs a URL with the city name and your API key, then returns an Observable. It is a good idea to create a new angular project specifically for this implementation to keep your experiments isolated.
Step 2: Define Data Interfaces
Defining interfaces for your API response ensures type safety throughout your application and makes your code much easier to maintain as it grows.
export interface WeatherData {
name: string
main: {
temp: number
humidity: number
pressure: number
}
weather: Array<{
description: string
icon: string
}>
wind: {
speed: number
}
}
By defining the WeatherData interface, we tell TypeScript exactly what structure to expect from the API. This prevents runtime errors when accessing properties like temp or description. It’s also helpful to convert a string to lowercase when passing city names to the API to ensure consistency in your requests.
Step 3: Component Logic with Signals
In the component, we use Angular Signals to manage the state of the weather data, loading status, and any potential errors.
import { Component, signal, inject } from '@angular/core'
import { DecimalPipe } from '@angular/common'
import { WeatherService, WeatherData } from './weather.service'
@Component({
selector: 'app-weather',
standalone: true,
imports: [DecimalPipe],
templateUrl: './weather.component.html'
})
export class WeatherComponent {
private weatherService = inject(WeatherService)
weather = signal<WeatherData | null>(null)
loading = signal<boolean>(false)
error = signal<string | null>(null)
searchCity(city: string) {
if (!city) return
this.loading.set(true)
this.error.set(null)
this.weatherService.getWeather(city).subscribe({
next: (data) => {
this.weather.set(data)
this.loading.set(false)
},
error: () => {
this.error.set('City not found. Please try again.')
this.loading.set(false)
this.weather.set(null)
}
})
}
}
Using signal allows the template to react automatically to data changes without the overhead of traditional change detection. We handle the next and error blocks of the subscription to update our signals accordingly. This pattern keeps the UI in sync with the current state of the request at all times.
Step 4: Search Form Template
For the UI, we use CoreUI form controls to create a clean search input. This ensures the app looks professional and is easy to use.
<div class="mb-4">
<div class="input-group">
<input
type="text"
#cityInput
class="form-control"
placeholder="Enter city name..."
(keyup.enter)="searchCity(cityInput.value)"
>
<button
class="btn btn-primary"
type="button"
(click)="searchCity(cityInput.value)"
>
Search
</button>
</div>
</div>
In this template, we use a template reference variable #cityInput to capture the user’s input. The (keyup.enter) and (click) events trigger the searchCity method. We recommend using CoreUI Input Group components to maintain a consistent design language with the rest of your dashboard.
Step 5: Displaying Weather Data
To display the results, we use the CoreUI Card component. This provides a structured layout for the temperature, humidity, and wind speed.
@if (weather(); as data) {
<div class="card shadow-sm">
<div class="card-body text-center">
<h2 class="card-title">{{ data.name }}</h2>
<img
[src]="'https://openweathermap.org/img/wn/' + data.weather[0].icon + '@2x.png'"
[alt]="data.weather[0].description"
>
<h3 class="display-4">{{ data.main.temp | number:'1.0-0' }}°C</h3>
<p class="text-capitalize text-muted">{{ data.weather[0].description }}</p>
<div class="row mt-4">
<div class="col">
<p class="mb-0 fw-bold">Humidity</p>
<span>{{ data.main.humidity }}%</span>
</div>
<div class="col">
<p class="mb-0 fw-bold">Wind</p>
<span>{{ data.wind.speed }} m/s</span>
</div>
</div>
</div>
</div>
}
The @if block (Angular’s new control flow) checks if the weather signal has data. We then bind the properties to the CoreUI Card structure. This provides a clear, readable presentation of the weather conditions for the selected city.
Step 6: Handling Loading and Errors
A robust application must handle loading states and errors gracefully to provide a good user experience.
@if (loading()) {
<div class="text-center my-5">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
}
@if (error()) {
<div class="alert alert-danger mt-3" role="alert">
{{ error() }}
</div>
}
We use the CoreUI Spinner to show activity during the API request and the CoreUI Alert component to display error messages. These visual cues are essential for keeping the user informed about what is happening in the background.
Best Practice Note:
When building production-ready apps, always move your API keys to environment files or a secure backend proxy to prevent exposure.
This is the same architectural pattern we follow in our Angular Dashboard Template, where we separate service logic from presentation components for maximum reusability and testability.
Using Signals instead of BehaviorSubject is the modern standard for Angular 17+ and significantly reduces the complexity of managing local state in your weather application.



