I have searched quite a bit and couldn't find any answer that solved my problem. So I am posting this question.
My issue is very similar to this question. Angular 2.0.1 Router EmptyError: no elements in sequence
But I couldn't resolve it even by adding pathMatch: 'full',.
I am getting an intermittent zonewaware error when try to navigate from a list table (navigates to detail view)
below is my module.
#NgModule({
imports: [
CommonModule,
RouterModule.forChild([
{
path: 'teams',
component: TeamsListComponent,
pathMatch: 'full',
resolve: {
TeamTemplate: TeamListTemplatesResolver
},
canActivate: [AuthenticatedGuard]
}, {
path: 'teams/:id',
component: TeamFormComponent,
pathMatch: 'full',
resolve: {
team: TeamFormTeamResolver,
resources: TeamFormResourcesResolver
},
canActivate: [AuthenticatedGuard]
}
]),
my authGuard service has a canActivate method which just returns a boolean.
public canActivate(): boolean {
if (this.authService.isLoggedIn()) {
return true;
}
this.router.navigate(['/logout', { redirect: location.pathname }]);
return false;
}
And here is the error:
Zone aware error
I could get a router event log with {enableTracing: true}:
Router Event: NavigationStart
Router Event: RoutesRecognized
Router Event: GuardsCheckStart
Router Event: GuardsCheckEnd
Router Event: ResolveStart
Router Event: NavigationError
Thanks for anyone who looked at this issue. I got the answer to my question.
As I described, I have few resolvers while I route to the detail page. On one of those resolvers there's a logic to get elements.
public resolve(_route: ActivatedRouteSnapshot, _state: RouterStateSnapshot): Observable<T[]> {
return this.service.browse({}).first();
}
https://stackoverflow.com/a/42346203/5162622 - As mentioned here, the first() is sending error notification as there's no values. So I replaced it with take(1) and all looks good.
As I mentioned above in the comment, it was good to know how to do event tracking while routing. That's how I could track this down.
Related
Currently i have the following route
routes.push({
path: "/",
redirect: "/Dashboard"
});
When I login, it will redirect me to the /dashboard screen.
But how can I update the route so that when I log in I can check the claims and then based on the role, redirect to a different view.
I tried the following, this is just sudo code as I cant really get it to work. Also if I can I would like to add an alias. Even if I can get it to work is would this be the correct way of doing it?
routes.push({
path: "/",
alias: "/dashboard", --> This does not seem to work with redirect.
redirect: to => {
getUserType(msalInstance).then(userType => {
if(userType == 'Client'){
return { path: '/clients/2/detail' }
}
else {
return { path: '/dashboard' }
}
});
}
});
and the route for the client-detail looks as follows.
routes.push({
path: "/clients/:id?/detail",
name: "client-detail",
props: true,
meta: {
requiresAuth: true,
layout: simpleLayout,
allowedUsers: clientUserTypes
},
component: loadView("client-detail"),
});
I have looked the the beforeEnter hook but when I add it, it seems to get ignored because the redirect is there.
I couldn't find the answer why when I am going back and forward in pages that implement the resolver, suddenly signlar stop working, and all my Angular 6 freeze. (you couldn't click on any link) the web site look ok, but nothing is working..
I am using
"ng2-signalr": "^6.1.0",
Angular 6.0.3
I implemented the ng2-signalr,
The Resolver did not work at start, than I changed it to use ISignalRConnection
I can do up to 5 time back and forward in the 6'th time the application Just FREEZE,
export class SignalrConnectionService implements
Resolve<ISignalRConnection> {
#Injectable()
export class SignalrConnectionService implements Resolve<ISignalRConnection>
{
constructor(private _signalR: SignalR) { }
resolve() {
console.log('ConnectionResolver. Resolving...');
//this._signalR.createConnection();
try {
return this._signalR.connect();
}
catch (err) {
console.log("Error in _signalR.connect: " + err.message);
}
//return this._signalR.createConnection();
}
}
Inside the Routing I did:
path: 'my_path',
component: MyComponent,
canActivate: [AuthGuard],
resolve: { connection: SignalrConnectionService }
If I remove the resolve line, the signalR is canceled and everything work (with no freeze and no signalR), but I need it..
Any help?
So I found the solution.
It seems that you don't need to use the resolver, instead in the constructor, do this:
constructor(private _signalR: SignalR) {
this._connection = _signalR.createConnection();
}
And only then in the ngInit you can put code like this:
this._connection.start().then((c) => {
//what ever
});
});
That seems to solve the problem :)
I am using angular 2.4 version and router version "^3.4.10".
I am trying to handle redirect url using auth guard service.
When user hit url 'domain/assignment/3/detail' and if user is not login then user redirected to 'domain/login' page.
and when user successfully login in to system then redirected to 'domain/assignment/3/detail' previous url which user tries to access.
I have implemented CanLoad guard on assignment module. so when user tries to access url 'domain/assignment/3/detail' and if user is not login, url stores into redirectUrl property of authservice (this.authService.redirectUrl).
so here is the issue comes in my case. i am not able to get full path of the url which user hit.
i am getting 'assignment' instead 'assignment/3/detail' within CanLoad guard.
how can i get full path so that i can redirect user to proper path within CanLoad guard.
CanLoad:
canLoad(route: Route): boolean {
let url = `/${route.path}`; // here i got url path 'assignment' instead 'assignment/3/detail'
return this.checkLogin(url);
}
Main routing app.routes.ts
const routes: Routes = [
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{ path: 'login', component: LoginComponent},
{
path: 'assignment',
loadChildren: './assignment/assignment.module#AssignmentModule',
canLoad: [AuthGuard]
},
{ path: '**', redirectTo: '', pathMatch: 'full' }];
Assignment routing: assignment-routing.ts
const assignmentRoutes: Routes = [
{
path: '',
component: AssignmentComponent,
canActivate: [AuthGuard]
children: [
{
path: '',
canActivateChild: [AuthGuard],
children: [
{
path: ':assignmentId/detail', component: AssignmentDetailComponent,
canActivate: [AuthGuard]
}
]
}]
}];
AuthGuard: auth-gurad.service.ts
import { Injectable } from '#angular/core';
import {
CanActivate, Router,
ActivatedRouteSnapshot,
RouterStateSnapshot,
CanActivateChild,
NavigationExtras,
CanLoad, Route
} from '#angular/router';
import { AuthService } from './auth.service';
#Injectable()
export class AuthGuard implements CanActivate, CanActivateChild, CanLoad {
constructor(private authService: AuthService, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
let url: string = state.url;
return this.checkLogin(url);
}
canActivateChild(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this.canActivate(route, state);
}
canLoad(route: Route): boolean {
let url = `/${route.path}`; // here i got url path 'assignment' instead 'assignment/3/detail'
return this.checkLogin(url);
}
checkLogin(url: string): boolean {
if (this.authService.isLoggedIn) {
if(this.authService.redirectUrl!=null){
let redirectUrl = this.authService.redirectUrl;
this.authService.redirectUrl = null;
this.this.router.navigate([redirectUrl]);
}
return true;
}
// Store the attempted URL for redirecting
this.authService.redirectUrl = url;
// Navigate to the login page
this.router.navigate(['/login']);
return false;
}
}
If you look the signature of the canLoad method, there is is a second parameter segments which you can use to generate the url.
canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean
You can generate the full path using following code:
canLoad(route: Route, segments: UrlSegment[]): boolean {
const fullPath = segments.reduce((path, currentSegment) => {
return `${path}/${currentSegment.path}`;
}, '');
console.log(fullPath);
}
Note: With this method you will get the full path, but you will not get any query parameters.
I have this exact same problem. These issues are related
https://github.com/angular/angular/issues/17359
https://github.com/angular/angular/issues/12411
And they even have an open PR about this which hasn't been merged yet
https://github.com/angular/angular/pull/13127
The workaround for now seems to be to replace the canLoad method with the canActivate, there you can have access to the full path, the bad thing of course is that you're loading the module
Edit: Also for you Assignment routing, there's no need to call canActivate for each child route. Have it defined on the parent route should apply the guard for all child routes.
This workaround from this reply on the github issue worked very well for me.
export class AuthnGuard implements CanLoad {
constructor(private authnService: AuthnService, private router: Router) { }
canLoad( ): Observable<boolean> | Promise<boolean> | boolean {
const navigation = this.router.getCurrentNavigation();
let url = '/';
if (navigation) {
url = navigation.extractedUrl.toString();
}
this.authnService.storeRedirectUrl(url);
There is another temporary workaround instead of replacing canLoad with canActivate:
You can store the url via redirectUrl = location.pathname and redirect later via this.router.navigateByUrl(redirectUrl);. Of course, if you work on a subpath (like domain) you have to adjust the pathname a little bit.
In Angular 9, following reply worked for me.
My code
export class AuthGuard implements CanLoad {
constructor(private router: Router, private loginService: LoginService) { }
canLoad( ): Observable<boolean> | Promise<boolean> | boolean {
if (this.loginService.isLoggedIn()) return true;
this.router.events.pipe(first(_ => _ instanceof NavigationCancel)).subscribe((event: NavigationCancel) => {
this.router.navigate(['/login'], {
queryParams: {
redirectTo: event.url
}
});
});
return false;
}
}
information available on
this.router.getCurrentNavigation().extractedUrl
relates to this issue https://github.com/angular/angular/issues/30633
I'm using
this.router.getCurrentNavigation().extractedUrl.toString()
to access the full route, since
segments.reduce((path, currentSegment) => {
return `${path}/${currentSegment.path}`;
}, '')
will not provide the full path if you have nested routes (only child routes).
I have two states in my application -- /auth and /masters. the latter is the state where i wld like to direct the user only once he or she has been authenticated.
So, i understand that we can use '$urlRouterProvider.otherwise' to configure a default state in the application to /auth. So the foll is my code:
angular.module('ngClassifieds', ['ngMaterial', 'ui.router', 'firebase'])
.config(function($mdThemingProvider, $stateProvider, $urlRouterProvider) {
$mdThemingProvider
.theme('default')
.primaryPalette('blue-grey')
.accentPalette('orange');
$urlRouterProvider.otherwise('/auth');
$stateProvider
.state('auth', {
url: '/auth',
templateUrl: 'components/auth/auth.tpl.html',
controller: 'authCtrl'
})
$stateProvider
.state('masters', {
url: '/masters',
templateUrl: 'components/classifieds.tpl.html',
controller: 'classifiedsCtrl'
});
});
Now, if i enter, for example, anything other than /masters, i am directed to /auth; however, if i enter /masters, i am not directed to /auth.
i was made to understand that i need to look for AUTH_REQUIRED error in Firebase (https://www.firebase.com/docs/web/libraries/angular/guide/user-auth.html) in order to achieve the desired result. However, i feel i'm punching above my weight in trying to incorporate the functionality. So i'd appreciate if you can provide me some guidane. This is how i have tried to refactor the above code, but it's a mess:
angular.module('ngClassifieds', ['ngMaterial', 'ui.router', 'firebase'])
.run(["$rootScope", "$state", function($rootScope, $state) {
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
// We can catch the error thrown when the $requireAuth promise is rejected
// and redirect the user back to the home page
if (error === "AUTH_REQUIRED") {
$state.go("auth");
}
});
}]);
.config(function($mdThemingProvider, $stateProvider, $urlRouterProvider) {
$mdThemingProvider
.theme('default')
.primaryPalette('blue-grey')
.accentPalette('orange');
$urlRouterProvider.otherwise('/auth');
$stateProvider
.state('auth', {
url: '/auth',
templateUrl: 'components/auth/auth.tpl.html',
controller: 'authCtrl',
resolve: {
// controller will not be loaded until $waitForAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth", function(Auth) {
// $waitForAuth returns a promise so the resolve waits for it to complete
return Auth.$waitForAuth();
}]
}
})
$stateProvider
.state('masters', {
url: '/masters',
templateUrl: 'components/classifieds.tpl.html',
controller: 'classifiedsCtrl',
resolve: {
// controller will not be loaded until $requireAuth resolves
// Auth refers to our $firebaseAuth wrapper in the example above
"currentAuth": ["Auth", function(Auth) {
// $requireAuth returns a promise so the resolve waits for it to complete
// If the promise is rejected, it will throw a $stateChangeError (see above)
return Auth.$requireAuth();
}]
}
});
});
I try to build dynamically routes from angular2 (fetch route config from server), after that I parse it and generate instruction for component route (I have parent routes config and child into different components, because I don`t know how define route for child component into one main.app.ts file).
The problem is when app started and try to create routes config and routeGenerator is not build routes yet (async delay) cant parse routes data (because async delay, so routesData undefined now) and app is crashig. I dont know what to do with this. Looking for lifecycle hood (some like - #Angular2BeforeAppStarted ) but found nothing.
import {Component, Input, OnChanges} from 'angular2/core';
import {RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, Router} from 'angular2/router';
/* ------- !Angular 2 native components ---------*/
import {routeGenInstance} from '../../config/routes/patient_routes';
protected const BUILT_MODULE_PATH: string = '/built/modules/patients/';
#Component({
template: `
<router-outlet ></router-outlet>
`,
directives: [RouterOutlet, ROUTER_DIRECTIVES]
})
#RouteConfig(routeGenInstance.getRouteDefinitions())
export class PatientsComponent {
#Input();
constructor() {}
}
Also i try to update routes in the same way (but app is crashed immediately because my Navigation link in navigation component is not have some correct link way)
import {RouteConfig, RouterOutlet, ROUTER_DIRECTIVES, Router} from 'angular2/router';
constructor(
private router: Router
) {
router.config([
routeGenInstance.getRoutesDefinition()
])
}
my route definitions use Async loader so they are correct and work whithout async delay. I don`t know how to make angular wait for my routes definitions and thet start to run the app.
Please, help me. Thanks.
UPD:
#Thierry many thanks for your help again. You are awesome my friend and mentor. One last question (last). Can you tell me how I can define routeConfig into one app file with child subrouting definition?
Its mean. I have main level routes into app files
{
path: '/',
name: 'Dashboard',
component: DashboardComponent,
useAsDefault: true
},
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent
},
and patient sub routes into patientsComponent (#RouteConfig)
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...},
{
"name": "Chart",
"path": "/chart/:id", component...
},
How to define this route config only into one app.file ? (How to configure route with sub routing in one file)?
An option could be to get your configuration before bootstrapping your application.
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
http.get('routes.json').map(res => res.json())
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('routesConfig', { useValue: data })
]);
});
Then you can have access the routes configuration by dependency injection and in a synchronous way:
#Component({
(...)
})
export class AppComponent {
constructor(#Inject('routesConfig') private routesConfig, private router:Router) {
// Configure here your routes
}
}
These two questions could help you:
How to bootstrap an Angular 2 application asynchronously
angular2 bootstrap with data from ajax call(s)
Edit
You can leverage the Observable.forkJoin method to load your route configuration from different requests:
var injector = Injector.resolveAndCreate([HTTP_PROVIDERS]);
var http = injector.get(Http);
Observable.forkJoin([
http.get('mainRoutes.json'),
http.get('childRoutes.json')
])
.map(responses => {
return {
main: res[0].json(),
children: res[1].json()
};
})
.subscribe(data => {
bootstrap(AppComponent, [
HTTP_PROVIDERS
provide('routesConfig', { useValue: data })
]);
});
Edit1
I think that you could try something like that:
[
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent,
childRoutes: [
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...
},
(...)
]
}
]
But you need to split the content to get different elements according to the hints you want to handle:
one for the root:
[
{
path: '/patients/...',
name: 'Patients',
component: PatientsComponent
}
]
several for children. For example for patients:
[
{
path: '/', // root is appRoot/patients/...
name: 'PatientsList', component...
},
(...)
]
In the new router (>= RC.3) https://angular.io/docs/js/latest/api/router/index/Router-class.html#!#resetConfig-anchor resetConfig can be used
router.resetConfig([
{ path: 'team/:id', component: TeamCmp, children: [
{ path: 'simple', component: SimpleCmp },
{ path: 'user/:name', component: UserCmp }
] }
]);
See also https://github.com/angular/angular/issues/9472#issuecomment-229230093
You can load components asynchronously by providing a SystemJsComponentResolver.
Right now, you can load routes asynchronously and imperatively update the configuration using resetConfig.
Once AppModules are in master, we will utilize those to implement async loading of subconfigs.
https://github.com/angular/angular/issues/11437#issuecomment-245995186 provides an RC.6 Plunker
Check this:
DynamicalAsyncRouter
https://github.com/Longfld/DynamicalAsyncRouter
Using Observables/Promises to provide route translations is not a reliable solution, hence the Angular router expects Route[] or Routes, but an HTTP request can only return an Observable/Promise.
The Angular app gets initialized, but the retrieval process of route translations still goes on using Observables/Promises.
As Thierry Templier said, to get your configuration before bootstrapping your application would solve the problem.
Also, check the #ngx-i18n-router/core on github.