7

I am building a calendar that can display different kinds of data. The following example explains my URL structure I think:

  • todo/2017/01/01 a day view of todos
  • birthdays/2017/01/01 a day view of birthdays
  • todo/2017/01 a month view of todos
  • birthdays/2017/01 a month view of birthdays
  • todo/2017 a year view of todos
  • birthdays/2017 a year view of birthdays

Up until now I have be able to pass in a Date object and reroute via

this.router.navigate([#HARDCODED# 'todo' #OR# 'birthday', year, ?month, ?day])

The problem is that I want to be able to navigate from todo/2017/01/01 => birthdays/2017/01/01 OR todo/2017/01 => birthdays/2017/01.

So I can't pass in the date parameters because some may not exist depending on which view I am in.

So how can I only switch out a single, internal parameter and reroute?

Something like

this.router.navigate(['todo', {ignoreTheRest: true}])

Otherwise I have to write a complicated switch statement for every possible combination.

Nate May
  • 3,814
  • 7
  • 33
  • 86
  • I believe you can achieve what you want with [optional route params](https://angular.io/docs/ts/latest/guide/router.html#!#optional-route-parameters), even I have never done it befor. I myself will need to try it out. – Timathon Jan 03 '17 at 09:28
  • Optional route params are not the right approach for your problem. András Szepesházi answer should fix it. – Timathon Jan 03 '17 at 15:50

1 Answers1

4

You can achieve this via the built-in ActivatedRoute service, your "one-stop-shop for route information", as they say in the documentation.

First, you need to inject the service in your component's constructor. Assuming that you have a Todo component an a Birthday component, in either case the constructor would look like:

constructor(private currentRoute: ActivatedRoute, private router: Router) { }

Then, in your ngOnInit() function, you need to subscribe to your ActivatedRoute instance's url property, which is an Observable:

ngOnInit() {
    this.currentRoute.url
        .subscribe(routeParts => {
            this.periodArray = [];
            for (let i = 1; i < routeParts.length; i++) {
                this.periodArray.push(routeParts[i].path);
            }
        });
}

The reason why your component route is provided as an Observable is that during your component's life cycle it can receive several different routes. Like in your case "/todo/2017" or "todo/2017/01" etc. Your component will only be created once, the ngOnInit() will also be called only once, but through subscribing to the ActivatedRoute.url observable, you will always get notified about the current route.

The above code block fills an array with all but the first url part from the activated route, which gives you all the passed parameters except for the initial "/todo" or "/birthday" segment.

Now you can navigate to the other component by simply adding the required path at the beginning of this parameter array, like:

navigateBirthdays() {
    this.periodArray.unshift('/birthday');
    this.router.navigate(this.periodArray);
}

Here is the full code for the Todo component and it's template:

todo.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';

@Component({
  templateUrl: './todo.component.html',
})
export class TodoComponent implements OnInit {

  periodArray = [];
  constructor(private currentRoute: ActivatedRoute, private router: Router) { }

  ngOnInit() {
    this.currentRoute.url
      .subscribe(routeParts => {
        this.periodArray = [];
        for (let i = 1; i < routeParts.length; i++) {
          this.periodArray.push(routeParts[i].path);
        }
      });
  }

  navigateBirthdays() {
    this.periodArray.unshift('/birthday');
    this.router.navigate(this.periodArray);
  }
}

todo.component.html

<p>
  List of Todo items for  {{periodArray.join("-")}}
</p>
<button (click)="navigateBirthdays()">Show Birthday items</button>

The Birthday component would look virtually identical. The above allows you to go back and forth between "/todo/2017/1/3" and "/birthday/2017/1/3" and also between "/todo/2017" and "/birthday/2017" etc. - without setting up any specific routing rules.

A side note: for optional parameters it is usually better not to include them in the URL path, but to provide them as an optional route object - see this section of the documentation.

András Szepesházi
  • 6,483
  • 5
  • 45
  • 59
  • Agree this is the straight forward approach. One thing to add is that we may need to do unsubscribe in ngOnDestroy, like: ``` export class TodoComponent implements OnInit, OnDestroy { subscriptions = []; ... ngOnInit() { const sub = this.currentRoute.url .subscribe(...); this.subscription.push(sub); } ngOnDestroy() { this.subscription.forEach(sub => sub.unsubscribe()) } } ``` – Timathon Jan 03 '17 at 15:51
  • 1
    @Timathon from the docs: "When subscribing to an observable in a component, you almost always arrange to unsubscribe when the component is destroyed. There are a few exceptional observables where this is not necessary. The ActivatedRoute observables are among the exceptions." But they also add: "Feel free to unsubscribe anyway. It is harmless and never a bad practice." :) – András Szepesházi Jan 03 '17 at 16:02
  • 1
    I vaguely remember that 'It is harmless and never a bad practice (to unsubscribe).', but just can't recall where exactly it's from. Now I know. Thanks. – Timathon Jan 03 '17 at 16:23
  • 1
    Excellent Solution! I ended up your `this.periodArray` to a service where I already store my parts of my url history, but in all, the use of unshifting and splicing an array will now pervade my application moving forward. – Nate May Jan 03 '17 at 18:46