This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by Niaz Bin Siraj
TheIntro();
Hello Devs!
Are you looking to build a dynamic photo blog without the hassle of a traditional backend? In these series of articles, I'll share my experience building a server-less photo blog for my wife's photography portfolio using Angular and Google Sheets.
Hosting on a GitHub Page is a good option for a static site, but for a dynamic site, you'll need a backend. That's where Google Sheets comes in handy as a database, and a Google form to create new blog entries with photos.
In this series, I'll take you through the journey of building this photo blog and share some of the challenges I faced along the way, as well as how I solved them.
In this first part, I'll discuss how to solve Angular router navigation issues.
TheProblem();
As we continue to build the "ESHARTISTIC" photo blog, let's take a closer look at the navigation bar.
(Fun fact: My wife came up with the name "ESHARTISTIC" for her photo blog, which is a combination of her name, ESHA, and the word ARTISTIC. I think it's a pretty cool name, don't you?)
As you can see in the picture, the navigation bar consists of four categories: Art
, Design
, Photography
, and Craft
, all of which will be rendered using the same component in Angular. Additionally, the About page will be rendered in a different component.
To achieve this, we need to define the routes in the Angular router. The route for the four categories will be list/:category
, where :category
can be art
, design
, photography
, or craft
. This route will use the same component to render the different categories. The route for the About page will be about
.
When a user clicks on any of the four category links in the navigation bar, the corresponding page should be rendered with a list of posts. To get the list of posts, we only need to call the getPostList()
method once when the component starts to load.
So that's the two things I want to achieve.
TheProbableSolution();
In Angular, ngOnInit()
is a lifecycle hook method that is called only once when a component is initialized.
-> In my case I can call getPostList()
inside ngOnInit()
like this:
import { ActivatedRoute } from '@angular/router';
export class ListComponent implements OnInit {
category: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.category = this.route.snapshot.paramMap.get('category');
this.getPostList(this.category);
}
private getPostList(category: string) {
// fetch posts for the current category
}
}
This code retrieves the current category parameter value from the URL and uses it to fetch the posts for that category using the getPostList()
method only once when the component is loaded.
If I am currently on the Art page and click on any of the four links in the navigation bar, the ngOnInit()
method will not be called again. This is because I am already on this component and ngOnInit()
has already been called. Instead, what I need to do is detect a change in the :category parameter of the router.
-> To detect changes to the :category parameter of the router, I can use the ngDoCheck()
lifecycle hook in Angular. This method is called every time the component is checked for changes, which makes it a good place to detect changes to the router parameter.
Here is an example implementation:
import { Component, OnInit, DoCheck } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit, DoCheck {
category: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.category = this.route.snapshot.paramMap.get('category');
this.getPosts(this.category);
}
ngDoCheck() {
const newCategory = this.route.snapshot.paramMap.get('category');
if (newCategory !== this.category) {
this.category = newCategory;
this.getPostList(this.category);
}
}
private getPostList(category: string) {
// fetch posts for the current category
}
}
Now if the new category is different from the current category, the category property is updated and the getPostList()
method is called with the new category. This ensures that the component always displays the correct content based on the current category in the router.
Now let's explore another approach to achieve the same result, which is by using ActivatedRoute
.
-> To reinitialize and the ngOnInit()
method to be called again, I can subscribe to the ActivatedRoute
parameter and detect changes in the route parameter. I can use the paramMap
observable to detect changes and reinitialize the component as needed.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-list',
templateUrl: './list.component.html',
styleUrls: ['./list.component.css']
})
export class ListComponent implements OnInit {
category: string;
constructor(private route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap.subscribe(params => {
this.category = params.get('category');
this.getPostList(this.category);
});
}
private getPostList(category: string) {
// fetch posts for the current category
}
}
In the ngOnInit()
method, we're subscribing to changes in the route parameter using this.route.paramMap.subscribe()
. Whenever the category parameter changes, the subscribe method will be called and we can then update the category property and call the getPostList()
method to fetch the posts for the new category.
In terms of detecting changes to the route parameters, using ActivatedRoute
is generally the preferred approach in Angular because it provides a more efficient and reliable way to access the current route parameters.
After implementing the solution using ActivatedRoute
, when navigating from the art page to design, photography, or craft pages, the getPostList()
method will be called again. However, if the user clicks on the art link while already on the art page, the getPostList()
method will not be called again because the parameter has not changed. Yeah! Life is not a full of roses!
Let's see how did I solve this problem.
TheSolution();
In order to achieve our objective of reloading the list component and fetching posts whenever any of the four navbar links are clicked, we must first create a dummy route mapped to a dummy component.
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutPageComponent } from './about-page/about-page.component';
import { DummyComponent } from './components/dummy/dummy.component';
import { ListPageComponent } from './list-page/list-page.component';
import { MainPageComponent } from './main-page/main-page.component';
const routes: Routes = [
{ path: 'post/:category', component: ListPageComponent },
{ path: 'post/:category/:id', component: MainPageComponent },
{ path: 'about', component: AboutPageComponent },
{ path: '', redirectTo: '/post/art', pathMatch: 'full' },
{ path: 'dummy', component: DummyComponent}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
We have added a dummy route mapped to a dummy component, the next step is to invoke an onClick()
method when clicking on the navbar link. This method will first navigate to the dummy route and then immediately navigate to the clicked navbar route. While we could add the onClick()
method in the navbar component .ts file, it may be required in other components as well. To address this, we have created a service with a navigateToRoute()
method implementation, allowing us to call this method whenever and wherever needed.
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
@Injectable({
providedIn: 'root'
})
export class RouteHandlerService {
constructor(private router: Router) { }
navigateToRoute(clickedRoute: string) {
const currentRouteUrl = clickedRoute;
this.router.navigateByUrl('/dummy', { skipLocationChange: true }).then(() => {
this.router.navigate([currentRouteUrl]);
});
}
}
The navigateToRoute()
method is defined in the RouteHandlerService
. It takes a parameter called clickedRoute
, which is the route that the user clicked on in the navbar.
Inside the navigateToRoute()
method, the current route URL is set to the clicked route. Then, the navigateByUrl()
method of the Router service is called with a dummy route and the option skipLocationChange
set to true
. This navigates to the dummy route without changing the URL in the browser's address bar.
Once the navigation to the dummy route is complete, the router.navigate
method is called to navigate to the clicked route. This changes the URL in the browser's address bar and loads the corresponding component.
Overall, this service provides a way to navigate to a new route in an Angular application without causing the browser's address bar to display the intermediate route. This can be useful when you want to reload a component when a user clicks on a link, but you don't want to change the URL in the browser's address bar.
Then I created a onClickMenuItem()
method in the navbar component and bind this with the navbar links.
import { Component } from '@angular/core';
import { Router } from '@angular/router';
import { RouteHandlerService } from 'src/app/services/route-handler.service';
import { AppConstants } from 'src/utils/appConstants';
import { Page } from 'src/utils/models/page';
@Component({
selector: 'app-navbar',
templateUrl: './navbar.component.html',
styleUrls: ['./navbar.component.scss']
})
export class NavbarComponent {
pages: Page[] = AppConstants.pages;
constructor(private router: Router, private routeHandlerService: RouteHandlerService) { }
onClickMenuItem(clickedRoute: string) {
this.routeHandlerService.navigateToRoute(clickedRoute);
}
}
<a routerLink="{{page.getRouterLink()}}" (click)="onClickMenuItem(page.getRouterLink())" class="menu-text nav-link">{{page.getTitle()}}</a>
Whenever a navbar link is clicked, the onClickMenuItem()
method will be invoked, which in turn calls the navigateRoute()
method. As a result, the list component is reloaded every time we click on a navbar link.
Then in list component I just called getPostList()
in ngOnInit()
method.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { PostType } from 'src/utils/enums/postType';
import { PostService } from '../services/post.service';
@Component({
selector: 'app-list-page',
templateUrl: './list-page.component.html',
styleUrls: ['./list-page.component.scss']
})
export class ListPageComponent implements OnInit {
constructor(
private postService: PostService,
private router: Router
) { }
ngOnInit() {
this.getPostList(this.getPostType(this.router.url));
}
async getPostList(postType: PostType) {
await this.postService.getPosts(postType);
}
private getPostType(routeUrl: string): PostType {
switch (routeUrl) {
case "list/design":
return PostType.DESIGN
case "list/photography":
return PostType.PHOTOGRAPHY
case "list/craft":
return PostType.CRAFT
default:
return PostType.ART
}
}
}
Conclusion();
Certainly! In conclusion, this is the approach I took to solve the problem of reloading the list component and fetching the latest posts every time a navbar link is clicked. However, there may be other solutions or approaches that can achieve the same result or even better results. If anyone has a better solution or approach, I encourage them to share it with the community and keep improving the development practices.
This content originally appeared on DEV Community 👩‍💻👨‍💻 and was authored by Niaz Bin Siraj
Niaz Bin Siraj | Sciencx (2023-02-15T20:44:39+00:00) Building a Dynamic Serverless Photo Blog with Angular & Google Sheets – 1: Solving Angular Router Navigation Issues. Retrieved from https://www.scien.cx/2023/02/15/building-a-dynamic-serverless-photo-blog-with-angular-google-sheets-1-solving-angular-router-navigation-issues/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.