Skip to content

Fixed path finding for the routes with empty paths and for the children with loadComponents #162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ yarn-error.log
*.launch
.settings/
*.sublime-workspace
.tool-versions

# Visual Studio Code
.vscode/*
Expand Down
59 changes: 9 additions & 50 deletions projects/ngx-quicklink/src/lib/quicklink-strategy.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { Injectable } from '@angular/core';
import { PreloadingStrategy, Router, Route, PRIMARY_OUTLET } from '@angular/router';
import { PreloadingStrategy, Router, Route } from '@angular/router';
import { PrefetchRegistry } from './prefetch-registry.service';
import { EMPTY } from 'rxjs';
import { findPath } from './utils/find-path';

@Injectable({ providedIn: 'root' })
export class QuicklinkStrategy implements PreloadingStrategy {
loading = new Set<Route>();

constructor(
private registry: PrefetchRegistry,
private router: Router,
) {}
constructor(private registry: PrefetchRegistry, private router: Router) {}

preload(route: Route, load: Function) {
if (this.loading.has(route)) {
// Don't preload the same route twice
return EMPTY;
}
const conn = typeof navigator !== 'undefined' ? (navigator as any).connection : undefined;
const conn =
typeof navigator !== 'undefined'
? (navigator as any).connection
: undefined;
if (conn) {
// Don't preload if the user is on 2G. or if Save-Data is enabled..
if ((conn.effectiveType || '').includes('2g') || conn.saveData) return EMPTY;
if ((conn.effectiveType || '').includes('2g') || conn.saveData)
return EMPTY;
}
// Prevent from preloading
if (route.data && route.data['preload'] === false) {
Expand All @@ -35,46 +37,3 @@ export class QuicklinkStrategy implements PreloadingStrategy {
return EMPTY;
}
}

const findPath = (config: Route[], route: Route): string => {
config = config.slice();
const parent = new Map<Route, Route>();
const visited = new Set<Route>();
while (config.length) {
const el = config.shift();
if (!el) {
continue;
}
visited.add(el);
if (el === route) break;
let children = el.children || [];
const current = (el as any)._loadedRoutes || [];
for (const route of current) {
if (route && route.children) {
children = children.concat(route.children);
}
children.forEach((r: Route) => {
if (visited.has(r)) return;
parent.set(r, el);
config.push(r);
});
}
}
let path = '';
let current: Route | undefined = route;

while (current) {
if (isPrimaryRoute(current)) {
path = `/${current.path}${path}`;
} else {
path = `/(${current.outlet}:${current.path}${path})`;
}
current = parent.get(current);
}

return path.replace(/\/\//, '/');
};

function isPrimaryRoute(route: Route) {
return route.outlet === PRIMARY_OUTLET || !route.outlet;
}
75 changes: 75 additions & 0 deletions projects/ngx-quicklink/src/lib/utils/find-path.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Route } from '@angular/router';
import { findPath } from './find-path';

const BASE_ROUTE: Route = {
path: 'home',
};
const NAMED_OUTLET_ROUTE: Route = {
path: 'aside',
outlet: 'side',
};

describe('findPath', () => {
it('should correctly build base route', () => {
expect(findPath([BASE_ROUTE], BASE_ROUTE)).toBe('/home');
});

it('should correctly build route with named outlet', () => {
expect(findPath([NAMED_OUTLET_ROUTE], NAMED_OUTLET_ROUTE)).toBe(
'/(side:aside)'
);
});

it('should correctly build route with one empty parent path', () => {
expect(
findPath(
[
{
path: '',
children: [BASE_ROUTE],
},
],
BASE_ROUTE
)
).toBe('/home');
});

it('should correctly build route with more than one empty parent path', () => {
expect(
findPath(
[
{
path: '',
children: [
{
path: '',
children: [BASE_ROUTE],
},
],
},
],
BASE_ROUTE
)
).toBe('/home');
});

it('should correctly build route with more than one empty parent path and nested outlet', () => {
expect(
findPath(
[
{
path: '',
children: [
{
path: '',
children: [BASE_ROUTE],
},
NAMED_OUTLET_ROUTE,
],
},
],
NAMED_OUTLET_ROUTE
)
).toBe('/(side:aside)');
});
});
45 changes: 45 additions & 0 deletions projects/ngx-quicklink/src/lib/utils/find-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { PRIMARY_OUTLET, Route } from '@angular/router';

export const findPath = (config: Route[], route: Route): string => {
config = config.slice();
const parent = new Map<Route, Route>();
const visited = new Set<Route>();
while (config.length) {
const el = config.shift();
if (!el) {
continue;
}
visited.add(el);
if (el === route) break;
let children = el.children || [];
const current = (el as any)._loadedRoutes || [];
for (const route of current) {
if (route && route.children) {
children = children.concat(route.children);
}
}
children.forEach((r: Route) => {
if (visited.has(r)) return;
parent.set(r, el);
config.push(r);
});
}
let path = '';
let current: Route | undefined = route;

while (current) {
if (isPrimaryRoute(current)) {
path = `/${current.path}${path}`;
} else {
path = `/(${current.outlet}:${current.path}${path})`;
}
current = parent.get(current);
}

// For routes with empty paths (the resulted string will look like `///section/sub-section`)
return path.replace(/[\/]+/, '/');
};

function isPrimaryRoute(route: Route) {
return route.outlet === PRIMARY_OUTLET || !route.outlet;
}
52 changes: 46 additions & 6 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,60 @@ import { QuicklinkStrategy } from 'ngx-quicklink';
const routes: Routes = [
{
path: 'home',
loadChildren: () => import('./home/home.module').then(m => m.HomeModule)
loadChildren: () => import('./home/home.module').then((m) => m.HomeModule),
},
{
path: 'social',
loadChildren: () =>
import('./social/social.module').then(m => m.SocialModule),
outlet: 'side'
}
import('./social/social.module').then((m) => m.SocialModule),
outlet: 'side',
},
{
path: 'other-section',
loadComponent: () =>
import('./other-section/other-section.component').then(
(c) => c.OtherSectionComponent
),
children: [
{
path: 'common-info',
loadComponent: () =>
import('./other-section/common-info/common-info.component').then(
(c) => c.CommonInfoComponent
),
},
{
path: ':subSectionSlug',
loadComponent: () =>
import('./other-section/sub-section/sub-section.component').then(
(c) => c.SubSectionComponent
),
children: [
{
path: 'side',
loadComponent: () =>
import('./other-section/sub-section/side/side.component').then(
(c) => c.SubSectionSideComponent
),
outlet: 'sub-section-side',
},
{
path: ':pageSlug',
loadComponent: () =>
import('./other-section/sub-section/page/page.component').then(
(c) => c.SectionPageComponent
),
},
],
},
],
},
];

@NgModule({
imports: [
RouterModule.forRoot(routes, { preloadingStrategy: QuicklinkStrategy })
RouterModule.forRoot(routes, { preloadingStrategy: QuicklinkStrategy }),
],
exports: [RouterModule]
exports: [RouterModule],
})
export class AppRoutingModule {}
6 changes: 5 additions & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<div>
<a routerLink="">Root</a><a *ngIf="visible" [routerLink]="primaryRoutePath">Home</a>
<a routerLink="">Root</a>
<br>
<a *ngIf="visible" [routerLink]="primaryRoutePath">Home</a>
<br>
<a routerLink="other-section">Other section</a>
</div>
<router-outlet></router-outlet>

Expand Down
12 changes: 12 additions & 0 deletions src/app/other-section/common-info/common-info.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'common-info',
standalone: true,
template: `
<section>
<h2>Common info page</h2>
</section>
`,
})
export class CommonInfoComponent {}
11 changes: 11 additions & 0 deletions src/app/other-section/other-section.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.other-section {
padding: 32px;
border: 1px solid #eee;
}

.expander {
display: grid;
height: 100vh;
background: #eee;
place-items: center;
}
29 changes: 29 additions & 0 deletions src/app/other-section/other-section.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { QuicklinkModule } from 'ngx-quicklink';

@Component({
selector: 'other-section',
standalone: true,
imports: [RouterModule, QuicklinkModule],
styleUrls: ['./other-section.component.css'],
template: `
<section class="other-section">
<h1>Container</h1>
<ul>
<li><a routerLink="/other-section/common-info">Common info</a></li>
<li>
<div class="expander"><span>Expander</span></div>
</li>
<li><a routerLink="/other-section/1/1">Section page</a></li>
<li>
<a [routerLink]="['1', { outlets: { 'sub-section-side': ['side'] } }]"
>Section aside</a
>
</li>
</ul>
<router-outlet></router-outlet>
</section>
`,
})
export class OtherSectionComponent {}
12 changes: 12 additions & 0 deletions src/app/other-section/sub-section/page/page.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'section-page',
standalone: true,
template: `
<section>
<h3>Section page</h3>
</section>
`,
})
export class SectionPageComponent {}
12 changes: 12 additions & 0 deletions src/app/other-section/sub-section/side/side.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Component } from '@angular/core';

@Component({
selector: 'sub-section-side',
standalone: true,
template: `
<aside>
<h3>Subsection aside</h3>
</aside>
`,
})
export class SubSectionSideComponent {}
11 changes: 11 additions & 0 deletions src/app/other-section/sub-section/sub-section.component.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.sub-section {
position: relative;
}

.sub-section-side {
position: fixed;
left: 0;
top: calc(50% - 100px);
height: 200px;
border: 1px solid red;
}
Loading