0

How do we style the :target in Angular?

For when we navigate to e.g. /categories/11#footnote-3, which opens the page for the details of category 11 and scrolls down to the element with id footnote-3.

Details

Linking to fragments

In Angular it's possible to link to a page fragment with:

<p>
  See the <a routerLink="/categories/11" fragment="footnote-3>footnote</a>.
</p>
<p id="footnote-3">
  Lorem ipsum…
</p>

How to style the linked fragment?

But how to style that target?

With css it should be possible to do this:

:target {
    outline: dashed 1px;
}

But this style seems to only be applied briefly when the page is reloaded, and then disappears.

7
  • Maybe subscribing to ActivatedRoute's fragment observable and setting class may work for you. In same place you can set timeout to remove class. I guess that's workaround. Commented May 14 at 9:56
  • @ANeves do you want to style the a or p tag? Commented May 14 at 10:05
  • you're already using routerLink. I need to see your routing file and check if you have imported router? Remaining looks fine to me. Commented May 14 at 17:17
  • @NarenMurali I wanted to style the target itself, in this case the <p …>; same as I would style with :target. But if I style something else, that is an OK work-around. Commented May 15 at 13:17
  • @ANeves what angular version? are you ok with a signals solution? Commented May 15 at 13:18

2 Answers 2

1

you’re right: :target does work, but only momentarily in many browsers, especially when routing with Angular. This is due to how Angular handles navigation without full page reloads, which causes :target styles to flash and vanish quickly or not apply at all.

Add a CSS Class After Navigation

Inject the Router and ActivatedRoute in your component:

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

@Component({
  selector: 'app-category-details',
  templateUrl: './category-details.component.html',
  styleUrls: ['./category-details.component.scss']
})
export class CategoryDetailsComponent implements AfterViewInit {

  constructor(private route: ActivatedRoute) {}

  ngAfterViewInit() {
    this.route.fragment.subscribe(fragment => {
      if (fragment) {
        const el = document.getElementById(fragment);
        if (el) {
          el.classList.add('highlight-target');

          // Optional: remove class after a delay
          setTimeout(() => {
            el.classList.remove('highlight-target');
          }, 2000);
        }
      }
    });
  }
}

Add styling in your CSS or SCSS:

.highlight-target {
  outline: dashed 2px orange;
  background-color: rgba(255, 165, 0, 0.1);
  scroll-margin-top: 100px; // if you have sticky headers
  transition: background-color 0.3s ease;
}
Sign up to request clarification or add additional context in comments.

Comments

1

First, convert the fragment observable from ActivatedRoute to a signal, using toSignal:

export class AppComponent {
  route = inject(ActivatedRoute);
  fragment = toSignal(this.route.fragment);
  name = 'Angular';

  ids: Array<String> = ['one', 'two', 'three', 'four'];
}

Then, we simply, use the string method .includes(...) to check if the ID matches the current fragment.

<section>
  <div
    class="section"
    *ngFor="let link of ids"
    [attr.id]="link"
    [ngClass]="{ 'is-active-section': fragment() === link }"
  >
    <h1>{{ link }}</h1>
    <div class="content">
      Lorem ipsum dolor sit amet consectetur, adipisicing elit. Incidunt saepe
      exercitationem praesentium modi repudiandae dicta consectetur ab veritatis
      quaerat quibusdam quod, dolorem fugit temporibus voluptas magni ad odio,
      adipisci doloremque!
    </div>
  </div>
</section>

Full code:

HTML:

<hello name="{{ name }}"></hello>
<div class="links">
  <ul>
    <li *ngFor="let link of ids">
      <a [routerLink]="'.'" [fragment]="link">{{ link }}</a>
    </li>
  </ul>
</div>
{{ fragment() }}
<section>
  <div
    class="section"
    *ngFor="let link of ids"
    [attr.id]="link"
    [ngClass]="{ 'is-active-section': fragment() === link }"
  >
    <h1>{{ link }}</h1>
    <div class="content">
      Lorem ipsum dolor sit amet consectetur, adipisicing elit. Incidunt saepe
      exercitationem praesentium modi repudiandae dicta consectetur ab veritatis
      quaerat quibusdam quod, dolorem fugit temporibus voluptas magni ad odio,
      adipisci doloremque!
    </div>
  </div>
</section>

TS:

import { Component, inject } from '@angular/core';
import { toSignal } from '@angular/core/rxjs-interop';
import { ActivatedRoute } from '@angular/router';
import { delay } from 'rxjs';
@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  standalone: false,
})
export class AppComponent {
  route = inject(ActivatedRoute);
  fragment = toSignal(this.route.fragment);
  name = 'Angular';

  ids: Array<String> = ['one', 'two', 'three', 'four'];
}

Stackblitz Demo

Introduce delay to end the styling:

Same as the above code, here we use linkedSignal, which is computed from a source signal, but we can change the value.

export class AppComponent {
  route = inject(ActivatedRoute);
  fragment = toSignal(this.route.fragment);
  timedFragment = linkedSignal(() => this.fragment());

Then using effect we trigger a setTimeout to fire after 2000s then reset the linkedSignal, until a new fragment arrives and the process is triggered again.

constructor() {
  effect(() => {
    if (this.fragment()) {
      setTimeout(() => {
        this.timedFragment.set('');
      }, 2000);
    }
  });
}

Stackblitz Demo

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.