Angular 2
Routing
Fragments
Async Data
Web Development

Angular 2 routing with fragments, async data

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Angular routing supports URL fragments (the #section part of a URL) for in-page navigation and async data loading via route resolvers and guards. Fragments scroll to a specific element on the page, while resolvers pre-fetch data before the component loads. Combining both lets you navigate to a page, wait for async data to load, and then scroll to the specified section. This is common in documentation sites, FAQ pages, and single-page applications with deep linking.

Basic Route Configuration

typescript
1// app-routing.module.ts
2import { NgModule } from '@angular/core';
3import { RouterModule, Routes } from '@angular/router';
4import { ArticleComponent } from './article/article.component';
5import { ArticleResolver } from './article/article.resolver';
6
7const routes: Routes = [
8  {
9    path: 'article/:id',
10    component: ArticleComponent,
11    resolve: {
12      article: ArticleResolver
13    }
14  }
15];
16
17@NgModule({
18  imports: [RouterModule.forRoot(routes, {
19    anchorScrolling: 'enabled',          // Enable fragment scrolling
20    scrollPositionRestoration: 'enabled' // Restore scroll position on back
21  })],
22  exports: [RouterModule]
23})
24export class AppRoutingModule {}

Using URL Fragments

typescript
1// Navigate to /article/42#comments
2// Template
3<a [routerLink]="['/article', 42]" fragment="comments">Go to comments</a>
4
5// Programmatic navigation
6import { Router } from '@angular/router';
7
8constructor(private router: Router) {}
9
10navigateToSection() {
11  this.router.navigate(['/article', 42], { fragment: 'comments' });
12}
typescript
1// article.component.ts — reading the fragment
2import { Component, OnInit } from '@angular/core';
3import { ActivatedRoute } from '@angular/router';
4
5@Component({
6  selector: 'app-article',
7  template: `
8    <h1>{{ article.title }}</h1>
9    <div id="content">{{ article.body }}</div>
10    <div id="comments">
11      <h2>Comments</h2>
12      <div *ngFor="let comment of article.comments">
13        {{ comment.text }}
14      </div>
15    </div>
16  `
17})
18export class ArticleComponent implements OnInit {
19  article: any;
20
21  constructor(private route: ActivatedRoute) {}
22
23  ngOnInit() {
24    // Get resolved data
25    this.article = this.route.snapshot.data['article'];
26
27    // React to fragment changes
28    this.route.fragment.subscribe(fragment => {
29      if (fragment) {
30        const element = document.getElementById(fragment);
31        element?.scrollIntoView({ behavior: 'smooth' });
32      }
33    });
34  }
35}

Route Resolver for Async Data

A resolver fetches data before the component loads, ensuring data is available when the page renders.

typescript
1// article.resolver.ts
2import { Injectable } from '@angular/core';
3import { Resolve, ActivatedRouteSnapshot } from '@angular/router';
4import { Observable } from 'rxjs';
5import { ArticleService } from './article.service';
6
7@Injectable({ providedIn: 'root' })
8export class ArticleResolver implements Resolve<any> {
9
10  constructor(private articleService: ArticleService) {}
11
12  resolve(route: ActivatedRouteSnapshot): Observable<any> {
13    const id = route.paramMap.get('id');
14    return this.articleService.getArticle(id);
15    // Router waits for this Observable to complete before activating the route
16  }
17}
typescript
1// article.service.ts
2import { Injectable } from '@angular/core';
3import { HttpClient } from '@angular/common/http';
4
5@Injectable({ providedIn: 'root' })
6export class ArticleService {
7  constructor(private http: HttpClient) {}
8
9  getArticle(id: string): Observable<any> {
10    return this.http.get(`/api/articles/${id}`);
11  }
12}

Scrolling to Fragment After Async Data Loads

When data loads asynchronously, the target element may not exist yet when the fragment is processed. Use AfterViewInit or a small delay to scroll after rendering.

typescript
1import { Component, OnInit, AfterViewChecked } from '@angular/core';
2import { ActivatedRoute } from '@angular/router';
3
4@Component({ /* ... */ })
5export class ArticleComponent implements OnInit, AfterViewChecked {
6  article: any;
7  private fragment: string | null = null;
8  private hasScrolled = false;
9
10  constructor(private route: ActivatedRoute) {}
11
12  ngOnInit() {
13    this.article = this.route.snapshot.data['article'];
14    this.fragment = this.route.snapshot.fragment;
15  }
16
17  ngAfterViewChecked() {
18    if (this.fragment && !this.hasScrolled) {
19      const element = document.getElementById(this.fragment);
20      if (element) {
21        element.scrollIntoView({ behavior: 'smooth' });
22        this.hasScrolled = true;
23      }
24    }
25  }
26}

Functional Resolver (Angular 15+)

Angular 15+ supports functional resolvers without a class.

typescript
1// app-routing.module.ts
2import { inject } from '@angular/core';
3import { ResolveFn } from '@angular/router';
4import { ArticleService } from './article.service';
5
6const articleResolver: ResolveFn<any> = (route) => {
7  const service = inject(ArticleService);
8  return service.getArticle(route.paramMap.get('id')!);
9};
10
11const routes: Routes = [
12  {
13    path: 'article/:id',
14    component: ArticleComponent,
15    resolve: { article: articleResolver }
16  }
17];

Common Pitfalls

  • Fragment scrolling not working: Angular's anchorScrolling: 'enabled' must be set in RouterModule.forRoot() options. Without it, fragments in the URL are ignored and no scrolling occurs. This is disabled by default.
  • Element not found when scrolling: If async data has not rendered yet, document.getElementById(fragment) returns null. The target element does not exist in the DOM until the data is bound. Use AfterViewChecked or setTimeout to wait for rendering before scrolling.
  • Resolver blocking navigation: Resolvers delay route activation until the Observable completes. If the API call is slow, the user sees no feedback. Add a loading indicator in a parent component or use a guard with a service instead of a resolver for non-blocking data fetching.
  • Fragment not updating on same-route navigation: Navigating from /article/1#section1 to /article/1#section2 does not trigger ngOnInit because the component is reused. Subscribe to route.fragment as an Observable to react to fragment changes without component re-creation.
  • Using ViewportScroller incorrectly: Angular provides ViewportScroller.scrollToAnchor(fragment) for programmatic scrolling, but it must be called after the view is rendered. Calling it in ngOnInit before the template renders results in no scrolling.

Summary

  • Enable anchorScrolling: 'enabled' in RouterModule.forRoot() for fragment-based scrolling
  • Use route resolvers to pre-fetch async data before component activation
  • Subscribe to route.fragment Observable to react to fragment changes on same-route navigation
  • Use AfterViewChecked to scroll to fragments after async data renders
  • Angular 15+ supports functional resolvers with inject() for simpler syntax

Course illustration
Course illustration

All Rights Reserved.