How to pass data from router component to children components in Aurelia? - javascript

Lets say we have a component in Aurelia named UserRouter, which is a child router and handles routing to UserProfile, UserImages and UserFriends.
I want the UserRouter to load in the user from the API (on canActivate) and then pass this this user data to sub components.
Loading in the data is fine, how do I pass it down to sub components so they can all read it? e.g. placing an attribute on <router-view>.
I've tried the bindingContext argument on the bind() method of sub components but this hasn't worked.
Thanks

The way I did this, was by adding additional information to the child router definition eg:
configureRouter(config, router){
config.map([
{ route: [''], name: 'empty', moduleId: './empty', nav: false, msg:"choose application from the left" },
{ route: 'ApplicationDetail/:id', name: 'applicationDetail', moduleId: './applicationDetail', nav: false, getPickLists : () => { return this.getPickLists()}, notifyHandler : ()=>{return this.updateHandler()} }
]);
in the first route from this example, I pass in a text message by adding my own porperty 'msg' to the route object.
in the second route I pass in some event handlers, but could have been some custom objects or antything else.
In the childmodel I receive these additional elements in the activate() method:
export class empty{
message;
activate(params, routeconfig){
this.message=routeconfig.msg || "";
}
}
I guess in your case, you would add the user object to the child router definition.

You could bind the router/user object to the <router-view>
<router-view router.bind="router"></router-view>
I have the same issue, so I'd love to see what you come up with!!

You could use the event aggregator to publish out an event with the data and the sub components could subscribe and listen for that event. This would avoid coupling between them. However, there may be a downside I'm not thinking of.

I worked out a solution for this, you simply tell the dependency injector to inject an instance of your parent router component using Parent.of(YourComponent).
import {inject, Parent} from 'aurelia-framework';
import {UsersRouter} from './router';
#inject(Parent.of(UsersRouter))
export class UserImages {
constructor(usersRouter) {
this.usersRouter = usersRouter;
}
activate() {
this.user = this.usersRouter.user;
}
}
You can also actually miss out the Parent.of part because the Aurelia DI system works its way up.
import {inject} from 'aurelia-framework';
import {UsersRouter} from './router';
#inject(UsersRouter)
export class UserImages {
constructor(usersRouter) {
this.usersRouter = usersRouter;
}
activate() {
this.user = this.usersRouter.user;
}
}

You can implement a generic method to pass objects:
configureRouter(config, router){
this.router = router;
...
this.router.navigateWithParams = (routeName, params) => {
let routerParams = this.router.routes.find(x => x.name === routeName);
routerParams.data = params;
this.router.navigate(routeName);
}
}
then you can use it programmatically like:
goToSomePage(){
let obj = { someValue: "some data", anotherValue: 150.44}
this.router.navigateWithParams("someRoute", obj );
}
finally, you can get the param on the destination page:
activate(urlParams, routerParams)
{
this.paramsFromRouter = routerParams.data;
}

Related

Reload page after 3 seconds with a promise in Angular

I'm trying to implement a solution for the login page. When user passes the wrong login/password I want the page to reload after 3 seconds. I was trying to do it with 2 solutions but can't make it work. The code would launch after click on a button when there is no match for credentials (else condition):
FIRST IDEA:
...else{
this.comUser = 'WRONG LOGIN OR PASSWORD'
this.alert = ""
setTimeout(function() {
window.location = "I would like it to use the routing to a component instead of URL here"; }
,3000);
SECOND IDEA WITH A PROMISE:
const reloadDelay = () => {
reload = this.router.navigate(['/login']);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log("działa");
resolve(reload)
}, 4000);
});
};
Thanks for any hints!
If you're using angular 9 you can try this
setTimeout(() => {this.domDocument.location.reload()}, 3000);
You need to import:
import { Inject } from '#angular/core';
import { DOCUMENT } from '#angular/common';
In order to use the above-mentioned method and have the constructor of component.ts configured as below.
constructor(#Inject(DOCUMENT) private domDocument: Document){}
This can now be done using the onSameUrlNavigation property of the Router config. In your router config enable onSameUrlNavigation option, setting it to reload . This causes the Router to fire an events cycle when you try to navigate to a route that is active already.
#ngModule({
imports: [RouterModule.forRoot(routes, {onSameUrlNavigation: 'reload'})],
exports: [RouterModule],
})
In your route definitions, set runGuardsAndResolvers to always. This will tell the router to always kick off the guards and resolvers cycles, firing associated events.
export const routes: Routes = [
{
path: 'invites',
component: InviteComponent,
children: [
{
path: '',
loadChildren: './pages/invites/invites.module#InvitesModule',
},
],
canActivate: [AuthenticationGuard],
runGuardsAndResolvers: 'always',
}
]
Finally, in each component that you would like to enable reloading, you need to handle the events. This can be done by importing the router, binding onto the events, and invoking an initialization method that resets the state of your component and re-fetches data if required.
export class InviteComponent implements OnInit, OnDestroy {
navigationSubscription;
constructor(
// … your declarations here
private router: Router,
) {
// subscribe to the router events. Store the subscription so we can
// unsubscribe later.
this.navigationSubscription = this.router.events.subscribe((e: any) => {
// If it is a NavigationEnd event re-initalise the component
if (e instanceof NavigationEnd) {
this.initialiseInvites();
}
});
}
initialiseInvites() {
// Set default values and re-fetch any data you need.
}
ngOnDestroy() {
if (this.navigationSubscription) {
this.navigationSubscription.unsubscribe();
}
}
}
With all of these steps in place, you should have route reloading enabled. Now for your timeperiod, you can simply use settimeout on route.navigate and this should reload it after desired time.

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! :-)

angular2 get route path as title page

What's the best way of outputting a page title depending on your route path in angular2 rather than hard-coding the title, I want to output a title in the controller instead.
If user go to /dashboard and the dashboard page will have Dashboard title:
{ path: 'dashboard', component: dashComponent}
Somewhere along:
if(path==dashboard){
title:string = "Dashboard"
} else if(path==something){
title:string = "Something"
}
HTML Output:
<h1>{{title}}</h1
this logic works but repeating location.path seems a little bit tedious
if(this.location.path() == '/order-ahead'){
console.log('Dashboard')
this.title = 'Dashboard';
} else {
console.log('its something else');
this.title = 'Something Else'
}
I guess, you should use more complicated logic to achieve more sophisticated solution. For example, using CanActivated guard, something like in this ticket: Angular 2 RC4 Router get intended route before activated
I think you can follow the guidance in the docs to set the title via the Title service:
import { Title } from '#angular/platform-browser';
bootstrap(AppComponent, [ Title ])
then in your component that has access to the route use something like this:
export class AppComponent {
public constructor(private titleService: Title ) { }
public setTitle( newTitle: string) {
this.titleService.setTitle( newTitle );
}
}
Here's a link to the docs on this: https://angular.io/docs/ts/latest/cookbook/set-document-title.html
Easiest solution, subscribe to route changes in the router (example for beta 3.0-2 of router):
import { Router, Event, NavigationEnd } from '#angular/router';
constructor(protected router: Router)
{
this.router.events.subscribe(this.routeChanges.bind(this));
}
protected routeChanges(event: Event)
{
if (event instanceof NavigationEnd) {
let url = event.url;
}
}

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 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