Search code examples
javascriptangulartypescriptangular-router

Having the Angular Router render URLs pasted into the browser?


I have the router configured like this:

const routes: Routes = [
  {
    path: 'topic/:id',
    component: TopicComponent,
    resolve: { topic: TopicResolverService }
  },  
  {
    path: '',
    pathMatch: 'full',
    component: SummaryCardListComponent
  }
]

And if I visit a topic directly like this:

http://localhost:4200/topic/concepts%2Fdemand%2Flead-time-demand

It redirects to the path http://localhost:4200/.

What do we need to do to make the router render the link pasted into the browser?

The topic resolver service looks like this:

@Injectable({
    providedIn: 'root'
})
export class TopicResolverService implements Resolve<Topic> {

    constructor( private s: StateService ) { }

    resolve(
        route: ActivatedRouteSnapshot) {
        const id = route.paramMap.get('id')
        return this.s.loadingTopicStore$.pipe(
            switchMap(()=>of(this.s.topicStore.findOneByID(id))
        ))
    }
}

Solution

  • If I use decodeURIComponent('concepts%2Fdemand%2Flead-time-demand') on your URI param, which is supposed to be an :id, it resolves to concepts/demand/lead-time-demand;

    Now this baffles angular router, it searches for nested route like:

    http://localhost:4200/topic/concepts/demand/lead-time-demand

    This obviously does not exist, so it falls back to the base URL.

    EDIT FROM QUESTION AUTHOR

    I had coded an Action that merged Observable events, and accidentally included the Observable that triggers when the Topic store loading was complete.

    The action allowed the user to select a Slice of topics (Concepts, Formulas, Guides...) and on a select from the user it would navigate to '' since that's the route that displays the slice.

    Anyways since a paste of a URL into the browser that matches the route causes the application to load, this in turn causes the this.s.loadingTopicStore$ event to fire, and that caused the router to navigate to ''.

    For those interested this was the design of the action:

      /**
       * Note that are always only rendering
       * `searchedTopics$` but we also
       * track `selectedTopics$` because
       * we search within this subset when 
       * it's selected.
       * 
       * This also resets `topicStore.query`.
       */
      onSelectTopicCategory() {
        merge(
          this.s.loadingTopicStore$,
          this.s.activeTopicCategory$).
          pipe(untilDestroyed(this)).subscribe(() => {
            this.s.selectedTopics$ = combineLatest(
              this.s.all$,
              this.s.guides$,
              this.s.blogs$,
              this.s.concepts$,
              this.s.formulas$,
              this.s.tasks$,
              this.s.activeTopicCategory$,
              this.s.loadingTopicStore$,
              this.onSelectTopicCategoryFunction)
            this.s.searchedTopics$ = this.s.selectedTopics$
            this.s.topicStore.query = ''
    
            //We have to subscribe to this otherwise
            //The combine latest function will never fire.
            //The reason is that we are only using
            //searchedTopics in the view, so we 
            //have to fire selectedTopics$ manually.
    
            this.s.selectedTopics$.
            pipe(untilDestroyed(this)).
            subscribe()
          })
      }
    
    

    And the function that is triggered by the merge:

    
      /**
       * Observe the active topic category.  
       * 
       * Note that we navigate to '' when a category
       * is selected such that we can see the selections
       * rendered.
       */
      onSelectTopicCategoryFunction(
        all,
        guides,
        blogs,
        concepts,
        formulas,
        tasks,
        active,
        loading) {
        if (loading == false) {
    //      this.router.navigate([''])
          switch (active) {
            case TopicCategories.ALL:
              return all
            case TopicCategories.GUIDES:
              return guides
            case TopicCategories.BLOGS:
              return blogs
            case TopicCategories.CONCEPTS:
              return concepts
            case TopicCategories.FORMULAS:
              return formulas
            case TopicCategories.TASKS:
              return tasks
            default:
              return all
          }
        }
        else return []
      }
    
    

    It's implemented with @fireflysemantics/slice:

    https://www.npmjs.com/package/@fireflysemantics/slice