Creating a Front-End for Your User Profile Store With Angular and TypeScript
We’re going to see how to create a client front-end written in Angular with TypeScript, that communicates with each of the API endpoints.
Join the DZone community and get the full member experience.
Join For FreeI recently wrote a tutorial titled, Creating a User Profile Store with Node.js and a NoSQL Database, as it is a very popular subject and use-case when it comes to NoSQL databases. In that tutorial, we created an API using Node.js and Couchbase for storing users, information associated to particular users, and expiring sessions. However, it was strictly backend related with no user interface.
What if we wanted to realistically use the user profile store API? We’re going to see how to create a client front-end written in Angular with TypeScript, that communicates with each of the API endpoints.
Before continuing, we are assuming that you’ve followed the previous tutorial and were successful. We’re also going to assume that you have the Angular CLI installed and ready to go.
Create an Angular Project With the Necessary Components
The client front-end will be completely fresh, as in we’re going to create it from nothing. From a design perspective, it isn’t going to look great because I have no class. Get it? That’s a CSS joke. In all seriousness, application attractiveness is not our goal, but instead functionality.
From the Angular CLI, execute the following command:
ng new profile-project-angular
The above command will create a new project in the CLI path. With the project created, we’ll need to create a component for each of our expected application pages.
Execute the following with the Angular CLI:
ng g component login
ng g component register
ng g component blogs
ng g component blog
Four components will be created, each with appropriate TypeScript and HTML files. Before we start adding logic to them, we need to tie them together for navigation purposes.
Open the project’s src/app/app.module.ts file and include the following:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from "@angular/router";
import { FormsModule } from "@angular/forms";
import { HttpModule } from "@angular/http";
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';
let routes = [
{ path: "", redirectTo: "/login", pathMatch: "full" },
{ path: "login", component: LoginComponent },
{ path: "register", component: RegisterComponent },
{ path: "blogs", component: BlogsComponent },
{ path: "blog", component: BlogComponent }
];
@NgModule({
declarations: [
AppComponent,
LoginComponent,
RegisterComponent,
BlogsComponent,
BlogComponent
],
imports: [
BrowserModule,
FormsModule,
HttpModule,
RouterModule,
RouterModule.forRoot(routes)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Most of the above was created by the Angular CLI, however, we added a few module imports and imported them along with the routes
into the @NgModule
block.
To activate our navigation, we’ll need to update a file. Open the project’s src/app/app.component.html file and replace all the content with the following:
<router-outlet></router-outlet>
At this point, the project has a basic configuration.
Let’s have a look at handling the creation of user profiles and signing in with the account information.
Handling Login and Registration
The first screen the user will see is the login screen. The purpose here is to collect a username and password, send it to the API, get a session id as a response, and use it in every future page of the application.
Open the project’s src/app/login/login.component.ts file and include the following TypeScript code:
import { Component } from '@angular/core';
import { Http, Headers, RequestOptions } from "@angular/http";
import { Router } from "@angular/router";
import "rxjs/Rx";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.css']
})
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 });
});
}
}
}
There are some important things happening in the above code so we’re going to break it down.
Our intention is to bind an object to a form in the HTML markup. This object will be the input
variable. In the constructor
method, we set each possible property as an empty value which will be reflected in the UI.
When the user decides to sign in, the appropriate header information is set for the request and the object is sent to the server. If successful, the response will be an object with the session id. We’re going to pass it to the next page as a query parameter.
The HTML markup that pairs with this TypeScript is found in the project’s src/app/login/login.component.html and it looks like the following:
<h1 style="margin: 0">Sign In</h1>
<form>
<label for="email">Email:</label><br />
<input type="email" name="email" [(ngModel)]="input.email" /><br />
<label for="password">Password:</label><br />
<input type="password" name="password" [(ngModel)]="input.password" /><br />
<button type="button" (click)="login()">Login</button>
</form>
<p>
<a [routerLink]="['/register']">Register</a>
</p>
I told you the UI was going to be basic, didn’t I?
What is important here is the use of the [(ngModel)]
attributes that are used for data binding. We also offer navigation to the registration page through the [routerLink]
attribute.
So what does the registration component look like?
Open the project’s src/app/register/register.component.ts file and include the following TypeScript code:
import { Component } from '@angular/core';
import { Http, Headers, RequestOptions } from "@angular/http";
import { Router } from "@angular/router";
import "rxjs/Rx";
@Component({
selector: 'app-register',
templateUrl: './register.component.html',
styleUrls: ['./register.component.css']
})
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"]);
});
}
}
}
You’ll notice from the above snippet that pretty much all the registration code is the same as the login code. That is because we’re just collecting form data and sending it to a different API endpoint.
The corresponding HTML found in the project’s src/app/register/register.component.html file looks like this:
<h1 style="margin: 0">Register</h1>
<form>
<label for="firstname">First Name:</label><br />
<input type="text" name="firstname" [(ngModel)]="input.firstname" /><br />
<label for="lastname">Last Name:</label><br />
<input type="text" name="lastname" [(ngModel)]="input.lastname" /><br />
<label for="email">Email:</label><br />
<input type="email" name="email" [(ngModel)]="input.email" /><br />
<label for="password">Password:</label><br />
<input type="password" name="password" [(ngModel)]="input.password" /><br />
<button type="button" (click)="register()">Register</button>
</form>
<p>
<a [routerLink]="['/login']">Login</a>
</p>
The HTML contains form elements bound with the [(ngModel)]
attribute as well as a link back to the login page.
Remember, after we’ve signed in we are passing the session to the user specific pages. This will allow us to get user specific information from our profile store.
Creating and Viewing Blog Articles for a Particular User in the Profile Store
After signing in, the user is taken to a page where they can view a list of blog articles that they’ve written. Remember, our backend is connecting NoSQL documents through a profile id that we’re defining.
To view blog articles, the session id passed from the previous page needs to be set as a header in a request. The result from the request can be immediately rendered to the screen.
Open the project’s src/app/blogs/blogs.component.ts file and include the following TypeScript code:
import { Component, OnInit } from '@angular/core';
import { Http, Headers, RequestOptions } from "@angular/http";
import { Router, ActivatedRoute } from "@angular/router";
import "rxjs/Rx";
@Component({
selector: 'app-blogs',
templateUrl: './blogs.component.html',
styleUrls: ['./blogs.component.css']
})
export class BlogsComponent implements OnInit {
private sid: string;
public entries: Array<any>;
public constructor(private http: Http, private router: Router, private route: ActivatedRoute) {
this.entries = [];
}
public ngOnInit() {
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 } });
}
}
This file follows a similar strategy to how we handled login and registration.
In terms of variables, we create a private variable called sid
which will hold the session passed from the previous page. The entries
variable will be an array of blog entries for an individual returned from the API endpoint.
When the page loads we should be displaying the blog entries. It is never a good idea to load or request data from within the constructor
method, so instead, we use the ngOnInit
method.
public ngOnInit() {
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;
});
});
}
In the ngOnInit
method, we can grab the passed parameters and construct an authorization header. Then we make a request against our endpoint that contains the header.
To create a new blog entry, we can pass the sid
to the next route:
public create() {
this.router.navigate(["/blog"], { "queryParams": { "sid": this.sid } });
}
This is just like what we saw with the login screen.
The HTML markup that powers the UI will be no more complex than the TypeScript logic that powers it. The HTML can be seen in the src/app/blogs/blogs.component.html like so:
<h1>Blogs You Have Written</h1>
<p>
<button type="button" (click)="create()">New</button> | <button [routerLink]="['/login']">Logout</button>
</p>
<span *ngFor="let entry of entries">
<h2>{{ entry.title }}</h2>
<p>{{ entry.content }}</p>
</span>
Creating a blog entry will have similar logic to what we’ve seen already. Open the project’s src/app/blog/blog.component.ts file and include the following TypeScript code:
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({
selector: 'app-blog',
templateUrl: './blog.component.html',
styleUrls: ['./blog.component.css']
})
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();
});
}
}
}
In the above code, we are initializing our form data and retrieving the sid
that was passed from the previous page.
When we try to save the entry, we construct an appropriate header that contains the session id and then send it to the API with the form data. Once complete, we can navigate backward in the stack.
The HTML for the UI of this page is in the src/app/blog/blog.component.html file like so:
<h1>New Blog Entry</h1>
<form>
<label for="title">Title</label><br />
<input type="text" name="title" [(ngModel)]="input.title" /><br />
<label for="content">Content</label><br />
<textarea name="content" [(ngModel)]="input.content" style="width: 300px; height: 100px"></textarea><br />
<button type="button" (click)="save()">Save</button>
</form>
The same rules as the other pages were applied.
Improving the Node.js Backend to the Profile Store
You may notice a few weird things happening when you try the backend and front-end together. For example, cross-origin resource sharing (CORS) errors might be thrown, or your data might not be immediately available after creation.
We’re going to fix those minor issues.
The first step is to fix any CORS issues that might come up from running our applications on different ports. Within your Node.js project, execute the following command:
npm install cors --save
This will download a package that we can use in our project.
Within your project’s app.js file, import the package and use it like the following:
app.use(Cors());
This will allow requests from anywhere. You should use care when doing this in production, but for our testing purposes, it solves our problem.
The next issue you might face is in the realm of query consistency in Couchbase. By default, Couchbase will try to be fast, which means it may return data faster than the indexes update. When this happens you might have freshly created data missing from your results.
I explained how to correct this in a previous tutorial, but a quick recap is below.
var query = N1qlQuery.fromString("SELECT `" + bucket._name + "`.* FROM `" + bucket._name + "` WHERE type = 'blog' AND pid = $pid");
query.consistency(N1qlQuery.Consistency.REQUEST_PLUS);
In our blogs
endpoint, we can define the scan consistency, and in this case, we are telling it to wait until the index has updated before returning the data. This is a fast operation, so don’t think that it will crawl when you do this. It just won’t be as fast as the default.
At this point, the front-end should work flawlessly with the backend!
Conclusion
You just saw how to create a simple client front-end using Angular and TypeScript for the user profile store we saw in a previous example. This client front-end is one of many possible examples for a front-end because this is a modular full stack application. This means that the backend can be in any language and the front-end can be in any language.
In a production scenario, you might consider creating an Angular service for handling the session id, so that way it doesn’t need to be passed around with each navigation event.
Published at DZone with permission of Nic Raboy, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments