Seamless Data Navigation: Utilizing Angular Routing for Displaying Detailed Data on Separate Pages (Part 2) in Your Angular-15 Ionic-7 App

Welcome to Part 2 of our series, "My First Angular-15 Ionic-7 App"! In this installment, we'll dive into the exciting world of Angular routing and learn how to display data details on separate pages in our app.

Routing plays a crucial role in creating a seamless and organized user experience. By utilizing Angular's powerful routing capabilities, we can navigate between different sections of our app and display detailed information on separate pages.

In Part 1, we laid the foundation for our Angular-15 Ionic-7 app and explored the basics of data management. Now, in Part 2, we'll take our app to the next level by implementing Angular routing. You'll learn how to configure routes, create navigation links, and dynamically display data details on separate pages.


Imagine the possibilities this opens up for your app. Users can effortlessly navigate through a list of items, select one, and view its detailed information on a dedicated page. This enhances usability and allows for a focused and immersive experience.

Throughout this tutorial, we'll guide you step-by-step, ensuring you understand the concepts and techniques behind Angular routing. From setting up routes to passing data between pages, we'll cover it all.

By the end of this article, you'll have the skills to create a navigation structure that seamlessly guides users through your Angular-15 Ionic-7 app, displaying data details on separate pages. This powerful feature will elevate the usability and engagement of your application.

So, are you ready to embark on this routing adventure? Join us in Part 2 of "Seamless Data Navigation: Utilizing Angular Routing for Displaying Detailed Data on Separate Pages (Part 2) in Your Angular-15 Ionic-7 App" as we delve into the intricacies of Angular routing and unlock a whole new level of user experience.

Let the routing journey begin!


Tutorial

Here I will use the concept of Router Links and Parameter mapping to get data via API calls from a SQL database and pass the individual object data on its specific page.

Since I have already set up the college database, I use the following PHP script to work with it:

<?php
/*
 * Following code will list all the modules on a course
 */

//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 datbase 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 moduleTable";

// 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 modules inside response Array
    $response["modules"] = array();

	// Loop through all results from Database
    while ($row = mysqli_fetch_array($result))
     {
        	// Assign results for each database row, to temp module array
            $module = array();
            $module["moduleNo"] = $row["moduleNo"];
            $module["moduleName"] = $row["moduleName"];
            $module["credits"] = $row["credits"];
            $module["website"] = $row["website"];
            $module["dueDate"] = $row["dueDate"];
            $module["location"] =$row["location"];
            $module["room"] = $row["room"];
            $module["lat"] = $row["lat"];
            $module["long"] = $row["long"];

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

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

}
else {
    // no modules found
    $response["success"] = 0;
    $response["message"] = "No modules found";

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

Then I enter the URL to access this file to the environment.ts file like this:

export const environment = {
  production: false,

  // create a variable to hold the url to the php_ionic folder -> json-data-modules.php file
  urlModules: '<http://localhost:8888/php_ionic/json-data-modules.php>',
}

Then I create a new service with this command: ionic generate service services/module-service.

In the module-service.ts file, I write the logic to make HTTP call from database like this:

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

// export interface Module
// the interface is used to define the structure of the data
export interface Module {
  [key: string]: any;
  credits: string;
  dueDate: string;
  lat: number;
  long: number;
  moduleName: string;
  moduleNo: string;
  room: string;
  website: string;
  location: string;
}

@Injectable({
  providedIn: 'root',
})
export class ModuleServiceService {
    // use the environment variable to hold the url to the php_ionic folder -> json-data-modules.php file
  urlModules = environment.urlModules;

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

  // create a method to get the data from the json-data-modules.php file
  // return type is Observable<Module>
  getModules(): Observable<Module> {
    return this.http.get<Module>(this.urlModules);
  }

  // created  a method to get only the object from modules array that has the moduleNo property that matches the moduleNo parameter
  // this will create a details page for each module
  //using angular pipe to filter the data
  getModuleDetails(moduleNo: string): Observable<Module> {
    return this.http.get<Module>(this.urlModules).pipe(
      // pipe is used to chain multiple operators
      map((result) => {
        let module = result['modules'].filter(
          (m: any) => m.moduleNo == moduleNo
        );
        // return the module object
        // if the module object exists, return the module object
        // if the module object does not exist, return null
        return module && module.length ? module[0] : null;
      })
    );
  }
}

In the above code, I have applied similar logic to make the http request as I did for the lecturer data. I have also created a getModuleDetails() method that took me a while to figure out. It fetches details of a specific module from a remote server using an HTTP GET request. It takes a string argument moduleNo, which represents the module number to retrieve, and returns an Observable of type Module. It uses the http.get() method to fetch the data from the server and applies a map() operator to transform the data to the Module type. The pipe() method allows for additional operators to be chained to the observable to further transform the data before it is consumed by the component.

Inside the map() operator, the response data is filtered to find the module object with the matching moduleNo, and if found, it is returned; otherwise, null is returned. The method returns the Observable, which will be subscribed to later on by the module-details page to receive the details of the requested module.

Note that I have created a Module interface within the service itself which will be used to define the structure of the data object and later on to define data type on other pages.

Next, I have imported the above service to tab1.page.ts file and written the method to subscribe to this service and receive the data in a variable like this:

import { Component } from '@angular/core';
import { IonicModule, ModalController } from '@ionic/angular';
import { OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { environment } from 'src/environments/environment';
import { ModuleServiceService, Module,} from '../../services/module-service.service';
import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss'],
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule, RouterLink],
})
export class Tab1Page implements OnInit {
  // create 2 variables to store results emitted from observable
  modules: any = [];
  newModules: any = [];
  urlModules = environment.urlModules;
  // inject module service into the constructor
  constructor(
    private moduleService: ModuleServiceService,
  ) {}

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

  // create a method to get the data from the json-data-modules.php file
  getModuleData() {
    // subscribe to the observable
    this.moduleService.getModules().subscribe((result) => {
      // store the data emitted from the observable into the modules variable
      this.modules = result;
      // console.log(this.modules);
      // store the data emitted from the observable into the newModules variable
      this.newModules = this.modules.modules;
    });
  }

  // store the url of the images from API in a variable
  urlRandomImages = environment.urlRandomImages;
}

Not that I have imported urlRandomImages which is an external API to generate random images for my modules every time I load in. Following is the URL I have placed in the environment.ts to make the API call:

// create a variable to hold random images api url
  urlRandomImages: '<https://picsum.photos>',

In the above code I have simply used the previously created module service and created a method to subscribe to the observable and store the modules in a newModules array which will be now used in the following tab1.page.html file to display the data as a list of cards:

<ion-header [translucent]="false">
  <ion-toolbar>
    <ion-title> My Modules </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" *ngIf="newModules">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large"> My Modules</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-card *ngFor="let module of newModules; let i = index" [routerLink]="['/tabs/tab1-details', module.moduleNo]"
      button>
    <ion-img [src]="urlRandomImages + '/300/150'"></ion-img>
    <ion-card-header>
      <ion-card-subtitle>Due: {{module.dueDate}}</ion-card-subtitle>
      <ion-card-title>{{i+1}}. {{module.moduleName}}</ion-card-title>
    </ion-card-header>
    <ion-card-content> </ion-card-content>
  </ion-card>
</ion-content>

In the above code, I have used the *ngFor angular directive and Ionic card component from ion-card: Card UI Components for Ionic Framework API to loop through the modules array and display their names and due dates in a list of cards. I have also used the Random Images API from Lorem Picsum as cover images for the cards.

Note that I have used the routerLink property binding to create an individual link to a tabs/tab1-details/moduleNo page that was created using ionic generate page pages/tab1-details command. To make this routerLink work, I had to import it into the typescript file of the page and also in main.ts file.

Also, I had to enter the router parameter in the respective path of tab1-details in the tabs.route.ts file like this:

{...
        path: 'tab1-details/:moduleNo',
        loadComponent: () =>
          import('../tab1-details/tab1-details.page').then(
            (m) => m.Tab1DetailsPage
          ),
      },

Next, I write the logic for getting individual module details using the method defined earlier in the module service in the following way:

import { Component, OnInit } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { IonicModule } from '@ionic/angular';
import { ActivatedRoute, RouterLink } from '@angular/router';
import {
  ModuleServiceService,
  Module,
} from 'src/app/services/module-service.service';
import { environment } from 'src/environments/environment';

@Component({
  selector: 'app-tab1-details',
  templateUrl: './tab1-details.page.html',
  styleUrls: ['./tab1-details.page.scss'],
  standalone: true,
  imports: [IonicModule, CommonModule, FormsModule, RouterLink],
})
export class Tab1DetailsPage implements OnInit {
  module: Module = {
    moduleNo: '',
    credits: '',
    dueDate: '',
    lat: 0,
    long: 0,
    moduleName: '',
    room: '',
    website: '',
    location: '',
  };

  constructor(
    private activatedRoute: ActivatedRoute, // used to get the moduleNo from the url
    private moduleService: ModuleServiceService,
  ) {}

  ngOnInit() {
    //used the parameter map to get the moduleNo from the url
    // the parameter map is a map of the route parameters
    // each detail page will have a different moduleNo and it will be passed in the url
    const moduleNo: any = this.activatedRoute.snapshot.paramMap.get('moduleNo');
    // console.log(moduleNo); // test

    // call the getModuleDetails() method and pass in the moduleNo
    // subscribe to the observable
    // result is the data emitted from the observable
    // store the data emitted from the observable into the module variable
    this.moduleService.getModuleDetails(moduleNo).subscribe((result) => {
      // console.log(result); // test
      this.module = result; // store the data emitted from the observable into the module variable
    });
  }

  // create a method to open the website in a new tab
  openHomePage() {
    window.open(this.module.website, '_blank');
  }

  // store the urlRandomImages variable from the environment.ts file into the urlRandomImages variable
  urlRandomImages = environment.urlRandomImages;
}

In the above code, I started off by defining a module property, setting it to Module type, and initiating the individual properties, with blank values, within the module without which the template returns an error. Then, at the page’s initialization, I created a moduleNo variable that uses the activatedRoute , snapshot and paramMap methods from the angular router to get the moduleNo string we passed in the tabs.route.ts file earlier. Then I used the method getModuleDetails(moduleNo) which was created in the module service to get individual module data and subscribed to the observable by storing the data in the newly created module object.

Next I have used this module object and its properties to display module specific data on tab1-details.page.ts file like this:

<ion-header [translucent]="false">
  <ion-toolbar>
    <ion-title>Module Details</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">Module Details</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-card *ngIf="module">
    <ion-img [src]="urlRandomImages + '/300/150'"></ion-img>
    <ion-card-header>
      <ion-card-subtitle>Module Name</ion-card-subtitle>
      <ion-card-title>{{module.moduleName}}</ion-card-title>
    </ion-card-header>
    <ion-card-content>
      <ion-list>
        <ion-item-group>
          <ion-item-divider>
            <ion-label>General Information</ion-label>
          </ion-item-divider>

          <ion-item>
            <ion-label>Due Date: {{module.dueDate}}</ion-label>
          </ion-item>
          <ion-item>
            <ion-label>Credits: {{module.credits}}</ion-label>
          </ion-item>
          <ion-item>
            <ion-label>Module No.: {{module.moduleNo}}</ion-label>
          </ion-item>
        </ion-item-group>
      </ion-list>

      <ion-list>
        <ion-item-group>
          <ion-item-divider>
            <ion-label>Contact</ion-label>
          </ion-item-divider>
          <ion-item>
            <ion-label>Website: {{module.website}}</ion-label>
          </ion-item>
          <ion-item>
            <ion-button
              (click)="openHomePage()"
              expand="block"
              fill="clear"
              shape="round"
            >
              Visit Website
            </ion-button>
          </ion-item>
        </ion-item-group>
      </ion-list>
    </ion-card-content>
  </ion-card>

  <ion-card *ngIf="module">
    <ion-card-header>
      <ion-card-title>Location</ion-card-title>
      <ion-card-subtitle>{{module.moduleName}}</ion-card-subtitle>
    </ion-card-header>
    <ion-card-content>
      <ion-list>
        <ion-item>
          <ion-label>Location: {{module.location}}</ion-label>
        </ion-item>
        <ion-item>
          <ion-label>Room: {{module.room}}</ion-label>
        </ion-item>
        <ion-item>
          <ion-label>Latitude: {{module.lat}}</ion-label>
        </ion-item>
        <ion-item>
          <ion-label>Longitude: {{module.long}}</ion-label>
        </ion-item>
      </ion-list>
    </ion-card-content>
  </ion-card>
</ion-content>

In the above template, I have used the *ngIf angular directive to check if a module data is returned to show the data. Also, I have used an Ionic card UI component within an ionic list component. In this {{module.moduleName}} , I have used string interpolation for one-way data binding to display the moduleName property value of the module object. I have also used [src] as property binding, again one-way data binding, to use the random images API within the HTML attribute. This template gives the following figure 4 and figure 5 as module list page and module details page:


Once the data is available on the modules main page and the URL is changing for the module-details page with a moduleNo at the end, and also the individual module object data is passing from one page to another, it is evident that the angular router is working as expected.

Conclusion

Congratulations on reaching the conclusion of Part 2 in our series, "My First Angular-15 Ionic-7 App"! We've covered a lot of ground in this installment, exploring the power of Angular routing and learning how to display data details on separate pages in our app.

By now, you should have a solid understanding of how to configure routes, create navigation links, and dynamically display data details on separate pages using Angular routing. This opens up a world of possibilities for your app, allowing users to seamlessly navigate through different sections and explore detailed information with ease.

Routing is a key component in creating a user-friendly and intuitive app experience. With Angular-15 and Ionic-7, you have the tools at your disposal to build a navigation structure that guides users through your app seamlessly.

But our journey doesn't end here. In the upcoming parts of this series, we'll continue to enhance our app's functionality and explore more advanced topics. You can look forward to learning about data manipulation, user interactions, performance optimization, and much more.

Remember, the key to mastering Angular routing lies in practice. Take the knowledge you've gained from Part 2 and apply it to your own app development projects. Experiment with different routing configurations, explore additional features, and create unique and engaging user experiences.

Thank you for joining us on this exciting routing adventure. We hope this series has inspired you to leverage the power of Angular-15 and Ionic-7 to create remarkable apps with seamless navigation and data display.

Stay connected and keep an eye out for the upcoming parts of this series. Let's continue our journey of building a fantastic Angular-15 Ionic-7 app together!

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