MOBILE-4470 angular: Fix snapshot types
Route snapshots are typed as non-optional, but we found a situation where it was undefined. Looking at the Angular source code, it seems like indeed snapshots can be undefined but they have been declared with a definite assignment assertion: https://github.com/angular/angular/blob/17.3.0/packages/router/src/router_state.ts#L231main
parent
daa16a32a4
commit
77d3ac9d43
|
@ -0,0 +1,13 @@
|
|||
diff --git a/node_modules/@angular/router/index.d.ts b/node_modules/@angular/router/index.d.ts
|
||||
index b8d7cc8..6511edf 100755
|
||||
--- a/node_modules/@angular/router/index.d.ts
|
||||
+++ b/node_modules/@angular/router/index.d.ts
|
||||
@@ -58,7 +58,7 @@ export declare class ActivatedRoute {
|
||||
/** The component of the route, a constant. */
|
||||
component: Type<any> | null;
|
||||
/** The current snapshot of this route */
|
||||
- snapshot: ActivatedRouteSnapshot;
|
||||
+ snapshot?: ActivatedRouteSnapshot;
|
||||
/** An Observable of the resolved route title */
|
||||
readonly title: Observable<string | undefined>;
|
||||
/** An observable of the URL segments matched by this route. */
|
|
@ -675,9 +675,7 @@ class AddonCalendarEventsSwipeItemsManager extends CoreSwipeNavigationItemsManag
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.id;
|
||||
return CoreNavigator.getRouteParams(route).id;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -351,9 +351,7 @@ class AddonCompetencyCompetenciesSwipeManager
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.competencyId;
|
||||
return CoreNavigator.getRouteParams(route).competencyId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -246,9 +246,7 @@ class AddonModAssignSubmissionSwipeItemsManager extends CoreSwipeNavigationItems
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.submitId;
|
||||
return CoreNavigator.getRouteParams(route).submitId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -188,9 +188,7 @@ class AddonModFeedbackAttemptsSwipeManager extends CoreSwipeNavigationItemsManag
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.attemptId;
|
||||
return CoreNavigator.getRouteParams(route).attemptId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ export class AddonModForumDiscussionPage implements OnInit, AfterViewInit, OnDes
|
|||
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
const routeData = this.route.snapshot.data;
|
||||
const routeData = CoreNavigator.getRouteData(this.route);
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId');
|
||||
this.cmId = CoreNavigator.getRouteNumberParam('cmId');
|
||||
this.forumId = CoreNavigator.getRouteNumberParam('forumId');
|
||||
|
@ -893,9 +893,9 @@ class AddonModForumDiscussionDiscussionsSwipeManager extends AddonModForumDiscus
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
const params = CoreNavigator.getRouteParams(route);
|
||||
|
||||
return this.getSource().DISCUSSIONS_PATH_PREFIX + snapshot.params.discussionId;
|
||||
return this.getSource().DISCUSSIONS_PATH_PREFIX + params.discussionId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
|
|||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
try {
|
||||
const routeData = this.route.snapshot.data;
|
||||
const routeData = CoreNavigator.getRouteData(this.route);
|
||||
this.courseId = CoreNavigator.getRequiredRouteNumberParam('courseId');
|
||||
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||
this.forumId = CoreNavigator.getRequiredRouteNumberParam('forumId');
|
||||
|
@ -700,9 +700,9 @@ class AddonModForumNewDiscussionDiscussionsSwipeManager extends AddonModForumDis
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
const params = CoreNavigator.getRouteParams(route);
|
||||
|
||||
return `${this.getSource().DISCUSSIONS_PATH_PREFIX}new/${snapshot.params.timeCreated}`;
|
||||
return `${this.getSource().DISCUSSIONS_PATH_PREFIX}new/${params.timeCreated}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ export class AddonModGlossaryEntryPage implements OnInit, OnDestroy {
|
|||
this.cmId = CoreNavigator.getRequiredRouteNumberParam('cmId');
|
||||
|
||||
const entrySlug = CoreNavigator.getRequiredRouteParam<string>('entrySlug');
|
||||
const routeData = this.route.snapshot.data;
|
||||
const routeData = CoreNavigator.getRouteData(this.route);
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
AddonModGlossaryEntriesSource,
|
||||
[this.courseId, this.cmId, routeData.glossaryPathPrefix ?? ''],
|
||||
|
@ -368,9 +368,9 @@ class AddonModGlossaryEntryEntriesSwipeManager
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
const params = CoreNavigator.getRouteParams(route);
|
||||
|
||||
return `${this.getSource().GLOSSARY_PATH_PREFIX}entry/${snapshot.params.entrySlug}`;
|
||||
return `${this.getSource().GLOSSARY_PATH_PREFIX}entry/${params.entrySlug}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -49,8 +49,8 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle
|
|||
return false;
|
||||
}
|
||||
|
||||
const params = route.snapshot.params;
|
||||
const queryParams = route.snapshot.queryParams;
|
||||
const params = CoreNavigator.getRouteParams(route);
|
||||
const queryParams = CoreNavigator.getRouteQueryParams(route);
|
||||
|
||||
if (queryParams.subwikiId == subwikiId) {
|
||||
// Same subwiki, so it's same wiki.
|
||||
|
@ -116,7 +116,9 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle
|
|||
|
||||
if (isSameWiki) {
|
||||
// User is seeing the wiki, we can get the module from the wiki params.
|
||||
path = path + `/${route.snapshot.params.courseId}/${route.snapshot.params.cmId}/edit`;
|
||||
const routeParams = CoreNavigator.getRouteParams(route);
|
||||
|
||||
path = path + `/${routeParams.courseId}/${routeParams.cmId}/edit`;
|
||||
} else if (wikiId) {
|
||||
// The URL specifies which wiki it belongs to. Get the module.
|
||||
const module = await CoreCourse.getModuleBasicInfoByInstance(
|
||||
|
|
|
@ -212,9 +212,7 @@ class AddonNotificationSwipeItemsManager extends CoreSwipeNavigationItemsManager
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.id;
|
||||
return CoreNavigator.getRouteParams(route).id;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -245,9 +245,7 @@ export class CoreListItemsManager<
|
|||
while (route.firstChild) {
|
||||
route = route.firstChild;
|
||||
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
segments.push(...snapshot.url);
|
||||
segments.push(...CoreNavigator.getRouteUrl(route));
|
||||
}
|
||||
|
||||
return segments.map(segment => segment.path).join('/').replace(/\/+/, '/').trim() || null;
|
||||
|
@ -276,7 +274,7 @@ export class CoreListItemsManager<
|
|||
*/
|
||||
private buildRouteMatcher(): (route: ActivatedRouteSnapshot) => boolean {
|
||||
if (this.pageRouteLocator instanceof ActivatedRoute) {
|
||||
const pageRoutePath = CoreNavigator.getRouteFullPath(this.pageRouteLocator.snapshot);
|
||||
const pageRoutePath = CoreNavigator.getRouteFullPath(this.pageRouteLocator);
|
||||
|
||||
return route => CoreNavigator.getRouteFullPath(route) === pageRoutePath;
|
||||
}
|
||||
|
|
|
@ -85,9 +85,7 @@ export class CoreSwipeNavigationItemsManager<
|
|||
const segments: UrlSegment[] = [];
|
||||
|
||||
while (route) {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
segments.push(...snapshot.url);
|
||||
segments.push(...CoreNavigator.getRouteUrl(route));
|
||||
|
||||
if (!route.firstChild) {
|
||||
break;
|
||||
|
|
|
@ -13,10 +13,11 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { mock, mockSingleton } from '@/testing/utils';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, UrlSegment } from '@angular/router';
|
||||
import { ActivatedRoute, UrlSegment } from '@angular/router';
|
||||
import { CoreRoutedItemsManagerSource } from '@classes/items-management/routed-items-manager-source';
|
||||
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
interface Item {
|
||||
path: string;
|
||||
|
@ -61,9 +62,7 @@ describe('CoreSwipeNavigationItemsManager', () => {
|
|||
mockSingleton(CoreNavigator, {
|
||||
navigate: jest.fn(),
|
||||
getCurrentRoute: () => mock<ActivatedRoute>({
|
||||
snapshot: mock<ActivatedRouteSnapshot>({
|
||||
url: [mock<UrlSegment>({ path: currentPath })],
|
||||
}),
|
||||
url: new BehaviorSubject([mock<UrlSegment>({ path: currentPath })]),
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
|
@ -95,7 +95,7 @@ export class CoreSplitViewComponent implements AfterViewInit, OnDestroy {
|
|||
|
||||
this.updateClasses();
|
||||
|
||||
this.outletRouteSubject.next(outletRoute);
|
||||
this.outletRouteSubject.next(outletRoute ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -111,7 +111,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
|||
*/
|
||||
async ngOnInit(): Promise<void> {
|
||||
// Increase route depth.
|
||||
const path = CoreNavigator.getRouteFullPath(this.route.snapshot);
|
||||
const path = CoreNavigator.getRouteFullPath(this.route);
|
||||
|
||||
CoreNavigator.increaseRouteDepth(path.replace(/(\/deep)+/, ''));
|
||||
|
||||
|
@ -247,7 +247,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
|||
* @inheritdoc
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
const path = CoreNavigator.getRouteFullPath(this.route.snapshot);
|
||||
const path = CoreNavigator.getRouteFullPath(this.route);
|
||||
|
||||
CoreNavigator.decreaseRouteDepth(path.replace(/(\/deep)+/, ''));
|
||||
this.selectTabObserver?.off();
|
||||
|
|
|
@ -307,7 +307,7 @@ export class CoreCourseProvider {
|
|||
return false;
|
||||
}
|
||||
|
||||
return Number(route.snapshot.params.courseId) == courseId;
|
||||
return Number(CoreNavigator.getRouteParams(route).courseId) == courseId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,7 +79,7 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
this.collapseLabel = Translate.instant('core.collapse');
|
||||
this.useLegacyLayout = !CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('4.1');
|
||||
|
||||
switch (route.snapshot.data.swipeManagerSource ?? route.snapshot.parent?.data.swipeManagerSource) {
|
||||
switch (route.snapshot?.data.swipeManagerSource ?? route.snapshot?.parent?.data.swipeManagerSource) {
|
||||
case 'courses':
|
||||
this.swipeManager = new CoreGradesCourseCoursesSwipeManager(
|
||||
CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(CoreGradesCoursesSource, []),
|
||||
|
@ -331,9 +331,7 @@ class CoreGradesCourseParticipantsSwipeManager extends CoreSwipeNavigationItemsM
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.userId;
|
||||
return CoreNavigator.getRouteParams(route).userId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -117,7 +117,7 @@ export class CoreUserProfilePage implements OnInit, OnDestroy {
|
|||
this.courseId = undefined;
|
||||
}
|
||||
|
||||
if (this.courseId && this.route.snapshot.data.swipeManagerSource === 'participants') {
|
||||
if (this.courseId && CoreNavigator.getRouteData(this.route).swipeManagerSource === 'participants') {
|
||||
const search = CoreNavigator.getRouteParam('search');
|
||||
const source = CoreRoutedItemsManagerSourcesTracker.getOrCreateSource(
|
||||
CoreUserParticipantsSource,
|
||||
|
@ -252,9 +252,7 @@ class CoreUserSwipeItemsManager extends CoreSwipeNavigationItemsManager {
|
|||
* @inheritdoc
|
||||
*/
|
||||
protected getSelectedItemPathFromRoute(route: ActivatedRouteSnapshot | ActivatedRoute): string | null {
|
||||
const snapshot = route instanceof ActivatedRouteSnapshot ? route : route.snapshot;
|
||||
|
||||
return snapshot.params.userId;
|
||||
return CoreNavigator.getRouteParams(route).userId;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, NavigationEnd, Params } from '@angular/router';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, Data, NavigationEnd, Params, UrlSegment } from '@angular/router';
|
||||
|
||||
import { NavigationOptions } from '@ionic/angular/common/providers/nav-controller';
|
||||
|
||||
|
@ -32,6 +32,7 @@ import { CoreMainMenuDelegate } from '@features/mainmenu/services/mainmenu-deleg
|
|||
import { CorePlatform } from '@services/platform';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { CorePromisedValue } from '@classes/promised-value';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Redirect payload.
|
||||
|
@ -459,7 +460,7 @@ export class CoreNavigatorService {
|
|||
return route;
|
||||
}
|
||||
|
||||
if (routeData && CoreUtils.basicLeftCompare(routeData, route.snapshot.data, 3)) {
|
||||
if (routeData && CoreUtils.basicLeftCompare(routeData, this.getRouteData(route), 3)) {
|
||||
return route;
|
||||
}
|
||||
|
||||
|
@ -477,11 +478,11 @@ export class CoreNavigatorService {
|
|||
* @returns Whether the route is active or not.
|
||||
*/
|
||||
isRouteActive(route: ActivatedRoute): boolean {
|
||||
const routePath = this.getRouteFullPath(route.snapshot);
|
||||
const routePath = this.getRouteFullPath(route);
|
||||
let activeRoute: ActivatedRoute | null = Router.routerState.root;
|
||||
|
||||
while (activeRoute) {
|
||||
if (this.getRouteFullPath(activeRoute.snapshot) === routePath) {
|
||||
if (this.getRouteFullPath(activeRoute) === routePath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -650,13 +651,13 @@ export class CoreNavigatorService {
|
|||
* @param route Route snapshot.
|
||||
* @returns Path.
|
||||
*/
|
||||
getRouteFullPath(route: ActivatedRouteSnapshot | null): string {
|
||||
getRouteFullPath(route: ActivatedRouteSnapshot | ActivatedRoute | null): string {
|
||||
if (!route) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const parentPath = this.getRouteFullPath(route.parent);
|
||||
const routePath = route.url.join('/');
|
||||
const parentPath = this.getRouteFullPath(this.getRouteParent(route));
|
||||
const routePath = this.getRouteUrl(route).join('/');
|
||||
|
||||
if (!parentPath && !routePath) {
|
||||
return '';
|
||||
|
@ -669,13 +670,63 @@ export class CoreNavigatorService {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a route, get url segments.
|
||||
*
|
||||
* @param route Route.
|
||||
* @returns Url segments.
|
||||
*/
|
||||
getRouteUrl(route: ActivatedRouteSnapshot | ActivatedRoute): UrlSegment[] {
|
||||
return this.getRouteProperty(route, 'url', []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a route, get its parent.
|
||||
*
|
||||
* @param route Route.
|
||||
* @returns Parent.
|
||||
*/
|
||||
getRouteParent(route: ActivatedRouteSnapshot | ActivatedRoute): ActivatedRouteSnapshot | ActivatedRoute | null {
|
||||
return this.getRouteProperty(route, 'parent', null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a route, get its data.
|
||||
*
|
||||
* @param route Route.
|
||||
* @returns Data.
|
||||
*/
|
||||
getRouteData(route: ActivatedRouteSnapshot | ActivatedRoute): Data {
|
||||
return this.getRouteProperty(route, 'data', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a route, get its params.
|
||||
*
|
||||
* @param route Route.
|
||||
* @returns Params.
|
||||
*/
|
||||
getRouteParams(route: ActivatedRouteSnapshot | ActivatedRoute): Params {
|
||||
return this.getRouteProperty(route, 'params', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a route, get its query params.
|
||||
*
|
||||
* @param route Route.
|
||||
* @returns Query params.
|
||||
*/
|
||||
getRouteQueryParams(route: ActivatedRouteSnapshot | ActivatedRoute): Params {
|
||||
return this.getRouteProperty(route, 'queryParams', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current route page can block leaving the route.
|
||||
*
|
||||
* @returns Whether the current route page can block leaving the route.
|
||||
*/
|
||||
currentRouteCanBlockLeave(): boolean {
|
||||
return !!this.getCurrentRoute().snapshot.routeConfig?.canDeactivate?.length;
|
||||
return !!this.getCurrentRoute().snapshot?.routeConfig?.canDeactivate?.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -725,6 +776,36 @@ export class CoreNavigatorService {
|
|||
return '../'.repeat(depth);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a route, get one of its properties.
|
||||
*
|
||||
* @param route Route.
|
||||
* @param property Route property.
|
||||
* @param defaultValue Fallback value if the property is not set.
|
||||
* @returns Property value.
|
||||
*/
|
||||
private getRouteProperty<T extends keyof ActivatedRouteSnapshot>(
|
||||
route: ActivatedRouteSnapshot | ActivatedRoute,
|
||||
property: T,
|
||||
defaultValue: ActivatedRouteSnapshot[T],
|
||||
): ActivatedRouteSnapshot[T] {
|
||||
if (route instanceof ActivatedRouteSnapshot) {
|
||||
return route[property];
|
||||
}
|
||||
|
||||
if (route.snapshot instanceof ActivatedRouteSnapshot) {
|
||||
return route.snapshot[property];
|
||||
}
|
||||
|
||||
const propertyObservable = route[property];
|
||||
|
||||
if (propertyObservable instanceof BehaviorSubject) {
|
||||
return propertyObservable.value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export const CoreNavigator = makeSingleton(CoreNavigatorService);
|
||||
|
|
Loading…
Reference in New Issue