How to subscribe to param changes in child route? - javascript

I have these routes
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{
path: 'explore',
component: ExploreComponent,
children: [
{ path: '', component: ProductListComponent },
{ path: ':categorySlug', component: ProductListComponent }
]
}
];
This means that the user can go to
/explore (no category)
or
/explore/computers (category computers)
From the parent (ExploreComponent), I want to be able to subscribe to the categorySlug param change, and handle the event of course. How can I do this?
EDIT:
I tried subscribing using:
this.activatedRoute.firstChild.params.subscribe(console.log);
And it gives me exactly what I want, but it dies once I go to /explore (without category). It only works when navigating using /explore/:categorySlug links.

You can subscribe to the params in your component ang get the parameter, e.g. like this:
import { Router, ActivatedRoute } from '#angular/router';
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.route.params.subscribe(params => {
this.categorySlug= params['categorySlug '];
});
// do something with this.categorySlug
}
Side note: In general you use a kind of master detail structure in your web app, so the first path goes to the master and the second one goes to the detail, each served with a different component, but in case that you want to use the same component for both of them, or there is no such a master-detail relationship, you should check if the parameter is null.

Related

CanActivate guard for multiple users in angular

I've got a working canActivate auth-guard like this:
return this.auth.user$.map(user => {
if (user) return true;
this.router.navigate(["/login"], {queryParams: {returnUrl: state.url}});
return false;
});
}
and use it for routes like this:
{path: 'my-project', component: ToolComponent, canActivate: [AuthGuard]}
i want to have multiple users be able to work on the same project
example:
User A & B on project 1
User C & B on project 2
and so on
what's the best way to to this?
ty!
the "typical" is assing to each user a "role" (admin/user), but in your case I imagine you has severals projects. So your "user" should has a property with the project can access and the path of your router can add "data". Some like
{ path: 'project1/ComponentOne', component: Component1,
canActivate: [CheckGuard],data:{project:"project1"} },
Your "checkGuard can take account the "user" and also the "data"
#Injectable()
export class CheckGuard implements CanActivate {
constructor(private router: Router,private accountService:AccountService) { }
canActivate(activatedRouteSnapshot: ActivatedRouteSnapshot,
routerStateSnapshot: RouterStateSnapshot) {
//in activatedRouteSnapshot.data you has the "data" of the router
console.log(activatedRouteSnapshot.data)
...your logic here..
}
}

Angular 6 - navigation to child route refreshes whole page

So I'm using Angular 6 and I'm trying to navigate to a child route from the parent route. The navigation is successful, however there is an unwanted page refresh upon rendering the child component. In other words, the navigation works but it also refreshes the page for no apparent reason. Here is my code:
const appRoutes: Routes = [
{
path: "parent/:param1/:param2", component: ParentComponent,
children: [
{ path: ":param3", component: ChildComponent }
]
},
{ path: "", redirectTo: "/index", pathMatch: "full" },
{ path: "**", redirectTo: "/index" }
];
My parent component looks like this:
import { Component } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "my-parent",
templateUrl: "./parent.component.html"
})
export class ParentComponent {
param1: string;
param2: string;
loading: boolean;
tutorials: any[];
constructor(public route: ActivatedRoute) {
this.loading = true;
this.param1= this.route.snapshot.params.param1;
this.param2 = this.route.snapshot.params.param2;
// get data here
}
}
And my child component looks like this:
import { Component } from "#angular/core";
import { ActivatedRoute } from "#angular/router";
#Component({
selector: "my-child",
templateUrl: "./child.component.html"
})
export class ChildComponent {
param1: string;
param2: string;
param3: string;
loading: boolean;
result: any;
constructor(public route: ActivatedRoute) {
this.loading = true;
this.param1= this.route.snapshot.params.param1;
this.param2 = this.route.snapshot.params.param2;
this.param3 = this.route.snapshot.params.param3;
}
}
Now, the way I try to navigate from the parent component to the child component is the following one:
<a [routerLink]="['/parent', param1, param2, param3]">
<b>Navigate</b>
</a>
As I've said, the navigation is successful, but there is an unwanted page refresh which I want to get rid of and I haven't been able to find a working solution. I don't really know what's causing it. I am new to Angular 6.
Thanks in advance for your answers.
EDIT: added parent component html
<router-outlet></router-outlet>
<div class="row" *ngIf="route.children.length === 0">
// content here
</div>
So I found a working solution, which while not very elegant, it... works. In my parent component I created a method like this one:
constructor(public route: ActivatedRoute, private router: Router) {
this.loading = true;
this.param1 = this.route.snapshot.params.param1;
this.param2 = this.route.snapshot.params.param2;
// get data
}
navigateToChild(param3: string) {
this.router.navigate([param3], { relativeTo: this.route });
}
And in the parent template, I did this:
<a (click)="navigateToChild(paramFromServer)">
<b>Navigate</b>
</a>
No more refreshes for this one.
Thank you for your help everyone.
Remove the leading / from [routerLink]= "['/parent'...]" url. The / is telling the app to find the component route from the root of the application whereas no leading / will try to redirect to the child relative to the current component.
Also make sure you have added a <router-outlet> to the parent.component.html as that is where the child component will first try to be added on navigate. If that is not available it might be causing a full refresh to load in the new component from scratch.
const appRoutes: Routes = [
{
path: "parent/:param1/:param2", component: ParentComponent,
children: [
{ path: ":param3", component: ChildComponent }
]
},
// remove this 2 lines
// redirect to index thing is not needed
];
You didn't define param3 in your ParentComponent. Also you may need to change the strategy of params so your ChildComponent can retrieve the params from its parent.
Please check this stackblitz:
https://stackblitz.com/edit/angular-tvhgqu
In my case 'href' was the problem. Using routerLink solved the problem.
Problematic Approach:
<a href='/dashboard/user-details'>User</a>
Solution:
<a routerLink='/dashboard/user-details'>User</a>

Unable to read Route Parameter in Angular 2

I have successfully implemented route parameter in Angular JS for my other components in same project and for the new component also I am following the same way but It's not working and I am unable to understand the problem in the code.
Below is the code of my routes file
import { Routes, RouterModule } from '#angular/router';
import { CustomerBillComponent } from '../components/customer_bill.component';
const routes: Routes = [
{ path: 'add-bill', component: CustomerBillComponent },
{ path: 'add-bill/:customer_reference', component: CustomerBillComponent },
];
export const routing = RouterModule.forRoot(routes);
(I tried using different routes also, but it didn't work)
Below is the code in My CustomerBillComponent.ts file
ngOnInit() {
//When I visit /#/add-bill/CR452152
let route_location = location['hash'].split('/')[1].toLowerCase();
console.log(route_location); //prints add-bill in Console
var customer_reference = this.route.snapshot.params['customer_reference'];
console.log(customer_reference); //prints undefined
}
Did I miss something
Thanks in advance!!!
I face almost the same problems. But the reason was different.
In my scenario, the lending page(Component Specified on routing) was different and I was trying to access the route params on the parent Route Component.
Below is the code to demonstrate my scenario:
home.module.routing.ts
import { NgModule } from '#angular/core';
import { Routes, RouterModule } from '#angular/router';
//other import statements...
const routes: Routes = [
{
path: 'home', component: HomeComponent,
children: [
{ path: 'buy/:mainType/:subType', component: BuyComponent, data: {} },
{ path: 'buy/:mainType', component: BuyComponent, data: { } },
{ path: '', redirectTo: 'buy', pathMatch: 'full' },
]
},
{ path: 'checkout', component: CheckoutCartComponent },
];
#NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class HomeRoutingModule { }
So, I had to access the RouteParam values in HomeComponent(Parent) and the Lending page was BuyComponent(Child Route of HomeComponent).
So, I was not getting values of route params using below code:
//Inside HomeComponent.ts
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.routeText = params['category'];
this.title = this.formatTitle(this.routeText);
});
}
As the lending page is BuyComponent, the above code will not work. Angular allows to access the route params in the above way only on the landing Component.
I tried a lot and finally got a solution to access the Route Params in parent route Component.
Below is the code to access the same:
//Inside HomeComponent.ts
const routeChildren = this.activatedRoute.snapshot.children;
if (routeChildren.length) {
//Your code here to assign route params to the Component's Data Member.
}
So, accessing this.activatedRoute.snapshot.children instead of this.route.params did the trick for me.
Hope this will help if someone lands here in search of the problem like me.
Best Regards.
You need to add ModuleWithProviders. It's a wrapper for module provider.
import {ModuleWithProviders} from '#angular/core';
export const routing: ModuleWithProviders = RouterModule.forRoot(router);
First try:
this.route.params.subscribe(
(params) => {
console.log(params);
}
)
And see what you get. If that doesn't work it's likely that you try to access a parent route's parameter, in which case you have to step up in the router tree in order to gain access to it.
You can do this with the .parent property, like such:
this.route.parent.params.subscribe(
(params) => {
console.log(params);
}
)
As many times as you need.

Linking directly to both parent+child views/controllers from the main navigation menu

Consider this example:
The main router is located in
app.js
someparent/childroute1
someparent/childroute2
route3
"someparent" is the "base controller and view". It has some reusable html markup in the view, custom elements and bindings which is to be shared by all its "child views and controllers". The child views and controllers will access these.
Inside "someparent.html" there's (besides the shared markup) also a <router-view> tag, in which the child routes and pages should be rendered inside, but there's no navigation inside someparent.html.
From the main router/routes in app.js it should be possible to click a link and land - not on the parent/base class "someparent" itself, but directly on the children of the "someparent" "base/parent views and controllers", rendering both, when you click a link in the navigation menu of app.html built from the routes in app.js (and maybe routes in someparent.js injecting the child router in the parent or what?).
So essentially what I need is to achieve almost the same thing as basic routing - but as I mentioned I need to have multiple of these routes / pages as partials of a parent view/controller. I couldn't find any info on this from googling extensively for weeks, so hopefully someone in here will be able to understand what I ask, and have an idea of how to go about this in Aurelia, the right way?
Create a class to contain your shared state and take a dependency on that class in your view-models. You can use the NewInstance.of resolver to control when shared state is created vs reused.
Here's an example: https://gist.run?id=4cbf5e9fa71ad4f4041556e4595d3e36
shared-state.js
export class SharedState {
fromdate = '';
todate = '';
language = 'English';
}
shared-parent.js
import {inject, NewInstance} from 'aurelia-framework';
import {SharedState} from './shared-state';
#inject(NewInstance.of(SharedState)) // <-- this says create a new instance of the SharedState whenever a SharedParent instance is created
export class SharedParent {
constructor(state) {
this.state = state;
}
configureRouter(config, router){
config.title = 'Aurelia';
config.map([
{ route: ['', 'child-a'], moduleId: './child-a', nav: true, title: 'Child A' },
{ route: 'child-b', moduleId: './child-b', nav: true, title: 'Child B' },
]);
this.router = router;
}
}
note: if you use #inject(SharedState) instead of #inject(NewInstance.of(SharedState)), a single instance of SharedState will be shared with all components. This may be what you are looking for, I wasn't sure. The purpose of #inject(NewInstance.of(SharedState)) is to make sure the parent and it's children have their own SharedState instance.
child-a.js
import {inject} from 'aurelia-framework';
import {SharedState} from './shared-state';
#inject(SharedState)
export class ChildA {
constructor(state) {
this.state = state;
}
}
child-b.js
import {inject} from 'aurelia-framework';
import {SharedState} from './shared-state';
#inject(SharedState)
export class ChildB {
constructor(state) {
this.state = state;
}
}
After better understanding the original question, I would propose the following solution, which takes advantage of the "Additional Data" parameter available in Aurelia's router. For more information, see http://aurelia.io/hub.html#/doc/article/aurelia/router/latest/router-configuration/4
app.js
configureRouter(config, router) {
this.router = router;
config.title = 'My Application Title';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'homefolder/home' },
{ route: 'someparent/child1', name: 'child1', moduleId: 'someparentfolder/someparent', settings: {data: 'child1'} },
{ route: 'someparent/child2', name: 'child2', moduleId: 'someparentfolder/someparent', settings: {data: 'child2'} },
{ route: 'someparent/child3', name: 'child3', moduleId: 'someparentfolder/someparent', settings: {data: 'child3'} }
]);
}
someparent.js
import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
#inject(Router)
export class SomeParent {
constructor(router) {
this.router = router;
}
}
someparent.html
<template>
<require from="./child1"></require>
<require from="./child2"></require>
<require from="./child3"></require>
<h1>Some Parent Page Title</h1>
<div if.bind="router.currentInstruction.config.settings.data == 'child1'">
<h2>Child Component 1</h2>
<child-component-one linkeddata.bind="child1"></child-component-one>
</div>
<div if.bind="router.currentInstruction.config.settings.data == 'child2'">
<h2>Child Component 2</h2>
<child-component-two linkeddata.bind="child2"></child-component-two>
</div>
<div if.bind="router.currentInstruction.config.settings.data == 'child3'">
<h2>Child Component 3</h2>
<child-component-three linkeddata.bind="child3"></child-component-three>
</div>
</template>
Additional thoughts:
I tested the above and it works. Hopefully by using the route settings data parameters you can "get the message through" to the parent router as to which child you want displayed. Depending on your specific application, you may prefer to implement this as a sub-router, but simply binding/unbinding the individual child views as I've demonstrated above is a simple solution. It also shows you how to access the extra parameters you can supply with each route in Aurelia's router.
I'm still relatively new to Aurelia (about 3 months) so there might be a more "expert" answer out there, but what you're trying to do is quite basic. Remember that Aurelia is based completely on components, to the point that every component is basically an element on a page. When rendering the "parent" view/controller, your "child" view/controllers are just elements on that parent page. So you only need to render the parent page and ensure that the child pages are linked correctly.
Router (in app.js):
configureRouter(config, router) {
this.router = router;
config.title = 'My Application Title';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'homefolder/home' },
{ route: 'someparent', name: 'someparentnamefornamedroutes-optional', moduleId: 'someparentfolder/someparent' },
]);
}
Parent ViewModel (in someparentfolder/someparent.js)
// imports go here, like: import { inject } from 'aurelia-framework';
// injects go here, like: #inject(MyClass)
export class SomeParent {
child1 = {
fname: "Debbie",
lname: "Smith"
};
constructor() {
}
}
Parent View (in someparentfolder/someparent.html)
<template>
<require from="./child1"></require>
<require from="./child2"></require>
<h1>Some Parent Page Title</h1>
<h2>Child Component 1</h2>
<child-component-one linkeddata.bind="child1"></child-component-one>
<h2>Child Component 2</h2>
<child-component-two></child-component-two>
</template>
Child 1 ViewModel (in someparentfolder/child1.js)
import { inject, bindable, bindingMode } from 'aurelia-framework';
import { Core } from 'core';
#inject(Core)
export class ChildComponentOne { // use InitCaps, which will be translated to dash case by Aurelia for the element ref in SomeParent
#bindable({ defaultBindingMode: bindingMode.twoWay }) linkeddata;
constructor(core) {
this.core = core;
}
attached() {
// example calling core function
var response = this.core.myCustomFunction();
}
}
Child 1 View (in someparentfolder/child1.html)
<template>
<h3>Hello, ${linkeddata.fname}</h3>
<p>Here's something from core: ${core.value1}</p>
</template>
(Use same concepts for Child 2 ViewModel and View)
Navigation Directly to Child Components:
The above scenario has each of the child components "embedded" in the SomeParent page. However, if you want to simply open each Child Component as its own navigation router view (to open directly in your main <router-view></router-view> content window), just use a router definition like this:
configureRouter(config, router) {
this.router = router;
config.title = 'My Application Title';
config.map([
{ route: ['', 'home'], name: 'home', moduleId: 'homefolder/home' },
{ route: 'someparent', name: 'someparent', moduleId: 'someparentfolder/someparent' },
{ route: 'someparent/child1', name: 'child1', moduleId: 'someparentfolder/child1' },
{ route: 'someparent/child2', name: 'child2', moduleId: 'someparentfolder/child2' },
{ route: 'someparent/child3', name: 'child3', moduleId: 'someparentfolder/child3' }
]);
}
One More Scenario:
Perhaps what you're looking for is a Singular class that contains a common place to store state and common functions that all of your components will access. I implemented this by creating a model called core.js. I've added those details above, and you would also create the following file in your project root (/src) folder.
Core Class (in /src/core.js):
// Some example imports to support the common class
import { inject, noView } from 'aurelia-framework';
import { HttpClient, json } from 'aurelia-fetch-client';
import { I18N } from 'aurelia-i18n';
import { EventAggregator } from 'aurelia-event-aggregator';
#noView // this decorator is needed since there is no core.html
#inject(EventAggregator, I18N, HttpClient)
export class Core {
value1 = "Test data 1";
value2 = "Test data 2";
constructor(eventAggregator, i18n, httpClient) {
// store local handles
this.eventAggregator = eventAggregator;
this.i18n = i18n;
this.httpClient = httpClient;
}
myCustomFunction() {
// some code here, available to any component that has core.js injected
}
}
Notes on Binding:
I gave you an example of how to set up two-way binding to the child component with a JavaScript object. Aurelia is very flexible and you could do this a lot of different ways but this seems to be fairly standard. If you don't need the child to manipulate the data contained in child1, you could delete the parenthetical notes on the #bindable decorator so it would simply be #bindable linkeddata;
You can add multiple parameters to link more than one piece of data, or group them into an object or array.
Since I'm still a relatively new user, I remember going through all of this. Please let me know if you have any follow-up comments or questions.
And by all means, if there are any true experts watching this, please teach me as well! :-)

Angular 2 router.navigate

I'm trying to navigate to a route in Angular 2 with a mix of route and query parameters.
Here is an example route where the route is the last part of the path:
{ path: ':foo/:bar/:baz/page', component: AComponent }
Attempting to link using the array like so:
this.router.navigate(['foo-content', 'bar-contents', 'baz-content', 'page'], this.params.queryParams)
I'm not getting any errors and from what I can understand this should work.
The Angular 2 docs (at the moment) have the following as an example:
{ path: 'hero/:id', component: HeroDetailComponent }
['/hero', hero.id] // { 15 }
Can anyone see where I'm going wrong? I'm on router 3.
If the first segment doesn't start with / it is a relative route. router.navigate needs a relativeTo parameter for relative navigation
Either you make the route absolute:
this.router.navigate(['/foo-content', 'bar-contents', 'baz-content', 'page'], this.params.queryParams)
or you pass relativeTo
this.router.navigate(['../foo-content', 'bar-contents', 'baz-content', 'page'], {queryParams: this.params.queryParams, relativeTo: this.currentActivatedRoute})
See also
https://github.com/angular/angular.io/blob/c61d8195f3b63c3e03bf2a3c12ef2596796c741d/public/docs/_examples/router/ts/app/crisis-center/crisis-detail.component.1.ts#L108
https://github.com/angular/angular/issues/9476
import { ActivatedRoute } from '#angular/router';
export class ClassName {
private router = ActivatedRoute;
constructor(r: ActivatedRoute) {
this.router =r;
}
onSuccess() {
this.router.navigate(['/user_invitation'],
{queryParams: {email: loginEmail, code: userCode}});
}
}
Get this values:
---------------
ngOnInit() {
this.route
.queryParams
.subscribe(params => {
let code = params['code'];
let userEmail = params['email'];
});
}
Ref: https://angular.io/docs/ts/latest/api/router/index/NavigationExtras-interface.html
As simpler as
import { Router } from '#angular/router';
constructor( private router:Router) {}
return(){this.router.navigate(['/','input']);}
Here you will be redirecting to route input .
If you wish to go to particular path with relative to some path then.
return(){this.router.navigate(['/relative','input']);}
Here on return() is the method we will be triggered on a button click
<button (click)=return()>Home

Categories