Web

Angular 19: inject(Location) Property vs Constructor DI

Why inject(Location) in property initializers fails in Angular 19 standalone components but constructor injection works. Fix timing issues with LOCATION_INITIALIZED and best practices for back buttons.

1 answer 1 view

Angular 19: Why does injecting Location with inject(Location) in a class property initializer have no effect while constructor injection works?

I’m building a standalone component and created a back button. When I try to inject the Location service using:

ts
private readonly location = inject(Location);

goBack() {
 this.location.back();
}

…the button has no effect. However, when I use constructor injection:

ts
constructor(private location: Location) {}

goBack() {
 this.location.back();
}

it works correctly. Both approaches use the same Location service from @angular/common, and I’ve imported CommonModule. What’s causing this difference in behavior, and how can I make inject(Location) work in a standalone component property (or what is the recommended approach)?

Angular inject(Location) in a class property initializer can appear to do nothing because the Location service (and the platform-level location plumbing) may not have finished its startup when that field initializer runs. Constructor injection happens at a slightly different point in the component creation flow and avoids that race, so location.back() behaves as expected. The simplest fix is to use constructor injection; if you prefer inject() in a field, inject and await the LOCATION_INITIALIZED token (or otherwise delay calling back() ) so the platform is ready.


Contents


Angular inject(Location): why property initializer can have no effect

Angular allows calling inject() in constructors and in field (property) initializers — see the inject() API and the DI context documentation for details — but that permission doesn’t remove the reality of service initialization order: field initializers run very early as part of instance creation. The inject() call itself succeeds, but the injected service may internally rely on platform/browser initialization that finishes slightly later.

What that means for Location:

  • The Location service (from @angular/common) delegates to platform location primitives and participates in Angular’s startup flow. There is a dedicated token, LOCATION_INITIALIZED, representing the moment the platform/location initialization is done: https://angular.dev/api/common/LOCATION_INITIALIZED and the Location API: https://angular.dev/api/common/Location.
  • If you bind private readonly location = inject(Location); in a field, that inject() runs while the component instance is being constructed. In some apps that timing can be before the platform/location subsystem has finished its setup. Calling location.back() before that readiness can be effectively a no-op — the Location instance exists, but the browser history or listeners it needs aren’t ready yet.

You didn’t get an NG0203 error (the “inject must be called from an injection context” error) because inject() was legal in a field initializer here; the problem is a timing/readiness mismatch, not an invalid injection call. See https://angular.dev/errors/NG0203 for the error rules.


How LOCATION_INITIALIZED and timing affect Location.back()

The LOCATION_INITIALIZED token is a Promise-like signal that resolves once the platform’s initial location work is done. If you try to drive browser history or rely on side effects before that promise resolves, results may vary.

Practical pattern: inject the LOCATION_INITIALIZED token and await it before you call location.back():

ts
import { Component, inject } from '@angular/core';
import { Location, LOCATION_INITIALIZED } from '@angular/common';

@Component({
 standalone: true,
 imports: [],
 template: `<button (click)="goBack()">Back</button>`
})
export class BackButtonComponent {
 private readonly location = inject(Location);
 private readonly locationReady = inject(LOCATION_INITIALIZED);

 async goBack(): Promise<void> {
 // ensure platform/location is initialized before calling back()
 await this.locationReady;
 this.location.back();
 }
}

Why this helps: awaiting LOCATION_INITIALIZED guarantees the platform-level setup (base href, history listeners, etc.) completed before you invoke history operations. See the token docs: https://angular.dev/api/common/LOCATION_INITIALIZED.


How to make inject(Location) work in a standalone component

You have several practical options, ordered from simplest to more explicit:

  1. Prefer constructor injection (recommended)
  • The cleanest and most obvious fix is constructor(private location: Location) {}. It avoids timing surprises and is the most idiomatic approach for a simple back button.
  1. Keep inject() but wait for LOCATION_INITIALIZED
  • If you like inject() in fields, inject the LOCATION_INITIALIZED token as shown above and await it before calling .back().
  1. Defer the action until a lifecycle moment that is guaranteed to be after platform init
  • Call location.back() in response to UI events that occur after initialization, and gate the action by a locationReady boolean set when the LOCATION_INITIALIZED promise resolves. That makes the UI explicit about readiness.
  1. Use the Router as a fallback
  • If you use the Angular Router, consider using Router.navigateByUrl(...) as a deterministic fallback when there is no previous history entry (or when location.back() doesn’t do anything). This is handy when you want predictable behavior rather than a browser-history-dependent back.

Hints and gotchas:

  • If you see “no effect” even after ensuring readiness, check whether there actually is a previous history entry (history length), or whether server-side rendering (SSR) is in play — SSR environments won’t perform client-side history navigation.
  • Location.back() ultimately calls into browser history; it won’t magically navigate if the previous entry doesn’t exist or the browser blocks the action.

Two concise patterns you can adopt immediately.

A. Constructor injection (recommended, simplest):

ts
import { Component } from '@angular/core';
import { Location } from '@angular/common';

@Component({
 standalone: true,
 template: `<button (click)="goBack()">Back</button>`
})
export class BackButtonComponent {
 constructor(private location: Location) {}

 goBack(): void {
 this.location.back();
 }
}

B. Field inject() + await LOCATION_INITIALIZED (if you prefer inject in fields):

ts
import { Component, inject } from '@angular/core';
import { Location, LOCATION_INITIALIZED } from '@angular/common';

@Component({ standalone: true, template: `<button (click)="goBack()">Back</button>` })
export class BackButtonComponent {
 private readonly location = inject(Location);
 private readonly locationReady = inject(LOCATION_INITIALIZED);

 async goBack(): Promise<void> {
 await this.locationReady;
 this.location.back();
 }
}

Pick A for clarity and minimum surprises. Pick B if your codebase consistently uses inject() in field initializers and you want to handle readiness explicitly.


Troubleshooting checklist

  • Confirm the component runs in the browser (not server-side rendering). SSR won’t run history.back() in the browser.
  • Verify CommonModule or other required modules are present for your component (you mentioned it’s imported).
  • Add a quick console.log(this.location) and console.log(history.length) in the click handler to see what’s happening.
  • Check whether there is actually a previous history entry. If history length is 1, back() may appear to do nothing.
  • If constructor injection works but field inject() doesn’t, try the LOCATION_INITIALIZED pattern above to rule out a startup race.
  • Consider using the Router to navigate to a fallback route if history navigation fails.

Sources


Conclusion

In short: the behavior isn’t a bug in inject() itself — it’s a timing/readiness issue around the platform/location initialization. For a back button in an Angular 19 standalone component, prefer constructor injection for simplicity and reliability, or keep using inject(Location) but await LOCATION_INITIALIZED before calling location.back().

Authors
Verified by moderation
Moderation
Angular 19: inject(Location) Property vs Constructor DI