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
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
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}
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.
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}
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}
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.
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.
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