Angular 6 - navigation to child route refreshes whole page - javascript

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>

Related

How to change the header on parent component according to route

Using Angular 7.x, I want to change my parent header component accordingly to the child routes and if they're activated or not. So in my case
AppComponent
Feature Module <= detect changes here
Child Components of feature module
So my routing is very simple, the app-routing.module just contains the loading of the feature module with loadChildren, nothing fancy here.
Then this feature module contains the routes for the child components. There is one parentComponent, we call it ParentComponent which contains the router-outlet. There is also some headers that I want to change accordingly to the childs.
SO i have two child components: create, and ':id' (detail page). When I trigger these routes I need the parent component to just change their text content accordingly. So for example when the create page is triggered I want to add the header: "Create new item", and for the :id page I want to show "Detail page".
Now, I have figured out I need to subscribe to the router events or on the activatedRoute (or snapshot). I'm at a loss here so I don't really know what to do here.
My parent component looks like this now:
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
#Component({
selector: 'parent-component',
templateUrl: './parent.component.html',
})
export class ParentComponent implements OnInit {
title = 'Parent Title';
// Check if it is possible to change the title according to the route
subtitle = 'Parent subtitle';
constructor(private readonly router: Router, private readonly route: ActivatedRoute) {}
ngOnInit(): void {
this.route.url.subscribe(data => {
if (this.route.snapshot.firstChild) {
console.log('yes', this.route.snapshot.firstChild);
// Change header with a if/else or switch case here
} else {
// Display standard text
console.log('no');
}
});
}
}
this is the output of the console.logs, notice in my real application the parent is named 'timesheets'.
Is there maybe another solution for this? Maybe a service, but all of the information is basically in the route already, so I'm trying to figure out if this is the way to go for my problem.
You can listen NavigationEnd events in ParentComponent or (I think) even better you can use a title service.
Solution 1:
In ParentComponent
import {NavigationEnd, Router} from '#angular/router';
import {filter} from 'rxjs/operators';
...
constructor(private router: Router, private readonly route: ActivatedRoute) {
this.subscribeRouterEvents();
}
subscribeRouterEvents = () => {
this.router.events.pipe(
filter(e => e instanceof NavigationEnd)
).subscribe(() => {
this.title = this.route.snapshot.data['title'];
// Assuming your route is like:
// {path: 'path', component: MyComponent, data: { title: 'Page Title'}}
});
Solution 2:
Using TitleService. Create a service that holds the page title, subscribe to title from ParentComponent and send new title to service from ChildComponent.
TitleService
#Injectable({
providedIn: 'root'
})
export class TitleService {
private defaultTitle = 'Page Title';
private titleSubject: BehaviorSubject<string> = new BehaviorSubject(this.defaultTitle);
public title: Observable<string>;
constructor(private titleService: Title) {
this.title = this.titleSubject.asObservable();
}
public setTitle(title: string) {
this.titleSubject.next(title);
}
}
ParentComponent
pageTitle = 'Page Title';
constructor(private titleService: TitleService) {}
ngOnInit(){
this.titleService.title.subscribe(value => this.pageTitle = value);
}
ChildComponent
pageTitle = 'Child Component Title';
constructor(private titleService: TitleService) {}
ngOnInit(){
this.titleService.setTitle(this.pageTitle);
}
You can try setting the title for a child as part of route like this.
const routes: Routes =[
{
path: 'create',
component: SomeComponent,
data : {header_title : 'some title'}
},
];
ngOnInit() {
this.title = this.route.data.subscribe(x => console.log(x));
}
and get the title in the child component and set that as header title using a service.

How to subscribe to param changes in child route?

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.

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

How to handle query parameters in angular 2

In my routable component I have
#RouteConfig {
{path: '/login', name: 'Login', component: LoginComponent}
}
But how do I get the query params if I go to app_url/login?token=1234?
RouteParams are now deprecated , So here is how to do it in the new router.
this.router.navigate(['/login'],{ queryParams: { token:'1234'}})
And then in the login component you can take the parameter,
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Capture the token if available
this.sessionId = this.route.queryParams['token']
}
Here is the documentation
To complement the two previous answers, Angular2 supports both query parameters and path variables within routing. In #RouteConfig definition, if you define parameters within a path, Angular2 handles them as path variables and as query parameters if not.
Let's take a sample:
#RouteConfig([
{ path: '/:id', component: DetailsComponent, name: 'Details'}
])
If you call the navigate method of the router like this:
this.router.navigate( [
'Details', { id: 'companyId', param1: 'value1'
}]);
You will have the following address: /companyId?param1=value1. The way to get parameters is the same for both, query parameters and path variables. The difference between them is that path variables can be seen as mandatory parameters and query parameters as optional ones.
Hope it helps you,
Thierry
UPDATE: After changes in router alpha.31 http query params no longer work (Matrix params #2774). Instead angular router uses so called Matrix URL notation.
Reference https://angular.io/docs/ts/latest/guide/router.html#!#optional-route-parameters:
The optional route parameters are not separated by "?" and "&" as they
would be in the URL query string. They are separated by semicolons ";"
This is matrix URL notation — something you may not have seen before.
It seems that RouteParams no longer exists, and is replaced by ActivatedRoute. ActivatedRoute gives us access to the matrix URL notation Parameters. If we want to get Query string ? paramaters we need to use Router.RouterState. The traditional query string paramaters are persisted across routing, which may not be the desired result. Preserving the fragment is now optional in router 3.0.0-rc.1.
import { Router, ActivatedRoute } from '#angular/router';
#Component ({...})
export class paramaterDemo {
private queryParamaterValue: string;
private matrixParamaterValue: string;
private querySub: any;
private matrixSub: any;
constructor(private router: Router, private route: ActivatedRoute) { }
ngOnInit() {
this.router.routerState.snapshot.queryParams["queryParamaterName"];
this.querySub = this.router.routerState.queryParams.subscribe(queryParams =>
this.queryParamaterValue = queryParams["queryParameterName"];
);
this.route.snapshot.params["matrixParameterName"];
this.route.params.subscribe(matrixParams =>
this.matrixParamterValue = matrixParams["matrixParameterName"];
);
}
ngOnDestroy() {
if (this.querySub) {
this.querySub.unsubscribe();
}
if (this.matrixSub) {
this.matrixSub.unsubscribe();
}
}
}
We should be able to manipulate the ? notation upon navigation, as well as the ; notation, but I only gotten the matrix notation to work yet. The plnker that is attached to the latest router documentation shows it should look like this.
let sessionId = 123456789;
let navigationExtras = {
queryParams: { 'session_id': sessionId },
fragment: 'anchor'
};
// Navigate to the login page with extras
this.router.navigate(['/login'], navigationExtras);
This worked for me (as of Angular 2.1.0):
constructor(private route: ActivatedRoute) {}
ngOnInit() {
// Capture the token if available
this.sessionId = this.route.snapshot.queryParams['token']
}
(For Childs Route Only such as /hello-world)
In the case you would like to make this kind of call :
/hello-world?foo=bar&fruit=banana
Angular2 doesn't use ? nor & but ; instead. So the correct URL should be :
/hello-world;foo=bar;fruit=banana
And to get those data :
import { Router, ActivatedRoute, Params } from '#angular/router';
private foo: string;
private fruit: string;
constructor(
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.route.params.forEach((params: Params) => {
this.foo = params['foo'];
this.fruit = params['fruit'];
});
console.log(this.foo, this.fruit); // you should get your parameters here
}
Source : https://angular.io/docs/ts/latest/guide/router.html
Angular2 v2.1.0 (stable):
The ActivatedRoute provides an observable one can subscribe.
constructor(
private route: ActivatedRoute
) { }
this.route.params.subscribe(params => {
let value = params[key];
});
This triggers everytime the route gets updated, as well: /home/files/123 -> /home/files/321
The simple way to do that in Angular 7+ is to:
Define a path in your ?-routing.module.ts
{ path: '/yourpage', component: component-name }
Import the ActivateRoute and Router module in your component and inject them in the constructor
contructor(private route: ActivateRoute, private router: Router){ ... }
Subscribe the ActivateRoute to the ngOnInit
ngOnInit() {
this.route.queryParams.subscribe(params => {
console.log(params);
// {page: '2' }
})
}
Provide it to a link:
<a [routerLink]="['/yourpage']" [queryParams]="{ page: 2 }">2</a>
Angular 4:
I have included JS (for OG's) and TS versions below.
.html
<a [routerLink]="['/search', { tag: 'fish' } ]">A link</a>
In the above I am using the link parameter array see sources below for more information.
routing.js
(function(app) {
app.routing = ng.router.RouterModule.forRoot([
{ path: '', component: indexComponent },
{ path: 'search', component: searchComponent }
]);
})(window.app || (window.app = {}));
searchComponent.js
(function(app) {
app.searchComponent =
ng.core.Component({
selector: 'search',
templateUrl: 'view/search.html'
})
.Class({
constructor: [ ng.router.Router, ng.router.ActivatedRoute, function(router, activatedRoute) {
// Pull out the params with activatedRoute...
console.log(' params', activatedRoute.snapshot.params);
// Object {tag: "fish"}
}]
}
});
})(window.app || (window.app = {}));
routing.ts (excerpt)
const appRoutes: Routes = [
{ path: '', component: IndexComponent },
{ path: 'search', component: SearchComponent }
];
#NgModule({
imports: [
RouterModule.forRoot(appRoutes)
// other imports here
],
...
})
export class AppModule { }
searchComponent.ts
import 'rxjs/add/operator/switchMap';
import { OnInit } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
export class SearchComponent implements OnInit {
constructor(
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.route.params
.switchMap((params: Params) => doSomething(params['tag']))
}
More infos:
"Link Parameter Array"
https://angular.io/docs/ts/latest/guide/router.html#!#link-parameters-array
"Activated Route - the one stop shop for route info" https://angular.io/docs/ts/latest/guide/router.html#!#activated-route
For Angular 4
Url:
http://example.com/company/100
Router Path :
const routes: Routes = [
{ path: 'company/:companyId', component: CompanyDetailsComponent},
]
Component:
#Component({
selector: 'company-details',
templateUrl: './company.details.component.html',
styleUrls: ['./company.component.css']
})
export class CompanyDetailsComponent{
companyId: string;
constructor(private router: Router, private route: ActivatedRoute) {
this.route.params.subscribe(params => {
this.companyId = params.companyId;
console.log('companyId :'+this.companyId);
});
}
}
Console Output:
companyId : 100
According to Angular2 documentation you should use:
#RouteConfig([
{path: '/login/:token', name: 'Login', component: LoginComponent},
])
#Component({ template: 'login: {{token}}' })
class LoginComponent{
token: string;
constructor(params: RouteParams) {
this.token = params.get('token');
}
}
Angular 5+ Update
The route.snapshot provides the initial value of the route parameter
map. You can access the parameters directly without subscribing or
adding observable operators. It's much simpler to write and read:
Quote from the Angular Docs
To break it down for you, here is how to do it with the new router:
this.router.navigate(['/login'], { queryParams: { token:'1234'} });
And then in the login component (notice the new .snapshot added):
constructor(private route: ActivatedRoute) {}
ngOnInit() {
this.sessionId = this.route.snapshot.queryParams['token']
}
In Angular 6, I found this simpler way:
navigate(["/yourpage", { "someParamName": "paramValue"}]);
Then in the constructor or in ngInit you can directly use:
let value = this.route.snapshot.params.someParamName;

How can my child component call a method on the parent component in Angular 2?

Background
Suppose I have some parent component, call it MatchList, that presents a list of Hero objects, among other things. Each Hero object has properties that are shown in some table. Now suppose I also have a button for each Hero that updates the route, loads a new view, and shows more details.
Before
http://heroic.com/match-list
After
http://heroic.com/hero-84
Problem
My problem essential is this: I want to call the router's navigate() method from a button in my MatchList template, but I receive the following error when I attempt to do so:
EXCEPTION: Error during evaluation of "click"BrowserDomAdapter.logError # ...
angular2.dev.js:21835 ORIGINAL EXCEPTION: TypeError: l_context.setPath is not a function...
angular2.dev.js:21835 TypeError: l_context.setPath is not a function at ...
In other words It looks like I cannot reference the parent component's router methods in the child template.
So, what is the correct and best way in Angular 2 for a child component access the methods of the parent component ( or 'context')?
I'd prefer if the solution was something cleaner than
class parent {
child: Child;
constructor(...) {
...
this.child.parent = this;
}
}
Sample Code
EDIT
I changed my template button to
(^click)="setPath(match.match_id)"
I am not longer receiving an error message, but nothing happens - I don't even get a console log confirming the click.
Snippets of what I have so far.
//Parent
#Component({
selector: 'dota-app',
directives: [Home, MatchesView, ROUTER_DIRECTIVES],
templateUrl: 'AppView.html'
})
#RouteConfig([
{ path: '/', component: Home, as: 'Home' },
{ path: '/matches', component: MatchesView, as: 'Matches' },
{ path: '/match-details', component: MatchDetailsView, as: 'MatchDetails'}
])
export class RootDotaComponent {
router: Router;
constructor(router: Router) {
this.router = router;
}
public setPath(linkParams: any[]|string): void {
if (typeof linkParams === "string")
linkParams = [linkParams];
this.router.navigate(<any[]>linkParams);
}
}
}
//Child
#Component({
selector: 'matches-view',
providers: [DotaRestDao],
})
#View({
templateUrl: './components/MatchesView/MatchesView.html',
directives: [CORE_DIRECTIVES]
})
export class MatchesView {
public result;
private dataService: DotaRestDao;
constructor(dataService: DotaRestDao) {
this.result = { matches: [] };
this.dataService = dataService;
this.dataService.getData({
baseUrl: DotaRestDao.MATCH_HISTORY_BASE
}).subscribe(
res => this.result = res.result,
err => console.log("something wrongable", err),
() => console.log('completed')
);
}
}
//Template
<table class="table">
...
<button (click)="setPath(match.match_id)">Match Detail Route</button>
</table>
In the context of this question, namely calling a parent router, the answer, it turns out, is trivial. See this plunker for details.
The main takeaway is that giving a router to a child component a la
class ChildComponent {
constructor(router: Router) {
...
}
}
does not create a new router, it merely extends the existing router of the parent component. Thus, the need to a reference to the parent object is obviated. Just call the methods of the childRouter and everything works as expected.

Categories