Angular & uirouter - how to define routes - javascript

I am creating an application that uses Angular 6 (6.0.6) and #uirouter/angular (2.0.0). But my app does not reconize the route '/login', it does only navigate to '/' (even if I remove the 'otherwise: '/'')
This application exists of multiple modules with multiple pages. The structure can be described as follows:
app/
-- home/
---- home.module.ts
---- home.component.ts
---- home.route.ts
---- home.scss
---- home.html
-- login/
---- login.module.ts
---- login.component.ts
---- login.route.ts
---- login.scss
---- login.html
-- app.module.ts
-- app.component.ts
Both modules have a separate routing file. The way I thought it would work was as follows (simplified example, don't mind the missing parts):
app.module.ts
#NgModule({
declarations: [AppComponent],
imports: [
UIRouterModule.forRoot({otherwise: '/'}),
HomeModule,
LoginModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
}
home.module.ts
#NgModule({
imports: [
UIRouterModule.forChild({states: [homeState]}),
NavbarModule
],
declarations: [HomeComponent],
providers: []
})
export class HomeModule {
}
home.route.ts
export const homeState: Ng2StateDeclaration = {
name: 'home',
url: '/',
component: HomeComponent
};
login.module.ts
#NgModule({
imports: [
UIRouterModule.forChild({states: [loginState]}),
],
declarations: [LoginComponent],
providers: []
})
export class LoginModule {
}
login.route.ts
export const loginState: Ng2StateDeclaration = {
name: 'login',
url: '/login',
component: LoginComponent
};
I expected that the UIRouterModule.forChild() method would just add the route to the configuration. But the /login route is not reconized and it just navigates back to /. I do not want to create a submodule where all my routing happens, because then I will also have to import all the dependencies my components have in that module (so far separation of concern and modularization).
This would look as follows for my example app:
const states = [homeState, loginState];
#NgModule({
imports: [
UIRouterModule.forChild({states: [states], otherwise: '/'}),
NavbarModule
],
declarations: [HomeComponent, loginComponent],
providers: []
})
export class AppRoutingModule {
}
The loginComponent does not need the NavBarModule, but still gets it this way..
Since this doesn't work I am clearly doing something wrong. Can anyone tell me what I am doing wrong and how I should fix it?

Your architecture may need a bit more thought. Have a look at the approach in this blog post which uses the native Angular router to achieve what you are trying to do.

Related

Why Angular always redirecting to main page?

Always I trying to GET '/' it shows static-root-component (component of my main page),
but when it is '/welcome' page immediately redirecting to '/' and also loading static-root-component instead of welcome-component
Initially I wanted to redirect users to welcome page if they aren't authorized, but login status only can be checked within JavaScript. After JS got info about login status it decides to redirect using location.replace("/welcome"), but... Angular again goes to '/'
"Funny" fact: there isn't any routing problems during debug with ng serve but it always happens with ng build
I don't know what's gone wrong and there is app.module.ts:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { StaticRootComponent } from './static-root/static-root.component';
import { WelcomeComponent } from './welcome/welcome.component';
import { HttpClientModule } from '#angular/common/http';
import { HttpService } from './http.service';
import { BrowserAnimationsModule } from '#angular/platform-browser/animations';
const appRoute: Routes = [
{ path: '', component: StaticRootComponent, pathMatch: 'full' },
{ path: 'welcome', component: WelcomeComponent }
];
#NgModule({
declarations: [
AppComponent,
StaticRootComponent,
WelcomeComponent
],
imports: [
BrowserModule,
AppRoutingModule,
BrowserAnimationsModule,
RouterModule.forRoot(appRoute),
HttpClientModule
],
providers: [HttpService],
bootstrap: [AppComponent]
})
export class AppModule { }
I can drop any other Angular file if needed
In your code, change like below
const appRoute: Routes = [
{ path: '', component: StaticRootComponent },
{ path: 'welcome', component: WelcomeComponent },
{ path: '**', redirectTo: '' }
];
In the component file, inject this like below
import { Router } from '#angular/router';
constructor(
private router: Router
) {}
When you want do navigation use the below code instead of location.replace("/welcome")
this.router.navigate(['/welcome']);
Check the Module you trying to instantiate in the constructor of the Component linked to the Routing Path you are trying to access
In this case:
Our Component: example.component.ts
Our Module: HttpClientModule that contains HttpClient
Our Routing Path: "/example"
and make sure that Module is already existing in the app.module.ts , here is an example:
example.component.ts
import {HttpClient} from '#angular/common/http'; //child of HttpClientModule
#Component({selector: 'app-example',templateUrl: './example.component.html', styleUrls: ['./example.component.css']})
export class ExampleComponent{
constructor(private httpClient: HttpClient) { }
}
now let's see both examples of app module with and without the Module import and see the difference
app.module.ts
Without including HttpClientModule in imports array
import { AppComponent } from './app.component';
#NgModule({
declarations: [...],
imports: [BrowserModule,...],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
In this case loading the "/example" path will redirect you to the main page path which is usually "/" and that's because example.component.ts is using HttpClient (child of HttpClientModule) but not finding it in app.module.ts .
app.module.ts
Including HttpClientModule in imports array
import { AppComponent } from './app.component';
import { HttpClientModule} from '#angular/common/http'; //Import that module you willing to use
#NgModule({
declarations: [...],
imports: [BrowserModule,HttpClientModule,...], //add the module we currently using
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
In this case loading the "/example" path will work properly since we added the required module in app.module.ts.
If that's your case that would definitely fix your problem unless you have something else forcing the redirection to home page "index.html" or any other path, else if that didn't fix your problem read the following notes:
Make sure to check there are no redirections in the app-routing.module.ts routes array like this
const routes: Routes = [
{path: 'example', component: ExampleComponent},
{ path: '/example', redirectTo: '' }
];
it should only be like so
const routes: Routes = [
{path: 'example', component: ExampleComponent}
];
Also make sure there is no routing behaviour causing the redirection like #angular/router through something like this.router.navigate(['/'])
PS: SAME ISSUE COULD IMPLY IF YOU USING A SERVICE THAT'S USING A MODULE WHICH IS NOT ADDED TO MODULE IMPORTS IN app.module.ts
My project based on MEAN (Mongo, Express, Angular and NODEJS)... The last was a source of problem
#Shakthifuture, you said you want to see full code and I started answering:
"What you wanna to see else? My data and server files doesn't affec..."
and I've starting think "what if affect?": routing in whole of project works by Angular, but all new connection to the site pass NodeJS and Express, so I forgot about 404 case...
THE PROBLEM:
In server script file index.js of project's root folder a long time ago I've added code about what to do if entered path not found:
app.use(function (req, res, next) {
res.redirect('/');
// IF 404 NOT FOUND
});
and above of it something like:
app.get('/', function (req, res) {
res.sendFile(`${__dirname}/angular/index.html`)
});
// send index if path is '/'
but nothing for '/welcome', that's why redirecting happens
THE SOLUTION:
let's add the '/welcome' handler:
app.get('/welcome', function (req, res) {
res.sendFile(`${__dirname}/angular/index.html`)
});
(again index.html due to SPA)

Angular wildcard route replacing child routes

I have a module called "host" with its own routing that I want to insert into the app-routing.module. However, I have the problem of the wildcard loading first and displaying PageNotFoundComponent, instead of the Host component loading. I have the following files.
host.module.ts
....
const routes: Routes = [
{
path: 'host',
children: [
{ path: '', component: HostComponent }
]
}
];
#NgModule({
declarations: [HostComponent],
imports: [
CommonModule,
RouterModule.forChild(routes)
]
})
export class HostModule { }
app-routing.module.ts
const routes: Routes = [
{ path: '', component: HomeComponent, pathMatch: "full"},
{ path: '**', component: PageNotFoundComponent }
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
app.module.ts
#NgModule({
declarations: [
AppComponent,
HomeComponent,
PageNotFoundComponent
],
imports: [
BrowserModule,
AppRoutingModule,
HostModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
app.component.html
<h2>Home</h2>
<ul>
<li>
<h2><a routerLink="/host">host</a></h2>
</li>
</ul>
<router-outlet></router-outlet>
Problem: When I run the app and click on the "Host" button, it loads the PageNotFoundComponent. I obviously want it to go to the HostComponent.
In your app.module.ts you need to reorder your imports
#NgModule({
declarations: [
AppComponent,
HomeComponent,
PageNotFoundComponent
],
imports: [
BrowserModule,
HostModule, <--- this before AppRoutingModule
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Reason being is because the order of the routes in the configuration matters. https://angular.io/guide/router#configuration
The ** path in the last route is a wildcard. The router will select this route if the requested URL doesn't match any paths for routes defined earlier in the configuration. This is useful for displaying a "404 - Not Found" page or redirecting to another route.
The order of the routes in the configuration matters and this is by design. The router uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. In the configuration above, routes with a static path are listed first, followed by an empty path route, that matches the default route. The wildcard route comes last because it matches every URL and should be selected only if no other routes are matched first.

Angular passing dynamic entry components into module and then passing them again into another module

I'm making a modal component for a component library. I made a 3rd party modal library that I'm using within my component library. A main feature is being able to pass a component via a service and dynamically adding it to the modal.
My modal lib has a static method that allows you to add your component to the module's entry components. It looks like:
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
Cool, that works. I can pass components into it when I import the module like this: A11yModalModule.withComponents([ModalContentComponent])
My problem occurs when I abstract this out another level. So now instead of 2 modules I have 3. I need to pass a component like I did above from the lib consumer's module, to my component module, and then into the modal module.
How can I pass components from the lib module to the modal module?
I think I'm getting close. Here are my 3 modules
// app module
#NgModule({
declarations: [AppComponent, ModalContentComponent],
imports: [
LibModalModule.withComponents([ModalContentComponent])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// lib module
#NgModule({
imports: [CommonModule],
declarations: [LibModal],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
imports: [CommonModule, A11yModalModule.withComponents(components)]
};
}
}
// a11y modal module
#NgModule({
imports: [CommonModule],
declarations: [ModalComponent],
exports: [],
providers: [ModalService, DomService],
entryComponents: [ModalComponent]
})
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
withComponents method should return ModuleWithProviders object which is just wrapper around a module that also includes the providers.
It can't have imports property or something else because it doesn't understand those properties. Here's an excerpt from angular source code that is responsible from reading metadata from ModuleWithProviders:
else if (importedType && importedType.ngModule) {
const moduleWithProviders: ModuleWithProviders = importedType;
importedModuleType = moduleWithProviders.ngModule;
if (moduleWithProviders.providers) {
providers.push(...this._getProvidersMetadata(
moduleWithProviders.providers, entryComponents,
`provider for the NgModule '${stringifyType(importedModuleType)}'`, [],
importedType));
}
}
As we can see angular compiler takes providers from the object that will returned in withComponents method.
So, in order to merge your modules you can either use your approach(provide ANALYZE_FOR_ENTRY_COMPONENTS in LibModalModule.withProviders) or reuse A11yModalModule.withComponents like:
#NgModule({
imports: [CommonModule, A11yModalModule],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
providers: A11yModalModule.withComponents(components).providers
};
}
}
(Tested with AOT)
Also A11yModalModule has to be imported in LibModalModule if we want its providers to be included in our root module injector (And i suppose you're going to use ModalService and DomService that are declated in A11yModalModule). The reason of this is that angular includes all providers from transitive module in root module injector.
See also:
Avoiding common confusions with modules in Angular
What you always wanted to know about Angular Dependency Injection tree
I had a bug that was giving me a false flag. Turns out you can just add the same withComponents method to the component library module and it passes the component through. I'd love an explanation on how this works if anyone knows.
// app module
#NgModule({
declarations: [AppComponent, ModalContentComponent],
imports: [
LibModalModule.withComponents([ModalContentComponent])
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
// lib module
#NgModule({
imports: [CommonModule, A11yModalModule],
declarations: [LibModal],
providers: [LibModalService],
exports: []
})
export class LibModalModule {
static withComponents(components: any[]) {
return {
ngModule: LibModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}
// a11y modal module
#NgModule({
imports: [CommonModule],
declarations: [ModalComponent],
exports: [],
providers: [ModalService, DomService],
entryComponents: [ModalComponent]
})
export class A11yModalModule {
static withComponents(components: any[]) {
return {
ngModule: A11yModalModule,
providers: [{
provide: ANALYZE_FOR_ENTRY_COMPONENTS,
useValue: components,
multi: true
}]
};
}
}

Angular condition in type provider with AOT

I have an Angular project which I compile with AOT. I want to be able to register ClassProvider that is resolved dynamically according to configuration. Simplified code I use is this:
const isMock = Math.random() > 0.5;
#NgModule({
// ...
providers: [
{ provide: MyServiceBase, useClass: (isMock) ? MyServiceMock : MyService },
],
bootstrap: [AppComponent]
})
export class AppModule { }
The problem is when I compile this with AOT I always get the same service. I would expect to get different service while hitting F5 (because of the randomness on the first line). When compiling without AOT it behaves as I expect.
Here is the whole code example on github: https://github.com/vdolek/angular-test/tree/aot-conditioned-provider-problem. It behaves differently with ng serve and ng serve --aot.
How can I achieve this? I know I could use FactoryProvider, but then I would have to duplicate the services dependencies (parameters of the factory function and deps property on the FactoryProvider).
To achieve the dynamic nature of your requirement, you need to use factory providers, via the useFactory attribute.
I've forked your repository, and amended your app.module.ts as follows, to work in AOT.
Amend app.module.ts as follows
export let myServiceFactory = () => {
const isMock = Math.random() > 0.5;
return isMock ? new MyServiceMock() : new MyService();
};
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [
{provide: MyServiceBase, useFactory: myServiceFactory},
],
bootstrap: [AppComponent]
})
export class AppModule {
}
In the case that your service is dependent on other services, which, most likely it will, you can use the deps argument, to pass on the required dependencies.
Let's say that MyServiceBase is dependent on two services, MyService1 and MyService2... Your factory function will look as follows :
export let myServiceFactory = (service1:MyService1, service2:MyService2) => {
const isMock = Math.random() > 0.5;
return isMock ? new MyServiceMock(service1, service2) : new MyService(service1, service2);
};
and your providers decleration would look as follows
providers: [
{
provide: MyServiceBase,
useFactory: myServiceFactory,
deps: [MyService1, MyService2]
},
]
This guide contains further detail on the various ways of achieving dependency injection in Angular.
I think as #jeanpaul-a said you don't have any choice other than to use factory. But managing dependencies could be not very clean. But what you could use is the Injector. I'll go with something like:
#NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, HelloComponent ],
providers: [
Dep1Service,
Dep2Service,
{ provide: MyServiceBase, useFactory: createService, deps: [Injector] }
],
bootstrap: [ AppComponent ]
})
export class AppModule { }
export function createService(injector: Injector) {
const isMock = Math.random() > 0.5;
if (mock) {
return new MyService1(injector.get(Dep2Service));
} else {
return new MyService2(injector.get(Dep1Service));
}
}
What you could also do is to set MyServiceBase as an interface and use InjectionToken.
You will find a working example here (not your class name however).

Ionic 2: unit test for component with ionic's markup/elements

I have a simple Angular 2 component for an Ionic 2 app. The component uses some Ionic's markup, such as:
<ion-card>
<h3>{{ rawcontent.name }}</h3>
<p *ngIf="rawcontent.description">{{ rawcontent.description }}</p>
</ion-card>
the component .ts is something like:
import { Component, Input } from '#angular/core';
import { NavController } from 'ionic-angular/';
import { Content } from '../../pages/content/content';
#Component({
selector: 'content-detail',
templateUrl: 'content-detail.html'
})
export class ContentDetailComponent {
#Input('data') rawcontent: any = {};
constructor(public nav: NavController) {
}
//other methods
}
I'm trying to write an unit test for it, but I got this error so far:
'ion-card' is not a known element:
1. If 'ion-card' is an Angular component, then verify that it is part of this module.
2. If 'ion-card' is a Web Component then add "CUSTOM_ELEMENTS_SCHEMA" to the '#NgModule.schemas' of this component
to suppress this message.
I don't know what to do now. In this case, ion-card is an Angular component, I guess. So, what to do next? I think I have to change my beforeEach, addining some config. Can anyone help?
beforeEach(() => TestBed.configureTestingModule({
declarations: [ ContentDetailComponent ],
providers: [
{ provide: NavController, useClass: NavMock }
]})
);
You need to import ionicModule. Add this in configureTestingModule
imports: [
IonicModule,
],
This is a good starting blog for testing Ionic 2 apps.
You need to configure the app with Ionic module root in beforeEach:
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MyApp]
providers: [
],
imports: [
IonicModule.forRoot(MyApp)
]
}).compileComponents();
}));

Categories