Next.js starter your AI actually understands. Ship internal tools in days not weeks. Pre-order $199 $499 → [Get it now]

How to build a notes app in Angular

A notes app combines CRUD operations, real-time search filtering, and rich text display in an intuitive interface that teaches essential Angular patterns. As the creator of CoreUI with Angular development experience since 2014, I use this project structure in CoreUI Angular templates as the starting point for more complex content management features. The architecture uses a reactive service with BehaviorSubject for state, the async pipe for memory-safe subscriptions, and combineLatest to derive the filtered list from both the notes array and the search query. This reactive approach avoids imperative subscription management and keeps the component template simple.

Create the notes service with reactive state.

// notes.service.ts
import { Injectable } from '@angular/core'
import { BehaviorSubject, combineLatest, map } from 'rxjs'

export interface Note {
  id: number
  title: string
  content: string
  color: string
  createdAt: Date
}

const COLORS = ['#fff9c4', '#f8bbd0', '#b3e5fc', '#c8e6c9', '#ffe0b2']

@Injectable({ providedIn: 'root' })
export class NotesService {
  private nextId = 1
  private notes$ = new BehaviorSubject<Note[]>([])
  private search$ = new BehaviorSubject<string>('')

  readonly search = this.search$.asObservable()

  readonly filteredNotes$ = combineLatest([this.notes$, this.search$]).pipe(
    map(([notes, query]) => {
      if (!query.trim()) return notes
      const q = query.toLowerCase()
      return notes.filter(n =>
        n.title.toLowerCase().includes(q) ||
        n.content.toLowerCase().includes(q)
      )
    })
  )

  add(title: string, content: string): void {
    const color = COLORS[Math.floor(Math.random() * COLORS.length)]
    const notes = [
      { id: this.nextId++, title, content, color, createdAt: new Date() },
      ...this.notes$.value
    ]
    this.notes$.next(notes)
  }

  remove(id: number): void {
    this.notes$.next(this.notes$.value.filter(n => n.id !== id))
  }

  setSearch(query: string): void {
    this.search$.next(query)
  }
}

combineLatest emits whenever either notes$ or search$ changes, deriving the filtered list reactively. The async pipe in the template subscribes and unsubscribes automatically.

Notes App Component

Build the main page with search and masonry grid layout.

// notes-app.component.ts
import { Component } from '@angular/core'
import { CommonModule } from '@angular/common'
import { ReactiveFormsModule, FormControl } from '@angular/forms'
import { CardModule, FormModule, ButtonModule, GridModule } from '@coreui/angular'
import { NotesService } from './notes.service'

@Component({
  selector: 'app-notes',
  standalone: true,
  imports: [CommonModule, ReactiveFormsModule, CardModule, FormModule, ButtonModule, GridModule],
  template: `
    <div class="mb-4 d-flex gap-3 align-items-center">
      <h1 class="mb-0">Notes</h1>
      <input cFormControl placeholder="Search notes..." [formControl]="searchControl"
        style="max-width: 300px" />
    </div>

    <!-- Add note form -->
    <c-card class="mb-4">
      <c-card-body>
        <input cFormControl [formControl]="titleControl" placeholder="Title" class="mb-2" />
        <textarea cFormControl [formControl]="contentControl" rows="3"
          placeholder="Note content..."></textarea>
        <button cButton color="primary" class="mt-2" (click)="addNote()">Add Note</button>
      </c-card-body>
    </c-card>

    <!-- Notes grid -->
    <c-row>
      <c-col sm="6" md="4" lg="3"
        *ngFor="let note of (notesService.filteredNotes$ | async)"
        class="mb-3"
      >
        <c-card [style.background]="note.color">
          <c-card-body>
            <h5>{{ note.title }}</h5>
            <p class="small">{{ note.content }}</p>
            <div class="d-flex justify-content-between align-items-center">
              <small class="text-muted">{{ note.createdAt | date:'MMM d' }}</small>
              <button cButton color="ghost" size="sm" (click)="notesService.remove(note.id)">
              </button>
            </div>
          </c-card-body>
        </c-card>
      </c-col>
    </c-row>

    <p *ngIf="(notesService.filteredNotes$ | async)?.length === 0" class="text-muted text-center mt-4">
      No notes found.
    </p>
  `
})
export class NotesAppComponent {
  titleControl = new FormControl('')
  contentControl = new FormControl('')
  searchControl = new FormControl('')

  constructor(public notesService: NotesService) {
    this.searchControl.valueChanges.subscribe(query => {
      this.notesService.setSearch(query ?? '')
    })
  }

  addNote(): void {
    const title = this.titleControl.value?.trim()
    const content = this.contentControl.value?.trim()
    if (!title || !content) return
    this.notesService.add(title, content)
    this.titleControl.reset()
    this.contentControl.reset()
  }
}

valueChanges on the search control connects form input to the service’s search state reactively. The async pipe handles subscription and renders the filtered list automatically whenever it changes.

Best Practice Note

This is the same notes app structure used in CoreUI Angular templates as a demonstration of reactive state management. Add localStorage persistence in the service to survive page reloads. For a production notes app, connect to the notes API in Node.js backend for multi-device sync. Rich text editing can be added with a library like Quill or TipTap.


Speed up your responsive apps and websites with fully-featured, ready-to-use open-source admin panel templates—free to use and built for efficiency.


About the Author

Subscribe to our newsletter
Get early information about new products, product updates and blog posts.
How to force a React component to re-render
How to force a React component to re-render

Passing props to child components in React function components
Passing props to child components in React function components

How to change opacity on hover in CSS
How to change opacity on hover in CSS

How to convert a string to boolean in JavaScript
How to convert a string to boolean in JavaScript

Answers by CoreUI Core Team