I've added a canLoad guard to a state that's lazy loaded. The problem that I'm having is that I can't get any route parameters if the state is being initialized from a different state using router.navigate().
So here is my route configuration:
path: 'programs/:programId/dashboard',
loadChildren: './program.module#ProgramModule',
canLoad: [ProgramGuard]
and this is the short version of ProgramGuard:
export class ProgramGuard implements CanLoad {
canLoad(route: Route): Observable<boolean> {
//route object doesn't have any reference to the route params
let programId = paramFromRoute;
return Observable.create(observer => {
if (programId == authorizedProgramId)
observer.complete(true);
else
observer.complete(false);
}
}
}
I have tried injecting ActivatedRoute to try to get them from there to get it from there, but nothing.
If the user types the URL in the browser, then there is no problem because I can extract the parameters from the location object. But when using route.navigate, the browser's location is still set to the previous state.
Any help or ideas will be greatly appreciated.
I tried to do something similar and ended up changing to a canActivate guard instead. Note also that the canLoad guards block any preloading that you may want to do.
In theory, if a user could not access a route, it would be great to not even load it. But it practice it seems to be too limited to allow making a determination.
Something you could try (I didn't think of it earlier when I was trying to do this) ... you could add a parent route (component-less) that has a canActivate guard that can check the parameters. Then route to the lazy loaded route if the user has authorization.
I was able to retrieve the path including the route parameters using The Location object.
canLoad() {
//dont even load the module if not logged in
if (!this.userSessionService.isSessionValid()) {
this.userSessionService.redirectUrl = this.location.path();
this.router.navigate(['/auth']);
return false;
}
return true;
}
You just need to inject the Location object in the constructor.
Now you can access the queryparams by using this snippet
this.router.getCurrentNavigation().extractedUrl.queryParams
inside the canLoad method and without losing lazyloading feature
I know its too late, but I found the solution that work like charm.
I hope this will help new members who face the same problem like me
canLoad(
route: Route,
segments: UrlSegment[]): Observable<boolean> | Promise<boolean> | boolean {
if (!this.auth.isLoggedIn()) {
this.route.navigate(['auth/login'], { queryParams: { redirect_url: '/' + segments[0].path } });
return false;
}
return true;
}
Why not building the url from the paths of the segments?
/**
* Test if the user as enough rights to load the module
*/
canLoad(route: Route, segments: UrlSegment[]): boolean | Observable<boolean> | Promise<boolean> {
// We build the url with every path of the segments
const url = segments.map(s => s.path).join('/')
// We handle the navigation here
return this.handleNavigation(url, route)
}
first you can declare variable Like the following :
routeSnapshot: ActivatedRouteSnapshot;
then in constructor call ActivatedRouteSnapshot class Like the following :
constructor(private routeSnapshot: ActivatedRouteSnapshot)
now you can use this.routeSnapshot into canLoad method
Related
I am using Ionic 6 and Angular 12 and I have couple pages in the app flow. I wen to page_4 and when I checked my DOM structure it looks like:
<ion-router-outlet>
<page_1></page_1>
<page_2></page_2>
<page_3></page_3>
<page_4></page_4>
</ion-router-outlet>
After page_4 I want to navigate to new page_2 and send some data via state object:
this.router.navigateByUrl('/page_2', { state: {dataHasChanged: true} });
I expected:
Ionic will create again new <page_2></page_2> tag into DOM bellow <page_4></page_4>
I can retrieve state data like I usually do:
this.router.getCurrentNavigation().extras?.state?.dataHasChanged
But result is different:
App back to the previous <page_2></page_2> and my DOM structure looks like:
<ion-router-outlet>
<page_1></page_1>
<page_2></page_2>
</ion-router-outlet>
this.router.getCurrentNavigation().extras is null and cannot see my data here. But I can see state data into windows.history.state object
Can someone please explain me why this happened or at least refer me to some valuable documentation ?
If you enter a page that has already been loaded, it will not load again, or at least, not always.
You can celar or update the content of the page inside ionViewWillEnter.
check this
add it in your page.ts
ionViewWillEnter() {
...
}
To solve the problem of navigation extras empty you can share data with a Service like this. It's not the cleanest way but it works
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root'
})
export class DataShareService {
private __dataDict = {};
setData(key: string, data: any) {
this.__dataDict[key] = data;
}
getData(key: string) {
return this.__dataDict[key];
}
}
My app will use two different auth strategies - one for users using a browser and another for the public API. I'll set a header for those using a browser, and then my app will set the auth strategy based on the value of that header.
I have set up the two auth strategies, and given them names. I can now do this in my controller methods:
#Get()
#UseGuards(AuthGuard('strategy_name'))
async find() { }
What I would like to do, is NOT have to specify the auth guard type next to every controller method, nor the logic for determining which type to use. Instead, I'd like to put this logic in one place, which will be read by ALL calls to AuthGuard().
What's the best way to do this? Is there some kind of filter/hook/interceptor for AuthGuard?
You can create a new Guard that acts as a delegate and chooses the proper AuthGuard
(and therewith AuthStrategy) based on your condition.
#Injectable()
export class MyAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const guard = this.getAuthGuard(context);
return guard.canActivate(context);
}
private getAuthGuard(context: ExecutionContext): IAuthGuard {
const request = context.switchToHttp().getRequest();
// Here should be your logic to determine the proper strategy.
if (request.header('myCondition')) {
return new (AuthGuard('jwt'))();
} else {
return new (AuthGuard('other-strategy'))();
}
}
Then use it instead of the standard AuthGuard in your controller:
#UseGuards(MyAuthGuard)
#Get('user')
getUser(#User() user) {
return {user};
}
I would like to provide a path that redirects to a given page based on query parameters. For example:
/redirect?page=hero&id=1
should redirect to:
/hero/1
Is there any way to do this in the route config? Something like:
{ path: 'redirect?page&id', redirectTo: ':page/:id' }
I can get the redirectTo to respect path parameters but not query parameters. Any ideas?
You can try to use redirectTo: '/:page/:id' and provide extracted from your URL page and id values using custom UrlMatcher():
...
const appRoutes: Routes = [
{
path: 'hero/:id',
component: TestComponent
},
{
matcher: redirectMatcher,
redirectTo: '/:page/:id'
}
];
...
/**
* custom url matcher for router config
*/
export function redirectMatcher(url: UrlSegment[]) {
if (url[0] && url[0].path.includes('redirect')) {
const path = url[0].path;
// sanity check
if (path.includes('page') && path.includes('id')) {
return {
consumed: url,
posParams: {
page: new UrlSegment(path.match(/page=([^&]*)/)[1], {}),
id: new UrlSegment(path.match(/id=([^&]*)/)[1], {})
}
}
}
}
return null;
}
STACKBLITZ: https://stackblitz.com/edit/angular-t3tsak?file=app%2Ftest.component.ts
There is another issue when using redirectTo: ..., active link is not updated, actually isActive flag is not set to true, it is seen on my stackblitz when acrive redirection links are not colored in red
No, there is no way of doing it by a configuration. YOu see, Angular's router does not explicitly define query parameters - any url can have an arbitrary number of query params, and the paths '/page/id' and '/page/id?key=value' are treated as the same in Angular and do map to the same component. There are other, more cumbersome workarounds. One is to create a dummy component and redirect based on ActivatedRoute.queryParams Observable from the component's ngOnInit method. You can easily see why this is a bad idea.
Another way is to create a resolver, this way you maybe can dismiss the component declaration and just redirect from the resolver, again, based on the ActivatedRoute.queryParams Observable, which seems cleaner.
But I do not really get why one would need such a route in a front end application, if you want someone to visit '/page/id', then just navigate them to the page, without any intermediary tricks.
I am using ember 2.7.0.while manually refreshing the page ember clears the ember-data as well us query parameters, so i am unable to load the page in setupController while refreshing. Is there any possible way to retain both model & query parameters, at least retaining query parameter would be fine to reload my page.
route.js
model(params) {
return this.store.peekRecord("book",params.book_id);
},
setupController(controller,model,params){
if(!model){
//fetch book record again if the model is null
}
controller.set('isdn',params.queryParams.isdn);
controller.set('book',model);
}
Any help should be appreciable.
Edited setupController as per Adam Cooper comment :
setupController(controller,model,params){
var isdn = params.queryParams.msisdn;
controller.set('isdn',isdn);
if(!model){
this.store.findRecord('book', isdn).then((customer) => {
this.set('book',customer);
},(resp,status) => {
this.set('errorMessage', `Book with this ${isdn} does not exist.`);
this.set('book', []);
});
}else{
controller.set('device',model);
}
}
Page gets rendered before "findRecord" returning promise.Is there any way to stop page rendering till find record resolves the promise?
You are setting in route properties instead of controller..
setupController(controller, model, params){
var isdn = params.queryParams.msisdn;
controller.set('isdn', isdn);
if(!model){
this.store.findRecord('book', isdn).then((customer) => {
controller.set('book', customer);
}, (resp, status) => {
controller.set('errorMessage', `Book with this ${isdn} does not exist.`);
controller.set('book', []);
});
}else{
controller.set('device', model);
}
}
Only the controller properties will decorate template.
You can even try the below, why don't you give opportunity to model hook to resolve since that will wait for the Promises to resolve.
model(params) {
var result = this.store.peekRecord("book",params.book_id);
if(result !== null){
result= this.store.findRecord('book', params.book_id)
}
return result;
}
setupController(controller,model){
controller.set('book',model);
}
You will need to generate an actual controller for your route and then define a queryParams property in the controller. It looks like the query param you're trying to hold onto is isdn so your controller should look something like:
export default Ember.Controller.extend({
queryParams: ['isdn']
});
"manually refreshing the page ember clears the ember-data as well us query parameters"
Once you completely refresh the browser, a new ember app instance is created and hence ember-data cannot be retained. Ember-data is just for the app on the UI, once ember is exited it will not be retained.
"as well us query parameters"
your query params are part of your url and it should not get cleared. Make sure the below two are present
Include queryParams in ur controller i.e.
queryParams: ['param1', 'param2']
And in your route make sure you have done
queryParams : {
param1: {
refreshModel: true
},
param2: {
refreshModel: true
}
}
"Page gets rendered before "findRecord" returning promise"
You are not doing something right, is the adapter, model, serializer etc defined correctly(if required) in order to use findRecord? Just to debug return a plain object and make sure ur setupController is called before rendering. i.e.
model() {
return {dummy: 'dummy'};
}
I'd like an Ember path /clinic/1 to automatically redirect to show the first doctor: /clinic/1/doctor/1.
Each clinic has many doctors.
Unfortunately if I use this code:
var doctor = clinicController.get('content.doctors.firstObject');
router.transitionTo('clinic.doctor.index', doctor);
... it does not work, as content.doctors.length is still 0 when this code runs in app_router.js.
Any ideas?
You should be able to do this:
App.DoctorsRoute = Ember.Route.extend({
model: function() {
return App.Doctor.find();
},
redirect: function() {
var doctor = this.modelFor('doctors').get('firstObject');
this.transitionToRoute('doctor', doctor);
}
});
This will work because:
If the model hook returns an object that hasn't loaded yet, the rest of the hooks won't run until the model is fully loaded.
If the redirect hook transitions to another route, the rest of the hooks won't run.
Note that as of 2426cb9, you can leave off the implicit .index when transitioning.
Redirecting on the Route doesn't work for me in my ember-data based app as the data isn't loaded at the point of redirection, but this does work for me...
In the roles controller I transition to the role route for the firstObject loaded.
Application.RolesController = Ember.ArrayController.extend({
selectFirstObject: function () {
if (this.get('content').get('isLoaded')) {
var role = this.get('firstObject');
this.transitionToRoute('role', role);
}
}.observes('content.isLoaded')
});
HTH, gerry
As an update, if you don't want to redirect because you have a nested route, you'll want to use conditional redirect based on the intended route.
redirect has two arguments passed to it, the model and the transition object. The transition has the property targetName where you can conditionally redirect based on its value.
redirect: function(model, transition){
if(transition.targetName ==='doctors.index'){
this.transitionTo('doctor', model.get('firstObject'));
}
}
For EmberCLI users, you'd achieve the same by doing the following:
//app/routes/clinics/show.js
import Ember from 'ember';
export default Ember.Route.extend({
redirect: function(model) {
var firstDoctor = model.get('doctors.firstObject');
this.transitionTo('doctor', firstDoctor);
}
});