This commit updates the router integration tests to cover both the
classic History and the new Navigation API. There is more work to be
done here, but this commit works to prove the efficacy of the
`FakeNavigation` implementation.
PR Close#53799
This commit updates the router integration tests to cover both the
classic History and the new Navigation API. There is more work to be
done here, but this commit works to prove the efficacy of the
`FakeNavigation` implementation.
PR Close#53799
This is a follow up to 5c1d441029
which added the `info` property to navigation requests. `RouterLink` now
supports passing that transient navigation info to the navigation
request.
This info object can be anything and doesn't have to be serializable.
One use-case might be for passing the element that was clicked. This
might be useful for something like view transitions. In the "animating
with javascript" example from the blog (https://stackblitz.com/edit/stackblitz-starters-cklnkm)
those links could have done this instead of needing to create a separate
directive that tracks clicks.
PR Close#53784
Use of the `SpyLocation` is problematic because it prevents location
APIs from reaching the platform level (`PlatformLocation`) and
`PathLocationStrategy`. This makes it difficult to test interactions
with those providers, including the ability to use the `Navigation` API,
which will live at the platform level.
PR Close#53640
This commit adds a property to the navigation options to allow
developers to provide transient navigation info that is available for
the duration of the navigation. This information can be retrieved at any
time with `Router.getCurrentNavigation()!.extras.info`. Previously,
developers were forced to either create a service to hold information
like this or put it on the `state` object, which gets persisted to the
session history.
This feature was partially motivated by the [Navigation API](https://github.com/WICG/navigation-api#example-using-info)
and would be something we would want/need to have feature parity if/when the
Router supports managing navigations with that instead of `History`.
PR Close#53303
There are cases where the application's default behavior is 'reload' and
a certain navigation might want to override this to be `ignore` instead.
This commit allows `onSameUrlNavigation` in the `router.navigateByUrl`
to be `ignore` where it was previously restricted to only `reload`.
PR Close#52265
The browserUrlTree is only used to support the onSameUrlNavigation: 'ignore' logic. We can achieve this functionality without having this state tracked inside the Router. Instead, we can re-examine what ignore means: We don't want to rerun the matching logic, guards, or resolvers when we already know that nothing is changing.
Outside of the "navigated", there are two things that constitute a "change":
1. The browser URL might change. Because of skipLocationChange, the browser URL might not always match the internal state of the Router (we can navigate to a path but skip updating the browser URL). If we're navigating to a place that would change the browser URL, we should process the navigation. Theoretically, all we need to really do is update the browser URL instead of processing the whole navigation w/ guards, redirects, and resolvers. But this doesn't matter that much because the default value for runGuardsAndResolvers will skip all of this anyways.
2. The internal state of the Router might change. That is, we're navigating to a new path and may or may not be updating the updating the browser URL.
If either of the above are true, we process the navigation. If both are false, we aren't changing anything so we can safely ignore the navigation request (as long as onSameUrlNavigation === 'ignore').
Why is this change important?
* Simplification of Router internals. The Router has a lot of special case handling and one-offs to handle a limited set of scenarios. Removing these when possible makes the code easier to follow
PR Close#48065
Currently internally Angular has some customized tsconfig files, because we don't align with the tsconfig of the rest of g3. These changes enable `noImplicitReturns` and `noPropertyAccessFromIndexSignature` to align better with the internal config.
PR Close#51728
This commit removes the `urlHandlingStrategy` from the public Router's API
BREAKING CHANGE:
`urlHandlingStrategy` has been removed from the Router public API.
This should instead be configured through the provideRouter or RouterModule.forRoot APIs.
PR Close#51631
This commit removes deprecated properties on the Router. These are meant
to be configured through DI and not meant to be changed during runtime.
BREAKING CHANGE: The following Router properties have been removed from
the public API:
- canceledNavigationResolution
- paramsInheritanceStrategy
- titleStrategy
- urlUpdateStrategy
- malformedUriErrorHandler
These should instead be configured through the `provideRouter` or
`RouterModule.forRoot` APIs.
PR Close#51502
Rather than the navigation transitions managing Router state, this
commit updates the Router to manage its own state based on ongoing
transition events. In the future, this can be abstracted even further to
have a totally separate class that manages the Router state. This would
allow the potential for swapping state manager implementations rather
than having to implement all types of state management in a single place.
One finding during tests was that unexpected errors thrown by the state management code moved
to the Router here will no longer be caught by the transition pipe's `catchError`.
This only includes calls to the following public Api methods:
* `go`, `replaceState`, `historyGo`, `isCurrentPathEqualTo` on `Location`
* `UrlSerializer.serialize`
* `UrlHandlingStrategy.merge`.
None of these methods should throw if the router is expected to function.
These might throw when tests include incomplete mocks, which is not
supported, or in cases where the actual browser methods like
`replaceState` would throw. This will already result in unexpected/unsupported
behavior. The failure case here is now arguably better - the navigation
itself still completes but the state update (either updating Router
internal state or updating the browser URL) fails separately and is
unhandled.
PR Close#48427
`RouterTestingModule` is not needed as of v16. Instead, TestBed
automatically provides `MockPlatformLocation` in order to help test
navigations in the application. The location mocks in the
RouterTestingModule aren't necessary anymore.
There doesn't appear to be any real documentation around
`RouterTestingModule` other than the API docs.
PR Close#49427
41e2a68e30 added a `type` property on all router events, and added all type of events to the `Event` union type, but forgot to remove `RouterEvent`.
This removes the benefit of the `type` field, because it is not possible to write:
```
this.router.events.pipe(filter((event: Event): event is NavigationEnd => event.type === EventType.NavigationEnd)).subscribe(/*...*/);
```
as `RouterEvent` does not have a `type` field (hence TS complains).
This commit fixes the issue by removing `RouterEvent` from the union type.
BREAKING CHANGE: The `RouterEvent` type is no longer present in the `Event` union type representing all router event types. If you have code using something like `filter((e: Event): e is RouterEvent => e instanceof RouterEvent)`, you'll need to update it to `filter((e: Event|RouterEvent): e is RouterEvent => e instanceof RouterEvent)`.
PR Close#46061
`withNavigationErrorHandler` is a close replacement for the
`RouterModule.forRoot.errorHandler` / `Router.errorHandler`.
It provides a quick, short way for users to define a function to handle
`NavigationError` events.
PR Close#48551
The router providers a configurable `onSameUrlNavigation` value that
allows developers to configure whether navigations to the same URL as
the current one should be processed or ignored. However, this only acts
as a default value and there isn't an API for easily overriding this for
a single navigation. Instead, developers are forced to update the value
of the property on the router instance and remember to reset it.
This feature fills a small gap in the Router APIs that enables
developers to accomplish the task of force reloading a bit easier.
Lengthy discussion about this here: #21115
PR Close#48050
This commit extracts the Router's navigation "transitions" to a separate
file. The goal here is twofold:
- Separate the Router's logic into more manageable chunks. Rather than
having to always edit a 1000+ line file, this separates different
pieces into smaller, more focused files
- More importantly, this sets the groundwork for separating the Router
state from the individual navigation transition. That is, it would be
good for the rxjs pipeline to _not_ modify the Router state but only
keep track of its own intermediate stages and emit outwards during
important events. The Router can subscribe to those events and make
updates to its state. This would theoretically allow us to swap those
implementations independently. That is, the Router's state management
can be changed without changing the transition handling and
vice-versa.
PR Close#48092
The Router currently silently ignores navigations for two reasons:
1. By default, same URL navigations are ignored. When this situation is
encountered, the navigation is ignored without any events
2. A `UrlHandlingStrategy` may ignore some URLs. For situations when the
strategy returns `false` for `shouldProcessUrl`, the Router silently
ignores the URL and updates its internal state without running
matching, guards, or resolver logic.
This commit adds new `NavigationSkipped` events for the above two situations.
PR Close#48024
If `history.pushState()` or `history.replaceState()` were called manually without including the `navigationId` field the state was being incorrectly discarded - that logic was for maintaining the original behavior of `NavigationStart.restoredState`.
Improves on #28176, fully fixes#28108, see also #28954
PR Close#48033
This commit adds the `provideLocationMocks()` function that returns mocks for the `Location` and `LocationStrategy` classes. This function can be used in tests to configure an environment where it's possible to fire simulated location events (helpful when testing Router configuration).
PR Close#47674
This commit updates the `RouterLink` to extend the selector to also include `<a>` and `<area>` elements, which were previously matched by the `RouterLinkWithHref` directive. The code of the directives was merged together (since there was a lot of duplication) and this commit finalizes the merge. The `RouterLinkWithHref` becomes an alias of the `RouterLink` directive.
Now there is no need to import and use the `RouterLinkWithHref` class, the `RouterLink` directive will match any element that has the `routerLink` attribute.
DEPRECATED:
The `RouterLinkWithHref` directive is deprecated, use the `RouterLink` directive instead. The `RouterLink` contains the code from the `RouterLinkWithHref` to handle elements with `href` attributes.
PR Close#47630
The `relativeLinkResolution` is no longer supported. The only behavior
now is the correct behavior.
BREAKING CHANGE: `relativeLinkResolution` is no longer configurable in
the Router. This option was used as a means to opt out of a bug fix.
PR Close#47623
This commit creates and exposes the APIs required to use the Angular Router without importing `RouterModule`.
The newly added APIs are tree-shakable and you can add features using special functions rather than using `ExtraOptions` to control the providers via an internal switch in Router code.
```
const appRoutes: Routes = [];
bootstrapApplication(AppComponent,
{
providers: [
provideRouter(appRoutes,
withDebugTracing(), // enables debug tracing feature
withInMemoryScrolling() // enables scrolling feature
]
}
);
```
This "features" pattern allows for router behavior to evolve in a backwards compatible and tree-shakable way in the future. This approach also makes features more discoverable.
The newly added APIs can be used in any application today (doesn't require an application to be bootstrapped using standalone-based APIs).
Note: APIs added in this commit are released in the "Developer Preview" mode, read more about this mode in Angular docs: https://angular.io/guide/releases#developer-preview
PR Close#47010
The current error message is absolute in that it thinks there is only
one possible way to provide Router twice. In fact, you can get a new
instance of the Router in several ways so the error message should
indicate the exact failure case with a _potential_ cause.
Based on findings in thread 0cbbd6aeec (commitcomment-80900192)
PR Close#47130
The current Router APIs require guards/resolvers to be present in the DI tree. This is because we want to treat all guards/resolvers equally and some may require dependencies. This requirement results in quite a lot of boilerplate for guards. Here are two examples:
```
const MY_GUARD = new InjectionToken<any>('my_guard');
…
providers: {provide: MY_GUARD, useValue: () => window.someGlobalState}
…
const route = {path: 'somePath', canActivate: [MY_GUARD]}
```
```
@Injectable({providedIn: 'root'})
export class MyGuardWithDependency {
constructor(private myDep: MyDependency) {}
canActivate() {
return myDep.canActivate();
}
}
…
const route = {path: 'somePath', canActivate: [MyGuardWithDependency]}
```
Notice that even when we want to write a simple guard that has no dependencies as in the first example, we still have to write either an InjectionToken or an Injectable class.
With this commit router guards and resolvers can be plain old functions.
For example:
```
const route = {path: 'somePath', component: EditCmp, canDeactivate: [(component: EditCmp) => !component.hasUnsavedChanges]}
```
Additionally, these functions can still use Angular DI with `inject` from `@angular/core`.
```
const route = {path: 'somePath', canActivate: [() => inject(MyDependency).canActivate()]}
```
PR Close#46684
The Router transition observable pipe keeps an outer reference to a `t`
variable for use in the `catchError` operator. However, this variable is
not updated with intermediate state. This commit fixes that so the
`catchError` can access properties that get updated in intermediate
states. Specifically, `RouterStateSnapshot` in the `NavigationError` for
now but could be more in the future.
PR Close#46842
Before this commit, the `NavigationCancellationCode` would always be set
to `Redirect` when encountering a "navigationcancelingError". However,
this error can also be thrown when `CanLoad` guars reject. This commit
ensures these cancellation errors have a code as well so this mistake
cannot be made again.
PR Close#46752
Currently it's not possible to return plain `UrlTree` from `CanMatchFn`,
only wrapped `UrlTree` into Observable or Promise is allowed.
These changes allow to return `UrlTree` from `CanMatchFn`.
PR Close#46455
The `Observable` chain is currenlty the most straightforward way to
handle navigation cancellations where we ensure that the cancelled
navigation does not continue to be processed. Until we design and
implement an alternative way to accomplish equivalent functionality,
we need to maintain the `Observable` chain wherever we might execute
user code. One reason for this isthat user code may contain redirects so we do not
want to execute those redirects if the navigation was already cancelled.
PR Close#46021
Currently we have two main types of guards:
`CanLoad`: decides if we can load a module (used with lazy loading)
`CanActivate` and friends. It decides if we can activate/deactivate a route.
So we always decide where we want to navigate first ("recognize") and create a new router state snapshot. And only then we run guards to check if the navigation should be allowed.
This doesn't handle one very important use case where we want to decide where to navigate based on some data (e.g., who the user is).
I suggest to add a new guard that allows us to do that.
```
[
{path: 'home', component: AdminHomePage, canUse: [IsAdmin]},
{path: 'home', component: SimpleHomePage}
]
```
Here, navigating to '/home' will render `AdminHomePage` if the user is an admin and will render 'SimpleHomePage' otherwise. Note that the url will remain '/home'.
With the introduction of standalone components and new features in the Router such as `loadComponent`,
there's a case for deprecating `CanLoad` and replacing it with the `CanMatch` guard. There are a few reasons for this:
* One of the intentions of having separate providers on a Route is that lazy
loading should not be an architectural feature of an application. It's an
optimization you do for code size. That is, there should not be an architectural
feature in the router to specifically control whether to lazy load something or
not based on conditions such as authentication. This is a slight nuanced
difference between the proposed canUse guard: this guard would control whether
you can use the route at all and as a side-effect, whether we download the code.
`CanLoad` only specified whether the code should be downloaded so canUse is more powerful and more appropriate.
* The naming of `CanLoad` will be potentially misunderstood for the `loadComponent` feature.
Because it applies to `loadChildren`, it feels reasonable to think that it will
also apply to `loadComponent`. This isn’t the case: since we don't need
to load the component until right before activation, we defer the
loading until all guards/resolvers have run.
When considering the removal of `CanLoad` and replacing it with `CanMatch`, this
does inform another decision that needed to be made: whether it makes sense for
`CanMatch` guards to return a UrlTree or if they should be restricted to just boolean.
The original thought was that no, these new guards should not allow returning UrlTree
because that significantly expands the intent of the feature from simply
“can I use the route” to “can I use this route, and if not, should I redirect?”
I now believe it should allowed to return `UrlTree` for several reasons:
* For feature parity with `CanLoad`
* Because whether we allow it as a return value or not, developers will still be
able to trigger a redirect from the guards using the `Router.navigate` function.
* Inevitably, there will be developers who disagree with the philosophical decision
to disallow `UrlTree` and we don’t necessarily have a compelling reason to refuse this as a feature.
Relates to #16211 - `CanMatch` instead of `CanActivate` would prevent
blank screen. Additional work is required to close this issue. This can
be accomplished by making the initial navigation result trackable (including
the redirects).
Resolves#14515
Replaces #16416Resolves#34231Resolves#17145Resolves#12088
PR Close#46021
* beforePreactivation hook is unused
* The only place that uses afterPreactivation does not use the arguments
Not to say we won't want to provide hooks similar to this in the future,
but the current state is over-engineered for what it's being used for.
PR Close#46321
This adds a test to illustrate the "canceling" behavior we get from the
`Observable` chain in the Router. A new navigation cancels ongoing ones,
and that means also preventing intermediate results in user guards from
executing. This test is important when thinking about future refactors
which might attempt to change the details of how the `applyRedirects`
operator executes.
PR Close#46231
Similarly to the symmetry being strengthened between children and loadChildren,
a new loadComponent property will be introduced as the asynchronous version of component.
This will allow for direct single-component lazy loading:
```
{path: 'lazy/a', loadComponent:
() => import('./lazy/a.component').then(m => m.ACmp)},
{path: 'lazy/b', loadComponent:
() => import('./lazy/b.component').then(m => m.BCmp)},
```
This option requires that the component being loaded is standalone and
is implemented as a runtime check.
Other notes:
* Components are not loaded until all guards and resolvers complete.
* Loading the component is included in the function passed to the router
preloading strategy
* `RouteConfigLoadStart` and `RouteConfigLoadEnd` events emit at the
start and end of the component loading
* `CanLoad` guards _do not_ apply to `loadComponent`. `canActivate`
should be used instead, just like you would do if it were simply
`component` instead.
PR Close#45705
This commit expands the `LoadChildrenCallback` to accept returning `Routes`
in addition to the existing `NgModule` type. In addition, it adds a
check to ensure these loaded routes all use standalone components.
The components must be standalone because if they were not,
we would not have the required `NgModule` which the component is declared in.
Existing API:
```
{path: 'lazy/route', loadChildren: import('./lazy').then(m => m.LazyModule)}
@NgModule({
imports: [
ExtraCmpModule,
RouterModule.forChild([
{path: 'extra/route', component: ExtraCmp},
]),
],
})
export class LazyModule {}
```
The new API for lazy loading route configs with standalone components
(no NgModule) is to expand `loadChildren` to allow returning simply a `Routes` array.
```
// parent.ts
{
path: 'parent',
loadChildren: () => import('./children').then(m => m.ROUTES),
}
// children.ts
export const ROUTES: Route[] = [
{path: 'child', component: ChildCmp},
];
```
Note that this includes minimal documentation updates. We need to
include a holistic update to the documentation for standalone components
in the future that includes this feature.
PR Close#45700
add the ariaCurrentWhenActive input to the RouterLinkActive directive so that
users can easily set the aria-current property to their active router
links
resolves#35051
PR Close#45167
In the standalone world, these concepts will no longer be one and the
same. You can load routes without them being inside an `NgModule` with
`RouterModule.forChild`. In addition, routes will be able to define
their own providers, which will be included in an injector that is not
necessarily lazy loaded.
PR Close#45593
Lazy loaded configs are not validated at runtime like the initial set of
routes are. This change also validates lazy loaded configs right after
they're loaded.
BREAKING CHANGE: Lazy loaded configs are now also validated once loaded like the
initial set of routes are. Lazy loaded modules which have invalid Route
configs will now error. Note that this is only done in dev mode so
there is no production impact of this change.
Fixes#25431
PR Close#45526
This change adds code to compute the corrected value for a link,
regardless of the `relativeLinkResolution` value. Then, if the
`relativeLinkResolution` is set to `legacy` and differs from the correct
value, a warning is printed to the console in dev mode.
This change is meant to assist in notifying developers that they have
code which relies on the deprecated, broken behavior so they can fix and
update the code before the `relativeLinkResolution` option is fully
removed.
PR Close#45523
This change adds code to compute the corrected value for a link,
regardless of the `relativeLinkResolution` value. Then, if the
`relativeLinkResolution` is set to `legacy` and differs from the correct
value, a warning is printed to the console in dev mode.
This change is meant to assist in notifying developers that they have
code which relies on the deprecated, broken behavior so they can fix and
update the code before the `relativeLinkResolution` option is fully
removed.
PR Close#45523
in layers
Right now route static data are collected from its parents based on the logic
described in `inheritedParamsDataResolve()`, merged into a single object
and then merged again with merged data from resolvers. This means that a
child's data can be overriden by a resolver in its parent (#34361).
However, what is the expected behavior is not described in the documentation.
This PR changes this behavior and merges static data and resolved data
in "layers" (route by route) so child's static data and resolved data
cannot be overriden by their parents.
Fixes#34361
PR Close#45276
This commit removes special (undocumented) logic in the Router code that is
meant to prevent duplicate navigations that result from location syncs in
AngularJS/Angular hybrid applications.
The duplicate navigations can occur when both the Router and the AngularJS sync
code detect a location change via a popstate/hashchange event. When this
happens, the Angular Router schedules a navigation to sync itself with
the browser, but the hybrid listener may also schedule an additional
navigation. There are a few reasons this logic should not be included in
the Router:
* This special logic is not tree shakeable so it introduces a bundle
size cost for all applications, most of which don't need it.
* There have been many updates to the routing pipeline to tolerate
duplicate navigations. That is, duplicate navigations can happen and
routing should still complete successfully.
* 0e8548f667
* 9e039ca68b
* The logic is really in the wrong place: The hybrid sync code should be
the location to handle this. If duplicate navigations are meant to be
avoided, the hybrid sync code should have handling to _not_ trigger
duplicate navs.
* This logic _also_ used to exist because the mock location
helper used for test incorrectly triggered popstate events during
router navigations. In order to avoid unexpected behavior in tests, this
logic needed to be added. This incorrect mocking may also have been
put in place because the upgrade module _would_ see a location change
event and trigger a duplicate navigation. The location mock has since been updated to
match real browser behavior so this is no longer necessary. The
upgrade module has also been updated to not trigger duplicate
navigations. The following commits are related to this:
* 202a1a5631
* c6a93001eb
Side note: The `setTimeout` in the location change listener is used to
ensure the ordering of duplicate navigations was consistent. You can see
that the logic being removed here expects the imperative navigation to precede the
popstate/hashchange. With the removal of this code, the `setTimeout` no
longer serves a purpose. However, it has been found that tests can rely
on this behavior (incorrectly) because they expect the navigation to be
complete but in reality, it hasn't even started because the test has not
flushed the timeout. Removing the timeout would be a breaking change as
a result.
PR Close#45240