Passing Data with Angular Services

If you followed this Angularization series, you might have noticed that passing data using @input and @output decorators is not exactly scalable.

Using services, it is easier to store data and make it available in the application.

Building on the application that we created in Introduction to Angular Services we will add an input element so that users can add a string to a list of items. This is a way to pass data using Angular Services

The input element is in OneComponent, the list of items is stored in a service and it is displayed in AppComponent.

The final application looks like the one below:

As in Introduction to Angular Services, CSS is mainly omitted for clarity. However you can find the whole code on GitHub.

Creating a new Service: BookService

We start by creating a class inside a newly created file called book.service.ts inside src/app.

The class will get

  1. favBooks: a private property to store a list of book titles as objects
  2. getBooksList: a method that simply returns favBooks
  3. createBook: a method that checks that the title is not empty, create a book object and adds it to favBooks
// book.service.ts

import { Injectable } from '@angular/core';
import { Book } from './models';

@Injectable({ providedIn: 'root' })
export class BookService {
  private favBooks: Book[] = [
    { title: 'Principles' },
    { title: 'The Story of Success' },
    { title: 'Extreme Economies' },
  ];

  getBooksList() {
    return this.favBooks;
  }

  createBook(bookTitle: string) {
    // simple check, title must be at least 1 char
    if (bookTitle.length !== 0) {
      const bookObj = { title: bookTitle };
      this.favBooks.push(bookObj);
    }
  }
}

Notice that I created a Book type in src/models.ts.

Data from user input

We change OneComponent so that there is an input element where users can write something. Once the user clicks on Add Title, a method (onAddBook) is called. This method (in one.component.ts) calls BookService to create the book object and to add it to favBooks.

HTML template

// one.component.html

<div>
  <input 
    type="text" 
    placeholder="Write a title" 
    #titleInput />
  <button (click)="onAddBook()">Add Title</button>  
</div>

Notice that I am using #titleInput. This is used to get direct access to the element in the DOM and it is not a good practice. However, it is the easiest way to get input data from users and focus on the service itself.
Alternatively, use ngModel or consider Angular forms.

Class

one.component.ts is using ViewChild and ElementRef to get the value of the user input. As said above, this is not the best way to handle data but for the sake of simplicity we will discuss angular forms in another post.

What we need to know is that this.titleInputReference.nativeElement.value gets the user data from the input element.

// one.component.ts

import { Component, ElementRef, ViewChild } from '@angular/core';
import { BookService } from '../book.service';

@Component({
  selector: 'app-one',
  templateUrl: './one.component.html',
  styleUrls: ['./one.component.css'],
})
export class OneComponent {
  @ViewChild('titleInput')
  titleInputReference!: ElementRef;

  constructor(private bookService: BookService) {}

  onAddBook() {
    this.bookService.createBook(
      this.titleInputReference.nativeElement.value
    );
  }
}

onAddBook() calls the createBook method of bookService and passes the value of the input element i.e. a string the user typed into the input element.

Display data in another component: AppComponent

we start by receiving data from the service in AppComponent by declaring the service in the constructor and calling the service in ngOnInit()

// app.component.ts

import { Component, OnInit } from '@angular/core';
import { BookService } from './book.service';
import { Book } from './models';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements OnInit {
  title = 'Passing Data with Angular Services';
  booksList: Book[] | undefined;

  constructor(private bookService: BookService) {}

  ngOnInit(): void {
    this.booksList = this.bookService.getBooksList();
  }
}

Finally, we update AppComponent template to display a list of books

// app.component.html

<div>
  <h1>{{ title }}</h1>
  <hr />
  <div>
    <app-one></app-one>
    <app-two></app-two>
  </div>
  <hr />
  <div *ngIf="booksList" class="wrapper">
    <div *ngFor="let book of booksList" class="book">
      {{ book.title }}
    </div>
  </div>
</div>

In this way, we are passing data from a component to another via a service. The same service could provide data to any other component in the application or even get updates from anywhere in the app.

32