Data Management Made Easy: Reading and Deleting Data with Supabase PostgreSQL Database as a Service (Part 24) in Your Angular-15 Ionic-7 App

 Welcome back to our ongoing series on building a multiplatform application with Angular-15 and Ionic-7! In our previous articles, we explored a wide range of topics, including authentication, data management, task organization, calendar integration, event management, media management, UI customization, enhanced user authentication, real-time data manipulation, creating a selectable and searchable data list, building a notepad-style rich text editor, and visualizing numerical data with interactive charts. Today, in Part 24, we will focus on reading and deleting data using the Supabase PostgreSQL database as a service.

Supabase is a powerful and versatile database service that simplifies data management and provides a scalable solution for applications. By utilizing the Supabase PostgreSQL database, we can efficiently read and delete data within our Angular-15 Ionic-7 app, ensuring seamless data operations.


In this installment, we will guide you through the process of reading and deleting data using the Supabase PostgreSQL database as a service. We will start by setting up the Supabase integration within our app and establishing a connection to the database.


Next, we will explore various techniques for retrieving data from the database, including querying specific records, filtering data, and handling complex data structures. We will also demonstrate how to perform deletion operations, ensuring the data remains accurate and up to date within our application.

Throughout this tutorial, we will emphasize best practices for data retrieval and deletion, error handling, and maintaining data integrity. By the end of this article, you will have a solid understanding of how to read and delete data using the Supabase PostgreSQL database as a service, enriching the functionality and efficiency of your Angular-15 Ionic-7 app.

So, join us in Part 24 of our series as we explore the powerful capabilities of the Supabase PostgreSQL database. Together, let's leverage this robust database service to streamline data management and ensure smooth data operations within our application.

Tutorial

Here I have simply connected a supabase database with the application to display a pre-defined feed of notifications. First, I have created a database of notifications in Supabase using the following SQL like I did for the achievements list but without a login this time:

--
-- For use with:
-- <https://github.com/supabase/supabase/tree/master/examples/todo-list/sveltejs-todo-list> or
-- <https://github.com/supabase/examples-archive/tree/main/supabase-js-v1/todo-list>
--

create table notifications (
  id bigint generated by default as identity primary key,
  user_id uuid references auth.users not null,
  notification text check (char_length(notification) > 3),
  is_complete boolean default false,
  inserted_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table notifications enable row level security;

create policy "Allow anyone to view notifications" on notifications for
    select using (true);

create policy "Allow anyone to delete notifications" on notifications for
    delete using (true);

Next, I will cretae the logic to communicate with this database using the previously created supabase service in supabase.service.ts file in the following way:

import { Injectable } from '@angular/core';
import { createClient, SupabaseClient, User } from '@supabase/supabase-js';
import { BehaviorSubject } from 'rxjs';
import { environment } from 'src/environments/environment';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';

const NOTIFICATION_DB = 'notifications'; // define the name of the table in the database

export interface Notification {
  id: number;
  inserted_at: string;
  is_read: boolean;
  notification: string;
  user_id: string;
}

@Injectable({
  providedIn: 'root',
})
export class SupabaseService {
  supabase: SupabaseClient; // define supabase as a SupabaseClient type variable (from @supabase/supabase-js)

  private _notifications: BehaviorSubject<any> = new BehaviorSubject([]); // this is a BehaviorSubject from rxjs that is used to store the notifications and is initialized with an empty array as the default value

  constructor(private router: Router) {
    // initialize supabase with the environment variables
    this.supabase = createClient(
      // createClient is a function from @supabase/supabase-js that is used to initialize supabase
      environment.supabaseUrl, // environment variables from src/environments/environment.ts
      environment.supabaseKey,
      {
        auth: {
          // auth is an object from @supabase/supabase-js that is used to store the logged-in session
          autoRefreshToken: true, // autoRefreshToken is a boolean from @supabase/supabase-js that is used to refresh the token for logged-in users
          persistSession: true, // persistSession is a boolean from @supabase/supabase-js that is used to store the logged-in session
        },
      }
    );

    this.supabase.auth.onAuthStateChange((event, session) => {
      this.loadNotifications();
      //notifications are made accessible without login here
      if (event == 'SIGNED_IN') {
        this._currentUser.next(session?.user);
        this.handleNotificationsChanged(); // handle notifications changed
      } else {
        this._currentUser.next(false);
        this.handleNotificationsChanged(); // handle notifications changed
      }
    });
  } 

  get notifications(): Observable<Notification[]> {
    // this is a getter function that is used to return the notifications BehaviorSubject as an Observable to access the private notifications BehaviorSubject from outside the service
    return this._notifications.asObservable(); // return the notifications BehaviorSubject as an Observable
  }

  async loadNotifications() {
    const query = await this.supabase.from(NOTIFICATION_DB).select('*'); // select all the notifications from the database table and store them in a variable called query
    // this is async because we are using await to wait for the query to finish before continuing
    // console.log('query: ', query);
    this._notifications.next(query.data); // pass the notifications to the notifications BehaviorSubject
  }

  async deleteNotification(id: number) {
    await this.supabase.from(NOTIFICATION_DB).delete().match({ id }); // delete the notification from the database table
  }

  handleNotificationsChanged() {
    // this function is used to handle changes to the notifications in the database table and update the notifications BehaviorSubject in real-time when the database table is updated
    this.supabase
      .channel(NOTIFICATION_DB) // channel is a function from @supabase/supabase-js that is used to listen for changes to the database table
      .on('postgres_changes', { event: '*', schema: '*' }, (payload: any) => {
        // on method takes in 3 arguments: the event type, the schema, and a callback function that takes in the payload
        //!Remember to make the above changes which are different from old version of supabase and what is on many videos. The above is the new way to do it. It took me a lot of time to read through the documentation to figure this out.
        console.log('payload: ', payload); // log the payload to the console

        // if the eventType is DELETE, UPDATE, or INSERT, then update the notifications BehaviorSubject with the new notifications from the database table
        if (payload.eventType === 'DELETE') {
          //take the old notifications and filter out the notification that was deleted
          const oldItem: Notification = payload.old;
          const newValue = this._notifications.value.filter(
            // filter out the notification that was deleted
            (item: Notification) => oldItem['id'] !== item.id
          );
          this._notifications.next(newValue); // pass the new notifications to the notifications BehaviorSubject to update the notifications
        }
      })
      .subscribe();
  }
}

The above code defines a TypeScript service called SupabaseService, which is used to interact with a Supabase database. The service uses the Supabase JavaScript library (@supabase/supabase-js) to connect to the database and perform CRUD operations on a table called NOTIFICATION_DB.

The service initializes the supabase variable by calling the createClient function from the Supabase library and passing in the Supabase URL and API key from the environment.ts file. It also sets some options for the auth object to enable automatic token refreshing and session persistence.

The service defines a private BehaviorSubject called _notifications which stores an array of notifications and is initialized with an empty array as the default value. A getter function called notifications is also defined to return this private BehaviorSubject as an Observable.

The service defines three methods to interact with the NOTIFICATION_DB table in the database:

  • loadNotifications: This method queries the database table for all notifications and updates the _notifications BehaviorSubject with the result.
  • deleteNotification: This method deletes a specific notification from the database table.
  • handleNotificationsChanged: This method sets up a real-time listener using the channel function from the Supabase library to listen for changes to the NOTIFICATION_DB table. When a change is detected, the method updates the _notifications BehaviorSubject with the new value.

In the constructor of the service, the auth.onAuthStateChange function from the Supabase library is used to handle changes to the user's authentication state (e.g. when they log in or log out). When the user logs in, the _currentUser BehaviorSubject is updated with the current user, and the handleNotificationsChanged method is called to update the _notifications BehaviorSubject with any new notifications. When the user logs out, the _currentUser BehaviorSubject is updated with false, and the handleNotificationsChanged method is called again to update the _notifications BehaviorSubject with the new value.

Next, I will use this service, and import it in the notifications page which is created via ionic geenrate page pages/notifications and write the logic in notifications.page.ts file like this:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule, ToastController } from '@ionic/angular';
import {
  SupabaseService,
  Notification,
} from 'src/app/services/supabase.service';

@Component({
  selector: 'app-notifications',
  templateUrl: './notifications.page.html',
  styleUrls: ['./notifications.page.scss'],
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule],
})
export class NotificationsPage implements OnInit {
  notifications = this.supabaseService.notifications; // define notifications as an array of Notification type variables from src/app/services/supabase.service.ts (this is the array of notifications that will be displayed in the list)
  constructor(
    private supabaseService: SupabaseService,
    private toastController: ToastController
  ) {}

  ngOnInit() {}

  delete(item: Notification) {
    // delete the todo item from the database
    this.supabaseService.deleteNotification(item.id); // remove the todo item from the database using the removeTodo function from src/app/services/supabase.service.ts

    // present a toast message to show todo item deleted
    this.toastController
      .create({
        message: 'Notification deleted',
        duration: 2000,
      })
      .then((toast) => {
        toast.present();
      });
  }
}

This code defines a component called NotificationsPage and implements the OnInit interface. The component displays a list of notifications and provides a way to delete individual notifications from the list.

The notifications property is defined as an array of Notification type variables, which is initialized to the notifications property of the supabaseService instance, which is an instance of the SupabaseService class defined in src/app/services/supabase.service.ts.

The delete method takes a Notification item as an argument and calls the deleteNotification method of the supabaseService instance to delete the notification item from the database. It then presents a toast message using the ToastController to notify the user that the notification has been deleted.

Next, I will write the template for this logic in notifications.page.html in the following way:

<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>Notifications</ion-title>

    <ion-buttons slot="start">
      <ion-back-button defaultHref="/"></ion-back-button>
    </ion-buttons>

    <ion-buttons slot="end">
      <ion-menu-button menu="main-menu"></ion-menu-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Notifications</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-list lines="full">
    <ion-item-sliding *ngFor="let item of notifications | async">
      <!-- We used async pipe above because it was an observable -->
      <ion-item>
        <ion-label
          >{{item.notification}}
          <p>{{ item.inserted_at | date:'short' }}</p>
        </ion-label>
      </ion-item>

      <ion-item-options side="end">
        <ion-item-option (click)="delete(item)" color="danger">
          <ion-icon name="trash" slot="icon-only"></ion-icon>
        </ion-item-option>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
</ion-content>

This is the template code for a page that displays a list of notifications. Let's break it down:

  • The first line sets the ion-content component to be fullscreen.
  • The ion-header component sets up a toolbar at the top of the page that collapses when the content is scrolled.
  • Inside the ion-toolbar, an ion-title component displays the text "Notifications".
  • The ion-list component creates a list of items that will be displayed on the page.
  • ngFor is an Angular directive that loops over the notifications array, which is an observable defined in the component. This allows us to display each notification item in the list.
  • ion-item-sliding is a component that allows users to swipe left or right on an item to reveal additional options.
  • Inside the ion-item component, we display the notification text and the date it was inserted. The date filter is used to format the date as 'short'.
  • ion-item-options displays options for the current item. In this case, we have a delete button, which when clicked calls the delete function from the component.
  • When the delete button is clicked, it displays a toast message confirming that the notification has been deleted.

The notifications are demonstrated in figures 94, 95, and 96 below:

Figure 94: Notifications



Figure 95: Slide & Delete



Figure 96: Item Deleted


Conclusion

In this twenty-fourth installment of our series on building a multiplatform application with Angular-15 and Ionic-7, we explored the process of reading and deleting data using the Supabase PostgreSQL database as a service. By incorporating Supabase into our app, we streamlined data management and ensured efficient data operations within our Angular-15 Ionic-7 application.

Supabase is a powerful and scalable database service that simplifies data management, and with its PostgreSQL database integration, we gained the ability to read and delete data seamlessly. Throughout this tutorial, we guided you through the process of setting up the Supabase integration within your app, establishing a connection to the database, and performing data retrieval and deletion operations.

We explored various techniques for retrieving specific records, filtering data, and handling complex data structures. Additionally, we demonstrated how to perform deletion operations, ensuring the data remains accurate and up to date within our application.

By following best practices for data retrieval and deletion, error handling, and maintaining data integrity, you now possess the knowledge to efficiently read and delete data using the Supabase PostgreSQL database as a service within your Angular-15 Ionic-7 app.

We hope this tutorial has provided you with valuable insights and practical techniques for leveraging the power of Supabase in your application. By utilizing this robust database service, you can streamline data management, optimize data operations, and enhance the overall efficiency and user experience of your application.

Thank you for joining us on this journey as we explored the fascinating world of app development with Angular-15, Ionic-7, and the Supabase PostgreSQL database as a service. Stay tuned for future installments where we will continue to explore new features and functionalities to make our app even more powerful, dynamic, and user-friendly.

Happy coding!

Popular Posts

Perform CRUD (Create, Read, Update, Delete) Operations using PHP, MySQL and MAMP : Part 4. My First Angular-15 Ionic-7 App

Visualize Your Data: Showcasing Interactive Charts for Numerical Data using Charts JS Library (Part 23) in Your Angular-15 Ionic-7 App

How to Build a Unit Converter for Your Baking Needs with HTML, CSS & Vanilla JavaScript