7
votes

I am running Angular 6 with RxJs 6.

I am trying to get the value out of paramMap and assign it to a property. The route looks like this:

{
    path: 'post/:postName',
    component: PostComponent
}

The post component currently looks like this:

import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { Observable, of } from 'rxjs';
import * as matter from 'yaml-front-matter';
import { Post } from '../../models/post';

@Component({
  selector: 'blog-post',
  templateUrl: './post.component.html',
  styleUrls: ['./post.component.css'],
  encapsulation: ViewEncapsulation.Emulated
})
export class PostComponent implements OnInit {
  created: Date;
  postName = '';
  markdown = '';
  title = '';
  loading = true;
  author = '';

  constructor(private route: ActivatedRoute) { }

  async ngOnInit () {
    this.route.paramMap.pipe(
      switchMap((params: ParamMap) =>
        of(params.get('postName'))
      )
    ).subscribe(d => this.postName = d);

    const file = await fetch(`/_posts/${this.postName}.md`);
    const text = await file.text();
    const content = text.trim();
    this.parseFrontMatter(content);
  }

  parseFrontMatter (content: string) {
    const frontMatter: Post = matter.loadFront(content);
    this.title = frontMatter.title;
    this.author = frontMatter.author;
    this.created = frontMatter.created;
    this.markdown = frontMatter.__content;
    this.loading = false;
  }
}

In the ngOnInit is the code for getting the value out of the paramMap. This code works unless you try to route back to the post component with a different parameter.

This can be observed at Devbin.io.

  1. Navigate to the posts page
  2. Click on the only post
  3. Now try to navigate to the about page which is just another post.
  4. Notice that it will not route to the About page even though the Url is updated.

The full source can be found on GitHub

All of the documentation and other Stack Overflow answers always assume you want to call a service in the switchMap function. The official documentation is here

My question is how do I get the route parameter and assign it to a property and keep navigation working while navigating back to the same route?

2

2 Answers

15
votes

This is because ngOnInit doesn't get called when you route to the same component. In your case /post/Angular-6-Router-How-To-Get-Route-Parameters and /post/About are triggering the same component: post.component.

So to make this work you could call a function inside subscribe in paramMap.pipe.

You can do something like this:

ngOnInit() {
    this.route.paramMap.pipe(
      switchMap((params: ParamMap) =>
        of(params.get('postName'))
      )
    ).subscribe((d) => {
      this.postName = d;
      this.postInit();
    });

  }

  async postInit() {
    const file = await fetch(`/_posts/${this.postName}.md`);
    const text = await file.text();
    const content = text.trim();
    this.parseFrontMatter(content);
  }

  parseFrontMatter(content: string) {
    const frontMatter: Post = matter.loadFront(content);
    this.title = frontMatter.title;
    this.author = frontMatter.author;
    this.created = frontMatter.created;
    this.markdown = frontMatter.__content;
    this.loading = false;
  }
3
votes

So I have cloned the repo and fixed the issue.

Replace this:

   async ngOnInit () {
        this.route.paramMap.pipe(
          switchMap((params: ParamMap) =>
            of(params.get('postName'))
          )
        ).subscribe(d => this.postName = d);

        const file = await fetch(`/_posts/${this.postName}.md`);
        const text = await file.text();
        const content = text.trim();
        this.parseFrontMatter(content);
      }

with the following:

  ngOnInit() {
    this.route.paramMap.pipe(
      switchMap((params: ParamMap) =>
        of(params.get('postName'))
      )
    ).subscribe(d => {
      this.postName = d;

      fetch(`/_posts/${this.postName}.md`)
        .then(data => data.text()
          .then(text => {
            const content = text.trim();
            this.parseFrontMatter(content);
          }));
    });
  }

Explanation: You were fetching the posts only when the component was initialized since your file fetch code was outside of the Observable subscribe method. I moved in and chained the promises and then passed the final value to your function parseFrontMatter