0

I'm having issue with path param routing,

  • where navigation works fine for any one of the category traversed first
  • but routing doesn't work from one category to the other, even though the url in the browser updates.
  • refreshing the browser loads the respective category.
  • category to other pages works fine.

navigation.component.html

<nav >
<ul>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/home']" routerLinkActive="active">home_link</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/first']" routerLinkActive="active">first_link</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/category/', '1']" routerLinkActive="active">link_1</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/category/', '2']"
            routerLinkActive="active">link_2</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/category/', '3']"
            routerLinkActive="active">link_3</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/category', '4']"
            routerLinkActive="active">link_4</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/category', '5']"
            routerLinkActive="active">link_5</a>
    </li>
    <li class="nav__item">
        <a class="nav__link" [routerLink]="['/category', '6']"
            routerLinkActive="active">link_6</a>
    </li>
</ul>
</nav>

package.json

{
  "name": "myApp",
  "version": "0.1.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "postinstall": "ngcc",
    "dev:ssr": "ng run myApp:serve-ssr",
    "serve:ssr": "node dist/myApp/server/main.js",
    "build:ssr": "ng build && ng run myApp:server",
    "build-prod:ssr": "ng build --configuration production && ng run myApp:server",
    "prerender": "ng run myApp:prerender"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^13.3.11",
    "@angular/cdk": "^13.3.9",
    "@angular/common": "^13.3.11",
    "@angular/compiler": "^13.3.11",
    "@angular/core": "^13.3.11",
    "@angular/forms": "^13.3.11",
    "@angular/platform-browser": "^13.3.11",
    "@angular/platform-browser-dynamic": "^13.3.11",
    "@angular/platform-server": "^13.3.11",
    "@angular/router": "^13.3.11",
    "@babel/core": "^7.19.0",
    "@fortawesome/angular-fontawesome": "^0.10.0",
    "@fortawesome/fontawesome-svg-core": "^1.2.27",
    "@fortawesome/free-brands-svg-icons": "^5.15.4",
    "@fortawesome/free-solid-svg-icons": "^5.15.4",
    "@nguniversal/common": "^13.1.1",
    "@nguniversal/express-engine": "^13.1.1",
    "@nguniversal/module-map-ngfactory-loader": "^8.2.6",
    "balanced-match": "^2.0.0",
    "express": "^4.15.2",
    "memory-cache": "^0.2.0",
    "ngx-sharebuttons": "^10.0.0",
    "ngx-spinner": "^13.1.1",
    "rxjs": "^7.5.6",
    "tslib": "^2.4.0",
    "zone.js": "^0.11.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^13.3.9",
    "@angular/cli": "^13.3.9",
    "@angular/compiler-cli": "^13.3.11",
    "@ngtools/webpack": "^13.3.9",
    "@nguniversal/builders": "^13.1.1",
    "@types/express": "^4.17.13",
    "@types/jasmine": "^3.6.0",
    "@types/node": "^12.11.1",
    "ajv": "^8.11.0",
    "codelyzer": "^6.0.2",
    "jasmine-core": "^3.6.0",
    "jasmine-spec-reporter": "^5.0.0",
    "karma": "^6.4.0",
    "karma-chrome-launcher": "^3.1.0",
    "karma-coverage": "^2.0.3",
    "karma-jasmine": "^4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "protractor": "^7.0.0",
    "ts-loader": "^9.3.1",
    "ts-node": "^8.3.0",
    "tslint": "^6.1.0",
    "typescript": "~4.6.4",
    "webpack": "^5.74.0",
    "webpack-cli": "^4.10.0"
  }
}

app-routing.module.ts

const routes: Routes = [
  {
    path: '', pathMatch: "full", redirectTo: '/home'
  },
  {
    path: 'home', pathMatch: "full", component: HomeComponent
  },
  {
    path: 'first', pathMatch: "full", component: FirstComponent
  },
  { path: 'category/:paramOne', component: SecondComponent },
  { path: '**', component: NotFoundComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload', initialNavigation: 'enabled' })],
  exports: [RouterModule]
})
export class AppRoutingModule { }

server.ts

import 'zone.js/dist/zone-node';

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';

import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';

// The Express app is exported so that it can be used by serverless Functions.
export function app(): express.Express {
  const server = express();
  const distFolder = join(process.cwd(), 'dist/myApp/browser');
  const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';

  // Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
  server.engine('html', ngExpressEngine({
    bootstrap: AppServerModule,
  }));

  server.set('view engine', 'html');
  server.set('views', distFolder);

  // Example Express Rest API endpoints
  // server.get('/api/**', (req, res) => {
  //   res.status(404).send('data requests are not supported');
  // });
  // Serve static files from /browser
  server.get('*.*', express.static(distFolder, {
    maxAge: '1y'
  }));

  // All regular routes use the Universal engine
  server.get('*', (req, res) => {
    console.log("server.ts=> * =>req.url: ", req.url);
    res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
  });

  return server;
}

function run(): void {
  const port = process.env['PORT'] || 4000;

  // Start up the Node server
  const server = app();
  server.listen(port, () => {
    console.log(`Node Express server listening on http://localhost:${port}`);
  });
}

// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
  run();
}

export * from './src/main.server';

app.component.ts

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  title = 'myApp';

  constructor(private router: Router) {
    let currentRoute = "";
    this.router.events.subscribe((event: Event) => {
      if (event instanceof NavigationStart) {
        console.log('Route change detected');
      }

      if (event instanceof NavigationEnd) {
        currentRoute = event.url;
        console.log(event);
      }

      if (event instanceof NavigationError) {
        console.log(event.error);
      }
    });
  }

}

logs

Node Express server listening on http://localhost:4000
NavigationEnd {
  id: 1,
  url: '/first',
  urlAfterRedirects: '/first'
}
NavigationEnd {
  id: 1,
  url: '/img/menu_249e9f3331e6f8b23256.svg',
  urlAfterRedirects: '/img/menu_249e9f3331e6f8b23256.svg'
}
NavigationEnd {
  id: 1,
  url: '/fonts/Apercu-Regular.woff',
  urlAfterRedirects: '/fonts/Apercu-Regular.woff'
}
NavigationEnd {
  id: 1,
  url: '/fonts/Apercu-Light.woff',
  urlAfterRedirects: '/fonts/Apercu-Light.woff'
}
NavigationEnd {
  id: 1,
  url: '/fonts/Apercu-Bold.woff',
  urlAfterRedirects: '/fonts/Apercu-Bold.woff'
}

No navigation logs happens after this.

Note: There's also serverStateInterceptor and browserStateInterceptor services for caching requests. However I am sure that they're not the cause and so have avoided adding them here for readability. caching transfer state implementation used from here.

Parisana Ng
  • 487
  • 4
  • 14

1 Answers1

1

This has nothing to do with SSR. You simply have to handle the route-change events yourself. The page isn't reconstructed when the same angular route is activated again. So you need to do the following:

@Component({ ... })
export class SongShowComponent implements OnInit, OnDestroy {
  constructor(
    private songService: SongService,
    private router: AdvancedRouter,
    private route: ActivatedRoute,
  ) {
    var id = parseInt(this.route.snapshot.paramMap.get('id'));
    this.loadSong(id);
  }

  private destroyed$ = new Subject();

  ngOnInit() {
    this.route.params
      .pipe(takeUntil(this.destroyed$))
      .subscribe((routeParams) => {
        this.loadSong(routeParams.id);
      });
  }

  ngOnDestroy() {
    this.destroyed$.next(true);
  }


  private loadSong(id: number) {
    this.songService.getSong(id, true).then((song) => {
      this.setSong(song);
    }).catch((error) => {
      console.error('Could not fetch song', error);
    });
  }
}
Pieterjan
  • 2,738
  • 4
  • 28
  • 55
  • 1
    Turns out there was a flaw in my CategoryComponent, the display-array needed to be reassigned instead of adding to it, on route change. The routing was happening fine. Thanks so much @Peiiterjan for taking the time to look into it. – Parisana Ng Sep 18 '22 at 05:20