3
votes

I've embedded an Angular 6 app in an existing .NET MVC 5 application. I added a fallback route pointed to Home/Index in the MVC app (RouteConfig.cs), so "unknown" routes would pass to the Angular app's router module (app.routes.ts). The Angular app's routes do not appear to be firing, the component associated with the path doesn't load.

The IDE is Visual Studio 2017 Enterprise 15.6.7, the MVC app is standard .NET Web application (not .NET Core), MVC 5.2.3.0; Node.js 8.11.3, npm 6.3.0, Typescript 2.9.2, Angular CLI 6.0.8, Angular 6.0.3; bootstrap 3.3.7, jquery 3.3.1; OS: Win 10 x64.

The MVC app's RouteConfig.cs is below; the catch-all route is at the bottom.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;

namespace Ang2Mvc5_0
{
    public class RouteConfig
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                name: "mvc",
                url: "{controller}/{action}/{id}",
                defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            // This is a catch-all for when no other routes match the request; let the Angular 2 router take care of it...
            routes.MapRoute(
                name: "default",
                url: "{*url}",
                defaults: new { controller = "Home", action = "Index" } // The view that bootstraps Angular 2 app
            );
        }
    }
}

When I created the Angular app (using CLI), the package.json entry for Typescript defaults to 2.7.2; package.json is below, the ngf-bootstrap entry is specific for a PluralSight tutorial.

Am I missing something?

{
    "name": "temp-app",
    "version": "0.0.0",
    "scripts": {
        "ng": "ng",
        "start": "ng serve",
        "build": "ng build",
        "test": "ng test",
        "lint": "ng lint",
        "e2e": "ng e2e"
    },
    "private": true,
    "dependencies": {
        "@angular/animations": "^6.0.3",
        "@angular/common": "^6.0.3",
        "@angular/compiler": "^6.0.3",
        "@angular/core": "^6.0.3",
        "@angular/forms": "^6.0.3",
        "@angular/http": "^6.0.3",
        "@angular/platform-browser": "^6.0.3",
        "@angular/platform-browser-dynamic": "^6.0.3",
        "@angular/router": "^6.0.3",
        "core-js": "^2.5.4",
        "ngf-bootstrap": "0.0.5",
        "rxjs": "^6.0.0",
        "toastr": "^2.1.4",
        "zone.js": "^0.8.26"
    },
    "devDependencies": {
        "@angular-devkit/build-angular": "~0.6.8",
        "@angular/cli": "~6.0.8",
        "@angular/compiler-cli": "^6.0.3",
        "@angular/language-service": "^6.0.3",
        "@types/jasmine": "~2.8.6",
        "@types/jasminewd2": "~2.0.3",
        "@types/node": "~8.9.4",
        "codelyzer": "~4.2.1",
        "jasmine-core": "~2.99.1",
        "jasmine-spec-reporter": "~4.2.1",
        "karma": "~1.7.1",
        "karma-chrome-launcher": "~2.2.0",
        "karma-coverage-istanbul-reporter": "~2.0.0",
        "karma-jasmine": "~1.1.1",
        "karma-jasmine-html-reporter": "^0.2.2",
        "protractor": "^5.4.0",
        "ts-node": "~5.0.1",
        "tslint": "~5.9.1",
        "typescript": "~2.7.2"
    }
}

The app.routes.ts is below; it's really simple, so far, I've not progressed very far into the app development.

import { NgModule } from '@angular/core'
import { Routes, RouterModule } from '@angular/router'

import { EventsListComponent } from './events/events-list.component'
import { EventDetailsComponent } from './events/event-details/event-details.component'

const routes: Routes = [
    {
        path: 'events',
        component: EventsListComponent
    },
    {
        path: 'events/:id',
        component: EventDetailsComponent
    },
    {
        path: '',
        redirectTo: '/events'
    }
];

@NgModule({
    imports: [RouterModule.forRoot(routes, { enableTracing: true })],
    exports: [RouterModule]
})

export class AppRoutingModule {
}

The EventsAppModule is below; note @ line 16, I've imported the AppRoutingModule (app.routes.ts), I can comment this line to disable routing.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { EventsAppComponent } from './events-app.component';
import { EventsListComponent } from './events/events-list.component';
import { EventThumbnailComponent } from './events/event-thumbnail.component';
import { EventDetailsComponent } from './events/event-details/event-details.component';
import { NavBarComponent } from './nav/navbar.component';
import { EventService } from './events/shared/event.service';
import { ToastrService } from './common/toastr.service';
import { AppRoutingModule } from './app.routes';

@NgModule({
    imports: [
        BrowserModule
        , AppRoutingModule
    ],
    declarations: [
        EventsAppComponent,
        EventsListComponent,
        EventThumbnailComponent,
        EventDetailsComponent,
        NavBarComponent
    ],
    providers: [
        EventService,
        ToastrService
    ],
    bootstrap: [EventsAppComponent]
})

export class EventsAppModule { }

As noted, when trying to use routing, with the <router-outlet></router-outlet> tag in the app's main component/page, the EventsListComponent doesn't load (I get a blank page). If I comment the AppRoutingModule from the app's module and switch to the <events-list></events-list> tag in the app's main component, the EventsListComponent loads fine (it displays a list of events, each encapsulated in an EventThumbnailComponent).

Also, it doesn't load http://localhost:39420/events when I run; it defaults to root, e.g., localhost:39420/ ...if I type '/events' in the url, I don't get an error, just a blank page. If I try to get the EventDetailsComponent, using localhost:39420/events/1 ...again, it just renders a blank page.

2
redirectTo: 'events' should be redirectTo: '/events' - SiddAjmera
Thanks, good point (fixed). Doesn't address the main issue, though? - jwdvorak
What's the url you're trying to navigate to again? - SiddAjmera

2 Answers

2
votes

redirectTo expects the exact path to which the router should navigate to. So it expects a / before route path that you might have defined.

Replace redirectTo: 'events' with redirectTo: '/events' in your route config and it should load EventsListComponent component properly.

Also add a pathMatch: 'full' to this route:

    {
        path: '',
        redirectTo: '/events',
        pathMatch: 'full'
    }

Here's a StackBlitz project to help you out.

1
votes

So this is what you have to catch all when no other routes match the request so that Angular can handle it.

// This is a catch-all for when no other routes match the request; let the Angular 2 router take care of it...
            routes.MapRoute(
                name: "default",
                url: "{*url}",
                defaults: new { controller = "Home", action = "Index" } // The view that bootstraps Angular 2 app
            );

You need to remove the defaults so it will look like this. And don't create a Controller/View for angular.

// This is a catch-all for when no other routes match the request; let the Angular 2 router take care of it...
            routes.MapRoute(
                name: "default",
                url: "{*url}",// you don't need a view
            );

After you run the command

ng build --prod

copy the dist files into a directory ex(myangularapp) withing the root directory of you mvc app. you will need to change the base tag href attribute so run

ng build --prod --base-href=/myangularapp/.

Then you can add a web.config file to the the directory that holds your angular files (myangularapp). Sample below.

<?xml version="1.0"?>
<configuration>
  <system.web></system.web>
  <system.webServer>
    <rewrite>
      <rules>
        <!--Redirect selected traffic to index -->
        <rule name="Index Rule" stopProcessing="true">
          <match url=".*" />
          <conditions logicalGrouping="MatchAll">
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
          </conditions>
          <action type="Rewrite" url="index.html" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>