Angular Book List App: Step-by-Step Beginner Tutorial

Build a simple Angular book list app step by step using components, services, forms, data binding and TypeScript.

One of the best ways to understand Angular is to build a small application instead of learning every concept separately.

In this beginner-friendly tutorial, we'll build a simple Book List application. The application will display books and allow the user to add a new book using a form.

While building it, you'll learn several important Angular concepts including components, services, interfaces, dependency injection, data binding, forms and TypeScript.

What We Are Going to Build

Our application will have two basic features:

  • Display a list of books
  • Add a new book to the list

Each book will contain:

  • ID
  • Title
  • Author
  • Description

The basic application flow will be:

User
  ↓
Book Component
  ↓
Book Service
  ↓
Book Data
  ↓
Component Template

Prerequisites

Before starting, make sure you have:

  • Node.js installed
  • npm available
  • Angular CLI installed
  • A code editor such as Visual Studio Code
  • Basic HTML and TypeScript knowledge

You can verify Node.js and npm with:

node --version
npm --version

Step 1: Install Angular CLI

If Angular CLI is not installed, run:

npm install -g @angular/cli

Then verify the installation:

ng version

Step 2: Create the Angular Project

Open a terminal and run:

ng new bookstore

Angular CLI may ask configuration questions such as whether you want routing and which stylesheet format you prefer.

After the project is created:

cd bookstore
ng serve

Open the local address displayed by Angular CLI in your browser. During development this is commonly:

http://localhost:4200

If the Angular starter application appears, your setup is working.

Step 3: Understand the Project Structure

Most of our work will happen inside the src/app directory.

We will create a structure similar to:

src/
└── app/
    ├── components/
    │   └── book/
    ├── models/
    │   └── book.ts
    ├── services/
    │   └── book.service.ts
    └── app.component.*

Step 4: Create the Book Model

Instead of using unstructured JavaScript objects everywhere, we'll define the shape of a book using a TypeScript interface.

Run:

ng generate interface models/book

Open:

src/app/models/book.ts

Replace its contents with:

export interface Book {
  id: number;
  title: string;
  author: string;
  description: string;
}

Now TypeScript knows exactly which properties a Book should contain.

Why Use a TypeScript Interface?

Interfaces help catch mistakes while developing the application.

For example:

const book: Book = {
  id: 1,
  title: 'Learning Angular',
  author: 'John Smith',
  description: 'Angular for beginners'
};

If a required property is missing or has an incompatible type, TypeScript can report the problem before the application reaches the user.

Step 5: Create the Book Service

Angular services are commonly used to keep data-access and reusable application logic outside UI components.

Generate a service:

ng generate service services/book

Open:

src/app/services/book.service.ts

Add:

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

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

  private books: Book[] = [
    {
      id: 1,
      title: 'Learning Angular',
      author: 'John Smith',
      description: 'A beginner-friendly Angular book'
    },
    {
      id: 2,
      title: 'TypeScript Basics',
      author: 'Jane Doe',
      description: 'Learn the fundamentals of TypeScript'
    }
  ];

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

  addBook(book: Book): void {
    this.books.push(book);
  }
}

What Does the Service Do?

The service has two methods:

getBooks()

returns the current collection of books.

And:

addBook(book)

adds a new book to the collection.

For this beginner example, the data exists only in memory. Refreshing the browser will reset it.

A real application could later replace this in-memory data with an API and database.

Step 6: Create the Book Component

Generate a component:

ng generate component components/book

Angular CLI will create the component files inside the components/book directory.

Step 7: Add the Component Logic

Open the generated TypeScript component file and use:

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

@Component({
  selector: 'app-book',
  standalone: true,
  imports: [FormsModule],
  templateUrl: './book.component.html',
  styleUrl: './book.component.css'
})
export class BookComponent implements OnInit {

  books: Book[] = [];

  newBook = {
    title: '',
    author: '',
    description: ''
  };

  errorMessage = '';

  constructor(private bookService: BookService) {}

  ngOnInit(): void {
    this.loadBooks();
  }

  loadBooks(): void {
    this.books = this.bookService.getBooks();
  }

  addBook(): void {

    const title = this.newBook.title.trim();
    const author = this.newBook.author.trim();

    if (!title || !author) {
      this.errorMessage = 'Title and author are required.';
      return;
    }

    const book: Book = {
      id: this.getNextId(),
      title: title,
      author: author,
      description: this.newBook.description.trim()
    };

    this.bookService.addBook(book);

    this.newBook = {
      title: '',
      author: '',
      description: ''
    };

    this.errorMessage = '';
  }

  private getNextId(): number {
    if (this.books.length === 0) {
      return 1;
    }

    return Math.max(...this.books.map(book => book.id)) + 1;
  }
}

Understanding Dependency Injection

Notice this line:

constructor(private bookService: BookService) {}

We don't manually create the service using:

new BookService()

Instead, Angular's dependency injection system supplies the BookService instance to the component.

Dependency injection makes applications easier to organize, test and maintain as they grow.

Understanding ngOnInit

Our component implements:

OnInit

and contains:

ngOnInit(): void {
  this.loadBooks();
}

Angular calls ngOnInit() after initializing the component, so we use it to load our initial book data.

Step 8: Create the Book Template

Open:

book.component.html

and add:

<section class="book-container">

  <h2>Book List</h2>

  @if (books.length === 0) {
    <p>No books available.</p>
  }

  @for (book of books; track book.id) {

    <div class="book-card">

      <h3>{{ book.title }}</h3>

      <p>
        <strong>Author:</strong>
        {{ book.author }}
      </p>

      <p>
        {{ book.description }}
      </p>

    </div>

  }

  <hr>

  <h2>Add a New Book</h2>

  @if (errorMessage) {
    <p class="error">
      {{ errorMessage }}
    </p>
  }

  <form (ngSubmit)="addBook()">

    <div>
      <label for="title">Title</label>

      <input
        id="title"
        name="title"
        type="text"
        [(ngModel)]="newBook.title"
        required>
    </div>

    <div>
      <label for="author">Author</label>

      <input
        id="author"
        name="author"
        type="text"
        [(ngModel)]="newBook.author"
        required>
    </div>

    <div>
      <label for="description">Description</label>

      <textarea
        id="description"
        name="description"
        [(ngModel)]="newBook.description">
      </textarea>
    </div>

    <button type="submit">
      Add Book
    </button>

  </form>

</section>

Understanding Angular Interpolation

This syntax:

{{ book.title }}

is called interpolation.

Angular evaluates the expression and displays its value in the HTML.

For example:

<h3>{{ book.title }}</h3>

might render:

<h3>Learning Angular</h3>

Understanding Two-Way Data Binding

Our form uses:

[(ngModel)]="newBook.title"

This connects the form input with the component property.

When the user types into the field, Angular updates the property. If the property changes programmatically, Angular can update the input.

This pattern is commonly known as two-way data binding.

Why Does the Input Need a Name?

Because these controls are inside a form and use ngModel, we've provided names such as:

name="title"
name="author"
name="description"

This allows Angular's form handling to identify the controls correctly.

Step 9: Add Basic CSS

Open:

book.component.css

and add:

.book-container {
  max-width: 700px;
  margin: 30px auto;
  padding: 20px;
}

.book-card {
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 15px;
  margin-bottom: 15px;
}

form div {
  margin-bottom: 15px;
}

label {
  display: block;
  margin-bottom: 5px;
  font-weight: 600;
}

input,
textarea {
  box-sizing: border-box;
  width: 100%;
  padding: 10px;
}

textarea {
  min-height: 100px;
}

button {
  padding: 10px 18px;
  cursor: pointer;
}

.error {
  font-weight: 600;
}

Step 10: Display the Book Component

For a standalone Angular application, import the component into your root component.

A minimal example is:

import { Component } from '@angular/core';
import { BookComponent } from './components/book/book.component';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [BookComponent],
  template: '<app-book></app-book>'
})
export class AppComponent {
}

If your project uses the older NgModule structure, the registration process is different. Follow the structure generated by the Angular version used in your project rather than mixing standalone and NgModule examples.

Step 11: Run the Application

Start the development server:

ng serve

Open the local development address shown in the terminal.

You should now see the initial books.

Enter a title, author and description, then click Add Book.

The new book should immediately appear in the list.

How the Application Works

Let's follow what happens when the page loads.

Angular creates BookComponent
        ↓
ngOnInit() runs
        ↓
loadBooks() runs
        ↓
BookService.getBooks()
        ↓
books[] is populated
        ↓
Template renders books

When the user adds a book:

User enters book details
        ↓
ngModel updates newBook
        ↓
Form calls addBook()
        ↓
Input is validated
        ↓
BookService.addBook()
        ↓
Book added to books[]
        ↓
Angular updates the UI

Important Limitation: Data Is Not Saved Permanently

Our current service stores books in memory:

private books: Book[] = [...]

Therefore, if you refresh the page, newly added books disappear.

That's expected for this simple tutorial.

A real application would normally store books using something such as:

  • A REST API
  • ASP.NET Core Web API
  • Node.js backend
  • SQL database
  • Cloud database

How This Could Work With ASP.NET Core

A more realistic architecture could look like:

Angular
   ↓ HTTP
ASP.NET Core Web API
   ↓
Entity Framework Core
   ↓
SQL Server

Angular would be responsible for the user interface, while ASP.NET Core would expose endpoints such as:

GET    /api/books
GET    /api/books/1
POST   /api/books
PUT    /api/books/1
DELETE /api/books/1

This would turn our small example into a full CRUD application.

Common Errors

ng: command not found

Angular CLI may not be installed globally or your terminal may not have access to its executable.

Try:

npm install -g @angular/cli

Can't bind to ngModel

If you are using a standalone component, make sure FormsModule is imported:

import { FormsModule } from '@angular/forms';

and included in the component:

imports: [FormsModule]

Component Is Not Displaying

Check that:

  • The component is imported correctly
  • The selector is correct
  • The selector appears in the parent template
  • The application compiled without errors

Books Disappear After Refresh

This is expected because this tutorial stores data only in memory. Persistent storage requires a backend, browser storage or another persistence mechanism.

Ideas to Improve the Project

Once the basic version works, try adding:

  • Edit book functionality
  • Delete book functionality
  • Search by title
  • Filter by author
  • Form validation messages
  • Book categories
  • Book cover images
  • Angular routing
  • REST API integration
  • ASP.NET Core backend
  • SQL Server database

Frequently Asked Questions

What is an Angular component?

A component represents part of the application's user interface and contains the logic and template required for that part of the UI.

What is an Angular service?

A service is commonly used for reusable logic, shared state or communication with external APIs. Angular's dependency injection system can provide services to components.

What is ngModel?

ngModel can bind form controls to component properties. The [(ngModel)] syntax provides two-way binding.

What is TypeScript?

TypeScript extends JavaScript with features such as static type checking. Angular applications are commonly developed using TypeScript.

Can Angular connect to ASP.NET Core?

Yes. An Angular application can call an ASP.NET Core Web API using HTTP, making the combination useful for full-stack applications.

Where should I go after this tutorial?

A good next project is to connect the book application to a real REST API and implement complete CRUD operations: create, read, update and delete.

Conclusion

This small book application demonstrates several Angular concepts working together rather than in isolation.

We created a TypeScript model, moved data handling into a service, injected that service into a component, displayed data in a template and created a form using two-way data binding.

Once you understand this flow, you can gradually add routing, validation, HTTP APIs and a backend such as ASP.NET Core to build more realistic applications.

Hello! My name is Aniket Shahane and I am a senior software consultant. I hold a post-graduate degree (MTech) in Computer Science and Engineering, and I have a passion for using my technical expertise to solve complex problems. I am excited to be here and eager to share my knowledge and experience with you.

Post a Comment