3

Angular’s DataLayer

 1 year ago
source link: https://devm.io/angular/angular-datalayer-tutorial
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Angular Tutorial – Part 3

Angular’s DataLayer

16. Aug 2022


The third part of this series is about Angular Services. We’ll see how we can use a service in a component and look at the basic mechanisms of dependency injection.

In Part 2, we already learned about the most important basic building blocks of an Angular application. We learned how to use properties and event binding to exchange data between a parent component and a child component. An Angular pipe also helps us transform the data that will be displayed in our UI. You can also implement your own pipes in addition to the ones that Angular already provides. We took advantage of this to implement filtering for our books in our sample project—an app for managing books.

In the last part, we learned about structural directives. This allowed us to iterate over our book list and generate a separate HTML node for each list entry. In the process, we learned about terms like container component and what we call dumb components. Our book.component.ts is our container component, since it holds the book list as a condition:

books = [{ title:'Moby Dick', author: 'Herman Melville'}, {...}, {...}]

But as you can imagine, creating the data statically in a component isn’t useful. This is because it’s very difficult for a component that isn’t a direct child or parent component to access this data. So we need a way to access data from a central location: a separate service.

A service is an essential part of the data layer of any Angular application. Just like a component, the service is a simple TypeScript class, for now. It can provide functions and data that can be called by Angular components.

In the following, we’ll mainly focus on the concepts of an Angular Service. We’ll learn how to use a service within a component, and by doing that, we’ll take a closer look at the basic mechanisms of dependency injection in an Angular application.

Inversion of Control

A service is nothing more than a TypeScript class. For example, we could simply create a new book-api.service.ts and implement a function there that would return the book list (Listing 1). To use this service, we could create an instance of this class in our component (Listing 2).

Listing 1

export class BookApiService {
  books = [{ title:'Moby Dick', author: 'Herman Melville'}, {...}, {...}];


  getAll(): Book[] {
    return this.books;
  }
}

Listing 2

import { Component } from '@angular/core';


@Component({
  ...
})
export class BookComponent {
  private bookApiService: BookApiService;
  constructor() {
    this.bookApiService = new BookApiService();
  }
}

Unfortunately, this approach leads to some difficulties. Creating dependencies directly causes increasingly cluttered code that’s difficult to maintain. For example, if we make adjustments to the constructor of our service, then we have to adjust all components that use the service too.

Another problem is that each component holds its own object instance. This means that if four components need our BookApiService as a dependency and create it themselves as an object, four instances of the service will also end up in the application.

In our particular use case, we just want to get a book list, so this is not a problem. But, we’ll also encounter cases where a component passes data to the service and then all components will be expected to receive this updated data. Of course, this won’t work if each component has a different instance.

Both problems can be avoided by handing over responsibility for creating dependencies to a higher-level entity. In our specific case, this means that no component will be allowed to create a service with the keyword new anymore. Instead, the Angular framework will take care of creating the instances.

This principle is also called Inversion of Control (IoC). The responsibility for creating an object is reversed and is no longer imposed upon the component. The component only requires its dependencies, which are automatically provided by Angular. For this purpose, Angular implements the Dependency Injection design pattern. It is also referred to as injecting all dependencies into the components. If you want to learn more about Inversion of Control and Dependency Injection, you can read Martin Fowler’s articles [1].

The two questions we now face are:

  • How do I request a dependency in the component?
  • How do I register my dependencies in an Angular application?

Request dependencies in a component

To request a dependency within a component, we use what is called constructor injection. That means that we tell Angular which dependencies we need by defining the required classes as arguments in the component's constructor (Listing 3).

Listing 3

import { Component } from '@angular/core';


@Component({...})
export class BookComponent {
  private bookApiService: BookApiService;


  constructor(bookApiService: BookApiService) {
    this.bookApiService = bookApiService;
  }
}

By specifying the TypeScript type BookApiService, Angular recognizes that the component needs the instance of this class. Angular looks for this instance in all the dependencies already registered and then returns it accordingly—but more on this later.

Within the constructor, we then assign the obtained instance to a property so that we can use the service for all components. There is also a short TypeScript form that provides exactly the same result with a few lines of code (Listing 4).

Listing 4

import { Component } from '@angular/core';


@Component({...})
export class BookComponent {
  constructor(private readonly bookApiService: BookApiService) {
  }
}

Specifying the visibility modifier (public or private) automatically creates a matching property within the component class and assigns the instance to the BookApiService class.

Additionally, it’s useful to provide the property with a readonly keyword, which makes sure that you don’t accidentally overwrite the bookApiService property within the component.

Creating a service

In the next step, we’ll create an Angular service in our project using the Angular CLI. Since this service provides us with the book list, it makes sense to create it in a services folder below the books folder:

> ng generate service book/services/book-api

This creates the book-api.service.ts file; its contents are shown in Listing 5.

Listing 5

import { Injectable } from '@angular/core';


@Injectable({
  providedIn: 'root'
})
export class BookApiService {
  constructor() { }
}

As mentioned earlier, a service is a simple TypeScript class. Here, we meet another decorator: Injectable. In order to inject a dependency like our BookApiService into another class via constructor injection, the class needs this decorator. Additionally, our service—like every class—also has a constructor. If needed, this can also inject dependencies.

If we add a getAll function to the service, which now returns the book list, we get it in our component. To do this, we need to inject BookApiService into our BookComponent (Listing 6).

Listing 6

@Component({
  ...
})
export class BookComponent implements OnInit {
  books: Book[];


  constructor(private readonly bookApiService: BookApiService) { 
    this.books = this.bookApiService.getAll();
  }
}

When we start our application again, we don’t see any visual changes (Fig. 1). However, this time the books are loaded by the _BookApiService _in the background. So far, the books are only available there statically - in a property. But our goal is to get them to a backend via an HTTP request.

Fig. 1

Fig. 1: The books are loaded from the service

For dependency injection to work via the constructor, Angular needs to know which classes are available—in other words, which instances have been registered in Angular.

But we were already able to use our service as a dependency without consciously registering it. How did that work?

Register dependencies

Each dependency must be registered in an Angular application. This is called providing a service. There are two ways to register or provision a service:

  • a service registers itself independently or
  • a service is registered directly and explicitly within a module.

Let’s look at both ways and their advantages and disadvantages.

The service registers itself

In this concept, introduced in Angular version 6, a service registers itself within its Injectable decorator. Let's look at this decorator ...


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK