So here we will see routing and navigation. And by the end of this article, you’ll have in depth understanding of how routing works in Angular. And you’ll be able to add navigation to your angular apps. More specifically we’ll learn about,
- Configuring Routes
- Implementing Single Page Application (SPA)
- Working with Routes and Query Parameters
- Programmatic Navigation
And if you’ve read my previous articles of Angular. Here is the roadmap for you to become a Ninja Developer of Angular.
Routing In A Nutshell
So far we’ve seen a few of the built-in modules in Angular. We’ve used the Forms Module, Reactive Forms Module, Http Module. And now it is the time to explore another module which is the Router Module. So in this module, we’ve a bunch of directives and services that we use for implementing navigation in our application. We’re going to learn about this module in this article.
Now there are 3 steps to implement navigation.
- Configure the Routes
Each route determines what component should be visible when the user navigates to a certain url. So route is the mapping of a path to a component.
- Add a router outlet
That is where we display the corresponding component when a given route becomes active.
- Add Links
So in this article we’ll explore each of these steps.
Configuring Routes
So we’ve covered 2 components with 2 different features in our last 3 articles. Now we’ll combine them and build the application with routing and implement navigation in the application. Here is the screenshot we’ll build in this article.
So let’s get started.
First of all as we already know that Navbar is a separate thing which we can reuse at multiple places. So let’s make a separate component for navbar.
- PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c navbar
Now we want navbar html. So we’ll use this navbar in our component html.
- <nav class="navbar navbar-default">
- <div class="container-fluid">
- <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
- <ul class="nav navbar-nav">
- <li class="active"><a href="#">Followers</a></li>
- <li><a href="#">Posts</a></li>
- </ul>
- </div>
- </div>
- </nav>
So in this navigation bar, we’ve 2 links (Followers, Posts). Now back in our app.module.ts.
Now create 3 more components and add the RouterModule import statement as well in app.module.ts
- PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c Home
- PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c GithubProfileComponent
- PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> ng g c NotFound
Now import this reference import statement as well.
- import { RouterModule } from "@angular/router";
And now we’re ready to our routes. So add module here as well (app.module.ts)
- imports: [
- BrowserModule,
- FormsModule,
- ReactiveFormsModule,
- HttpModule,
- RouterModule.forRoot()
- ]
forRoot() is the static method defined in the RouterModule class. And we use this to define our root route in our application. As our application grows, we may want to break that application into smaller and more manageable modules. Then in each module, we’re going to have a set of routes for that particular area of the application. Then instead of using forRoot(), we’ll use forChild(). We’ll discuss it later on in detail. So in forRoot() here we pass an array of Routes, each route is an object with 2 properties (path, component).
- RouterModule.forRoot([
- { path: '', component: }
- ])
With this we’re telling angular router that whenever the browser address changes to this path display this component. So now here we add the first route for our home page.
- RouterModule.forRoot([
- { path: '', component: HomeComponent }
- ])
Note that here we don’t have ‘/’ leading slash in path. So none of our routes start with slash. And empty path ‘’ represents the homepage or the default route. Now let’s add more routes
- RouterModule.forRoot([
- { path: '', component: HomeComponent },
- { path: 'followers', component: GithubFollowersComponent },
-
- { path: 'profile/:username', component: GithubProfileComponent },
- { path: 'posts', component: PostsComponent },
- { path: '**', component: NotFoundComponent }
- ])
To add the parameter, we use colon (:) and then we add the name of the parameter (username). And if the user navigates to the different url which is not the valid route, here we display the Notfound page. And for the path we use ‘**’ it can catch every url in the browser address bar.
Now the order of these routes is important. So if we put the last route at the beginning of the array, ** is the wildcard which is going to catch any route and we only gonna see NotFound page. So we need to place more specific routes on the top. If we change the path of our 3rd route from profile to followers then the 2nd route will always execute with GithubFollowersComponent.
- { path: 'followers', component: GithubFollowersComponent },
- { path: 'followers/:username', component: GithubProfileComponent },
So we need to place our more specific route first and then the general route.
- RouterModule.forRoot([
- {
- path: '',
- component: HomeComponent
- },
- {
- path: 'followers/:username',
- component: GithubProfileComponent
- },
- {
- path: 'followers',
- component: GithubFollowersComponent
- },
- {
- path: 'posts',
- component: PostsComponent
- },
- {
- path: '**',
- component: NotFoundComponent
- }
- ])
Now it is more readable and more cleaner.
RouterOutlet
Now first of all let’s take a look in the browser.
So here is an issue. If you see our above code of RouterModule.forRoot() we’ve defined the home component on root path. And if you open your home.component.html
This is the html which is generated with Angular CLI. Now the issue is why our HomeComponent html is not rendering on the root path here. Where is the HomeComponent selector (app-home) in this browser inspect element?
The answer of all these questions is to implement the 2nd step of routing to add router outlet and that’s where Angular router is going to display the component that is associated with current route. So let’s go back to the app.component.html. And add the router-outlet html element.
- <navbar></navbar>
- <router-outlet></router-outlet>
This is the directive that is defined in router module. When Angular sees this, it is going to render the component associated with the current route after this router-outlet. So it is not going to render it inside this element.
Look here we’ve app-home html element, this is our home component. We can see it’s not rendered inside router-outlet. It is rendered after this element.
Now let’s test our routes. And now our home component is visible. Now request the url (http://localhost:4200/followers) in the browser address bar, and it is working fine and showing the result.
And if we request the url (http://localhost:4200/posts)
So our routes are working properly. Now if we request the specific follower (http://localhost:4200/followers/1)
So our more specific route is working properly. Now if we change to invalid-url (http://localhost:4200/invalid)
Here we get the not-found page. So all our routes are working properly. Now let’s add the links in the navbar.
RouterLink
Now we’re ready to add links. So let’s go to navbar.component.html and here we’ve 2 links (followers, posts).
- <ul class="nav navbar-nav">
- <li class="active"><a href="#">Followers</a></li>
- <li><a href="#">Posts</a></li>
- </ul>
In both li we’ve separate href=”#” tags. Now the thing is in Angular application we don’t use href=”#” tags. Let me tell you the reason, why we don’t use href.
So change the href as,
- <ul class="nav navbar-nav">
- <li class="active"><a href="/followers">Followers</a></li>
- <li><a href="/posts">Posts</a></li>
- </ul>
Now open the browser and click on followers navbar menu button. You’ll notice that the browser page flickers (blinks) on a new request. So every time we click on the link, the entire page is downloaded and the Angular app is reinitialized that’s why the page blanks for just a split second. As your application grows or it has more codes, the cost of the startup is going to be higher so that delay might increase. Now if we look into the browser network tab.
All the resources of the browser are redownloaded every time when we click on the page, this is something that we want to avoid. Otherwise there is no point in Angular to build modern appliatin.
What Do We Want?
When we click on the link, we want only the content of the new page to be downloaded instead of the entire application with all the resources. So here we use the directive called routerLink and this directive is also defined in the RouterModule.
- <ul class="nav navbar-nav">
- <li class="active"><a routerLink="/followers">Followers</a></li>
- <li><a routerLink="/posts">Posts</a></li>
- </ul>
Now save the file and open the browser network tab.
Look here we’ve all the resources downloaded in the first time when we open the application. Now clear the network tab and click the navigation link on any navigation bar once again. And now we do not have the flickering effect and only the followers and post request is reloaded.
As you can see I’ve requested post navigation bar and then followers after refreshing my browser. In this list, you’ll not see any javascript file to be downloaded once again. All of these resources were downloaded only the first time. So we referred the applications build in this way Single Page Application (SPA). So essentially a single page application is downloaded from the web server and as the user navigates from one page to another, only the content of the target page is downloaded.
Now open the followers page and add the link to each follower. So here we’ve a link for each follower,
- <div class="media-body">
- <h4 class="media-heading">
- <a href="#">{{ follower.login }}</a>
- </h4>
- <a href="follower.html_url">{{ follower.html_url }}</a>
- </div>
And here we’ll replace this href with routerLink. However, this time we’re dealing with dynamic url. When we’re dealing with route parameters, instead of using routerLink as an attribute we should use the property binding syntax. So,
- <h4 class="media-heading">
- <a [routerLink]="#">{{ follower.login }}</a>
- </h4>
And here instead of binding it with string, we should bind it with an expression. And in this expression we’ve an array, the first element in this array is the path and after this first element we add all the route parameters. And currently we’ve only 1 route parameter.
- <h4 class="media-heading">
- <a [routerLink]="['/followers', follower.id]">
- {{ follower.login }}
- </a>
- </h4>
Now back in the browser and if we click the first follower, we go to the profile page. And note the url of the browser’s location bar.
So for recap once again, for simple routes we can use routerLink directive as an attribute and set it to the string value. And if we’re dealing with route parameter, we should use the property binding syntax and set the value of this property to an array. The first element in this array is the path and the subsequent element is the route argument.
RouterLinkActive
As we can see the followers link in the navigation bar is always highlighted. And if we navigate to somewhere else like posts, followers link is still active. Let’s see how can we fix this problem, in navbar.component.html here we’ve active class which is part of the bootstrap.
- <ul class="nav navbar-nav">
- <li class="active"><a routerLink="/followers">Followers</a></li>
- <li><a routerLink="/posts">Posts</a></li>
- </ul>
Now we want to apply this active class dynamically, so here we’ve another directive. And as the value of routerLinkActive, we set the string that includes a list of css classes that should be applied when this link is active. Here we’ve just one css class ‘active’ but we can add more. And these classes should be separated via space.
- <li routerLinkActive="active current"><a routerLink="/followers">Followers</a></li>
Now apply the same directive and test the application.
- <ul class="nav navbar-nav">
- <li routerLinkActive="active"><a routerLink="/followers">Followers</a></li>
- <li routerLinkActive="active"><a routerLink="/posts">Posts</a></li>
- </ul>
And look now it is working fine,
And here we’ve dynamically rendered the class active on navigation li.
Getting the Route Parameters
So in app.module.ts, we’ve defined this route that takes the parameter username.
- {
- path: 'followers/:username',
- component: GithubProfileComponent
- }
Let’s see how we extract this parameter in our GithubProfileComponent because in the real world application, most likely we want to get this parameter and use the service to get the profile of the given user. So let’s go to the github-profile.component.ts, and in order to get access the route parameters we need to inject the ActivatedRoute class in our constructor. So,
- import { Component, OnInit } from '@angular/core';
- import { ActivatedRoute } from '@angular/router';
-
- @Component({
- selector: 'app-github-profile',
- templateUrl: './github-profile.component.html',
- styleUrls: ['./github-profile.component.css']
- })
- export class GithubProfileComponent implements OnInit {
-
- constructor(private route: ActivatedRoute) { }
-
- ngOnInit() {
- }
-
- }
This import statement is part of the RouterModule which we’ve imported in the start of the article. Now in ngOnInit() we get the route parameters from this object (route.paramMap) that’s the property that gives us all the parameters in this route.
Look, paramMap type is Observable<ParamMap> and we know that we use subscribe method to get the values emitted in this Observable
So let’s implement this intellisense.
- ngOnInit() {
- this.route.paramMap
- .subscribe(params => {
- console.log(params);
- });
- }
Now let’s go back to the browser.
Look in the console, here we have 1 key username because in app.module we set the name of our parameter as username
- {
- path: 'followers/:username',
- component: GithubProfileComponent
- }
Now this console object has another property called params and here we’ve keyvalue pairs for route parameter and their value. Here username contains some numbers, this is actually the user id. And this is the structure of paramMap object. Now back in ngOnInit(), let’s look at the members defined in this class.
So here we’ve get(), getAll(), has() functions and keys field. Here we saw the keys field which returns all the keys or all the route parameters. We use get() method to get the value of the given route parameter. We use getAll() to get the all route parameters. And has() to see if we’ve the parameter by the given name in this object. So here to get the value of username parameter, we call the get()
- ngOnInit() {
- this.route.paramMap
- .subscribe(params => {
- params.get('username');
- });
- }
As we can see in the above browser image, params username contains actually user id. So always pay attention to name your route parameters because it can create confusion for other developers which will also increase the cost of maintenance of the application. So,
- {
- path: 'followers/:id',
- component: GithubProfileComponent
- }
And now come back to the github-profile component.
- ngOnInit() {
- this.route.paramMap
- .subscribe(params => {
- params.get('id');
- });
- }
The return type of get() method is string but if we want to convert the string into number, we’ll write as
- ngOnInit() {
- this.route.paramMap
- .subscribe(params => {
- let id = +params.get('id');
- console.log(id);
- });
- }
And in most real world applications, we send this id to the service to get the profile of this user. But here we just log this on the console. And now test the application one more time.
So here we’ve user_id displayed on the console. So this is how we get route parameters. But you might be thinking why this paramMap is defined as Observable. To implement the Observable in the code is little bit hard for accessing the parameters. You might be thinking why we don’t have an api like this,
Or potentially we could have,
- this.route.paramMap[‘id’]
These techniques are easier to work with instead of Observable.
Why Are Route Parameters Observable?
To understand the answer to this question, we first need to know what happens to our components during navigation. So let’s say the user is navigating from 1st page to another page. And on the first page, here we’ve a component A. They click on the link or a button to go to a different page where we display component B.
On navigation, Angular destroys the component A and remove it from the DOM and then it is going to create and initialize component B and render its template in the DOM.
Also to refresh your memory, we’ve component lifecycle hooks. So earlier we have discussed OnInit and OnDestroy Interfaces. So if you want to be notified when the component be initialized or destroy, we get implement these interfaces and add the corresponding methods in your component. So this is component lifecycle during navigation.
Now let’s take a look at a different scenario. Imagine we’ve a link or button on a component A that allows the user to navigate away from this page but also get to the same page.
Let’s take an example, Imagine we’re building a banking application where the user can see all the transactions and the details of each transaction. On the transaction detail page, we’ve 2 buttons, previous and next. When the user clicks on either of these buttons they go to the previous or next transaction and techniquelly they’re on the same page. So in this case, it doesn’t make sense for Angular to destroy this component only to recreate it straight away after. So in this particular usecase what we have is the same component instance but with different route parameters. So Angular will not destroy this component and it’s going to keep it in the DOM and this is the reason why route parameters are defined as Observable which brings to a different level of understanding about Observables.
An Observable is techniquelly a collection of asynchronous data that arrives over time. In the article of CRUD operation with fake http service, we simply subscribed to an Observable to get the response from the server and that particular case, we had only 1 item or 1 object in our observable that was the response from the server. But we can use observables to model streams of asynchronous data. So data comes into the stream and anyone who subscribes to this observable will be notified.
Now as the real world example, here in github-profile component we’re subscribing to this paramMap observable. So github-profile component is an observer of this observable, anytime there is new data in this collection github-profile component will be notified. As a metaphor, think of a mailing list. When you are subscribed to the mailing list of the blog. You’ll be notified via emails. We’ve the exact same concept here
- this.route.paramMap
- .subscribe(params => {
So we’re subscribing to the paramMap which is the collection of route parameters that can change over time and everytime there is a new route parameter will be notified.
For better understanding, make our code simple
- ngOnInit() {
- console.log("Github Profile OnInit");
- }
Now back in the browser and click on the first follower which brings us to github-profile page. During this navigation the old component which was the follower component was destroy and the new component which is the github-profile is initialized and placed in the DOM. So now if we look into the console, we’ve the OnInit console log text.
And if we go back to the followers page again, now the github-profile component is destroyed and is no longer in the DOM. And if we again go the 2nd follower github profile, we’ll see 2 messages on the console.
So this shows that everytime we go away from this page, go to the followers list and come back ‘Github-profile component is reinitialized’.
Now let’s see when we give the user the ability to stay on the same component and navigate back and forward. So we’re not going to navigate away from the component. This component stays in the DOM but the route parameters are going to change. And here we should pay great attention. With this new implementation, we’re going to see only 1 OnInit message in the console because the component instance is not gonna be reinitialized. It stays in the DOM. So let’s take an example and open the github-profile.component.html and add the button here.
- <p>
- github-profile works!
- </p>
- <button [routerLink]="['/followers', 123]" class="btn btn-primar">Next</button>
Here I’ve supplied the magic number for route parameter. So when it clicks on the button, will be on the same page but the route parameter is changed.
Look we’re navigating from github-profile component (which contains the “Github Profile OnInit” text in OnInit()) to followers/123 url. And if you remember this route url (/followers/:id) points to the same github-profile.component.ts
- {
- path: 'followers/:id',
- component: GithubProfileComponent
- }
Look our route is changed and there is no message in the console on this navigation. And it verifies that Angular didn’t destroy this component and recreate it, here we’ve the single instance.
So if we have an application with this kind of navigation built-in to it and we’ve seen ngOnInit() calls only once, in order to get access to the route parameters again and again we need to subscribe to the route map observables. That’s why we have
- this.route.paramMap.subscribe();
Every time we have a new parameter, we can get that into the body of subscribe() method.
- this.route.paramMap
- .subscribe(params => {
- let id = +params.get('id');
- console.log(id);
- });
Now let’s test the application, if we click on the first follower we’ll see the user id of the first follower in the console. And then we click on the next button, we’ll see the 123 user id once again in the console below to the first follower user id.
Point
Keep in mind, when we’re navigating from component A to again the same component. ngOnInit() function calls only once. Component A doesn’t destroy and reinitialize in this case. But when we’re navigating to the same component, obviously the route parameters become changed in this case. So we need to get access this new route parameter and we already know that ngOnInit() function in this case execute only once then Observable helps us to get access the paramMap object of data. Because we subscribe to the observable and when the values are changed, we’ll be notified.
But in your application, if you don’t have this kind of scenario, if you don’t allow the user to stay on the same page and navigate back and forth there is a simpler way to get access to route parameters. So if you’re 100% sure that user navigates away from the page, go somewhere else and then come back, in this case instead of subscribing to observable we can use snapshot. So,
- ngOnInit() {
- let id = this.route.snapshot.paramMap.get('id');
- console.log(id);
- }
This is the real paramMap object, it is not the observable. So back in the browser,
- Go to the followers page
- Go to the 1st follower page.
We should see the id of first follower on the console.
- Now assuming that we don’t have next button here, we always go back to the followers list via followers navigation bar. And go to the 2nd follower and because profile component is reinitialized, we can use the snapshot to get the id of the 2nd And in the console now we have 2 entries.
Routes with Multiple Parameters
So here we’ll see how to work with routes containing multiple parameters. We’ll see few things relevant to SEO. So here in our github-profile page of any follower, the url is
http://localhost:4200/followers/131355
we only have the id of the follower. And as the technique to improve the search engine optimization, we should add the username in each url instead of just the user id. Like,
http://localhost:4200/followers/john/131355
Look this is the technique that stackoverflow uses for SEO,
Here we’ve questions and then question id and then title of the question. So let’s modify our route to make it more SEO friendly.
Now back in app.module.ts and includes the follower name in the route path.
- {
- path: 'followers/:id/:username',
- component: GithubProfileComponent
- }
Now let’s go back to the github-follower.component.html page.
- <h4 class="media-heading">
- <a [routerLink]="['/followers', follower.id, follower.login]">
- {{ follower.login }}
- </a>
- </h4>
And here we add one more property ‘follower.login’ in routerLink. Now you might be wondering what is follower.login, from where this property came? Actually this property is the part of github api, Look,
Now let’s go to the followers page and click on the follower, and we’ll see id and the username of the follower in url.
So this is how we deal with route with multiple parameters.
Query Parameters
Now we want to add the optional parameters in our route. So here we’ll add the query parameters on the followers page and we’ll make these parameters optional. So let’s see how to add optional parameters in the routes. Here in navbar.component.html, we render the link of the followers page.
- <ul class="nav navbar-nav">
- <li routerLinkActive="active"><a routerLink="/followers">Followers</a></li>
- <li routerLinkActive="active"><a routerLink="/posts">Posts</a></li>
- </ul>
And here in routerLink directive of Followers we export the property that we can use in our property binding expression. So,
- <li routerLinkActive="active">
- <a routerLink="/followers"
- [queryParams]="{
- page: 1,
- order: 'newest'
- }">Followers</a>
- </li>
Here we bind queryParams property to an object and here in this object, we add all the optional parameters. Now open the followers page in the browser and it will automatically open the page with route parameters.
But it is the sending part, we are sending the route parameters from navbar follower link to the github-followers page. Now let’s receive these parameters in github-followers.component.ts, and we know that we use ActivatedRoute to get the route parameters.
- export class GithubFollowersComponent implements OnInit {
- followers: any[];
-
- constructor(
- private route: ActivatedRoute,
- private service: GithubFollowersService
- ) { }
-
- ngOnInit() {
-
- let id = this.route.snapshot.paramMap.get('id');
- this.route.paramMap.subscribe();
-
-
-
- this.route.queryParamMap.subscribe();
- let page = this.route.snapshot.queryParamMap.get('page');
-
- this.service.getAll()
- .subscribe(followers => this.followers = followers);
- }
- }
You can see here we’ve observable/snapshot both objects for queryParamMap as well. Now in the scenario, where you’re passing the page number and their sort order it is very likely that the user is going to get back to the same component. So somewhere on the followers page, we’re going to have a pagination component with page numbers. So when the user clicks on the pagination page number, user will click on the same page but the route parameters will change. So that means, in most cases we use observable approach here to subscribe it. So let’s make our component code simple,
- export class GithubFollowersComponent implements OnInit {
- followers: any[];
- constructor(
- private route: ActivatedRoute,
- private service: GithubFollowersService
- ) { }
-
- ngOnInit() {
- this.route.paramMap.subscribe();
- this.route.queryParamMap.subscribe();
- this.service.getAll()
- .subscribe(followers => this.followers = followers);
- }
- }
Now you might think, how do we subscribe to both these observables? Let’s say you want to load all the followers in the first page. Currently we’re getting all followers from our service,
- this.service.getAll()
- .subscribe(followers => this.followers = followers);
But in the more realistic application, our code is going to look like this
- ngOnInit() {
-
- this.route.paramMap
- .subscribe(params => {
- });
-
- this.route.queryParamMap
- .subscribe(params => {
- });
- this.service.getAll()
- .subscribe(followers => this.followers = followers);
- }
Now here is the question, how can we get both the required and optional parameters and then call the server to get the list of followers?
Subscribing to Multiple Observables
So let’s see how we can subscribe to 2 different observables at the same time. As we know, an Observable is a stream of asynchronous data that arrives over time. Now instead of having 2 separate subscriptions to these 2 observables
We should combine these observables into a new observable and then we’ll subscribe to that observable.
So back in our github-followers.component.ts and we import Observable and one of the factory methods to combine the Observables ‘combineLatest’
- import { Observable } from "rxjs";
- import { combineLatest } from "rxjs/observable/combineLatest";
And as we’ve already discussed in our previous articles, this is the static method on the Observable class. We can use this to create a new observable and then we’ll subscribe to that observable.
- ngOnInit() {
- let obs = combineLatest([
- this.route.paramMap,
- this.route.queryParamMap
- ]);
-
- obs.subscribe(combined => {
-
- });
- }
Now here we can pass the error function just like how we subscribe to the simple observable. Here combined is an array of 2 elements. The first element is the latest paramMap object and the 2nd element is the queryParamMap object
- ngOnInit() {
- let obs = combineLatest([
- this.route.paramMap,
- this.route.queryParamMap
- ]);
-
- obs.subscribe(combined => {
- let id = combined[0].get('id');
- let page = combined[1].get('page');
- });
- }
Now here in this example we don’t have id parameter passed on this page, but if you’re working with both the required and optional query parameters this is how you can get access all these parameters. Now we’ve value for all these parameters, we can use our service to get the data from the server. So our code will look something like this,
- obs.subscribe(combined => {
- let id = combined[0].get('id');
- let page = combined[1].get('page');
-
- this.service.getAll();
- });
getAll() takes no parameters but let’s say here we add the parameters and set the values
- export class GithubFollowersComponent implements OnInit {
- followers: any[];
-
- constructor(
- private route: ActivatedRoute,
- private service: GithubFollowersService
- ) { }
-
- ngOnInit() {
- let obs = combineLatest([
- this.route.paramMap,
- this.route.queryParamMap
- ]);
-
- obs.subscribe(combined => {
- let id = combined[0].get('id');
- let page = combined[1].get('page');
-
-
- this.service.getAll()
- .subscribe(followers => this.followers = followers);
- });
- }
So here we have subscribe inside another subscribe, and this looks little big ugly. With reactive extensions we can rewrite this code in cleaner and more maintainable way.
SwitchMap Operator
So let’s go back to our data service, look the return type of getAll() method and of course it is Observable of any. From now on, anytime you see Observable think of a collection. So here we have a collection and in this collection every item is of any type.
Now let’s see what happens when we comment out our pipe function code. Look at the return type of getAll()
Now it returns Observable of Response. So here we have a collection and in this collection we’re getting response object that arrives asynchronously. But Why?
Because the get() of http class return an Observable of Response.
Because we are returning the results immediately that’s why the return type get all method is also Observable of Response. Now let’s see what happens when we apply the map operator,
Now instead of Observable<Response>, we get Observable<any> because we’re mapping the Response object to a new object of type any. When we call the json(), it returns object of type any
So here is a way to visualize what happens here. Initially we have an Observable of Response, so every item in this Observable or in this collection will be an instance of Response class then we apply the map operator and as a result we got new Observable and in this Observable every time is of any type instead of Response.
Now back to our github-followers component, here we are combining 2 different Observables and subscribing to that result. Let’s look at the type of combine parameter, the type of this parameter is ParamMap array
Here is the simpler way to visualize this. So here we are subscribing to an Observable and every item in this Observable is of type ParamMap array. Now similar to how we use the map operator, we can use the map operator and map a ParamMap array into Followers array. So we’ll give ParamMap array as an input and as the output we will get Followers array.
So back in github-followers component and here we’ll add some operators
- import { map, switchMap } from "rxjs/operators";
Now back to ngOnInit(), after we combining the Observables we’ll apply the map operator. And look here the parameter is ParamMap,
Now at this point we’ll call the server to get the list of followers and return them and then we’ll subscribe to that Observable and as the result, we’ll have the array of Followers. So cut and paste the code and move it into the map operator,
- obs.pipe(
- map(combined => {
- let id = combined[0].get('id');
- let page = combined[1].get('page');
-
-
- this.service.getAll()
- .subscribe(followers => this.followers = followers);
- })
- ).subscribe(combined => {
-
- });
Now we don’t need the subscribe() with getAll() anymore because in this map operator the intention is to return the list of followers. So later below when we subscribe, we have the list of followers.
- obs.pipe(
- map(combined => {
- let id = combined[0].get('id');
- let page = combined[1].get('page');
-
- this.service.getAll();
- })
- ).subscribe(followers => this.followers = followers);
Now we have not any subscribe() method inside subscribe(). We use the map operator to transform object of one type into another. Now here is the compilation error with this.followers
So if you look at the type of followers, you’ll see it is void
Because in the map operator we’re not returning anything, so the return type of map is void. And when we subscribe to this, we’re actually subscribing an Observable where every item is void. So here we need to return something and what we return from the map operator will be the input into our subscribe() method. So let’s return the output of our service.
Now it is no longer void but Observable<any> and here we still have a compilation issue. So what we have done here,
We use the map operator to convert ParamMap array object into Observable<any> because getAll() is returning Observable<any>.
But we want in subscribe() is followers array, not Observable of followers array. So to fix this problem, instead of the map operator we’ll use switchMap
- obs.pipe(
- switchMap(combined => {
- let id = combined[0].get('id');
- let page = combined[1].get('page');
-
- return this.service.getAll();
- })
- ).subscribe(followers => this.followers = followers);
And now the compilation error is gone. Now if we hover the mouse on followers parameter, its type is any which in this case is followers array and if we hover the mouse on switchMap it is returning Observable<any>. And when we subscribe to switchMap operator, it becomes any array.
So this is was the more clean and elegant way to combine the observables, request the server and subscribe to the results.
Programmatic Navigation
We have seen how to use the routerLink directive to add links in various parts of the application. Now there are times that you may want to navigate the user programmatically. So let’s get started,
Open github-profile.component.ts and here in the constructor we’ll inject the router service.
- export class GithubProfileComponent {
- constructor(private router: Router) { }
- }
The import statement is,
- import { ActivatedRoute, Router } from '@angular/router';
Now let’s make the submit event handler,
- export class GithubProfileComponent {
- constructor(private router: Router) { }
- submit(){
- this.router.navigate(['/followers', 1, 2, 3])
- }
- }
Navigate method takes the array parameter of any and in this array the first element is the route path and remaining elements are the values for the required route parameters. But here in our case, we have not any required parameter but we pass the query parameters here. So,
- submit(){
- this.router.navigate(['/followers'], {
-
- })
- }
The 2nd object is NavigationExtra object and in this object we have property called queryParams and we set this to an object and set the query parameters.
- export class GithubProfileComponent {
-
- constructor(private router: Router) { }
-
- submit(){
- this.router.navigate(['/followers'], {
- queryParams: { page: 1, order: 'newest' }
- });
- }
- }
Now the last thing is, update your github-profile.component.html as well
- <p>
- github-profile works!
- </p>
- <!-- <button [routerLink]="['/followers', 123]" class="btn btn-primar">Next</button> -->
- <button
- class="btn btn-primary"
- (click)="submit()"
- >
- Submit
- </button>
Now let’s test this and back in the browser in followers page. Select any follower and click submit button and it will again navigate to the follower page with optional query parameter.
It is working.
Let’s Make Something New
Let’s make something new that we have learned from this article. We’ll create a new project using Angular CLI, we have 2 pages in this application. In the Home page we have list of dates, and imagine these dates are the archives of the blogs. So each archive is a year and month and when we click on the archive, it goes to the archive page where we see all the posts of that archive. Actually we’re focusing on routing and navigation in this application.
We should have 2 required parameters in the url.
http://localhost:4200/archieve/2017/1
And if we don’t supply any of these required parameters, it go to the not found page.
One more thing -- we’ll make some content of the page dynamic which will be display based on the parameter passed through the route.
We should have a button on each achieve page to go to the back page. And here we’ll build programmatic navigation. So don’t add link under the button with the help of routerLink directive. Make sure when you click the button, the method behind the button is called and from that method we navigate the user back to the home page.
One last thing in order to render the list simply define the array in class and initialize the array with 3 objects and each object should have 2 properties year and month.
Solution
So first of all create a new project.
- PS C:\Users\Ami Jan\HelloWorld\MyFirstAngularProject> cd /
- PS C:\> cd ./Users/"Ami Jan"/
- PS C:\Users\Ami Jan> mkdir Routing
- PS C:\Users\Ami Jan> cd ./Routing/
- PS C:\Users\Ami Jan\Routing> ng new Routing
- PS C:\Users\Ami Jan\Routing> cd ./Routing/
- PS C:\Users\Ami Jan\Routing\Routing> code .
Now let’s create the component in this new project.
- PS C:\Users\Ami Jan\Routing\Routing> ng g c home
- PS C:\Users\Ami Jan\Routing\Routing> ng g c not-found
- PS C:\Users\Ami Jan\Routing\Routing> ng g c archieve
Now let’s go to the app.module.ts and import the router module here.
- import { RouterModule } from "@angular/router";
Next we need to configure the routes here as well,
- imports: [
- BrowserModule,
- RouterModule.forRoot([
- { path: '', component: HomeComponent},
- { path: 'archive/:year/:month', component: ArchieveComponent},
- { path: '**', component: NotFoundComponent},
- ])
- ]
Now we need to add router-outlet. So let’s go to the app.component.html which is the container for application. Delete all the markup of app.component.html and add router-outlet. Now make sure that routes are configured properly, so back in the browser and see the home page.
Now go to url (http://localhost:4200/archive/2018/1)
And let’s go to the invalid url (http://localhost:4200/archive/2018)
Now let’s render the list of archives on the home page. According to the mentioned requirements, we make the array of archives. So go to the home.component.ts
- export class HomeComponent implements OnInit {
- archives = [
- { year: 2018, month: 6 },
- { year: 2018, month: 7 },
- { year: 2018, month: 8 }
- ];
- constructor() { }
- ngOnInit() {
- }
- }
Now go to the home.component.html and here we render these archives,
- <h1>Home Page</h1>
- <ul>
- <li *ngFor="let archive of archives">
- <a>{{ archive.year + '/' + archive.month }}</a>
- </li>
- </ul>
Here we use routerLink directive because we need to pass parameters, instead of using it as an attribute and using it as a 1 time initialization, we need to use it as property binding syntax, bind this to an array. The first element of the array is path and the subsequent elements are route parameters.
- <h1>Home Page</h1>
- <ul>
- <li *ngFor="let archive of archives">
- <a [routerLink]="['/archive', archive.year, archive.month ]">
- {{ archive.year + '/' + archive.month }}
- </a>
- </li>
- </ul>
Now let’s test this in the browser, so here is our archive home page.
And when we click any archive link we go to the archive page.
Next we need to extract these parameters and display on this same page. So,
- export class ArchieveComponent implements OnInit {
- constructor(private route: ActivatedRoute) { }
- ngOnInit() {
- }
- }
Now we need to define a couple of fields and on page initialization we need to read the route parameter values. So,
- export class ArchieveComponent implements OnInit {
- year: number;
- month: number;
-
- constructor(private route: ActivatedRoute) { }
-
- ngOnInit() {
-
- let params = this.route.snapshot.paramMap;
- this.year = +params.get('year');
- this.month = +params.get('month');
- }
-
- }
Now let’s implement these fields in our templates. So open archive.component.html
- <h1>Archive for {{ year }} / {{ month }}</h1>
This is how we make it dynamic. We have bound the properties with component properties. Now let’s test this in the browser.
Change the values of the route parameters and it is working fine, showing dynamic values on the view. Now add the button on this view to implement the programmatic navigation. So,
- <h1>Archive for {{ year }} / {{ month }}</h1>
-
- <button
- (click)="viewAll()">
- View All
- </button>
Now implement this programmatic navigation. And for this purpose we need to inject Router object in the archive component constructor.
- export class ArchieveComponent implements OnInit {
- year: number;
- month: number;
-
- constructor(
- private router: Router,
- private route: ActivatedRoute
- ) { }
-
- ngOnInit() {
- let params = this.route.snapshot.paramMap;
- this.year = +params.get('year');
- this.month = +params.get('month');
- }
-
- viewAll() {
- this.router.navigate(['/']);
- }
- }
So note that here we use the leading slash but when we define the route in our app.module.ts, we don’t use leading slash.
Now let’s test the application.
It is working fine when you click on the view all button, it will navigate to the home page.