3

Creating a Basic Angular App

In this chapter, we'll design and build a simple Local Weather app using Angular and a third-party web API with an iterative development methodology. We'll focus on delivering value first while learning about the nuances and optimal ways of using Angular, TypeScript, Visual Studio (VS) Code, Reactive Programming, and RxJS. Before we dive into coding, we need to build a roadmap of features, create a mock-up of the application we intend to build, and diagram the high-level architecture of our app.

You'll be introduced to Angular fundamentals to build a simple web app and become familiar with the new Angular platform and full-stack architecture.

In this chapter, you are going to learn the following:

The most up-to-date versions of the sample code for the book are on GitHub at the repository linked below. The repository contains the final and completed state of the code. You can verify your progress at the end of this chapter by looking for the end-of-chapter snapshot of code under the projects folder.

For Chapter 3:

  1. Clone the repository https://github.com/duluca/local-weather-app.
  2. Execute npm install on the root folder to install dependencies.
  3. The code sample for this chapter is under the sub-folder:
    projects/ch3
    
  4. To run the Angular app for this chapter, execute:
    npx ng serve ch3
    

Beware that the source code in the book or on GitHub may not always match the code generated by Angular CLI. There may also be slight differences in implementation between the code in the book and what's on GitHub because the ecosystem is ever-evolving. It is natural for the sample code to change over time. Also, on GitHub, expect to find corrections, fixes to support newer versions of libraries, or side-by-side implementations of multiple techniques for the reader to observe. The reader is only expected to implement the ideal solution recommended in the book. If you find errors or have questions, please create an issue or submit a pull request on GitHub for the benefit of all readers.

You can read more about updating Angular in Appendix C, Keeping Angular and Tools Evergreen. You can find this appendix online from https://static.packt-cdn.com/downloads/9781838648800_Appendix_C_Keeping_Angular_and_Tools_Evergreen.pdf or at https://expertlysimple.io/stay-evergreen.

Let's start by creating a high-level plan to understand what to implement before you start coding.

Planning using Kanban and GitHub projects

Having a roadmap before getting on the road is critical in ensuring that you reach your destination. Similarly, building a rough plan of action before you start coding is very important in ensuring project success. Building a plan early on enables your colleagues or clients to be aware of what you're planning to accomplish. However, any initial plan is guaranteed to change over time.

Agile software development aims to account for the change of priorities and features over time. Kanban and Scrum are the two most popular methodologies that you can use to manage your project. Each methodology has a concept of a backlog and lists that capture planned, in progress, and completed work. A backlog, which contains a prioritized list of tasks, establishes a shared understanding of what needs to be worked on next. Lists that capture the status of each task act as information radiators, where stakeholders can get updates without interrupting your workflow. Whether you're building an app for yourself or someone else, keeping a live backlog and tracking the progress of tasks pays dividends and keeps the focus on the goal you're trying to achieve.

In implementing the Local Weather app, we are going to leverage a GitHub project to act as a Kanban board. In an enterprise, you can use ticketing systems or tools that can keep a backlog, implement the Scrum methodology, and display Kanban boards. In GitHub, issues represent your backlog. You can leverage the built-in Projects tab to define a scope of work that represents a release or a sprint to establish a Kanban board. A GitHub project directly integrates with your GitHub repository's issues and keeps track of the status of issues via labels. This way, you can keep using the tool of your choice to interact with your repository and still, effortlessly, radiate information. In the next section, you are going to set up a project to achieve this goal.

Setting up a GitHub project

Let's set up a GitHub project:

  1. Navigate to your GitHub repository in your browser.
  2. Switch over to the Projects tab.
  3. Click on Create a new project, as shown in the screenshot that follows:

    Figure 3.1: Creating a new project in GitHub

  4. Provide a name in the Project board name box.
  5. Select a Project template, such as Automated Kanban.
  6. Later in the book, we'll enable GitHub flow for your GitHub projects. With GitHub flow, changes to your repository are processed through Pull Requests (PR). In the future, you may want to select the Automated Kanban with reviews template, which automatically keeps track of the status of a PR, radiating more detailed information about the inner workings of the software development process.
  7. Click on Create project.

Observe your Kanban board, which should appear as follows:

Figure 3.2: The Kanban board for your project

If you have existing issues on your repository, you may be prompted to add cards to your board. You can safely ignore this for now and return to it with the + Add cards button. You are also presented with several To do cards. Feel free to review and dismiss these cards to clear out your board.

If you would like to keep track of every release or sprint, you can create a new project for each one. Creating new projects helps keep track of percentage completion for a given release or sprint, at the cost of introducing additional management overhead.

Next, we are going to configure the project as a Kanban board instead of a GitHub Project, which is a lightweight methodology to organize your work you might choose over other methodologies like Scrum.

Configuring a Kanban board

Kanban does not define formal iterations or releases of your work. If you would like to have a low-overhead process, where you only work with a single project, you can do this by introducing a Backlog column to your project.

Now let's add a Backlog column:

  1. Click on + Add column.
  2. For Column name enter Backlog.
  3. For Preset select To do.
  4. Under Move issues here when…, select Newly added, as shown here:
    A screenshot of a social media post  Description automatically generated

    Figure 3.3: Where to select "Newly added"

  5. Click on Create column.
  6. Drag the column to become the leftmost column.

With this setup, new issues are added to the Backlog, allowing you to manually maintain the items you intend to work on in the To do column.

Creating a backlog for the Local Weather app

Let's create a backlog of issues to keep track of your progress as you implement the design of your application. When creating issues, you should focus on delivering functional iterations that bring some value to the user.

The technical hurdles you must clear to achieve those results are of no interest to your users or clients.

Here are the features we plan to build in our first release:

Let's also add some features that we won't implement in this book as a way to demonstrate how a backlog can capture your ideas:

Feel free to add other features you can think of to your backlog.

Begin by creating the preceding features as issues on GitHub. Make sure to assign each new issue to the project you created earlier in the chapter. Once created, move the preceding defined features to the To do column. When you begin working on a task, move the card into the In progress column and when it's completed, move it to the Done column. The following is what the board looks like as we plan to begin working on the first feature – Display Current Location weather information for the current day:

Figure 3.4: A snapshot of the initial state of the board on GitHub

Note that I also added an issue to Create a mock-up for the app and moved it to Done, which is something I'll cover in the next section. Also, GitHub might automatically move a card from one state to another as you open and close them.

Ultimately, GitHub projects provide an easy-to-use GUI so that non-technical people can easily interact with GitHub issues. By allowing non-technical people to participate in the development process on GitHub, you unlock the benefits of GitHub becoming the single source of information for your entire project. Questions, answers, and discussions about features and issues are all tracked as part of GitHub issues, instead of being lost in emails. You can also store wiki-type documentation on GitHub. So, by centralizing all project-related information, data, conversations, and artifacts on GitHub, you are greatly simplifying the potentially complicated interaction of multiple systems that require continued maintenance at a high cost. For private repositories and on-premise enterprise installations, GitHub has a very reasonable cost. If you're sticking with open source, as we are in this chapter, all these tools are free.

As a bonus, I created a rudimentary wiki page on my repository at https://github.com/duluca/local-weather-app/wiki. Note that you can't upload images to README.md or wiki pages. To get around this limitation, you can create a new issue, upload an image in a comment, and copy and paste the URL for it to embed images to README.md or wiki pages. In the sample wiki, I followed this technique to embed the wireframe design into the page.

With a roadmap in place, you're now ready to create a mock-up of your application.

Wireframe design

There are some great tools out there to do rough-looking mock-ups to demonstrate your idea with surprising amounts of rich functionality. If you have a dedicated UX designer, such tools are great for creating quasi prototypes. However, as a full-stack developer, I find the best tool out there to be pen and paper. This way, you don't have to learn yet another tool (YAT), and it is a far better alternative to having no design at all. Putting things on paper saves you from costly coding detours down the line and if you can validate your wireframe design with users ahead of time, even better. My app is called LocalCast Weather, but get creative and pick your own name. Behold, the wireframe design for your weather app:

Figure 3.5: Hand-drawn wireframe for LocalCast. (Tip: I did use a ruler!)

The wireframe shouldn't be anything fancy. I recommend starting with a hand-drawn design, which is very quick to do and carries over the rough outlines effectively.

There are great wireframing tools out there. I suggest and use a couple of them throughout this book, however, in the first days of your project, every hour matters.

Granted, this kind of rough design may never leave the boundaries of your team, but please know that nothing beats getting that instantaneous feedback and collaboration by putting your ideas down on paper or a whiteboard.

High-level architecture

No matter how small or large your project is, it is critical to start with a sound architecture that can scale if duty calls. Most of the time, you can't accurately predict the size of your project ahead of time. Sticking to the architectural fundamentals discussed in Chapter 1, Introduction to Angular and Its Concepts, results in an architecture that is not overly burdensome, so you can quickly execute a simple app idea. The key is to ensure proper decoupling from the get-go.

In my view, there are two types of decoupling. One is soft-decoupling, where a "Gentlemen's Agreement" is made not to mix concerns and you try and not mess up the code base. This can apply to the code you write, all the way to infrastructure-level interactions. If you maintain your frontend code under the same code structure as your backend code, and if you let your REST server serve up your frontend application, then you are only practicing soft-decoupling.

You should instead practice hard-decoupling, which means frontend code lives in a separate repository, never calls the database directly, and is hosted on its web server altogether. This way, you can be sure that, at all times, your REST APIs or your frontend code is entirely replaceable and independent of other code. Practicing hard-decoupling has monetary and security benefits as well. The serving and scaling needs of your frontend application are guaranteed to be different from your backend, so you can optimize your host environment appropriately and save money. If you whitelist access to your REST APIs to only the calls originating from your frontend servers, you will vastly improve your security. Consider the following high-level architecture diagram for our LocalCast Weather app:

Figure 3.6: LocalCast high-level architecture

The high-level architecture shows that our Angular web application is completely decoupled from any backend. It is hosted on its web server, can communicate with a web API such as OpenWeatherMap, or optionally be paired with a backend infrastructure to unlock rich and customized features that a web API alone can't provide, such as storing per-user preferences or complementing the OpenWeatherMap API's dataset with our own.

Regardless of your backend technology, I recommend that your frontend always resides in its repository, and is served using its web server that does not depend on your API server.

In Chapter 10, RESTful APIs and Full-Stack Implementation, you'll deep-dive into learning how a MEAN stack application, using MongoDB, Express, Angular, and Node, comes together in practice.

Now that we have our features, wireframe designs, and high-level architecture in place, we can start implementing our app.

Crafting UI elements using components and interfaces

In Chapter 2, Setting Up Your Development Environment, you should have created an Angular application. We'll use that as our starting point. If you haven't done so, please go back to Chapter 2, Setting Up Your Development Environment, and create your project.

In this section, you'll leverage Angular components, interfaces, and services to build the current weather feature in a decoupled, cohesive, and encapsulated manner.

The landing page of an Angular app, by default, resides in app.component.html. So, start by editing the template of AppComponent with basic HTML, laying out the initial landing experience for the application.

We are now beginning the development of Feature 1: Display Current Location weather information for the current day so you can move the card in the Github project to the In progress column.

  1. Delete any existing code in the template file app.component.html
  2. Add a header as an h1 tag, followed by the tagline of our app as a div, and placeholders for where we may want to display the current weather, demonstrated as shown in the following code block:
    src/app/app.component.html
    <div style="text-align:center">
      <h1>
        LocalCast Weather
      </h1>
      <div>Your city, your forecast, right now!</div>
      <h2>Current Weather</h2>
      <div>current weather</div>
    </div>
    
  3. Remove the unused title property from the component class, so it's empty
    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 {}
    
  4. In the terminal, execute npm start
  5. Navigate to http://localhost:5000 on your browser

You should now be able to observe the changes you're making in real time in the browser.

Note that you should use the integrated terminal within VS Code to run commands, so you don't have to jump around different windows. Use [CTRL+`] on Windows or [^+`] on Mac to bring the terminal up. In case you're not familiar, ` is a backtick and is usually on the same key as ~ (tilde).

Adding an Angular component

We need to display the current weather information, where <div>current weather</div> is located. To achieve this, we need to build a component that is responsible for displaying the weather data.

The reason behind creating a separate component is an architectural best practice that is codified in the Model-View-ViewModel (MVVM) design pattern. You may have heard of the Model-View-Controller (MVC) pattern before. The vast majority of web-based code written circa 2005-2015 was written following the MVC pattern. MVVM differs from the MVC pattern in meaningful ways, as I explained in my 2013 article on DevPro:

An effective implementation of MVVM inherently enforces proper separation of concerns. Business logic is clearly separated from presentation logic. So, when a View is developed, it stays developed, because fixing a bug in one View's functionality doesn't impact other views. On the flip side, if [you use] visual inheritance effectively and [create] reusable user controls, fixing a bug in one place can fix issues throughout the application.

Angular provides a practical implementation of MVVM:

ViewModels neatly encapsulate any presentation logic and allow for simpler View code by acting as a specialized version of the model. The relationship between a View and ViewModel is straightforward, allowing more natural ways to wrap UI behavior in reusable user controls.

You can read more about the architectural nuance, with illustrations, at http://bit.ly/MVVMvsMVC.

Next, you create your very first Angular component, which includes the View and the ViewModel, using the Angular CLI's ng generate command:

  1. In the terminal, execute npx ng generate component current-weather

    Ensure that you are executing ng commands under the local-weather-app folder, and not under the parent folder where you initialized the project. Also, note that npx ng generate component current-weather can be rewritten as ng g c current-weather. This book utilizes the shorthand format going forward and expects you to prepend npx, if necessary.

  2. Observe the new files created in your app folder:
    src/app
    ├── app.component.css
    ├── app.component.html
    ├── app.component.spec.ts
    ├── app.component.ts
    ├── app.module.ts
    ├── current-weather
      ├── current-weather.component.css
      ├── current-weather.component.html
      ├── current-weather.component.spec.ts
      └── current-weather.component.ts
    

    A generated component has four parts:

    • current-weather.component.css contains any CSS that is specific to the component and is an optional file.
    • current-weather.component.html contains the HTML template that defines the look of the component and rendering of the bindings and can be considered the View, in combination with any CSS styles used.
    • current-weather.component.spec.ts contains Jasmine-based unit tests that you can extend to test your component functionality.
    • current-weather.component.ts contains the @Component decorator above the class definition and is the glue that ties together the CSS, HTML, and JavaScript code. The class itself can be considered the ViewModel, pulling data from services and performing any necessary transformations to expose sensible bindings for the View, shown as follows:
    src/app/current-weather/current-weather.component.ts
    import { Component, OnInit } from '@angular/core'
    @Component({
      selector: 'app-current-weather',
      templateUrl: './current-weather.component.html',
      styleUrls: ['./current-weather.component.css'],
    })
    export class CurrentWeatherComponent implements OnInit {
      constructor() {}
      ngOnInit() {}
    }
    

    If the component you're planning to write is a simple one, you can write it using inline styles and an inline template to simplify the structure of your code. If we were to rewrite the component above using inline templates and styles, it would look like the following example:

    example
    import { Component, OnInit } from '@angular/core'
    @Component({
      selector: 'app-current-weather', 
      template: `
      <p>
        current-weather works!
      </p>
      `,
      styles: []
    })
    export class CurrentWeatherComponent implements OnInit {
      constructor() {}
      ngOnInit() {}
    }
    

    However, we won't be inlining this template. So, keep your generated code as-is.

    When you executed the generate command, in addition to creating the component, the command also added the new component you created in the app's root module, app.module.ts, avoiding the otherwise tedious task of wiring up components together:

    src/app/app.module.ts
    ...
    import { 
      CurrentWeatherComponent 
    } from './current-weather/ current-weather.component'
    ...
    @NgModule({
    declarations: [
        AppComponent, 
        CurrentWeatherComponent
      ],
    ...
    

    The bootstrap process of Angular is, admittedly, a bit convoluted. This is the chief reason the Angular CLI exists. index.html contains an element named <app-root>. When Angular begins execution, it first loads main.ts, which configures the framework for browser use and loads the app module. The app module then loads all its dependencies and renders within the aforementioned <app-root> element. In Chapter 7, Creating a Router-First Line-of-Business App, when we build a line-of-business app, we create feature modules to take advantage of the scalability features of Angular.

    Now, we need to display our new component on the initial AppComponent template, so it is visible to the end user.

  3. Add the CurrentWeatherComponent to AppComponent by replacing <div>current weather</div> with <app-current-weather></app-current-weather>:
    src/app/app.component.html
    <div style="text-align:center">
      <h1>
        LocalCast Weather
      </h1>
      <div>Your city, your forecast, right now!</div>
      <h2>Current Weather</h2>
      <app-current-weather></app-current-weather>
    </div>
    
  4. If everything worked correctly, you should see this:

Figure 3.7: Initial render of your Local Weather app

Note the icon and name in the tab of the browser window. As a web development norm, in the index.html file, update the <title> tag and the favicon.ico file with the name and icon of your application to customize the browser tab information. If your favicon doesn't update, append the href attribute with a unique version number, such as href="favicon.ico?v=2". As a result, your app will start to look like a real web app, instead of a CLI-generated starter project.

Now that you have seen an Angular component in action, let's cover some basics of what is going on under the covers.

Demystifying Angular components

As discussed in Chapter 1, Introduction to Angular and Its Concepts, an Angular component is implemented as an ES2015 class, which allows us to leverage OOP concepts. Classes are traditionally present in strongly-typed languages, so it is excellent that JavaScript implements classes as a dynamically typed language. Classes allow us to group (encapsulate) functionality and behavior in self-contained units (objects). We can define the behavior in very generalized and abstract ways and implement an inheritance hierarchy to share and morph behavior into differing implementations.

Considering the CurrentWeatherComponent class that follows, I can highlight some benefits of classes:

@Component(...)
export class CurrentWeatherComponent implements OnInit {
  constructor() {}
  ngOnInit() {}
}

Unlike a function, you can't directly use code within a class. It must be instantiated as an object with the new keyword. This means that we can have multiple instances of any given class and each object can maintain its internal state. In this case, Angular instantiates a component for us behind the scenes. A constructor of a class is executed at the time of its instantiation. You can put any code that initializes other classes or variables inside a constructor. However, you shouldn't make an HTTP call or attempt to access DOM elements from a constructor. This is where the OnInit life cycle hook comes into play.

As Angular is initializing CurrentWeatherComponent as an object, it is also going through the entire graph of modules, components, services, and other dependencies to ensure all interdependent code is loaded into memory. During this time, Angular can't yet guarantee the availability of HTTP or DOM access. After all classes are instantiated, Angular goes through the classes that are decorated with @Component, implements the OnInit interface, and calls the ngOnInit function within our class. This is why we need to put any code that needs HTTP or DOM access during the first load of our component into ngOnInit.

Classes can have properties, variables, and functions. From an Angular template, you can access any property, variable, or function inside of an expression. The syntax of an expression looks like {{ expression }}, [target]="expression", (event)="expression" or *ngIf="expression".

Now you have a good understanding of how the code, or the ViewModel, behind the template, the View, is instantiated and how you can access that code from the template. In the next section, we'll build an interface, which is a contract that defines the shape of an object.

Defining your model using interfaces

Now that your View and ViewModel are in place, you need to define your model. If you look back on the design, you'll see that the component needs to display:

You first need to create an interface that represents this data structure. We are creating an interface instead of a class because an interface is an abstraction that does not contain any implementation. When creating touchpoints or passing data between various components, we can ensure a decoupled design if we rely on an abstract definition over an object that may implement unpredictable custom behavior, leading to bugs.

Start by creating the interface:

  1. In the terminal, execute npx ng generate interface ICurrentWeather
  2. Observe a newly generated file named icurrent-weather.ts with an empty interface definition that looks like this:
    src/app/icurrent-weather.ts
    export interface ICurrentWeather {
    }
    

    This is not an ideal setup, since we may add numerous interfaces to our app, and it can get tedious tracking down various interfaces. Over time, as you add concrete implementations of these interfaces as classes, it makes sense to put classes and their interfaces in their files.

    Why not just call the interface CurrentWeather? This is because, later on, we may create a class to implement some interesting behavior for CurrentWeather. Interfaces establish a contract, establishing the list of available properties on any class or interface that implements or extends the interface. It is always important to be aware of when you're using a class versus an interface. If you follow the best practice of always starting your interface names with a capital I, you will always be conscious of what type of object you are passing around. Hence, the interface is named ICurrentWeather.

  3. Rename icurrent-weather.ts to interfaces.ts
  4. Also, implement the interface as follows:
    src/app/interfaces.ts
    export interface ICurrentWeather {
      city: string
      country: string 
      date: Date 
      image: string
      temperature: number 
      description: string
    }
    

    This interface and its eventual concrete representation as a class is the Model in MVVM. So far, I have highlighted how various parts of Angular fit the MVVM pattern; going forward, I'll refer to these parts by their actual names.

    Now, we can import the interface into the component and start wiring up the bindings in the template of CurrentWeatherComponent.

  5. Import ICurrentWeather
  6. Switch back to templateUrl and styleUrls
  7. Define a local variable called current with type ICurrentWeather:
    src/app/current-weather/current-weather.component.ts
    import { Component, OnInit } from '@angular/core' 
    import { ICurrentWeather } from '../interfaces'
    @Component({
      selector: 'app-current-weather',
      templateUrl: './current-weather.component.html',
      styleUrls: ['./current-weather.component.css'],
    })
    export class CurrentWeatherComponent implements OnInit {
      current: ICurrentWeather
      constructor() {}
      ngOnInit() {}
    }
    

    If you just type current:ICurrentWeather, you can use the Auto Fixer in VS Code to automatically insert the import statement.

    In the constructor, you need to temporarily populate the current property with dummy data to test your bindings.

  8. Implement dummy data as a JSON object and declare its adherence to ICurrentWeather using the as operator:
    src/app/current-weather/current-weather.component.ts
    ...
    constructor() { 
      this.current = {
        city: 'Bethesda', 
        country: 'US', 
        date: new Date(),
        image: 'assets/img/sunny.svg', 
        temperature: 72,
        description: 'sunny',
      } as ICurrentWeather
    }
    ...
    

    In the src/assets folder, create a subfolder named img and place an image of your choice to reference in your dummy data.

    You may forget the exact properties in the interface you created. You can get a quick peek at them by holding Ctrl + hovering over the interface name with your mouse, as shown:

    Figure 3.8: Ctrl + hover over the interface

    Now, update the template to wire up your bindings with a basic HTML-based layout.

  9. Begin implementing the template:
    src/app/current-weather/current-weather.component.html
    <div>
      ...
    </div>
    
  10. Within the parent div, define another div to display the city and country information using binding:
    <div>
      <span>{{current.city}}, {{current.country}}</span>
      ...
    </div>
    

    Note that within the span, you can use static text to position the two properties. In this case, the city and country are separated by a comma, followed by a space.

  11. Below city and country, display the date using binding and a DatePipe to define a display format for the property:
    <span>{{current.date | date:'fullDate'}}</span>
    

    To change the display formatting of current.date, we used the DatePipe above, passing in 'fullDate' as the format option. In Angular, various out-of-the-box and custom pipe | operators can be used to change the appearance of data without actually changing the underlying data. This is a very powerful, convenient, and flexible system to share such user interface logic without writing repetitive boilerplate code.

    In the preceding example, we could pass in 'shortDate' if we wanted to represent the current date in a more compact form. For more information on various DatePipe options, refer to the documentation at https://angular.io/api/common/DatePipe.

  12. Define another div to display the temperature information, formatting the value using DecimalPipe and bind an image of the current weather to an img tag:
    <div>
      <img [src]='current.image'>
      <span>{{current.temperature | number:'1.0-0'}}˚F</span>
    </div>
    

    We bind the image property to the img tag's src attribute using the square bracket syntax. Next, we format current.temperature so that no fractional values are shown, using DecimalPipe. The documentation is at https://angular.io/api/common/DecimalPipe.

    Note that you can render ˚C and ˚F using their respective HTML codes: &#8451; for ˚C and &#8457; for ˚F.

  13. Create a final div to display the description property:
    <div>
      {{current.description}}
    </div>
    
  14. Your final template should look as follows:
    src/app/current-weather/current-weather.component.html
    <div>
      <div>
        <span>{{current.city}}, {{current.country}}</span>
        <span>{{current.date | date:'fullDate'}}</span>
      </div>
      <div>
        <img [src]='current.image'>
        <span>{{current.temperature | number:'1.0-0'}}˚F</span>
      </div>
      <div>
        {{current.description}}
      </div>
    </div>
    
  15. If everything worked correctly, your app should be looking similar to this screenshot:

Figure 3.9: App after wiring up bindings with dummy data

Congratulations – you have successfully wired up your first component!

Now let's update the app so that we can pull live weather data from a Web API.

Using Angular Services and HttpClient to retrieve data

Now you need to connect your CurrentWeather component to the OpenWeatherMap APIs to pull live weather data. However, we don't want to insert this code directly into our component. If we did this, we would have to update the component if the API changed. Now imagine an app with dozens or hundreds of views and imagine how this would create a significant maintainability challenge.

Instead, we'll leverage an Angular service, a singleton class, which can provide the current weather information to our component and abstract away the source of the data. The abstraction decouples the UI from the Web API. Leveraging this separation of concerns, in the future, we could enhance our service to pull from multiple APIs or a local cache to load weather information without having to change the UI code.

In the upcoming sections, we'll go over the following steps to accomplish this goal:

  1. Creating a new Angular service
  2. Importing HttpClientModule and injecting it into the service
  3. Discovering the OpenWeatherMap API
  4. Creating a new interface that conforms to the shape of the API
  5. Writing a get request
  6. Injecting the new service into the CurrentWeather component
  7. Calling the service from the ngOnInit function of the CurrentWeather component
  1. Finally, mapping the API data to the local ICurrentWeather type using RxJS functions so that your component can consume it

Creating a new Angular service

Any code that goes outside of the boundaries of a component should exist in a service; this includes inter-component communication (unless there's a parent-child relationship), API calls of any kind, and any code that caches or retrieves data from a cookie or the browser's localStorage. This is a critical architectural pattern that keeps your application maintainable in the long term. I expand upon this idea in my DevPro MVVM article at link https://www.itprotoday.com/microsoft-visualstudio/mvvm-and-net-great-combo-web-application-development.

To create an Angular service, use the Angular CLI:

  1. In the terminal, execute npx ng g s weather --flat false
  1. Observe the new weather folder that's created:
    src/app
    ...
    └── weather
    ├── weather.service.spec.ts
    └── weather.service.ts
    

A CLI-generated service has two parts:

The service is generated as shown here:

src/app/weather/weather.service.ts
import { Injectable } from '@angular/core'
@Injectable({
  providedIn: 'root',
})
export class WeatherService {
  constructor() {}
}

Note that the providedIn property ensures that the root module provides the weather service in app.module.ts.

Next, let's see the dependency injection mechanism in Angular, which allows services and modules to be used by other services, components, or modules without the developer having to manage the instantiation of the shared objects.

Injecting dependencies

To make API calls, you need to leverage the HttpClient module in Angular. The official documentation (https://angular.io/guide/http) explains the benefits of this module succinctly:

"With HttpClient, @angular/common/http provides a simplified API for HTTP functionality for use with Angular applications, building on top of the XMLHttpRequest interface exposed by browsers. Additional benefits of HttpClient include testability support, strong typing of request and response objects, request and response interceptor support, and better error handling via APIs based on Observables."

Let's start by importing the HttpClientModule into our app so we can inject the HttpClient provided by the module into the WeatherService:

  1. Add HttpClientModule to app.module.ts, as follows:
    src/app/app.module.ts
    ... 
    import { HttpClientModule } from '@angular/common/http' 
    ... 
    @NgModule({ 
      ... 
      imports: [..., HttpClientModule]
      ...
    }) 
    
  2. Inject HttpClient, provided by the HttpClientModule in the WeatherService, as follows:
    src/app/weather/weather.service.ts
    import { HttpClient } from '@angular/common/http'
    import { Injectable } from '@angular/core'
    @Injectable()
    export class WeatherService {
      constructor(private httpClient: HttpClient) {}
    }
    

Now, httpClient is ready for use in your service.

Discovering OpenWeatherMap APIs

Since httpClient is strongly typed, we need to create a new interface that conforms to the shape of the API we'll call. To be able to do this, you need to familiarize yourself with the Current Weather Data API:

  1. Read the documentation by navigating to http://openweathermap.org/current:

    Figure 3.10: OpenWeatherMap Current Weather Data API documentation

    You need to use the API named By city name, which allows you to get current weather data by providing the city name as a parameter so that our web request looks as follows:

    api.openweathermap.org/data/2.5/weather?q={city name},{country code}
    
  2. On the documentation page, click on the link under Example of API calls, and you will see a sample response like the following:
    http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b1b15e88fa797225412429c1c50c122a1
    {
      "coord": { 
        "lon": -0.13,
        "lat": 51.51
      },
      "weather": [
        {
          "id": 300,
          "main": "Drizzle",
          "description": "light intensity drizzle",
          "icon": "09d"
        }
      ],
      "base": "stations",
      "main": {
        "temp": 280.32,
        "pressure": 1012,
        "humidity": 81,
        "temp_min": 279.15,
        "temp_max": 281.15
      },
      "visibility": 10000,
      "wind": {
        "speed": 4.1,
        "deg": 80
      },
      "clouds": {
      "all": 90
      },
      "dt": 1485789600,
      "sys": {
        "type": 1,
        "id": 5091,
        "message": 0.0103,
        "country": "GB",
        "sunrise": 1485762037,
        "sunset": 1485794875
      },
      "id": 2643743,
      "name": "London",
      "cod": 200
    }
    

    Given the existing ICurrentWeather interface that you have already created, this response contains more information than you need. You need to write a new interface that conforms to the shape of this response, but only specify the pieces of data you intend to use. This interface only exists in the WeatherService and we won't export it since the other parts of the application don't need to know about this type.

  3. Create a new interface named ICurrentWeatherData in weather.service.ts between the import and @Injectable statements
  4. The new interface should like this:
    src/app/weather/weather.service.ts
    interface ICurrentWeatherData { 
      weather: [{
        description: string,
        icon: string
      }],
      main: {
        temp: number
      },
      sys: {
        country: string
      },
      dt: number,
      name: string
    }
    

With the ICurrentWeatherData interface, we are defining new anonymous types by adding children objects to the interface with varying structures. Each of these objects can be individually extracted out and defined as their own named interface. Especially note that weather is an array of the anonymous type that has the description and icon properties.

Next, let's learn how you can introduce environment variables into your Angular application, so the test and production versions of your app can rely on different values.

Storing environment variables

It's easy to miss, but the sample URL in the previous sections—http://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=b1b15e88fa797225412429c1c50c122a1—contains a required appid parameter. You must store this key in your Angular app. You can store it in the weather service, but in reality, applications need to be able to target different sets of resources as they move from development to testing, staging, and production environments. Out of the box, Angular provides two environments: one prod and the other one as the default.

Before you can continue, you need to sign up for a free OpenWeatherMap account and retrieve your appid. You can read the documentation for appid at http://openweathermap.org/appid for more detailed information.

  1. Copy your appid, which is a long string of characters and numbers
  2. Store your appid in environment.ts
  3. Configure baseUrl for later use:
    src/environments/environment.ts
    export const environment = {
      production: false,
      appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
      baseUrl: 'http://',
    }
    

In code, we use a camel-case appId to keep our coding style consistent.

Since URL parameters are case-insensitive, appId works as well as appid.

Next, let's implement an HTTP GET to get the current weather data.

Implementing an HTTP GET operation

Now, we can implement the GET call in the WeatherService class:

  1. Add a new function to the WeatherService class named getCurrentWeather
  2. Import the environment object
  3. Implement the httpClient.get function
  4. Return the results of the HTTP call:
    src/app/weather/weather.service.ts
    import { HttpClient } from '@angular/common/http'
    import { environment } from '../../environments/environment'
    ...
    export class WeatherService {
      constructor(
        private httpClient: HttpClient
      ) { }
    getCurrentWeather(city: string, country: string) { 
      return this.httpClient
        .get<ICurrentWeatherData>(
        `${environment.baseUrl}api.openweathermap.org/data/2.5/weather?` +
            `q=${city},${country}&appid=${environment.appId}`
        )
      }
    }
    

    Note the use of ES2015's String Interpolation feature. Instead of building your string by appending variables to one another like environment.baseUrl + 'api.openweathermap.org/data/2.5/weather?q=' + city + ',' + country + '&appid=' + environment.appId, you can use the backtick syntax to wrap `your string`. Inside the backticks, you can have newlines and directly embed variables in the flow of your string by wrapping them with the ${dollarbracket} syntax. However, when you introduce a newline in your code, it is interpreted as a literal newline \n. To break up the string in your code, you can add a backslash \, but then the next line of your code can have no indentation. It is easier to just concatenate multiple templates, as shown in the preceding code sample.

    Using a long and complicated string is an error-prone process. Instead, we can use the HttpParams object to build the URL programmatically.

  5. Leverage HttpParams to simplify the URL:
    src/app/weather/weather.service.ts
    import { HttpClient, HttpParams } from '@angular/common/http'
    import { environment } from '../../environments/environment'
    ...
    export class WeatherService {
      constructor(private httpClient: HttpClient) { }
      getCurrentWeather(city: string, country: string) {
        const uriParams = new HttpParams()
          .set('q', `${city},${country}`)
          .set('appid', environment.appId)
        return this.httpClient
          .get<ICurrentWeatherData>(
        `${environment.baseUrl}api.openweathermap.org/data/2.5/weather`,
            { params: uriParams }
          )
      }
    }
    

Now let's connect the dots so that we can get the current weather data from the CurrentWeather component leveraging the Weather service.

Retrieving service data from a component

To be able to use the getCurrentWeather function in the CurrentWeather component, you need to inject the service into the component:

  1. Inject the WeatherService into the constructor of the CurrentWeatherComponent class
  2. Remove the existing code that created the dummy data in the constructor:
    src/app/current-weather/current-weather.component.ts
    constructor(private weatherService: WeatherService) { }
    

    Note the use of TypeScript generics with the get function using the caret syntax, like <TypeName>. Using generics is a development-time quality-of-life feature. By providing the type information to the function, input and/or return variable types of that function display as you write your code and are validated during development and also at compile time.

  3. Call the getCurrentWeather function inside the ngOnInit function:
    src/app/current-weather/current-weather.component.ts
    ngOnInit() { 
      this.weatherService.getCurrentWeather('Bethesda', 'US')
        .subscribe((data) => this.current = data)
    }
    

Fair warning: do not expect this code to be working just yet, because data is of type ICurrentWeatherData and current is of type ICurrentWeather. You can observe the error, which should say "error TS2322: Type 'Observable<ICurrentWeatherData>' is not assignable to type 'Observable<ICurrentWeather>'." Let's look at what's goes in the next segment.

Angular components have a rich collection of life cycle hooks that allow you to inject your custom behavior when a component is being rendered, refreshed, or destroyed. ngOnInit() is the most common life cycle hook you're going to use. It is only called once, when a component is first instantiated or visited. This is where you want to perform your service calls. For a deeper understanding of component life cycle hooks, check out the documentation at https://angular.io/guide/lifecycle-hooks.

Note that the anonymous function you have passed to subscribe is an ES2015 arrow function. If you're not familiar with arrow functions, it may be confusing at first. Arrow functions are quite elegant and simple. Consider the following arrow function:

(data) => { this.current = data }

You can rewrite it simply as:

function(data) { this.current = data }

There's a special condition—when you write an arrow function that transforms a piece of data, such as:

(data) => { data.main.temp }

This function effectively takes ICurrentWeatherData as an input and returns the temp property. The return statement is implicit. If you rewrite it as a regular function, it looks as follows:

function(data) { return data.main.temp }

When the CurrentWeather component loads, ngOnInit fires once, which calls the getCurrentWeather function, which returns an object with the type Observable<ICurrentWeatherData>.

An Observable is the most basic building block of RxJS and represents an event emitter, which emits any data received over time with the type of ICurrentWeatherData as described in the official documentation.

The Observable object by itself is benign and won't send a request over the network unless it is being listened to. You can read more about Observables at https://reactivex.io/rxjs/class/es6/Observable.js~Observable.html.

By calling .subscribe on the Observable, you're essentially attaching a listener to the emitter. You've implemented an anonymous function within the subscribe method, which gets executed whenever a new piece of data is received and an event is emitted. The anonymous function takes a data object as a parameter, and the specific implementation, in this case, assigns the piece of data to the local variable named current. Whenever current is updated, the template bindings you implemented earlier pull in the new data and render it on the View. Even though ngOnInit executes only once, the subscription to the Observable persists. So, whenever there's new data, the current variable updates and the View rerenders to display the latest data.

The root cause of the error at hand is that the data that is being emitted is of type ICurrentWeatherData; however, our component only understands data that is shaped as described by the ICurrentWeather interface. In the next section, you'll need to dig deeper into RxJS to understand how best to accomplish that task.

Beware, VS Code and the CLI sometimes stop working. As previously noted, as you code, the npm start command is running in the integrated terminal of VS Code. The Angular CLI, in combination with the Angular Language Service plugin, continuously watches for code changes and transpiles your TypeScript code to JavaScript so that you can observe your changes with live-reloading in the browser. The great thing is that when you make coding errors, in addition to the red underlining in VS Code, you also see some red text in the terminal, or even the browser, because the transpilation has failed. In most cases, when correcting the error, the red underlining goes away and the Angular CLI automatically re-transpiles your code and everything works. However, in specific scenarios, note that VS Code fails to pick typing changes in the IDE so that you won't get autocompletion help, or the CLI tool may get stuck with a message saying webpack: Failed to compile.

You have two main strategies to recover from such conditions:

  • Click on the terminal and hit Ctrl + C to stop running the CLI task and restart by executing npm start.
  • If that doesn't work, quit VS Code with Alt + F4 for Windows or + Q for macOS and restart it. Given Angular and VS Code's monthly release cycles, I'm confident that in time the tooling will only improve.

Let's resolve the type mismatch issue by transforming the shape of the data.

Transforming data using RxJS

We are going to use an RxJS reactive pipe (or data stream) to reshape the structure of data coming from the external API to fit the shape of the data we expect within our Angular app. If we don't do this, then our code will fail due to a type mismatch error.

Refer to Chapter 1, Introduction to Angular and Its Concepts, to get a deeper understanding of RxJS and reactive programming.

Implementing Reactive transformations

To avoid future mistakes such as returning an unintended type of data from your service, you need to update the getCurrentWeather function to define the return type as 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 lets you know that the type Observable<ICurrentWeatherData> is not assignable to the 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
      }
    }
    
  3. Update ICurrentWeather.date to the number type

    While writing the transformation function, note that the API returns the date as a number. This number represents the amount of time in seconds since the Unix epoch (timestamp), which is January 1, 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 a 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 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.

  4. 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 capable framework with a wide API surface. An 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 negatively impacts the size of the final deliverable, including app performance and memory use. You must add each operator you intend to use individually.

  5. Apply the map function to the data stream returned by the httpClient.get method through a pipe
  6. Pass the data object into the transformToICurrentWeather function:
    src/app/weather/weather.service.ts
    ...
    return this.httpClient
      .get<ICurrentWeatherData>(
        `${environment.baseUrl}api.openweathermap.org/data/2.5/weather`,
        { params: uriParams }
      )
      .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 that the CurrentWeather component can consume it.

  7. Ensure that your app compiles successfully
  8. Inspect the results in the browser:

    Figure 3.11: Displaying live data from OpenWeatherMap

    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 1: Display Current Location weather information for the current day. Commit your code!

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

    Figure 3.12: GitHub project Kanban board status

Great work! You're now familiar with the fundamental architecture of Angular. You also started implementing code in the reactive paradigm by leveraging RxJS.

Now let's increase the resiliency of our app by guarding against null or undefined values that can break your application code.

Null guarding in Angular

In JavaScript, the undefined and null values are a persistent issue that must be proactively dealt with every step of the way. This is especially critical when dealing with external APIs and other libraries. If we don't deal with undefined and null values, then your app may present badly rendered views, console errors, issues with business logic, or even a crash of your entire app.

There are multiple strategies to guard against null values in Angular:

You may use one or more of these strategies. However, in the next few sections I demonstrate why the *ngIf strategy is the optimal one to use.

To simulate the scenario of getting an empty response from the server, go ahead and comment out the getCurrentWeather call in ngOnInit of CurrentWeatherComponent:

src/app/current-weather/current-weather.component.ts
ngOnInit(): void {
    // this.weatherService
    //   .getCurrentWeather('Bethesda', 'US')
    //   .subscribe(data => (this.current = data))
}

Let's start with implementing the property initialization strategy to guard against null values.

Property initialization

In statically-typed languages such as Java, it is drilled into you that proper variable initialization/instantiation is the key to error-free operation. So, let's try that in CurrentWeatherComponent by initializing current with default values:

src/app/current-weather/current-weather.component.ts
constructor(private weatherService: WeatherService) { 
  this.current = {
    city: '',
    country: '', 
    date: 0, 
    image: '',
    temperature: 0, 
    description: '',
  }
}

The outcome of these changes reduces the number of console errors from two to zero. However, the app itself is not in a presentable state, as you can see here:

Figure 3.13: Results of property initialization

To make this View presentable to the user, we have to code with default values on every property on the template. So, by fixing the null guarding issue with initialization, we created a default value handling issue. Both the initialization and the default value handling are O(n) scale tasks for developers. At its best, this strategy is annoying to implement and at its worst, highly ineffective and error-prone, requiring, at a minimum, O(2n) effort per property.

Next, let's learn about Angular's safe navigation operator, which comes in handy when dealing with objects that are external to our application when we can't control which properties may be null or undefined.

The safe navigation operator

Angular implements the safe navigation operation, ?., to prevent unintended traversals of undefined objects. So, instead of writing initialization code and having to deal with template values, we can just update the template.

Remove the property initialization code from the constructor and instead update the template as shown:

src/app/current-weather/current-weather.component.html
<div>
  <div>
    <span>{{current?.city}}, {{current?.country}}</span>
    <span>{{current?.date | date:'fullDate'}}</span>
  </div>
  <div>
    <img [src]='current?.image'>
    <span>{{current?.temperature}}</span>
  </div>
  <div>
    {{current?.description}}
  </div>
</div>

This time, we didn't have to make up defaults, and we let Angular deal with displaying undefined bindings. The app itself is in somewhat better shape. There's no more confusing data being displayed; however, it still is not in a presentable state, as shown here:

Figure 3.14: Results of using the safe navigation operator

You can probably imagine ways in which the safe navigation operator could come in handy, in far more complicated scenarios. However, when deployed at scale, this type of coding still requires, at a minimum, O(n) level of effort to implement.

When presenting data to the user, we don't want to present empty values. The easiest way to clean up the UI would be to leverage the ngIf directive to hide the entire div.

Null guarding with *ngIf

The ideal strategy is to use *ngIf, which is a structural directive, meaning Angular stops traversing DOM tree elements beyond a falsy statement.

In the CurrentWeather component, we can easily check to see whether the current variable is null or undefined before attempting to render the template:

  1. Undo the implementation of the safe navigation operators from the previous section
  2. Update the topmost div element with *ngIf to check whether current is an object, as shown:
    src/app/current-weather/current-weather.component.html
    <div *ngIf="!current">
      no data
    </div>
    <div *ngIf="current">
    ...
    </div>
    

    Now observe the console log and that no errors are being reported. You should always ensure that your Angular application reports zero console errors. If you're still seeing errors in the console log, ensure that you have correctly reverted the OpenWeather URL to its correct state or kill and restart your npm start process. I highly recommend that you resolve any console errors before moving on.

  3. Observe that the UI will now show that there's no data:

    Figure 3.15: Results of using null guarding with *ngIf

  4. Re-enable the getCurrentWeather call in ngOnInit of CurrentWeatherComponent:
    src/app/current-weather/current-weather.component.ts
    ngOnInit(): void {
      this.weatherService
          .getCurrentWeather('Bethesda', 'US')
          .subscribe(data => (this.current = data))
    }
    
  5. Commit your changes.

With null guarding, you can ensure that your UI always looks professional.

Summary

Congratulations! In this chapter, you created your first Angular application with a flexible architecture while avoiding over-engineering. This was possible because we first built a roadmap and codified it in a Kanban board that is visible to your peers and colleagues. We stayed focused on implementing the first feature we put in progress and didn't deviate from the plan.

You learned how to avoid coding mistakes by proactively declaring the input and return types of functions and working with generic functions. You used the date and decimal pipes to ensure that data is formatted as desired while keeping formatting-related concerns mostly in the template, where this kind of logic belongs.

Finally, you used interfaces to communicate between components and services without leaking the external data structure to internal components. By applying all these techniques in combination, which Angular, RxJS, and TypeScript allowed us to do, you ensured proper separation of concerns and encapsulation. As a result, the CurrentWeather component is now truly reusable and composable; this is not an easy feat to achieve.

If you don't ship it, it never happened. In the next chapter, we'll prepare this Angular app for a production release by troubleshooting application errors, ensuring automated unit and e2e tests pass, and containerizing the Angular app with Docker so that it can be published on the web.

Further reading

Questions

Answer the following questions as best as you can to ensure that you've understood the key concepts from this chapter without Googling. Do you need help answering the questions? See Appendix D, Self-Assessment Answers online at https://static.packt-cdn.com/downloads/9781838648800_Appendix_D_Self-Assessment_Answers.pdf or visit https://expertlysimple.io/angular-self-assessment.

  1. I introduced the concept of a Kanban board. What is it, and what role does a Kanban board play in our software application development?
  2. What were the different Angular components we generated using the Angular CLI tool to build out our Local Weather app after we initially created it, and what function and role do each of them serve?
  3. What are the different ways of binding data in Angular?
  4. Why do we need services in Angular?
  5. What is an observable in RxJS?
  6. What is the easiest way to present a clean UI if the data behind your template is falsy?