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

This post is in continuation of ‘Part 3. My First Angular-15 Ionic-7 App: Filter Displayed Data Based on a Parameter using Set Constructor’ and I strongly recommend going through the previous post or at least Part 0 to understand how the app is set up.

Till now, I have simply accessed data from SQL database and displayed it on my application. That is the ‘Read’ operation from the CRUD operations. Now I work with student data to not only read the data, but also to create a new dataset, update the existing one, and even delete them individually.


READ Student Data

I begin by using the student data PHP file that connects my previously created SQL database:

<?php

//Enable cross domain Communication - Beware, this can be a security risk
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');

//include db connect class
require_once 'db_connect.php';

// Get access to database instance
$db = Database::getInstance();

// Get database connection from database
$conn = $db->getConnection();

//Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
// Create query using SQL string
$sql_query = "SELECT * FROM studentTable";

// Query database using connection
$result = $conn->query($sql_query);

// check for empty result
if (mysqli_num_rows($result) > 0)
 {
	// Create Array for JSON response
	$response = array();

    // Create Array called students inside response Array
    $response["students"] = array();

	// Loop through all results from Database
    while ($row = mysqli_fetch_array($result))
     {
        	// Assign results for each database row, to temp student array
            $student = array();
            $student["studentID"] = $row["studentID"];
            $student["firstName"] = $row["firstName"];
            $student["lastName"] = $row["lastName"];
            $student["moduleNo1"] = $row["moduleNo1"];
            $student["moduleNo2"] = $row["moduleNo2"];
            $student["courseID"] =$row["courseID"];

       // push single student into final response array
        array_push($response["students"], $student);
    }
    // success
    $response["success"] = 1;

    // print JSON response
    print (json_encode($response));
}
else {
    // no students found
    $response["success"] = 0;
    $response["message"] = "No students found";

    // print no students JSON
    print (json_encode($response));
}
?>

Once the php file is placed in D:\\MAMP\\htdocs\\php_ionic I need to create a service that will access this in the ionic app.

I place the URL to this PHP file in the environment.ts file like this:

export const environment = {
  production: false,
  // create url variable to hold the php_ionic json-data-students.php file
  urlStudents: '<http://localhost:8888/php_ionic/json-data-students.php>',
};

Next, I create a new student data service that will make the HTTP call with this command: ionic generate service services/student-data and write the following logic in the student-service.ts file:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { environment } from 'src/environments/environment';

export interface Student {
  [key: string]: any; // using index signature to avoid error TS7017 (element implicitly has an 'any' type because type 'Student' has no index signature)
  courseID: string;
  firstName: string;
  lastName: string;
  moduleNo1: string;
  moduleNo2: string;
  studentID: string;
}

@Injectable({
  providedIn: 'root',
})
export class StudentServiceService {
  // create a variable to store the url of the json-data-students.php file
  urlStudents = environment.urlStudents;

  // Inject HttpClient into the constructor
  constructor(private http: HttpClient) {}

  // create a method to get the data from the json-data-students.php file
  getStudents(): Observable<any> {
    // return type is Observable<any>
    return this.http.get(this.urlStudents);
  }
}

The service exports an interface named Student that defines the structure of a student object. The Student interface has properties for course ID, first name, last name, module numbers, and student ID. The Student interface also has an index signature to avoid an error that may arise when an object of type Student is used in a way that requires an index.

The StudentServiceService class is decorated with @Injectable, which marks the class as one that can be used as a provider for the dependency injection system of Angular. The providedIn property is set to 'root', indicating that the service should be provided at the root level of the application. It has a constructor that takes an instance of HttpClient. The class defines a method named getStudents(), which returns an Observable that will emit the JSON data from the specified URL. The method uses this.http.get() to send an HTTP GET request to the urlStudents URL, which is the URL of the JSON data source. The http.get() method returns an Observable that emits the response as a JSON object. The getStudents() method returns the Observable returned by http.get(). This method will further be used to display student data on the specific students page.

Next, I create the logic to get the student data and display it on My Classroom page which is tab2.page.ts and the logic is as follows:

export class Tab2Page implements OnInit {
  // create 2 variables to store results emitted from observable
  students: any;
  newStudents: any = [];

  // inject student service into the constructor
  constructor(
    private studentService: StudentServiceService,
  ) {}

  // create a method to get the data from the json-data-students.php file
  getStudentData() {
    // subscribe to the observable
    // result is the data emitted from the observable
    this.studentService.getStudents().subscribe((result) => {
      // store the data emitted from the observable into the students variable
      this.students = result;
      // console.log(this.students);

      // store the data emitted from the observable into the newStudents variable
      this.newStudents = this.students.students;
    });
  }

  // call the getStudentData() method when the page loads
  ngOnInit() {
    // call the getStudentData() method
    this.getStudentData();
  }
}

In the above code, the Tab2Page class has two class-level variables, students and newStudents. students variable stores the result emitted from an observable returned by the getStudents() method in the StudentServiceService. newStudents variable initializes as an empty array, and the data emitted from the observable will be stored in this variable.

The Tab2Page class constructor takes an instance of the StudentServiceService. The getStudentData() method uses the getStudents() method of the injected StudentServiceService instance which I created earlier to retrieve data from the database, by subscribing to an observable returned by getStudents(). The subscribe() method takes a callback function that stores the result emitted from the observable into the students variable, and then into the newStudents variable by extracting the students property from the result.

The ngOnInit() method is called when the component is initialized, and it calls the getStudentData() method to retrieve data and store it in the students and newStudents variables.

Next I used this newStudents array to populate the list on My Classroom page’s template file viz. tab2.page.html in the following way:

<ion-header [translucent]="false">
  <ion-toolbar>
    <ion-title> My Classroom </ion-title>
    <ion-buttons slot="start">
      <ion-button routerLink="/tabs/notifications">
        <ion-icon slot="icon-only" name="notifications-outline"></ion-icon>
      </ion-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">My Classroom</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-list lines="full">
    <ion-item-sliding *ngFor="let student of newStudents">
      <ion-item>
        <ion-label>
          <h2>{{student.firstName}} {{student.lastName}}</h2>
          <p>ID: {{student.studentID}}</p>
        </ion-label>
      </ion-item>
      ...
    </ion-item-sliding>
  </ion-list>
...
</ion-content>

In the above template file, I have used the *ngFor angular directive to loop through the newStudents array and display the individual property values in an Ionic list UI component. I have used the Ionic Item Sliding UI component that will allow me to add the update student details and delete student icon buttons behind the list item which will be visible when the user slides right or left on the item. Figure 8 shows the list of students on My Classroom page with their full names and student ID in the list items:

Figure 8: My Classroom page with a list of students

CREATE Student Data

Next, I will use the following PHP file to add a new student to the database:

<?php
//Enable cross domain Communication - Beware, this can be a security risk
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token , Authorization');

//include db connect class
require_once 'db_connect.php';

// Get access to database instance
$db = Database::getInstance();

// Get database connection from database
$conn = $db->getConnection();

//Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

    //Receive the RAW post data.
    $inputJSON = trim(file_get_contents('php://input'));

    //Attempt to decode the incoming RAW post data from JSON.
    $_POST = json_decode($inputJSON, TRUE);

    $studentID = $_POST['studentID'];
    $firstName = (string)$_POST['firstName'];
    $lastName = (string)$_POST['lastName'];
    $moduleNo1 = $_POST['moduleNo1'];
    $moduleNo2 = $_POST['moduleNo2'];
    $courseID = $_POST['courseID'];

    // Create query using SQL string
    $sql_query = "INSERT INTO studentTable (studentID, firstName, lastName, moduleNo1, moduleNo2, courseID) VALUES ( $studentID , '$firstName', '$lastName', $moduleNo1, $moduleNo2, $courseID)";

    // Query database using connection
    $result = $conn->query($sql_query);

    $_SESSION['mysql_conn'] = $conn;

    function query($sql){
	return mysqli_query($_SESSION['mysql_conn'], $sql)or die(mysqli_error($_SESSION['mysql_conn']));
	}

// check for empty result
    if ($result)
    {
        // Create Array for JSON response
        $response = array();

        // success
        $response["success"] = 1;
        $response["message"] = "Data Inserted";

        // print data inserted JSON
        print (json_encode($response));

    } else {
        // fail
        $response["success"] = 0;
        // no student inserted
        $response["message"] = "No Data Inserted";

        // print no data inserted JSON
        print (json_encode($response));
}
?>

Once the PHP file is placed in D:\\MAMP\\htdocs\\php_ionic, I run the MAMP server, and begin creating a new service to create student data using this PHP file. I use the following command to create the new service:

ionic generate service services/student-create 

Then I write the logic within the student-create.service.ts file that makes use of the HTTP Client module and makes a post request like this:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class StudentCreateService {
  // create a variable to hold the url to json create students php file
  urlCreateStudents =
    '<http://localhost:8888/php_ionic/json-create-students.php>';

  constructor(private http: HttpClient) {}

  //create a post data function to send data to the json create students php file
  postData(data: any) {
    return this.http.post(this.urlCreateStudents, data, {
      headers: new HttpHeaders({
        Accept: 'text/plain',
        'Content-Type': 'text/plain',
      }),
      responseType: 'text',
    });
  }
}

Note that here I have placed the URL to localhost in the service itself instead of the enviroment.ts file which is also not wrong. Then it has a**postData()** method that sends data to the server using HTTP POST. The postData() method takes an input parameter called data that is of type any. It sends a POST request to the URL specified in urlCreateStudents variable with the data as the request body. It also sets the request headers as text/plain and the response type as text.

Next I will create a new page which will pop up as an addStudent Modal on the Classroom page when the user clicks on add floating action button:

ionic generate page pages/add-student

Then I will move the route of this page from app.route.ts to the tabs.route.ts file like this:

{
        path: 'add-student',
        loadComponent: () =>
          import('../add-student/add-student.page').then(
            (m) => m.AddStudentPage
          ),
      },

Also, since this will be a modal that will appear on all the pages, modals are supposed to be imported into the app.component.ts file and entered into the list of component imports in the following way (without which the modal won’t pop up):

...
import { AddStudentPage } from './pages/add-student/add-student.page';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.scss'],
  standalone: true,
  imports: [
    IonicModule,
    CommonModule,
    FormsModule,
    AddStudentPage,
  ],
})

Next, I create a Floating Action Button (FAB) UI component from ion-fab: Floating Action Button for Android and iOS Ionic Apps (ionicframework.com) on tab2.page.html file which will call a method to open the modal page I just created:

<ion-header [translucent]="false">
...
</ion-header>

<ion-content [fullscreen]="true">
  ...

  <ion-list lines="full">
    ...
  </ion-list>
  
  <ion-fab vertical="bottom" horizontal="end" slot="fixed">
    <ion-fab-button (click)="openModal()"
      ><ion-icon name="person-add"></ion-icon
    ></ion-fab-button>
  </ion-fab>
</ion-content>

Next, I write the logic for openModal() method in the tab2.page.ts file like this:

export class Tab2Page implements OnInit {
  students: any;
  newStudents: any = [];
  // inject student service into the constructor
  constructor(
    private studentService: StudentServiceService,
    private modalCtrl: ModalController,
  ) {}

  getStudentData() {...}

  // call the getStudentData() method when the page loads
  ngOnInit() {
    // call the getStudentData() method
    this.getStudentData();
  }

  // create a method to open the modal that will add a new student
  async openModal() {
    // create the modal
    const modal = await this.modalCtrl.create({
      component: AddStudentPage,
    });

    modal.onDidDismiss().then((data) => {
      //check if data is returned
      // if not the modal was cancelled
      if (data['data']) {
        this.newStudents.push(data['data']);
      } else {
        console.log('Modal was cancelled');
      }
    });

    return await modal.present();
  }
}

In the above code, I have created an asynchronous method called openModal() that opens a modal for adding a new student. It creates a modal by calling the create() method of the modalCtrl object and passing it an Angular component AddStudentPage as the component parameter.

The modal.onDidDismiss() method returns a Promise that resolves when the modal is dismissed. The then() method takes a callback function that is called when the Promise is resolved. Inside the callback function, it checks if any data was returned from the modal using the data property. If data was returned, it pushes the data into an array called newStudents, which is a class-level variable. If no data is returned, it logs a message to the console.

Finally, the modal.present() method is called to present the modal to the user.

The method openModal() is marked as async because it makes use of asynchronous operations, such as creating and presenting a modal, and waiting for the user to dismiss the modal before performing any further actions. By marking the method as async, the method can use the await keyword to pause execution until the modal has been dismissed and the Promise is resolved.

Now I have a full screen modal without any content in the template file and no logic to take in the student details. I begin with the logic in add-student.page.tsfile like this:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { Student } from './../../services/student-service.service';
import { ModalController } from '@ionic/angular';
import { ToastController } from '@ionic/angular';
import { StudentCreateService } from '../../services/student-create.service';

@Component({
  selector: 'app-add-student',
  templateUrl: './add-student.page.html',
  styleUrls: ['./add-student.page.scss'],
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule],
})
export class AddStudentPage implements OnInit {
  //create newStudent property from Student Interface
  // this will be used to store the data from the form
  // and will be passed to the student service
  // to be added to the database
  // the property is of type Student
  // and is initialized with empty strings
  //otherwise the form will throw an error
  newStudent: Student = {
    courseID: '',
    firstName: '',
    lastName: '',
    moduleNo1: '',
    moduleNo2: '',
    studentID: '',
  };

  constructor(
    private modalCtrl: ModalController, // inject the modal controller
    private studentCreateService: StudentCreateService, // inject the student create service
    private toastCtrl: ToastController // inject the toast controller
  ) {}

  ngOnInit() {}

  addStudent() {
    this.studentCreateService.postData(this.newStudent).subscribe(
      (res) => {
        console.log('Success: Student Record is added' + res); // this is the response from the server
        this.dismiss(true); // dismiss the modal and return true
        this.showToast('Student Record is added'); // show a toast message
      },
      async (err) => {
        console.log('Error: Student Record is not added' + err); // this is the response from the server
        this.dismiss(false); // dismiss the modal and return false
        this.showToast('Student Record is not added'); // show a toast message
      }
    );

    // if there is no input in the form, alert the user to add a student record
    if (
      this.newStudent.courseID === '' ||
      this.newStudent.firstName === '' ||
      this.newStudent.lastName === '' ||
      this.newStudent.moduleNo1 === '' ||
      this.newStudent.moduleNo2 === '' ||
      this.newStudent.studentID === ''
    ) {
      alert('You did not enter all fields. Please add a student record');
      this.dismiss(false); // dismiss the modal and return false
    }
  }

  // create a method to dismiss the modal
  dismiss(returnStudent: boolean) {
    if (returnStudent) {
      // if the student is added to the database
      this.modalCtrl.dismiss(this.newStudent); // return the newStudent object
    } else {
      // if the student is not added to the database
      this.modalCtrl.dismiss(); // dismiss the modal
    }
  }

  // create a method to show a toast message
  async showToast(message: string) {
    // the message is passed as a parameter
    const toast = await this.toastCtrl.create({
      // create a toast
      message: message, // set the message
      duration: 2000, // set the duration
    });
    toast.present(); // present the toast
  }
}

In the above code, I create an instance of a newStudent object from the Student interface and initialize it with empty strings. This object will be used to store data from a form that a user will fill in to add a new student record to a database.

The constructor of the component injects the ModalController, StudentCreateService, and ToastController services that are used to create a modal window, send HTTP POST request to create a new student record, and display a toast message respectively.

The component has a method addStudent() that sends a POST request to the server with the data entered in the form. If the request is successful, it dismisses the modal and shows a success toast message. If the request fails, it dismisses the modal and shows an error toast message.

The component also has a method to dismiss the modal and return the newStudent object or dismiss it without returning anything, and a method to show a toast message with a specified message and duration.

Next I implement this logic and create a form in the add-student.page.html file like this:

<ion-header>
  <ion-toolbar>
    <ion-buttons slot="start">
      <ion-button defaultHref="tab2" color="medium" (click)="dismiss(false)"
        >Cancel</ion-button
      >
    </ion-buttons>
    <ion-title>Add Student</ion-title>
    <ion-buttons slot="end">
      <ion-button (click)="addStudent()" [strong]="true">Add</ion-button>
    </ion-buttons>
  </ion-toolbar>
</ion-header>
<ion-content>
  <ion-list lines="none">
    <ion-item>
      <ion-label>First Name</ion-label>
      <ion-input
        aria-label="First Name"
        class="ion-margin-top"
        type="text"
        [(ngModel)]="newStudent.firstName"
        label="First Name"
        label-placement="floating"
        fill="outline"
      ></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Last Name</ion-label>
      <ion-input
        class="ion-margin-top"
        label="Last Name"
        label-placement="floating"
        fill="outline"
        aria-label="Last Name"
        type="text"
        [(ngModel)]="newStudent.lastName"
      ></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Student ID</ion-label>
      <ion-input
        class="ion-margin-top"
        label="Student ID"
        label-placement="floating"
        fill="outline"
        aria-label="Student ID"
        type="text"
        [(ngModel)]="newStudent.studentID"
      ></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Course ID</ion-label>
      <ion-input
        class="ion-margin-top"
        label="Course ID"
        label-placement="floating"
        fill="outline"
        aria-label="Course ID"
        type="text"
        [(ngModel)]="newStudent.courseID"
      ></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Module No.1</ion-label>
      <ion-input
        class="ion-margin-top"
        label="Module No. 1"
        label-placement="floating"
        fill="outline"
        aria-label="Module No. 1"
        type="text"
        [(ngModel)]="newStudent.moduleNo1"
      ></ion-input>
    </ion-item>
    <ion-item>
      <ion-label>Module No. 2</ion-label>
      <ion-input
        class="ion-margin-top"
        label="Module No. 2"
        label-placement="floating"
        fill="outline"
        aria-label="Module No. 2"
        type="text"
        [(ngModel)]="newStudent.moduleNo2"
      ></ion-input>
    </ion-item>
  </ion-list>
  <ion-button
    (click)="addStudent()"
    class="ion-margin"
    expand="full"
    fill="solid"
    shape="round"
  >
    Add Student
  </ion-button>
  <ion-button
    (click)="dismiss(false)"
    class="ion-margin"
    expand="full"
    fill="clear"
    shape="round"
  >
    Cancel
  </ion-button>
</ion-content>

The above template file uses Ionic input UI components to take in the student details through [(ngModel)] two-way data binding. The ‘Add Student’ button calls the method I created earlier which will post the data to the student database. The ‘Cancel’ button will dismiss the modal and return the user back to the Classroom page. Once the form is ready, it looks like the one in Figure 9 and a filled-out form in Figure 10:

Figure 9: Add student form with empty input fields


Figure 10: Add student form filled up with new student details

On clicking the Add Student button, the new student gets added at the end of the student list on the My Classroom page with a toast message at the bottom indicating that the student has been added.

UPDATE Student Data

Now, I have to implement the ‘Update’ operation that will take an existing student’s details and update them in the list and in the SQL database.

I begin by using the PHP file for updating student details in database:

<?php
//Enable cross domain Communication - Beware, this can be a security risk
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token , Authorization');

//include db connect class
require_once 'db_connect.php';

// Get access to database instance
$db = Database::getInstance();

// Get database connection from database
$conn = $db->getConnection();

//Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}
    //Receive the RAW post data.
    $inputJSON = trim(file_get_contents('php://input'));

    //Attempt to decode the incoming RAW post data from JSON.
    $data = json_decode($inputJSON, TRUE);

    $studentID = $data['studentID'];
    $firstName = (string)$data['firstName'];
    $lastName = (string)$data['lastName'];
    $moduleNo1 = $data['moduleNo1'];
    $moduleNo2 = $data['moduleNo2'];
    $courseID = $data['courseID'];

    $id = $_GET['id'];

    // Create query using SQL string
 
    $sql_query = "UPDATE studentTable SET firstName = '$firstName', lastName= '$lastName', moduleNo1 = $moduleNo1, moduleNo2 = $moduleNo2, courseID = $courseID where studentID = $id";

    // Query database using connection
    $result = $conn->query($sql_query);

// check for empty result
    if ($result)
    {

        // Create Array for JSON response
        $response = array();
        // success
        $response["success"] = 1;
        $response["message"] = "Data Updated";

        // print data inserted JSON
        print (json_encode($response));

    } else {
        // fail
        $response["success"] = 0;
        // no student inserted
        $response["message"] = "No Data Updated";

        // print no data inserted JSON
        print (json_encode($response));
}
?>

Next, I created a student-update service which will work with using the command:

ionic generate service services/student-update

Now, I’ll write the logic to first get individual student details which will be used to display on a separate update-student page, and then a function to update the student details using the PUT request from HTTP client module in the student-update.service.ts like this:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from 'src/environments/environment';
import { Student } from 'src/app/services/student-service.service';

@Injectable({
  providedIn: 'root',
})
export class StudentUpdateService {
  urlStudents = environment.urlStudents;

  constructor(private http: HttpClient) {}

  // created  a method to get only the object from students array that has the studentID property that matches the studentID parameter
  // this will create a details page for each student
  //using angular pipe to filter the data
  getStudentDetails(studentID: string): Observable<Student> {
    return this.http.get<Student>(this.urlStudents).pipe(
      map((result) => {
        // map the result to a new array of objects
        let student = result['students'].filter(
          (s: any) => s.studentID == studentID // filter the array of objects to return only the object that has the studentID property that matches the studentID parameter
        );
        return student && student.length ? student[0] : null; // return the first object in the array
      })
    );
  }

  urlUpdateStudents = environment.urlUpdateStudents;

  updateStudent(id: any, data: any) {
    // create a function called updateStudent that will subscribe to the updateStudentService and update student details in the database
    return this.http.put(this.urlUpdateStudents + '?id=' + id, data, {
      headers: new HttpHeaders({
        Accept: 'text/plain',
        'Content-Type': 'text/plain',
      }),
      responseType: 'text',
    });
  }
}

Note that before writing the logic for the above service I had added the URL to access the PHP script from localhost in environment.ts file like this:

export const environment = {
  production: false,
  // create a variable to hold the url to the php_ionic folder -> json-update-student.php file
  urlUpdateStudents: '<http://localhost:8888/php_ionic/json-update-student.php>',
};

In the StudentUpdateService , the service class uses the Angular HttpClient to perform HTTP requests to retrieve and update student records in a database.

The getStudentDetails method takes in a studentID parameter and returns an Observable of a Student object. It uses the HttpClient's get() method to retrieve all the student records from the database, and then uses the map() operator to filter the array of objects and return only the object that has the studentID property that matches the studentID parameter.

The updateStudent method takes in an id parameter and a data parameter, and returns an Observable that represents the HTTP request to update the student record in the database. It uses the HttpClient's put() method to send the update request, passing in the id and data parameters as query parameters and request body respectively.

The environment.urlStudents and environment.urlUpdateStudents variables are used to store the base URLs for the API endpoints.

Next, I need to create an update-student modal which will display an already-filled-in form with individual student details where user will be able to update student details. I create the page with the command:

ionic generate page pages/update-student

Then I move the route of this page from app.route.ts to tabs.route.ts and attach a studentID to the path like this (which will be later used for routing):

{
        path: 'update-student/:studentID',
        loadComponent: () =>
          import('../update-student/update-student.page').then(
            (m) => m.UpdateStudentPage
          ),
      },

Before creating the update-student form’s logic and template, I need to import Router Link in the parent page i.e. tab2.page.ts and create a router link attribute within the tab2.page.html template in the following way:

<ion-header [translucent]="false">
...
</ion-header>

<ion-content [fullscreen]="true">
 ...

  <ion-list lines="full">
    <ion-item-sliding *ngFor="let student of newStudents">
      ...
      <ion-item-options side="start">
        <ion-item-option
          [routerLink]="['/tabs/update-student', student.studentID]"
          color="warning"
          ><ion-icon name="create-outline" slot="icon-only"></ion-icon
        ></ion-item-option>
      </ion-item-options>
    </ion-item-sliding>
  </ion-list>
  ...
</ion-content>

Above I have used the routerLink property binding to add the student ID to the individual modal’s URI which will display individual student details on the update student page. Figure 11 shows how the update icon appears on sliding the list item to right:

Figure 11: Classroom list with sliding option to update student details

Now, I can start building the logic to update the student details on the update-student.ts file in the following way:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule, ToastController } from '@ionic/angular';

import { StudentUpdateService } from 'src/app/services/student-update.service';
import { ActivatedRoute } from '@angular/router';
import { RouterLink, Router } from '@angular/router';

@Component({
  selector: 'app-update-student',
  templateUrl: './update-student.page.html',
  styleUrls: ['./update-student.page.scss'],
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule, RouterLink],
})
export class UpdateStudentPage implements OnInit {
  student: any = {};

  constructor(
    private studentUpdateService: StudentUpdateService,
    private activatedRoute: ActivatedRoute,
    private router: Router,
    private toastCtrl: ToastController
  ) {}

  ngOnInit() {
    const studentID: any =
      this.activatedRoute.snapshot.paramMap.get('studentID');
    console.log(studentID); // test
    this.studentUpdateService
      .getStudentDetails(studentID)
      .subscribe((result) => {
        console.log(result); // test
        this.student = result; // store the data emitted from the observable into the module variable
        // the module variable here is an individual object from the modules array
      });
  }

  // create a function called updateStudentDetails that will subscribe to the updateStudentService and update student details in the database
  updateStudentDetails(id: any, data: any) {
    this.studentUpdateService.updateStudent(id, data).subscribe(
      (res: any) => {
        console.log('Success: Student Record is updated' + res); // this is the response from the server
        console.log(res);
        // navigate to the tab2 page
        this.router.navigate(['/tabs/tab2']);
        // //reload the window to display the updated data
        // window.location.reload();
        // show the toast success message
        this.showToast('Student Record is updated');

        //reload the window after 3 seconds to display the updated data
        setTimeout(() => {
          window.location.reload();
        }, 2100);
      },
      async (err: any) => {
        console.log('Error: Student Record is not updated' + err); // this is the response from the server
        // show the toast error message
        this.showToast('Student Record is not updated');
      }
    );
  }

  // create a show toast method
  async showToast(message: string) {
    const toast = await this.toastCtrl.create({
      message: message,
      duration: 2000,
    });
    toast.present();
  }
}

In the above code, the component receives the previously created service as constructor parameters and defines a module-level variable called student. In the ngOnInit method, the component retrieves the studentID parameter from the URL and uses the StudentUpdateService to fetch the details of the corresponding student. The data emitted by the observable is stored in the student variable. The component also defines a function called updateStudentDetails that uses the StudentUpdateService to update the student record in the database. It subscribes to the observable returned by the service's updateStudent method and handles the success and error cases. If the update is successful, the function shows a success toast message, reloads the window after 2.1 seconds, and navigates to the tab2 page. If the update fails, the function shows an error toast message. Finally, the component defines a helper method called showToast that displays a toast message using the ToastController service.

Next, I implement this logic within the template file update-student.page.html like this:

<ion-header [translucent]="false">
  <ion-toolbar>
    <ion-title>Edit Student</ion-title>
    <ion-buttons slot="start">
      <ion-back-button defaultHref="tabs/tab2"></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-list lines="none">
    <ion-item-group>
      <ion-item-divider>
        <ion-label>Student ID</ion-label>
      </ion-item-divider>
      <ion-item>
        <ion-label>{{student.studentID}}</ion-label>
        <ion-label slot="end">Not Editable</ion-label>
      </ion-item>
    </ion-item-group>

    <ion-item-group>
      <ion-item-divider>
        <ion-label>Edit Below Details: </ion-label>
      </ion-item-divider>

      <ion-item>
        <ion-input
          class="ion-margin-top"
          label="First Name"
          label-placement="floating"
          fill="outline"
          aria-label="First Name"
          type="text"
          [(ngModel)]="student.firstName"
          [value]="student.firstName"
        ></ion-input>
      </ion-item>
      <ion-item>
        <ion-input
          class="ion-margin-top"
          label="Last Name"
          label-placement="floating"
          fill="outline"
          aria-label="Last Name"
          type="text"
          [(ngModel)]="student.lastName"
          [value]="student.lastName"
        ></ion-input>
      </ion-item>
      <ion-item>
        <ion-input
          class="ion-margin-top"
          label="Course ID"
          label-placement="floating"
          fill="outline"
          aria-label="Course ID"
          type="text"
          [(ngModel)]="student.courseID"
          [value]="student.courseID"
        ></ion-input>
      </ion-item>
      <ion-item>
        <ion-input
          class="ion-margin-top"
          label="Module No. 1"
          label-placement="floating"
          fill="outline"
          aria-label="Module No. 1"
          type="text"
          [(ngModel)]="student.moduleNo1"
          [value]="student.moduleNo1"
        ></ion-input>
      </ion-item>
      <ion-item>
        <ion-input
          class="ion-margin-top"
          label="Module No. 2"
          label-placement="floating"
          fill="outline"
          aria-label="Module No. 2"
          type="text"
          [(ngModel)]="student.moduleNo2"
          [value]="student.moduleNo2"
        ></ion-input>
      </ion-item>
    </ion-item-group>
  </ion-list>
  <ion-button
    (click)="updateStudentDetails(student.studentID, student)"
    expand="block"
    class="ion-margin"
    fill="solid"
    shape="round"
  >
    Update Details
  </ion-button>

  <ion-button
    defaultHref="tabs/tab2"
    expand="block"
    fill="none"
    color="medium"
    [routerLink]="['/tabs/tab2']"
    >Cancel</ion-button
  >
</ion-content>

Above, I have the ionic input components to display and update individual student details. I have used the [value] property binding to display the existing value of the input field, and [(ngModel)] two-way data binding to take in the new value of the and update the details in the database and on the main students page.

The student ID cannot be edited because it is the primary key in the SQL database which cannot be edited by default. It took me a while to figure this out since I was trying to edit it before realising this lesson.

The Update Details button uses click event binding to call the updateStudentDetails(student.studentID, student) method that takes the student ID and student object as arguments which I created above in the TS file. This successfully updates the specific student details. Figure 12 and 13 demonstrate the update student form:

Figure 12: Existing student details appear on update student form

Figure 13: Updated student details which gets updated in database and list on submitting

DELETE Student Data

Now it’s time to build the last of CRUD operations, i.e. ‘DELETE’ operation. For this I begin by using the below PHP file to communicate with the previously set up SQL database:

<?php

//Enable cross domain Communication - Beware, this can be a security risk
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token , Authorization');

//include db connect class
require_once 'db_connect.php';

// Get access to database instance
$db = Database::getInstance();

// Get database connection from database
$conn = $db->getConnection();

//Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);
}

        // Get the student ID from the DELETE request
        $studentID = $_REQUEST['id'];

    // Create query using SQL string
    $sql_query =  $sql_query = "DELETE from studentTable where studentID = '$studentID'";

    // Query database using connection
    $result = $conn->query($sql_query);

// check for empty result
    if ($result)
    {

        // Create Array for JSON response
        $response = array();

        // success
        $response["success"] = 1;
        $response["message"] = "Data Deleted";

        // print data inserted JSON
        print (json_encode($response));

    } else {
        // fail
        $response["success"] = 0;
        // no student inserted
        $response["message"] = "No Data Deleted";

        // print no data inserted JSON
        print (json_encode($response));
}
?>

Next I place this file in D:\\MAMP\\htdocs\\php_ionic and begin creating a service that will access this PHP file with the following command:

ionic generate service services/student-delete

Next, I write the logic to make an HTTP request to the delete script in student.delete.ts file like this:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpHeaders } from '@angular/common/http';

@Injectable({
  providedIn: 'root',
})
export class StudentDeleteService {
  urlDeleteStudents = '<http://localhost:8888/php_ionic/json-delete-student.php>';

  constructor(private http: HttpClient) {}

  // create a delete method to delete student item data based on student id
  deleteStudent(id: any) {
    // console.log(id);
    return this.http.delete(this.urlDeleteStudents + '?id=' + id, {
      headers: new HttpHeaders({
        Accept: 'text/plain',
        'Content-Type': 'text/plain',
      }),
      responseType: 'text',
    });
  }
}

In the above code, the service class provides a method for deleting a student item from a server using HTTP delete request. It has a constructor that accepts an instance of the Angular HttpClient class as a parameter, which is used to send the HTTP request to the server. It has a single method named deleteStudent that takes an ID of the student item to delete as a parameter. This method constructs an HTTP delete request with the specified URL and the provided student ID as a query parameter. It sets the request headers to accept and send plain text data and sets the response type to text. When this method is called, it returns an Observable of the HTTP response from the server, which can be subscribed to and processed by main classroom page component.

Next, I will import this service into my previously created tab2.page.ts file in the following way:

// create a method to delete a student
  deleteStudent(id: any) {
    this.studentDeleteService.deleteStudent(id).subscribe(
      // subscribe to the observable returned by the deleteStudent() method
      (result) => {
        console.log('SUCCESS' + result);
        // call the getStudentData() method
        this.getStudentData();
        this.showToast('Student Record is deleted');
      },
      (error) => {
        console.log('ERROR' + error);
        this.showToast('Student Record is not deleted');
      }
    );
  }

  // create a method to show toast message
  async showToast(message: string) {
    const toast = await this.toastCtrl.create({
      message: message,
      duration: 2000,
    });
    toast.present();
  }

In the above code, the deleteStudent method takes in a id parameter and uses a studentDeleteService instance to make an HTTP DELETE request to delete the student record with the corresponding ID. It then subscribes to the observable returned by the deleteStudent() method and logs a success message to the console if the delete operation is successful, calls the getStudentData() method, and displays a toast message indicating that the student record has been deleted. If the delete operation fails, it logs an error message and displays a toast message indicating that the student record was not deleted. The showToast method takes in a message parameter and uses the toastCtrl instance to create and present a toast message with the specified message and duration of 2000 milliseconds.

Next, to implement this logic on the student list, I add the item-sliding-option with a that calls the above deleteStudent() method when the click event is called in the tab2.page.html file:

...

<ion-content [fullscreen]="true">
  ...
  <ion-list lines="full">
    <ion-item-sliding *ngFor="let student of newStudents">
      <ion-item>...</ion-item>
      <ion-item-options side="end">
        <ion-item-option
          (click)="deleteStudent(student.studentID)"
          color="danger"
          ><ion-icon name="trash" slot="icon-only"></ion-icon
        ></ion-item-option>
      </ion-item-options>
...
</ion-content>

When the user clicks on the delete button, the specific student data object gets deleted from the SQL database and from the displayed student list on the classroom page as show on Figure 14 and 15 below:

Figure 14: Student delete option shows on sliding to left on single item. 

Figure 15: Student record object is deleted and toast message appears.

And with this, finally the CRUD operations are complete using the student data. I had some bugs and difficulties with getting the PHP scripts to work properly but ultimately I figured out the proper HTTP requests and console logging in browser, and echoing in PHP everything at every stage helps to understand how data is flowing through the application. Also, I realised by the end that there could be a way to use a single modal to add a student and to update a student but maybe I’ll try that one next time. 

Next up I will be using some latitude and longitude data from the module data we created in previous parts, and display them on individual maps using Leaflet JS library. Thankyou for reading!

Popular Posts

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