2

To preface: all of what I'm about to describe works locally, but does not work when my code is deployed in a Windows environment in an Azure web app service. Also, I have read the answer to this question, and I don't believe it applies to my particular situation; I am not using any custom domains.

For more background: I am not containerizing my app; I am building and deploying code with Azure DevOps.

I have a relatively simple Angular 7 (CLI) app with some simple routing. I have a number of routing modules that are imported into their respective feature modules, and then the feature modules are imported into the main/core app module: app.module.ts.

In most cases, I am navigating users to a component at a route such as /aboutUs/ourMission or /support/donations. These routes work well locally and remotely.

What doesn't work well remotely are routes like /admin or /register.

app.module.ts:

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app-routing.module';
import {AppComponent} from './app.component';
import {NgbModule} from '@ng-bootstrap/ng-bootstrap';
import {HeaderComponent} from './header/header.component';
import {VolunteerComponent} from './volunteer/volunteer.component';
import {TagComponent} from './tag/tag.component';
import {SummerCampComponent} from './summer-camp/summer-camp.component';
import {RegisterComponent} from './register/register.component';
import {FooterComponent} from './footer/footer.component';
import {StoreModule} from '@ngrx/store';
import {metaReducers, reducers} from './reducers';
import {StoreDevtoolsModule} from '@ngrx/store-devtools';
import {environment} from '../environments/environment';
import {ProductionEffects} from './effects/production.effects';
import {EffectsModule} from '@ngrx/effects';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {ButtonModule, CalendarModule, DropdownModule} from 'primeng/primeng';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AdminModule} from './admin/admin.module';
import {CommonModule} from '@angular/common';
import {TokenInterceptor} from './interceptors/token.interceptor';
import {ParticipantEffects} from './effects/participant.effects';
import {AboutUsModule} from './about-us/about-us.module';
import {EducationModule} from './education/education.module';
import {OurSeasonModule} from './our-season/our-season.module';
import {SupportModule} from './support/support.module';
import {TicketsModule} from './tickets/tickets.module';
import {VolunteerModule} from './volunteer/volunteer.module';

@NgModule({
  declarations: [
    AppComponent,
    HeaderComponent,
    VolunteerComponent,
    TagComponent,
    SummerCampComponent,
    RegisterComponent,
    FooterComponent
  ],
  imports: [
    CommonModule,
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    HttpClientModule,
    ReactiveFormsModule,
    DropdownModule,
    NgbModule,
    StoreModule.forRoot(reducers, {metaReducers}),
    EffectsModule.forRoot([ProductionEffects, ParticipantEffects]),
    !environment.production ? StoreDevtoolsModule.instrument() : [],
    FormsModule,
    ButtonModule,
    CalendarModule,
    AboutUsModule,
    AdminModule,
    EducationModule,
    OurSeasonModule,
    SupportModule,
    TicketsModule,
    VolunteerModule
  ],
  providers: [
    {
      provide: HTTP_INTERCEPTORS,
      useClass: TokenInterceptor,
      multi: true
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
}

Using one imported module as an example, here's AboutUsModule:

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';

import {AboutUsRoutingModule} from './about-us-routing.module';
import {MissionComponent} from './mission/mission.component';
import {PastShowsComponent} from './past-shows/past-shows.component';
import {BoardComponent} from './board/board.component';
import { AboutUsMissionComponent } from './about-us-mission/about-us-mission.component';

@NgModule({
  declarations: [
    MissionComponent,
    PastShowsComponent,
    BoardComponent,
    AboutUsMissionComponent
  ],
  imports: [
    CommonModule,
    AboutUsRoutingModule
  ]
})
export class AboutUsModule {
}

... and the AboutUsRoutingModule:

import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {MissionComponent} from './mission/mission.component';
import {PastShowsComponent} from './past-shows/past-shows.component';
import {BoardComponent} from './board/board.component';

const routes: Routes = [
  {path: 'aboutUs/mission', component: MissionComponent},
  {path: 'aboutUs/pastShows', component: PastShowsComponent},
  {path: 'aboutUs/board', component: BoardComponent}
];

@NgModule({
  imports: [RouterModule.forChild(routes)],
  exports: [RouterModule]
})
export class AboutUsRoutingModule {
}

All of these routes will work remotely and locally.

Example of what doesn't work remotely, but works locally:

AdminModule:

import {NgModule} from '@angular/core';
import {AdminComponent} from './admin.component';
import {AdminRoutingModule} from './admin-routing.module';
import {ReactiveFormsModule} from '@angular/forms';
import {UserService} from '../services/user.service';
import {StoreModule} from '@ngrx/store';
import * as fromAdminState from './reducers';
import * as fromUserReducer from './reducers/user-reducer.reducer';
import {EffectsModule} from '@ngrx/effects';
import {UserEffects} from './effects/user.effects';
import {AdminDashboardComponent} from './admin-dashboard/admin-dashboard.component';
import {TabMenuModule} from 'primeng/tabmenu';
import {CommonModule} from '@angular/common';
import {SharedModule} from 'primeng/shared';
import {AdminMenuBarComponent} from './admin-menu-bar/admin-menu-bar.component';
import {AdminShowsComponent} from './admin-shows/admin-shows.component';
import {AdminParticipantsComponent} from './admin-participants/admin-participants.component';
import {SiteAdminComponent} from './site-admin/site-admin.component';
import {AccordionModule, CalendarModule, DropdownModule, InputTextareaModule} from 'primeng/primeng';
import {AdminProductionFormComponent} from './admin-shows/admin-production-form/admin-production-form.component';
import {ParticipantComponent} from './admin-participants/participant/participant.component';
import {TableModule} from 'primeng/table';
import {ParticipantsTableComponent} from './participants-table/participants-table.component';
import {AuditionTimeFormComponent} from './admin-shows/admin-production-form/audition-time-form/audition-time-form.component';

@NgModule({
  imports: [
    AdminRoutingModule,
    ReactiveFormsModule,
    StoreModule.forFeature('adminState', fromAdminState.reducers, {metaReducers: fromAdminState.metaReducers}),
    StoreModule.forFeature('userReducer', fromUserReducer.userReducer),
    EffectsModule.forFeature([UserEffects]),
    TabMenuModule,
    CommonModule,
    SharedModule,
    AccordionModule,
    TableModule,
    CalendarModule,
    DropdownModule,
    InputTextareaModule
  ],
  declarations: [
    AdminComponent,
    AdminDashboardComponent,
    AdminMenuBarComponent,
    AdminShowsComponent,
    AdminParticipantsComponent,
    SiteAdminComponent,
    AdminProductionFormComponent,
    ParticipantComponent,
    ParticipantsTableComponent,
    AuditionTimeFormComponent
  ],
  providers: [UserService]
})

export class AdminModule {
}

. . . and the AdminRoutingModule:

import {RouterModule, Routes} from '@angular/router';
import {AdminComponent} from './admin.component';
import {NgModule} from '@angular/core';
import {AdminDashboardComponent} from './admin-dashboard/admin-dashboard.component';
import {AdminDashboardGuard} from './admin-dashboard.guard';

const adminRoutes: Routes = [
  {
    path: 'admin',
    pathMatch: 'full',
    component: AdminComponent
  },
  {
    path: 'admin-dashboard',
    component: AdminDashboardComponent,
    canActivate: [AdminDashboardGuard]
  }
];

@NgModule({
  imports: [
    RouterModule.forChild(adminRoutes)
  ],
  exports: [
    RouterModule
  ]
})

export class AdminRoutingModule {
}

To summarize: all of these pieces work locally, and I can freely navigate to all components under the routes I've shared. Once published out to my Azure web app service, what I see when accessing a route such as /admin is the following:

The resource you are looking for has been removed, had its name changed, or is temporarily unavailable.

I really don't have a good guess as to why this may be happening. I guess the host's web server software (IIS?) is trying to serve something up on that route rather than turning that control over to the application code. I don't know if that estimation is valid, so I'd love some insight. Thank you.

1 Answer 1

7

To make the routing works, add this to the web.config file

<configuration>
    <system.webServer>
         <rewrite>
            <rules>
              <rule name="AngularJS Routes" stopProcessing="true">
                <match url=".*" />
                <conditions logicalGrouping="MatchAll">
                  <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
                  <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
                </conditions>
                <action type="Rewrite" url="/" />
              </rule>
            </rules>
          </rewrite>
          <caching enabled="true" enableKernelCache="true">
              <profiles>
                  <add extension=".js" policy="DisableCache" kernelCachePolicy="DisableCache" />
              </profiles>
          </caching>
    </system.webServer>
</configuration>
Sign up to request clarification or add additional context in comments.

3 Comments

That by itself did not work. I assume I need to make sure this is making it into the build output?
yeah this file needs to go with the build
to have the web.config moved to the dist folder every time the app is built it its path needs to be added to the assets in the angular.json

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.