Our website is made possible by displaying online advertisements to our visitors. Please consider supporting us by disabling your ad blocker.

Passing Complex Data Through The Angular Router With NativeScript

TwitterFacebookRedditLinkedInHacker News

I recently wrote a tutorial for navigating a NativeScript Angular application using the Angular Router. In this tutorial I demonstrated how to create a multiple page application, navigate between pages, and even pass simple string parameters between the pages during navigation. However, what happens when the data you need to pass isn’t so simple?

We’re going to take a look at some of the ways to pass data between routes in a NativeScript application that makes use of Angular.

Before getting too far ahead of ourselves, I recommend revisiting the article I wrote regarding navigation as this will be more or less a continuation of that article.

There are many ways to accomplish the task of passing around data in an Angular application. We’re going to look at two of those possibilities.

Using Query Parameters

The first, and probably the quickest, way to pass data around would be to use query parameters. Now this isn’t the same as the URL parameters that we saw previously.

Let me explain.

If viewing the URL within a web application, you’d notice that URL parameters look something along the lines of /people/:id where :id could be any value in the path. You can have as many of those parameters as you want, but the more you have, the messier they become.

Query parameters look more along the lines of /people?id= where id can equal anything and you can have as many parameters as you want. The query parameters would be separated by the ampersand character.

So where am I going with this and how does it relate to Angular or NativeScript?

When working with query parameters, you don’t need to define them in your routes file, and they can be named parameters. For example, take the following TypeScript code:

import {Component} from "@angular/core";
import {Router, NavigationExtras} from "@angular/router";

@Component({
    selector: "page1",
    templateUrl: "./components/page1/page1.html",
})
export class Page1Component {

    public constructor(private router: Router) { }

    public onTap() {
        let navigationExtras: NavigationExtras = {
            queryParams: {
                "firstname": "Nic",
                "lastname": "Raboy"
            }
        };
        this.router.navigate(["page2"], navigationExtras);
    }

}

The Angular navigate method, found in the Router component, accepts an optional NavigationExtras object. This object has a property called queryParams where you can pass any object you want.

There is a catch however. The queryParams object must be flat. This means no nested objects or arrays can be present. Remember though, this could be a step up from the plain strings we were using with the URL parameters.

In the receiving page, you would receive these query parameters like the following:

import {Component} from "@angular/core";
import {ActivatedRoute} from "@angular/router";

@Component({
    selector: "page2",
    templateUrl: "./components/page2/page2.html",
})
export class Page2Component {

    public firstname: string;
    public lastname: string;

    public constructor(private route: ActivatedRoute) {
        this.route.queryParams.subscribe(params => {
            this.firstname = params["firstname"];
            this.lastname = params["lastname"];
        });
    }

}

Receiving query parameters is really no different than receiving URL parameters.

So what if you want to pass around more complex parameters than just flat objects or strings? Well you could serialize objects into strings and pass them around like this:

let navigationExtras: NavigationExtras = {
    queryParams: {
        "firstname": "Nic",
        "lastname": "Raboy",
        "address": JSON.stringify({
            "city": "San Francisco",
            "state": "California"
        })
    }
};

On the receiving end, you’d receive address as a string which can be parsed back into an object.

However, there are much better ways to accept complex data without bothering with object serialization and parsing.

Using Application Providers

While this next method isn’t technically related to the Angular Router, it is a method for passing around data nonetheless. A perfectly acceptable way to pass around data would be to use an application provider that is shared with all pages of your application.

Take the following provider:

import { Injectable } from '@angular/core';

@Injectable()
export class Data {

    public storage: any;

    public constructor() { }

}

This provider has a public variable called storage that can be used on any page we inject it into. This variable will be of the same instance on every page.

With that said, let’s revisit the first page:

import {Component} from "@angular/core";
import {Router, NavigationExtras} from "@angular/router";
import { Data } from "../../providers/data/data";

@Component({
    selector: "page1",
    templateUrl: "./components/page1/page1.html",
})
export class Page1Component {

    public constructor(private router: Router, private data: Data) {}

    public onTap() {
        this.data.storage = {
            "firstname": "Nic",
            "lastname": "Raboy",
            "address": {
                "city": "San Francisco",
                "state": "California"
            }
        }
        this.router.navigate(["page2"]);
    }

}

Notice how we’re injecting that Data provider into our page and setting it to an object before we plan to navigate? On the receiving page we can do something like this:

import {Component} from "@angular/core";
import { Data } from "../../providers/data/data";

@Component({
    selector: "page2",
    templateUrl: "./components/page2/page2.html",
})
export class Page2Component {

    public constructor(private data: Data) {
        console.log(JSON.stringify(this.data.storage));
    }

}

Notice we’ve injected the Data provider again, but instead of setting it, we’re reading from it.

Just to clear up any loose ends, bootstrapping the Data provider in the application’s @NgModule would look like the following:

// this import should be first in order to load some required settings (like globals and reflect-metadata)
import { platformNativeScriptDynamic, NativeScriptModule } from "nativescript-angular/platform";
import { NgModule } from "@angular/core";
import { AppComponent } from "./app.component";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { appComponents, appRoutes } from "./app.routing";
import { Data } from "./providers/data/data";

@NgModule({
    declarations: [AppComponent, ...appComponents],
    bootstrap: [AppComponent],
    imports: [
        NativeScriptModule,
        NativeScriptRouterModule,
        NativeScriptRouterModule.forRoot(appRoutes)
    ],
    providers: [Data]
})
class AppComponentModule {}

platformNativeScriptDynamic().bootstrapModule(AppComponentModule);

Notice how we’ve included providers in the above code.

Conclusion

You just saw two alternatives to passing around URL parameters found in the previous article I wrote on the topic of navigation in an Angular NativeScript application. While query parameters and providers are not the only ways to accomplish the job, they are probably the easiest in my opinion.

The Angular documentation references Router data and resolves, but those can probably be avoided in many scenarios. At the end of the day, you may just want to re-evaluate your data needs.

A video version of this article can be seen below.

Nic Raboy

Nic Raboy

Nic Raboy is an advocate of modern web and mobile development technologies. He has experience in C#, JavaScript, Golang and a variety of frameworks such as Angular, NativeScript, and Unity. Nic writes about his development experiences related to making web and mobile development easier to understand.