Angular 7 routing not working when clicking on the leaflet marker - leaflet

I am working with angular 7 and leaflet js for the map representation.
I want to navigate to another page when click on the marker. But routing is not working properly.
eg:
L.marker.on('click', function(){
this.router.navigateByUrl('/dashboard');
});
when click on the marker, the url changed to '/dashboard' but the map still showing in the page.
when click on the html element, navigation working fine.
Could any one please help me on this.
Thanks in advance

You need to define the two routes and then listen to the marker on click event to be able to navigate.
For instance have these two routes map and dashboard on app.module.ts and land on map view when initializing the app:
const appRoutes: Routes = [
{ path: "map", component: MapComponent },
{ path: "dashboard", component: DashboardComponent },
{ path: "", redirectTo: "/map", pathMatch: "full" }
];
#NgModule({
declarations: [AppComponent, MapComponent, DashboardComponent],
imports: [BrowserModule, RouterModule.forRoot(appRoutes)],
...
})
and then add <router-outlet></router-outlet> on app.html to be able to navigate to different pages
Then you might have a map component where the map will be held. Add the marker on the map, listen to the click event & navigate to dashboard page:
L.marker([51.505, -0.09], this.markerIcon)
.addTo(this.map)
.on("click", e => {
this.router.navigateByUrl("/dashboard");
});
I also added a button to navigate back to map component from the dashboard page
Demo

It is obviously too late to answer the original question, but if anybody gets here with the same issue, here is the solution.
The origin of the problem is that when we are trying to execute Angular navigation directly in the Leaflet event callback. But Leaflet map with its markers is outside Angular zone (= execution context):
marker.on('click', () => { // we are in Leaflet
this.router.navigate('/dashboard'); // but this is Angular
});
So the solution is to run the navigation code from Angular zone:
marker.on('click', () => {
this._ngZone.run(() => { // here we are back in Angular
this.router.navigate('/dashboard');
});
});
Don't forget to include private _ngZone: NgZone, in component constructor.
For more detailed explanation check this one. The message in the title is the one you see as warning in console when trying to execute the first code section above.

Related

Ionic 4 Scroll Position in Service/Guard

I am trying to implement a feature similar to whats available in Facebook i.e. if you have scrolled the news feed, pressing hardware back button takes you to the top of the list.
For this I think believe canDeactivate of Router Guards would be the proper ways.
But I am unable to find a way to check if the page has been scrolled or not.
I have tried window.pageYOffset but this always returns 0, accessing ViewChild within a Guard always returns null.
Can anyone please guide how to achieve this?
There are two approaches for this that should help you.
First, starting with Ionic 4, you can register you back button handler using the Platform features:
https://www.freakyjolly.com/ionic-4-overridden-back-press-event-and-show-exit-confirm-on-application-close/
this.platform.backButton.subscribeWithPriority(999990, () => {
//alert("back pressed");
});
Secondly, you can use more features of Ionic 4 called scrollEvents.
I have explained how to use this feature in other answers:
How to detect if ion-content has a scrollbar?
How to detect scroll reached end in ion-content component of Ionic 4?
ionic 4 - scroll to an x,y coordinate on my webView using typeScript
Hopefully that will get you moving in the right direction.
I think that last answer should solve most of your issue, so something like this:
Freaky Jolly has a tutorial explaining how to scroll to an X/Y coord.
First, you need scrollEvents on the ion-content:
<ion-header>
<ion-toolbar>
<ion-title>
Ion Content Scroll
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content [scrollEvents]="true">
<!-- your content in here -->
</ion-content>
In the code you need to use a #ViewChild to get a code reference to the ion-content then you can use its ScrollToPoint() api:
import { Component, ViewChild } from '#angular/core';
import { Platform, IonContent } from '#ionic/angular';
#Component({
selector: 'app-home',
templateUrl: 'home.page.html',
styleUrls: ['home.page.scss'],
})
export class HomePage {
// This property will save the callback which we can unsubscribe when we leave this view
public unsubscribeBackEvent: any;
#ViewChild(IonContent) content: IonContent;
constructor(
private platform: Platform
) { }
//Called when view is loaded as ionViewDidLoad() removed from Ionic v4
ngOnInit(){
this.initializeBackButtonCustomHandler();
}
//Called when view is left
ionViewWillLeave() {
// Unregister the custom back button action for this page
this.unsubscribeBackEvent && this.unsubscribeBackEvent();
}
initializeBackButtonCustomHandler(): void {
this.unsubscribeBackEvent = this.platform.backButton.subscribeWithPriority(999999, () => {
this.content.scrollToPoint(0,0,1500);
});
/* here priority 101 will be greater then 100
if we have registerBackButtonAction in app.component.ts */
}
}

Ionic: reloading current child state from side menu causes loss of header

I'm creating a simple Ionic menu app and I would like to reload the current state from the side menu.Here's a Plunkr to illustrate the problem.
In the 'Search' state, the current time is displayed. The desired behavior is the following: when I click 'Search' from the side menu, the time is refreshed, i.e. the controller is reloaded. This should happen even when I'm already on the Search page. In reality, the controller is of course much more complex, but this example is enough to illustrate the problem.
I started from the ionic 'menu' starter template. To be able to reload the state, I changed two things:
disabled view caching in app.js config function: $ionicConfigProvider.views.maxCache(0);
In menu.html, I'm passing ui-sref-opts to explicitly reload the state:
ui-sref="app.search" ui-sref-opts="{reload: true}"
The result is that the time is indeed updated, however, the header of the view is gone.
Any suggestions as to what I'm doing wrong?
try something like this in the routing:
.state('tab.home', {
url: '/home',
cache: false, //<-- FORCE TO CLEAR CACHE
views: {
'tab-home': {
templateUrl: 'templates/home/home.html',
controller: 'HomeController'
}
}
})
You can broadcast an event to achieve that. But if you want to keep it like that, you could use a parameter and it would be all right because basically the one in control of the date var is your menu and not your state.
So you could do this:
app.js
.state('app.search', {
url: "/search",
params:{
date: new Date()
},
views: {
'menuContent' :{
templateUrl: "search.html",
controller: 'SearchCtrl'
}
}
})
Menu item
<ion-item nav-clear menu-close ui-sref="app.search({date: updateDate()})">
Search
</ion-item>
controllers.js (App controller or specific menu controller)
.controller('AppCtrl', function($scope) {
$scope.updateDate = updateDate;
function updateDate(){
return new Date();
}
})
controllers.js
.controller('SearchCtrl', function($scope, $stateParams) {
$scope.now = $stateParams.date;
})

How do I get the title of the current Router Config Aurelia

I want message to persist through all pages of my application, and be set after the view-model and view are bounded. Every page I have a very simple template.
<template>
<h1>${message}</h1>
</template>
The code behind is empty for each page ('Page1', 'Home', 'Page2').
Here is my app.js
import {inject} from 'aurelia-framework';
export class App {
configureRouter(config, router) {
config.title = 'Pathways';
config.map([
{ route: ['','home'], name: 'home', moduleId: 'home', nav: true, title:'Home' },
{ route: 'page1', name: 'page1', moduleId: 'page1', nav: true, title:'Page1' }
]);
// Create a binding to the router object
this.router = router;
}
attached() {
this.message = this.router.currentInstruction.config.title;
}
}
The problem is, when I first load the app. ${message} is blank. Then, when I navigate to page1, the message becomes Home !! I thought that maybe the currentInstruction was lagging behind, so I added another page. Page2 if you will, but it turns out no matter what I do after the first navigation the message is always Home
So on app initialization message is blank, and then upon the first navigation message will remain Home for the entirety.
My question is, what is the "Aurelia" way to get the title of each page the user is currently on? I would think injecting the router on each page is costly
I can see two options:
The first and easiest one is using a getter property that listens to router.currentInstruction. For instance:
#computedFrom('router.currentInstruction')
get message() {
if (this.router.currentInstruction !== null)
return this.router.currentInstruction.config.title;
}
Running Example https://gist.run/?id=b01abe2859bc2bea1af0f8d21fc2045f
The second option is using the EventAggregator. For example:
attached() {
this.subscription = this.ea.subscribe('router:navigation:success', () => {
this.message = this.router.currentInstruction.config.title;
});
}
detached() {
this.subscription.dispose(); //remember to always dispose the subscription
}
Running example https://gist.run/?id=7d30d4e8d479cc209ca9013e10e91505
They say we should try to avoid the EventAggregator. So, I'd go with the first option.
You can use router.currentInstruction.config.title direct on your app.html page and it will be updated every time then router title is changed (it's using one-way binding):
<h1>title: ${router.currentInstruction.config.title}</h1>
Best way (thanks comments):
${router.currentInstruction.config.navModel.title}

Get passed data on next page after calling "to" from NavContainer

I am on my way building a Fiori like app using SAPUI5. I have successfully built the Master page, and on item click, I pass the context and navigate to Detail page.
The context path from Master page is something like /SUPPLIER("NAME"). The function in App.controoler.js is as follows:
handleListItemPress: function(evt) {
var context = evt.getSource().getBindingContext();
this.myNavContainer.to("Detail", context);
// ...
},
But I would like to know how I can access this context in the Detail page. I need this because I need to use $expand to build the URL and bind the items to a table.
There is an example in the UI5 Documentation on how to deal with this problem using an EventDelegate for the onBeforeShow function which is called by the framework automatically. I adapted it to your use case:
this.myNavContainer.to("Detail", context); // trigger navigation and hand over a data object
// and where the detail page is implemented:
myDetailPage.addEventDelegate({
onBeforeShow: function(evt) {
var context = evt.data.context;
}
});
The evt.data object contains all data you put in to(<pageId>, <data>). You could log it to the console to see the structure of the evt object.
Please refer the "shopping cart" example in SAP UI5 Demo Kit.
https://sapui5.hana.ondemand.com/sdk/test-resources/sap/m/demokit/cart/index.html?responderOn=true
Generally, in 'Component.js', the routes shall be configured for the different views.
And in the views, the route has to be listened to. Please see below.
In Component.js:
routes: [
{ pattern: "cart",
name: "cart",
view: "Cart",
targetAggregation: "masterPages"
}
]
And in Cart.controller.js, the route has to be listened. In this example, cart is a detail
onInit : function () {
this._router = sap.ui.core.UIComponent.getRouterFor(this);
this._router.attachRoutePatternMatched(this._routePatternMatched, this);
},
_routePatternMatched : function(oEvent) {
if (oEvent.getParameter("name") === "cart") {
//set selection of list back
var oEntryList = this.getView().byId("entryList");
oEntryList.removeSelections();
}
}
Hope this helps.

Switch class on tabs with React.js

So I have a tab-component that has 3 items:
React.DOM.ul( className: 'nav navbar-nav',
MenuItem( uid: 'home')
MenuItem( uid: 'about')
MenuItem( uid: 'contact)
)
And in the .render of MenuItem:
React.DOM.li( id : #props.uid, className: #activeClass, onClick: #handleClick,
React.DOM.a( href: "#"+#props.uid, #props.uid)
)
Every time I click an item, a backbone router gets called, which will then call the tab-component, which in turn will call a page-component.
I'm still trying to wrap my head around the fact there's basically a one-way data-flow. And I'm so used to manipulating the DOM directly.
What I want to do, is add the .active class to the tab clicked, and make sure it gets removed from the inactive ones.
I know the CSS trick where you can use a data- attribute and apply different styling to the attribute that is true or false.
The backbone router already has already gotten the variable uid and calls the right page. I'm just not sure how to best toggle the classes between tabs, because only one can be active at the same time.
Now I could keep some record of which tab is and was selected, and toggle them etc. But React.js already has this record-keeping functionality.
The #handleClick you see, I don't even want to use, because the router should tell the tab-component which one to give the className: '.active' And I want to avoid jQuery, because React.js doesn't need direct DOM manipulation.
I've tried some things with #state but I know for sure there is a really elegant way to achieve this fairly simple, I think I watched some presentation or video of someone doing it.
I'm really have to get used to and change my mindset towards thinking React-ively.
Just looking for a best practice way, I could solve it in a really ugly and bulky way, but I like React.js because it's so simple.
Push the state as high up the component hierarchy as possible and work on the immutable props at all levels below. It seems to make sense to store the active tab in your tab-component and to generate the menu items off data (this.props in this case) to reduce code duplication:
Working JSFiddle of the below example + a Backbone Router: http://jsfiddle.net/ssorallen/4G46g/
var TabComponent = React.createClass({
getDefaultProps: function() {
return {
menuItems: [
{uid: 'home'},
{uid: 'about'},
{uid: 'contact'}
]
};
},
getInitialState: function() {
return {
activeMenuItemUid: 'home'
};
},
setActiveMenuItem: function(uid) {
this.setState({activeMenuItemUid: uid});
},
render: function() {
var menuItems = this.props.menuItems.map(function(menuItem) {
return (
MenuItem({
active: (this.state.activeMenuItemUid === menuItem.uid),
key: menuItem.uid,
onSelect: this.setActiveMenuItem,
uid: menuItem.uid
})
);
}.bind(this));
return (
React.DOM.ul({className: 'nav navbar-nav'}, menuItems)
);
}
});
The MenuItem could do very little aside from append a class name and expose a click event:
var MenuItem = React.createClass({
handleClick: function(event) {
event.preventDefault();
this.props.onSelect(this.props.uid);
},
render: function() {
var className = this.props.active ? 'active' : null;
return (
React.DOM.li({className: className},
React.DOM.a({href: "#" + this.props.uid, onClick: this.handleClick})
)
);
}
});
You can try react-router-active-componet - if you working with boostrap navbars.
You could try to push the menu item click handler up to it's parent component. In fact I am trying to do something similar to what you are doing.. I have a top level menubar component that I want to use a menubar model to render the menu bar and items. Other components can contribute to the top level menubar by adding to the menubar model... simply adding the top level menu, the submenuitem, and click handler (which is in the component adding the menu). The top level component would then render the menubar UI and when anything is clicked, it would use the "callback" component click handler to call to. By using a menu model, I can add things like css styles for actice/mouseover/inactive, etc, as well as icons and such. The top level menubar component can then decide how to render the items, including mouse overs, clicks, etc. At least I think it can.. still working on it as I am new to ReactJS myself.