Implementing Reactive transformations

To avoid future mistakes in returning the unintended type of data from your service, you need to update the getCurrentWeather function to define the return type to be Observable<ICurrentWeather> and import the Observable type, as shown:

src/app/weather/weather.service.ts
import { Observable } from 'rxjs'
import { ICurrentWeather } from '../interfaces'
...
export class WeatherService {
...
getCurrentWeather
(city: string, country: string): Observable<ICurrentWeather> {
}
...
}

Now, VS Code will let you know that Type Observable<ICurrentWeatherData> is not assignable to type Observable<ICurrentWeather>:

  1. Write a transformation function named transformToICurrentWeather that can convert ICurrentWeatherData to ICurrentWeather
  2. Also, write a helper function named convertKelvinToFahrenheit that converts the API provided Kelvin temperature to Fahrenheit:
src/app/weather/weather.service.ts

export class WeatherService {
...
private transformToICurrentWeather(data: ICurrentWeatherData): ICurrentWeather {
return {
city: data.name,
country: data.sys.country,
date: data.dt * 1000,
image: `http://openweathermap.org/img/w/${data.weather[0].icon}.png`,
temperature: this.convertKelvinToFahrenheit(data.main.temp),
description: data.weather[0].description
}
}

private convertKelvinToFahrenheit(kelvin: number): number {
return kelvin * 9 / 5 - 459.67
}
}

Note that you need to be converting the icon property to an image URL at this stage. Doing this in the service helps preserve encapsulation, binding the icon value to the URL in the view template will break the Separation of concerns (SoC) principle. If you wish to create truly modular, reusable, and maintainable components, you must remain vigilant and strict in terms of enforcing SoC. The documentation for Weather Icons and details of how the URL should be formed, including all the available icons can be found at http://openweathermap.org/weather-conditions.

On a separate note, the argument can be made that Kelvin to Fahrenheit conversion is actually a view concern, but we have implemented it in the service. This argument holds water, especially considering that we have a planned feature to be able to toggle between Celsius and Fahrenheit. A counter argument would be that at this time, we only need to display in Fahrenheit and it is part of the job of the weather service to be able to convert the units. This argument makes sense as well. The ultimate implementation will be to write a custom Angular Pipe and apply it in the template. A pipe can easily bind with the planned toggle button as well. However, at this time, we only need to display in Fahrenheit and I would err on the side of not over-engineering a solution.

  1. Update ICurrentWeather.date to the number type

While writing the transformation function, you will note that the API returns the date as a number. This number represents time in seconds since the UNIX epoch (timestamp), which is January 1st, 1970 00:00:00 UTC. However, ICurrentWeather expects a Date object. It is easy enough to convert the timestamp by passing it into the constructor of the Date object like new Date(data.dt). This is fine, but also unnecessary, since Angular's DatePipe can directly work with the timestamp. In the name of relentless simplicity and maximally leveraging the functionality of the frameworks we use, we will update ICurrentWeather to use number. There's also a performance and memory benefit to this approach if you're transforming massive amounts of data, but that concern is not applicable here. There's one caveat—JavaScript's timestamp is in milliseconds, but the server value is in seconds, so a simple multiplication during the transformation is still required.

  1. Import the RxJS map operator right below the other import statements:
src/app/weather/weather.service.ts
import { map } from 'rxjs/operators'

It may seem odd to have to manually import the map operator. RxJS is a very capable framework with a wide API surface. Observable alone has over 200 methods attached to it. Including all of these methods by default creates development time issues with too many functions to choose from and also, it negatively impacts the size of the final deliverable, including app performance and memory use. So you must add each operator you intend to use inpidually.

  1. Apply the map function to data stream returned by  httpClient.get method through a pipe
  2. Pass the data object into the transformToICurrentWeather function:
src/app/weather/weather.service.ts
...
return this.httpClient
.get<ICurrentWeatherData>(
`http://api.openweathermap.org/data/2.5/weather?q=${city},${country}&appid=${environment.appId}`
).pipe(
map(data =>
this.transformToICurrentWeather(data)
)
)
...

Now incoming data can be transformed as it flows through the stream, ensuring that the OpenWeatherMap Current Weather API data is in the correct shape, so it can be consumed by the CurrentWeather component.

  1. Ensure that your app compiles successfully
  2. Inspect the results in the browser:

Displaying Live Data from OpenWeatherMap

Finally, you should see that your app is able to pull live data from OpenWeatherMap and correctly transform server data into the format you expect.

You have completed the development of Feature 1Display Current Location weather information for the current day. Commit your code and move the card in Waffle to the Done column.

  1. Finally, we can move this task to the Done column:

Waffle.io Kanban Board Status