Before continuing, we will set the 'material' theme to all of our widgets. It will be included to jQWidgets tags as a property with the following syntax:
[theme]="'material'"
We will make our project more rich and with more features.
id
.AppRoutingModule
We will use the familar approach with a command line to create our routing module. Creating it in the top-level module is an Angular best practice.
ng generate module app-routing --flat --module=app
This will generate the app-routing.module.ts
file in the src/app
folder.
`--flat` puts the file in `src/app` instead of its own folder.
`--module=app` tells the CLI to register it in the `imports` array of the `AppModule`.
The content of the newly created file should looks as that:
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [
CommonModule
]
})
export class AppRoutingModule { }
We do not need a CommonModule
because we do not declare components within it so we can delete the @NgModule.declarations
and delete CommonModule
references too.
Now we will declare Routes
in the RouterModule
. For this purpose, we should declare both of them from @angular/router
library. We will add new metadata to this @NgModule.exports
array and within it will add RouterModule
. This will make our new component work as router directive, available for use in the AppModule
components that will need it.
After the changes the AppRoutingModule
should look in the following way:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
@NgModule({
exports: [ RouterModule ]
})
export class AppRoutingModule {}
If we want to navigate to different components easily and to stay on one and the same page - to be a Single Page Application (SPA) - Angular provides Routes.
This becomes a ngRoute
module that provides you a suitable option to navigate between different 'pages' without reloading the application.
The main properties of one Route
are:
path
: it is relevant to the URL of the browser address bar and connects it with the [routerLink]
that will direct.component
: used to add the component to which this route
will navigate.We will create one array as a constant and also, import
the HeroesComponent
to which we will navigate throw the route
and the browser URL address will look like this: localhost:4200/heroes
.
import { HeroesComponent } from './heroes/heroes.component';
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
RouterModule.forRoot()
The next step is to change the metadata of the @NgModule
and set imports
as an array with the routes
. This happens easily with the RouterModule.forRoot()
set into the array:
imports: [ RouterModule.forRoot(routes) ],
The app-routing.module.ts
should have the following changes:
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HeroesComponent } from './heroes/heroes.component';
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent }
];
@NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
RouterOutlet
We could determinate the exact place where the components will be visualized through the <router-outlet>
element. Now we can replace the <app-heroes>
element with it into the AppComponent
template:
<h1>{{title}}</h1>
<router-outlet></router-outlet>
<app-messages></app-messages>
The `RouterOutlet` is one of the router directives that became available to the `AppComponent` because `AppModule` imports `AppRoutingModule` which exported `RouterModule`.
If the Angular's server is still running you will see the result.
ng serve
There is only the title and if you add /heroes
to the end of the URL will see the difference.
Writing the URL is not the best way to navigate somewhere.
For this purpose an anchor
element with the attribute - routerLink
is used, which will determine the path
to the wanted component. The RouterLink
directive turns user clicks into router navigation.
We will wrap the <a routerLink="/heroes">Heroes</a>
element into the <nav>
element (src/app/app.component.html
):
<h1>{{title}}</h1>
<nav>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
We will add a new component to separate the application in different parts. This will make the routing more sensitive in the application. Again we will use the familiar command to achieve it.
ng generate component dashboard
Open the DashboardComponent
template and make the following changes:
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
Here we will use the familiar ngFor
.
In this component, we will show part of the Heroes
and it will be similar to the Heroes
.
heroes
array property.constructor
, we will add private heroService
property injected in HeroesComponent
ngOnInit()
lifecycle hook we will call getHeroes()
but will get only part of the array.getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes.slice(1, 5));
}
The DashboardComponent
should look like that source code below:
The dashboard.component.html
:
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
The dashboard.component.ts
:
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
@Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
}
We should include the DashboardComponent
in the AppRoutingModule
and add a new path to the array of routes
:
import { HeroesComponent } from './heroes/heroes.component';
import { DashboardComponent } from './dashboard/dashboard.component';
const routes: Routes = [
{ path: 'heroes', component: HeroesComponent },
{ path: 'dashboard', component: DashboardComponent }
];
We should handle the case when the URL in the browser's address bar does not specify to which route to be initially navigated. In this case, there is a redirectTo
property and we will use it here like this:
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
This will redirect the empty path to /dashboard
.
We need to navigate back and forth between the DashboardComponent
and the HeroesComponent
. Add a routerLink
to "/dashboard"
into the src/app/app.component.html
:
<h1>{{title}}</h1>
<nav>
<a routerLink="/dashboard">Dashboard</a>
<a routerLink="/heroes">Heroes</a>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
Let's stylize our view.
Firstly we should include the jqxButton
component. For this purpose open the tsconfig.json
file located in the root directory - the folder before src/
folder. There add "node_modules/jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons.ts"
. If you have done this before it is not necessary.
We will put the anchor elements inside the jqxButton
component.
In the app.component.html
add the following changes:
<h1>{{title}}</h1>
<nav>
<jqxButton style="padding: 0;"><a style="margin-top: 0; text-decoration: none;" routerLink="/dashboard">Dashboard</a></jqxButton>
<jqxButton style="padding: 0;"><a style="margin-top: 0; text-decoration: none;" routerLink="/heroes">Heroes</a></jqxButton>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
The next CSS style adjustment will be in the dashboard.component.css
in src/app/app.component.css
:
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center; margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607D8B;
border-radius: 2px;
}
.module:hover {
background-color: #EEE;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
Note: We using the same styles as in the "Angular tutorial - Tour Of Heroes".
HeroDetailComponent
The HeroDetailComponent
is available only from the HeroesComponent
.
The details appeared when clicking on a row of the jqxGrid.
It will be more intuitive if we click on the hero in the Dashboard to see its details, too.
Also, another option to navigate to these details should be by a writing of the URL in the browser address bar.
HeroesComponent
The <app-hero-detail>
element is not necessary to exist in the heroes.component.html
of the HeroesComponent
because we will use routing for this purpose. Now we will delete the <app-hero-detail>
element from there.
HeroDetailComponent
routeWe are already familiar with these steps below. Let's open the AppRoutingModule
that is contained in src/app/
and do following changes:
Import the HeroDetailComponent
import { HeroDetailComponent } from './hero-detail/hero-detail.component';
Determinate the new route - { path: 'detail/:id', component: HeroDetailComponent },
We have a new symbol here - colon (:), which is followed by the id
of the hero - :id
. This is as a placeholder. In this way, we will navigate to the details of a particular hero by this id
description.
Now our array of routes
should look in this way:
const routes: Routes = [
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
{ path: 'dashboard', component: DashboardComponent },
{ path: 'detail/:id', component: HeroDetailComponent },
{ path: 'heroes', component: HeroesComponent }
];
HeroDetailComponent
Currently, the DashboardComponent
hero links do nothing.
We will set a parameterized dashboard route. With *ngFor
we will iterate all heroes and with hero.id
will add a specific parameter to each routerLink
.
Replace the existing anchor element with this one:
<a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}">
Your src/app/dashboard/dashboard.component.html
file should be similar as this:
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a *ngFor="let hero of heroes" class="col-1-4" routerLink="/detail/{{hero.id}}">
<div class="module hero">
<h4>{{hero.name}}</h4>
</div>
</a>
</div>
id
Before this step, our HeroDetailComponent
did not show any interaction after changes. This happens because it displayed the hero
that becomes the parent HeroesComponent
set to the HeroDetailComponent
property.
With the new way to obtain the hero-to-display in the HeroDetailComponent
:
id
id
via the HeroService
Add the following imports to the src/app/hero-detail.component.ts
:
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';
import { HeroService } from '../hero.service';
Inject the ActivatedRoute
, HeroService
, and Location
services into the constructor, saving their values in private fields:
constructor(
private route: ActivatedRoute,
private heroService: HeroService,
private location: Location
) {}
The ActivatedRoute
holds information about the route in the instance of the HeroDetailComponent
. This component is interested in the route's bag of parameters extracted from the URL. The "id" parameter is the id of the hero to display.
The HeroService
gets hero's data from the remote server and this component will use it to get the hero-to-display.
The location
is an Angular service for interacting with the browser. You will use it later to navigate back to the view that navigated here.
We will use the ActivatedRoute
directive and its options to get the extra parameters in the URL. Add the following changes - add the getHero()
method that is invoked in the ngOnInit()
lifecycle hook in the src/app/hero-detail/hero-detail.component.ts
:
ngOnInit(): void {
this.getHero();
}
getHero(): void {
const id = +this.route.snapshot.paramMap.get('id');
this.heroService.getHero(id)
.subscribe(hero => this.hero = hero);
}
After rebuilding the project with these changes you will get the following error.
ERROR in src/app/hero-detail/hero-detail.component.ts(29,22): error TS2551: Property 'getHero' does not exist on type 'HeroService'. Did you mean 'getHeroes'?
This happens because the getHero()
method does not exist.
The route.snapshot
is a static image of the route information shortly after the component was created.
The paramMap
is a dictionary of route parameter values extracted from the URL and with the get()
method we specify which parameter we want to get - in this case, "id"
. The returned value is always a string
and for this purpose (+) in front of this value converts it to a number
.
HeroService.getHero()
Open the src/app/hero.service.ts
and mentioned method:
getHero(id: number): Observable<Hero> {
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
Note the backticks (`) that define a JavaScript template literal for embedding the "id".
The getHero()
method is an asynchronous operation as the getHeroes()
. We are familiar with this already - it returns mock hero as an Observable
.
HeroesComponent
hero links
When we click on the Grid's rows nothing happens now. We have a different logic.
In this paragraph, you will get to know how to access an URL with navigate()
method which is a part of the Router
. For this purpose, we will make an update in our HeroesComponent
.
The first step is to import the Router
from '@angular/router'
:
import { Router } from '@angular/router';
Also, we should add a private property - router
into the constructor.
Add following changes in the src/app/heroes/heroes.component.ts
file:
import { Component, OnInit } from '@angular/core';
import { Hero } from '../hero';
import { HeroService } from '../hero.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-heroes',
templateUrl: './heroes.component.html',
styleUrls: ['./heroes.component.css']
})
export class HeroesComponent implements OnInit {
heroes: Hero[];
constructor(private heroService: HeroService, private router: Router) { }
ngOnInit() {
this.getHeroes();
}
getHeroes(): void {
this.heroService.getHeroes()
.subscribe(heroes => this.heroes = heroes);
}
rowdetails(event: any): void {
let args = event.args;
let rowData = args.row.bounddata;
let heroID = rowData.Id;
this.router.navigate([ './detail/' + heroID.toString() ]);
}
}
Firstly we will prepare the data. We can get data of currently selected hero
from the event
object. We need to know the Id
of the hero to navigate to its details.
Here we use the Router.navigate()
method which expects an array with the URL path that we want to navigate. The heroID
variable is from type number
. We should parse it to string
because the URL should be from this type and we should set it to the URL path.
After you save these changes you will be able to navigate to the details.
Now we can delete the unused variables - as selectedHero: Hero;
property in the src/app/heroes/heroes.component.ts
.
It is good practice to clear unused source code. In this way, it will be more readable.
It will be better if we can navigate back to hero list or dashboard view.
Let add "go back" button on the HeroDetailComponent
template:
<jqxButton (click)="goBack()">go back</jqxButton>
We have already imported the relevant references with the familiar steps:
tsconfig.json
:"node_modules/jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons.ts",
Next is to import jqxButtonComponent
into the AppModule
:
import { jqxButtonComponent } from 'jqwidgets-scripts/jqwidgets-ts/angular_jqxbuttons';
...
@NgModule({
declarations: [
...
jqxButtonComponent,
...
],
})
All these steps we did before in the MessagesComponent
.
On the click
event, we will 'go back' with goBack()
. The next step is to implement this option to navigate back to the browser's history stack using Location
. Add the goBack()
implementation via location.back()
into the src/app/hero-detail/hero-detail.component.ts
:
goBack(): void {
this.location.back();
}
Now we can test the achieved result. Let's view the details of the chosen hero from dashboard view or hero list and go back with the button.