Continuing down the path of user profile stores, we had previously seen how to create one with Node.js and Couchbase NoSQL as well as a web client front-end for it using Angular. What if we wanted to take this into a mobile application, which is the norm for all applications as of now.
There are many different mobile frameworks out there and we’re lucky that some even support Angular, which is what we had used in the previous example. We’re going to see how to convert our client front-end to mobile using NativeScript and Angular.
Before going forward, the assumption is that you’ve completely both of the two previous tutorials, one on creating the profile store backend and the other on creating the profile store web front-end. We’re also going to assume that your development environment is property configured for mobile development, whether that be Android, iOS, or both.
The flow of events in this application will match what we saw in the web version.
Create a New NativeScript with Angular Project
Assuming that you’ve gotten the NativeScript CLI installed and configured, execute the following to create a new project:
1 |
tns create profile-project-ns --ng |
The --ng
flag in the above command is important because it means we are creating an Angular project rather than a NativeScript Core project.
As of right now, the NativeScript CLI doesn’t share the Angular CLI capabilities of generating components. For this reason, we’re going to have to create each of the HTML and TypeScript files manually.
If you’re on Mac or Linux, execute the following from within the NativeScript project:
1 2 3 4 5 6 7 8 9 10 11 12 |
mkdir -p app/login mkdir -p app/register mkdir -p app/blogs mkdir -p app/blog touch app/login/login.component.html touch app/login/login.component.ts touch app/register/register.component.html touch app/register/register.component.ts touch app/blogs/blogs.component.html touch app/blogs/blogs.component.ts touch app/blog/blog.component.html touch app/blog/blog.component.ts |
If you’re on Windows, just create those directories and files manually. If you really wanted to, you could copy these directories and files from your web project from the previous tutorial.
Defining the Components to Represent Each Screen
Starting in the same direction as the web version, we’re going to focus on user login. Open the project’s app/login/login.component.ts file and include the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
import { Component } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { Router } from "@angular/router"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-login', templateUrl: './login.component.html' }) export class LoginComponent { public input: any; constructor(private http: Http, private router: Router) { this.input = { "email": "", "password": "" }; } public login() { if(this.input.email && this.input.password) { let headers = new Headers({ "content-type": "application/json" }); let options = new RequestOptions({ headers: headers }); this.http.post("http://localhost:3000/login", JSON.stringify(this.input), options) .map(result => result.json()) .subscribe(result => { this.router.navigate(["/blogs"], { "queryParams": result }); }); } } } |
The above code is exactly what we saw in the web version with two exceptions. I’ve removed the CSS reference since we did not create a CSS file on a component basis. I’ve also added a moduleId
so relative paths could work with our component. These are both Angular related items with nothing to do with NativeScript.
The HTML is where things are different. Open the project’s app/login/login.component.html file and include the following XML markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<ActionBar title="{N} Profile Store"></ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="Email" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.email" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Password" class="label font-weight-bold m-b-5"></Label> <TextField class="input" secure="true" [(ngModel)]="input.password" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Button class="btn btn-primary" text="Login" (tap)="login()"></Button> </StackLayout> <StackLayout class="input-field"> <Label text="Register here." [nsRouterLink]="['/register']" class="text-center"></Label> </StackLayout> </StackLayout> |
NativeScript has its own markup for the UI. The same rules apply in terms of Angular, but creating UI components is just a bit different.
For example, we are still using the [(ngModel)]
attributes, but instead of div
tags, we have StackLayout
tags.
Now let’s take a look at the registration component. Open the project’s app/register/register.component.ts file and include the following TypeScript:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import { Component } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { Router } from "@angular/router"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-register', templateUrl: './register.component.html' }) export class RegisterComponent { public input: any; public constructor(private http: Http, private router: Router) { this.input = { "firstname": "", "lastname": "", "email": "", "password": "" }; } public register() { if(this.input.email && this.input.password) { let headers = new Headers({ "content-type": "application/json" }); let options = new RequestOptions({ headers: headers }); this.http.post("http://localhost:3000/account", JSON.stringify(this.input), options) .map(result => result.json()) .subscribe(result => { this.router.navigate(["/login"]); }); } } } |
Again, the only changes we made in the above code, compared to the previous example, is in the CSS removal and moduleId
addition.
Not bad when it comes to creating cross platform web and mobile applications right?
The HTML for the UI that powers the TypeScript logic is found in the app/register/register.component.html file and it looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
<ActionBar title="{N} Profile Store"></ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="First Name" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.firstname" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Last Name" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.lastname" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Email" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.email" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Password" class="label font-weight-bold m-b-5"></Label> <TextField class="input" secure="true" [(ngModel)]="input.password" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Button class="btn btn-primary" text="Register" (tap)="register()"></Button> </StackLayout> <StackLayout class="input-field"> <Label text="Login here." [nsRouterLink]="['/login']" class="text-center"></Label> </StackLayout> </StackLayout> |
The final two components are going to be no different from what we’re already experiencing.
Open the project’s app/blogs/blogs.component.ts file and include the following TypeScript code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
import { Component, OnInit } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { Router, ActivatedRoute } from "@angular/router"; import { Location } from "@angular/common"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-blogs', templateUrl: './blogs.component.html' }) export class BlogsComponent implements OnInit { private sid: string; public entries: Array<any>; public constructor(private http: Http, private router: Router, private route: ActivatedRoute, private location: Location) { this.entries = []; } public ngOnInit() { this.location.subscribe(() => { let headers = new Headers({ "authorization": "Bearer " + this.sid }); let options = new RequestOptions({ headers: headers }); this.http.get("http://localhost:3000/blogs", options) .map(result => result.json()) .subscribe(result => { this.entries = result; }); }); this.route.queryParams.subscribe(params => { this.sid = params["sid"]; let headers = new Headers({ "authorization": "Bearer " + params["sid"] }); let options = new RequestOptions({ headers: headers }); this.http.get("http://localhost:3000/blogs", options) .map(result => result.json()) .subscribe(result => { this.entries = result; }); }); } public create() { this.router.navigate(["/blog"], { "queryParams": { "sid": this.sid } }); } } |
No changes to see in the above other than the two previously mentioned, so we can move into our HTML for the page.
Open the project’s app/blogs/blogs.component.html file and include the following HTML markup:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<ActionBar title="{N} Profile Store"> <ActionItem text="New" ios.position="right" (tap)="create()"></ActionItem> <NavigationButton text="Back"></NavigationButton> </ActionBar> <GridLayout rows="*, auto" columns="*"> <ListView [items]="entries" class="list-group" row="0" col="0"> <ng-template let-entry="item"> <StackLayout class="list-group-item"> <Label text="{{ entry.title }}" class="h2"></Label> <Label text="{{ entry.content }}"></Label> </StackLayout> </ng-template> </ListView> <StackLayout row="1" col="0" padding="10" backgroundColor="#F0F0F0"> <Label text="Logout" [nsRouterLink]="['/login']"></Label> </StackLayout> </GridLayout> |
Let’s wrap this application up with the final component that our profile store API and web front-end has to offer.
Open the project’s app/blog/blog.component.ts file and include this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
import { Component, OnInit } from '@angular/core'; import { Http, Headers, RequestOptions } from "@angular/http"; import { ActivatedRoute } from "@angular/router"; import { Location } from "@angular/common"; import "rxjs/Rx"; @Component({ moduleId: module.id, selector: 'app-blog', templateUrl: './blog.component.html' }) export class BlogComponent implements OnInit { private sid: string; public input: any; public constructor(private http: Http, private route: ActivatedRoute, private location: Location) { this.input = { "title": "", "content": "" }; } public ngOnInit() { this.route.queryParams.subscribe(params => { this.sid = params["sid"]; }); } public save() { if(this.input.title && this.input.content) { let headers = new Headers({ "content-type": "application/json", "authorization": "Bearer " + this.sid }); let options = new RequestOptions({ headers: headers }); this.http.post("http://localhost:3000/blog", JSON.stringify(this.input), options) .map(result => result.json()) .subscribe(result => { this.location.back(); }); } } } |
If you didn’t copy over your CSS file, don’t forget to remove it from the @Component
block as seen in the previous components.
The HTML UI to go with this TypeScript is found in the project’s app/blog/blog.component.html file and it looks like the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<ActionBar title="{N} Profile Store"> <NavigationButton text="Back"></NavigationButton> </ActionBar> <StackLayout class="form"> <StackLayout class="input-field"> <Label text="Title" class="label font-weight-bold m-b-5"></Label> <TextField class="input" [(ngModel)]="input.title" autocapitalizationType="none"></TextField> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Label text="Content" class="label font-weight-bold m-b-5"></Label> <TextView class="input" [(ngModel)]="input.content" autocapitalizationType="none"></TextView> <StackLayout class="hr-light"></StackLayout> </StackLayout> <StackLayout class="input-field"> <Button class="btn btn-primary" text="Save" (tap)="save()"></Button> </StackLayout> </StackLayout> |
As of right now you may be scratching your head on all this NativeScript XML markup. How Angular works with the UI hasn’t changed, but if you’re interested in learning about the NativeScript markup, check out their official documentation. Get familiar with the StackLayout
, GridLayout
and individual UI component tags.
Bringing it Together
We’ve created all these Angular components for NativeScript, but we haven’t brought them together via the Angular Router.
In the web version of this guide, the route information was in the app.module.ts file. While we could do that, NativeScript broke it out into a separate file.
Open the project’s app/app.routing.ts file and include the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { NgModule } from "@angular/core"; import { NativeScriptRouterModule } from "nativescript-angular/router"; import { Routes } from "@angular/router"; import { LoginComponent } from "./login/login.component"; import { RegisterComponent } from "./register/register.component"; import { BlogsComponent } from "./blogs/blogs.component"; import { BlogComponent } from "./blog/blog.component"; const routes: Routes = [ { path: "", redirectTo: "/login", pathMatch: "full" }, { path: "login", component: LoginComponent }, { path: "register", component: RegisterComponent }, { path: "blogs", component: BlogsComponent }, { path: "blog", component: BlogComponent } ]; @NgModule({ imports: [NativeScriptRouterModule.forRoot(routes)], exports: [NativeScriptRouterModule] }) export class AppRoutingModule { } |
A lot of the above code came with our new project template. We imported each of our components, and created a route for them.
Likewise, the components need to be imported in the project’s app/app.module.ts file. Open this file and include the following:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
import { NgModule, NO_ERRORS_SCHEMA } from "@angular/core"; import { NativeScriptModule } from "nativescript-angular/nativescript.module"; import { AppRoutingModule } from "./app.routing"; import { NativeScriptHttpModule } from "nativescript-angular/http"; import { NativeScriptFormsModule } from "nativescript-angular/forms"; import { AppComponent } from "./app.component"; import { LoginComponent } from "./login/login.component"; import { RegisterComponent } from "./register/register.component"; import { BlogsComponent } from "./blogs/blogs.component"; import { BlogComponent } from "./blog/blog.component"; @NgModule({ bootstrap: [ AppComponent ], imports: [ NativeScriptModule, AppRoutingModule, NativeScriptHttpModule, NativeScriptFormsModule ], declarations: [ AppComponent, LoginComponent, RegisterComponent, BlogsComponent, BlogComponent ], providers: [], schemas: [ NO_ERRORS_SCHEMA ] }) export class AppModule { } |
In addition to importing each component and adding them to the declarations
array, we also imported a few modules such as the NativeScriptHttpModule
and NativeScriptFormsModule
. In pure Angular, these are called the HttpModule
and FormsModule
.
In theory, the application is ready to go.
Resolving App Transport Security Issues (ATS) for iOS
Because the Node.js and Couchbase API is running locally, we are using HTTP rather than HTTPS. iOS will throw errors if we try to access HTTP resources.
This is easily fixable by adding an ATS policy.
Open the project’s app/App_Resources/iOS/Info.plist and add the following alongside the other XML:
1 2 3 4 |
<dict> <key>NSAllowsArbitraryLoads</key> <true /> </dict> |
The above essentially whitelists all HTTP endpoints. It is not safe for production, but safe for testing.
More information on ATS in NativeScript applications can be read about in a previous article I wrote titled, Fix iOS 9 App Transport Security Issues in NativeScript.
Conclusion
You just saw how easy it was to take your web client front-end and convert it to mobile using NativeScript and Angular. The user profile store example quickly became a full stack example using the JavaScript stack. We had a Node.js with Couchbase Server backend, Angular web front-end, and NativeScript mobile front-end.
The next step, or option, would be to use the Couchbase Mobile components rather than HTTP calls against the API.
Hello,
I tried to run this code but POST is not working. When I try to register I’m getting the error below. From online I notice that http://localhost is not working on android sdk. I use the IP address both 10.0.2.2 and 127.0.0.1 but still getting the same error.
JS: ERROR Response with status: 200 for URL: null