Search code examples
angularangular-routingcanonical-link

How to redirect the route with some id to human friendly url and handle canonical url in angular routes?


I have two url format

example.com/posts/:postId and example.com/posts/:postId/:postTitle just like stack-overflow individual question url.

In angular route I need to redirect all the urls with :postId to :postId/:PostTitle or directly loading the page with :postId/:PostTitle lands to same page.

post.module.ts

const routes: Routes = [
    {
        path: ':postId',
        resolve: {data: PdResolver}, 
        redirectTo: ':postId/:PostTitle', 
        pathMatch: 'full'
    },
    {
        path: ':postId/:PostTitle',
        component: PostComponent,
        data: {
            title: 'This has to be the post fetched title'
        }
    }
];
  • redirectTo: ':postId/:PostTitle': here I don't have a postTitle so it throws error
  • How can I append the postTitle to the url and also avoid duplicate url issue.
  • What are some best practices to follow this pattern.

I don't have the title so first I need to get postTitle using the postId, so we have a post data resolver (PdResolver),

Question Update: For a workaround:

I tried with child component which at least solves the case but still wondering if this is the approach I should follow or not.

    {
        path: ':postId',
        component: PostOutletComponent,
        resolve: {data: PdResolver},
        children: [
            {
                path: ':postTitle',
                component: PostComponent,
            }]

    }

And in PostOutletComponent I am navigating further to child component after fetching the title

PostOutletComponent.ts

static stripHtml(html: string) {
        let div = document.createElement('DIV');
        div.innerHTML = html;
        const cleanText = div.innerText;
        div = null;
        return cleanText;
    }

 ngOnInit() {
        this.reolveData = this._route.snapshot.data;
        let postTitle = PostOutletComponent.stripHtml(
            this.reolveData.data.data.post_title);
        postTitle = postTitle.replace(/\s+/g, '-').toLowerCase();
        this.router.navigate([postTitle], {relativeTo: this._route});
    }

Solution

  • I made a Stackblitz based on your code: https://stackblitz.com/edit/angular-r1tvx2

    It solves a problem which occurs if a user navigates to the URL with existing ID but wrong title. In the above example the title is corrected as Stack Overflow does.

    I made it using only one resolver and one component class. The Resolver takes care of redirecting to the correct URL. Note that the Resolver is placed in the more specific route with title.

    Route configuration, the order of the routes is important, first comes the more specific route with title:

      {
        path: "posts/:id/:title",
        component: PostComponent,
        resolve: { somedata: PostDetailResolverService }
      },
      {
        path: "posts/:id",
        redirectTo: "posts/:id/title-to-be-repalced",
      },
      { path: "**", component: PageNotFoundComponent }
    ];
    

    Resolver, redirects to the URL with the correct title:

      resolve(
        route: ActivatedRouteSnapshot,
        state: RouterStateSnapshot
      ): Observable<Somedata> | Observable<never> {
        let id = +route.paramMap.get("id");
        let title = route.paramMap.get("title");
        return this.getData(id).pipe(
          take(1),
          mergeMap(res => {
            if (res) {
              console.log("Resolver - Navigation continues to the route with data:", res);
              if (!title || title != res.title) {
                this.router.navigate(["posts/" + res.id + "/" + res.title]);
              } 
              return of(res);
            } else {
              // id not found
              console.log("Navigation cancelled");
              this.router.navigate([""]);
              return EMPTY;
            }
          })
        );
      }