Angular - trigger unsavedChanges directive when user clicks on a non-submit button - angularjs

How do I trigger the angular-unsavedChanges directive to check to see if a form is dirty when a user clicks on a button in a form that is not a submit button? I want a message to pop-up if the user clicks on a button that would navigate them to a different page, if changes have been made to the current form. It seems to work on a refresh and a link to a different page, but not a button that will be loading a different template. TIA.

Without sample code, I can't be sure, but I think that since the directive you are using specifically binds to the $locationChangeStart event, you could try the following :
configure your $routeProvider to monitor url changes ( I suppose it is already what you are doing with your SPA, but correct me if I'm wrong)
$routeProvider
.when('/otherPage', {
templateUrl: 'partials/myOtherPage.html',
controller: 'OtherCtrl',
})
Use you the ng-click on your button to call the $location service (don't forget to pass it to your controller)
$scope.navigateToOtherPage = function() {
$location.path('/otherPage');
}
Since $location fires the $locationChangeStart event right before changing the url, your directive should be able to capture it and proceed with its expected behaviour.

Related

How can I prevent ng-model from being changed in select based on result of function?

I have a select drop down menu populated with ng-options. I want to create a pop-up that will require the user to confirm they want to change the selected option before continuing.
So far, I have tried using an ng-change, but that doesn't work because you can't prevent the change (for the purposes of my app, I cannot simply put the value back to what it was before the change in an ng-change function). I have tried ng-click, but this fires when the user clicks on the select box, whereas I want this to fire when the user actually selects one of the options from the select box for UI purposes. I have investigated ng-model-options, but my problem is I cannot seem to get an event to fire when an option is selected.
Code below demonstrating what this select looks like:
<select ng-model='uiConfig.selectedInteractionType' ng-options='interactionType for interactionType in uiConfig.interactionTypes'></select>
I had this exact same requirement, albeit for a different element. I had a button that was really an anchor tag where I passed in some values, including a URL when the user clicked the button. I then needed to open an $mddialog modal and let the user cancel or click continue to send them to a redirected site.
What you need is a directive here. Put the value(s) in $scope. I used attributes for this:
restrict: 'A',
and then in my link function I used
attributes.$observe
to pull in my values.
Then use something like this in your directive function:
element.on('click' function ($event) {
$event.preventDefault(); //To stop the event flow.
$mdDialog.show({
templateUrl: "path to your modal html document",
controller: myController,
clickOutsideToClose: true,
locals:{
href: $scope.href,
myValue: $scope.myValue
}
Using $locals allows you to pass in the values to the directive.

Handling page or tab change in angualajs

How to detect unsaved page changes check while moving another page or tab in angularjs 1.5 application.
One of approach is using directives however how to pass related form named to the directive instead of using hard coded solution?
I tried using the service approach as mentioned below but my nested view controller is not able to access the form name. Iam getting $scope.myForm as undefined.
You can handle page change with the event $locationChangeStart for ng-route or $stateChangeStart for ui-router (perform the logic you want inside):
$scope.$on('$locationChangeStart', function(event) {
if ($scope.myForm.$invalid) {
event.preventDefault();
}
});
To tab change etc, you can disable your tab with something like or watever approach you prefer
ng-disabled="!myForm.$valid"
EDIT
You may look at this post to use a service/factory approach :
https://stackoverflow.com/a/25459689/5138917
The module below seem to work for me
https://github.com/facultymatt/angular-unsavedChanges

Fire an event when user moves out of speciifc route in AngularJS

I am using AngularJS 1.3. Assume I have created several routes in my application. But when user hits a specifc route/url & then tries to move to another route/url, I want to fire some event. I do not want to fire this event on every URL change.
So only when user comes out of this url http://localhost:9000/data/55677c/edit, I want to fire one function available in XYZ controller.
Here is my scenario:
I have a page which looks like this:
<div class="well">
<button id='edit-btn' type="button" ng-click='saveContent()'>
<div ng-include="'components/grid/comOne.html'"></div>
</div>
components/grid/comOne.html page contains one grid and it has its own controller which takes care of data management of the grid.
This grid is shown in two pages. One in editable mode and one is non-ediatble mode. While user is in editable mode and try to move out of the page without saving the info, I need to fire an event in order to discard ant changes user has made to the grid data.
Please suggest
If the listening controller is a parent controller you could $emit the event.
Or you could have a common service like this:
angular.module('x').factory('CommonLogic', function(){
var pageChangeListeners = [];
return {
listenToPageChange: listenToPageChange
};
function listenToPageChange(callback){
pageChangeListeners.push(callback);
}
function pageChanged(){
for(var i = 0; i < pageChangeListeners.length; i++){
pageChangeListeners[i]();
}
}
});
then when leaving that url (track that via $routeChangeStart) you can call: commonLogic.pageChanged()
In the controller where you want to take action just:
commonLogic.listenToPageChange(function(){..}).
Obviously this should be improved to avoid duplicate registration of the listener ... etc.
I hope I'm not overcomplicating this. Could you describe your use case in more detail ?
I guess you want to use $routeChangeStart:
$rootScope.$on( "$routeChangeStart", function(event, next, current) {
});
You can put this in the scope of your current controller which might be edit as your url says.
From the docs:
$routeChangeStart
Broadcasted before a route change. At this point the route services starts resolving all of the dependencies needed for the route change to occur. Typically this involves fetching the view template as well as any dependencies defined in resolve route property. Once all of the dependencies are resolved $routeChangeSuccess is fired.
The route change (and the $location change that triggered it) can be prevented by calling preventDefault method of the event. See $rootScope.Scope for more details about event object.
Type:broadcast
Target:root scope

UI Router event on navigate to self page

I'm using ui-router for my page routing in my AngularJS app. I'm encountering an issue where if I click a hyperlink on a page that refers to the page/route I'm already on (i.e. self), nothing will happen.
For example, if you're scrolled halfway down a page and you click a hyperlink that refers to the current page, it will seem as though the link doesn't load, when in actual fact nothing needs to be loaded as the user is already on the right page.
The goal in this situation is to have the viewport moved to the top of the page, but I cannot get any event from ui-router for this event. I've tried $stateChangeStart and $stateChangeSuccess, but as no route change occurs, these do not fire.
My urls are of the form http://localhost:3000/projects/myproject and ui-router is configured as follows:
state('app.projects', {
url: '/projects/:projectname',
templateUrl: 'projects.html',
controller: 'ProjectsCtrl'
}).
Without adding extra logic in the controller (which would obfuscate the natural href="projects/myproject" format), what can be done to achieve this?
Without controller:
<a ui-sref="stateName({reload: true})" target="_blank">Link Text</a>
With controller:
$state.go($state.name, {}, {reload: true});
You can simply use # at the end of the link. href="projects/myproject#"

Angular, show content in a modal for a browsing user but show content on a new page for a new user

I'm trying to show the same content (with the same url) in a modal to users who are already on my site browsing, but on a new page for new users.
For example here the user journey on canopy:
Go to https://canopy.co/ and click on a link. Content is loaded in a modal with the url updated to https://canopy.co/products/12309
However go straight to https://canopy.co/products/12309 and it loads it on it's own view, without a modal.
How can I do this with angular? I've tried a load of things with ui-router and multiple views but it ends up being rendered twice.
Thanks for your help.
There are probably several approaches to this. This is mine.
Outline
When the state changes, we want to check whether we could potentially serve the content within a modal over the current view, rather than replacing the current view entirely. We'll define this in our routing config - views that can host a modal will have a modalMaster flag, and views that can be displayed within a modal will have a modalSlave flag.
We'll use ui.router's $stateChangeStart event to intercept the state changes, and cancel them as necessary, this is where we'll open the modal.
Challenges
We need to maintain a knowledge of the state of a modal, if there is one
Preventing a state change also prevents the URL changing, so we need to do that manually
Using the browser back button should close the modal and take us back to the state beneath it.
Code
Note that all code is in coffeescript and jade, I find it much neater. If you need to transcribe, use js2coffee.org.
Router config:
.config ($stateProvider, $urlRouterProvider)->
# Add details on which templates can open models containing which other
# templates.
$stateProvider.state 'shop',
url: '/'
templateUrl: 'shop.html'
modalMaster: true
$stateProvider.state 'item',
url: '/item/{id}'
templateUrl: 'item.html'
modalSlave: true
$urlRouterProvider.otherwise('/');
# Enable handling of url changes ourselves.
$urlRouterProvider.deferIntercept()
The last line is important as it allows us to stop the url changes updating the view later on.
State and URL event listeners
.run ($rootScope, $state, $modal, $location, $urlRouter, $timeout)->
# set some flags for use later.
modalInstance = toStatePrevented = entering = null
# Intercept state changes - if we're in a modal master and we're going to a
# modal slave, open a modal containing the slave state instead.
$rootScope.$on '$stateChangeStart', (event, toState, toParams, fromState)->
if fromState.modalMaster and toState.modalSlave
event.preventDefault()
# set a property to show which state is currently in the modal
$state.current.inModal = toState
entering = true # for url handling
$timeout -> # change url manually
$location.path($state.href(toState, toParams), false)
, 0 # the 0 timeout makes sure the preventDefault doesn't stop url
# change
# open a modal containing the new state, you can merge this with
# modal specific properties like "resolve" etc. if you want them
modalInstance = $modal.open(toState)
# clear our flags when the modal closes - this only uses dismiss so we
# only need "catch" right now.
modalInstance.result.catch(()->
modalInstance = toStatePrevented = $state.current.inModal =
entering = null
$state.go(fromState)
)
$rootScope.$on '$locationChangeSuccess', (event, toUrl, fromUrl)->
event.preventDefault()
# if there is nothing in a modal, just handle as normal
if !$state.current.inModal? then $urlRouter.sync()
# dismiss modal if url changes and modal is visible
else if not entering and modalInstance? then modalInstance.dismiss()
# if we are opening a modal, set a flag so that next time the url changes
# we will dismiss it.
else entering = false
The comments should explain most of this. In practise you will probably need a bit more than what is here - for example, you should make sure the case that a link has been clicked from inside the modal is handled correctly, but this should get you started.
Naturally, I have made a plunker to demonstrate this solution here, but make sure you use the "launch preview in a separate window" functionality to see everything working to its full effect.
Result
Navigating from shop to item opens a modal, but navigating directly to item via a URL opens the item on its own as the ui-view.

Resources