Fire an event when user moves out of speciifc route in AngularJS - 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

Related

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

Angular - How can I clear factory variables when the view changes?

I'm fairly new to angular, so bear with me. :-)
I have a list of contacts in one view. I put together a service with some setter and getter functions to hold the contact ID. When the user clicks a contact on the list view, it sets the ID in the service and moves to the edit form view. This is working well, but when I'm done editing and click to go back to the list view I want to clear the variables in the service. How would I do that?
I found one other answer on this topic, but I think it's using the built in angular router instead of ui-router.
Angular updating and clearing factory variables
Also, wouldn't the method in that answer also clear the variables when the user moved from the list to the edit form in the first place?
Thanks!
If it's a route change you can use $locationChangeStart, see $location
For example in your edit controller, register for the event on route change:
var onloc = $scope.$on('$locationChangeStart', function (event, newUrl, oldUrl) {
myService.cleanUp(whatever);
onloc();
});

How to target a specific Ionic view with $rootScope.$broadcast

Using Ionic, calling $rootScope.$broadcast("$ionicView.enter") will trigger all child views having an event handler for the $ionicView.enter event.
How can I narrow this down to target the handler for a specific view?
You haven't provided any code samples, but it sounds like what you are describing doesn't require triggering an event with specific data, but listening to the event w/ specific data.
Perhaps this snippet can help you:
$rootScope.$on( "$ionicView.enter", function( scopes, states ) {
//here's an example of storing network error state in rootscope
//and some random important state that requires your data to be refreshed
if( $rootScope.networkErrorOccurred && states.stateName == "myImportantState" ) {
//refresh data due to network error code goes here
}
});
When calling $rootScope.$broadcast("$ionicView.enter") the "states" object is none existent and hence you cannot rely on the stateName.
But, you can add it yourself, like so:
$rootScope.$broadcast('$ionicView.enter', {stateName: 'myImportantState'});
This will make Travis example working.
The other way I can think of is to save a reference to the view's scope and emit an event directly on it.
I must say I wouldn't recommend it, but maybe that's what you're looking for.
Inside the view's controller
app.controller('someViewCtrl', function($scope) {
//Save a reference to the view's scope, using window for brevity
window.someViewScope = $scope;
}
Somewhere else in the app
window.someViewScope.$emit('$ionicView.enter')

$locationChangeStart never broadcasted; $browser.onUrlChange never called after page load

My angular app is non-standard in that we don't use route provider or UI router. WE use a durandal style of navigation that swaps out views using $http.
locationChangeStart never fires I think due to the fact that we are not changing our URL once in the SPA (though we plan to do so in the future sometimes). I can't test this, but if we remain in the SPA and we change the URL to point to a new view thru code (again, not using routeProvider/UiRouter) should I expect this event to fire?
My main issue is that I cannot handle these events: manual url change, back/forward/history buttons, external link clicks
I see $browser#onUrlChange is the function that broadcasts the $locationChangeStart event but that function only gets called on page load for me, but it seems to be exactly the code that should be getting called.
Documentation:
$browser#onUrlChange
It's only called when the url is changed from outside of angular:
user types different url into address bar
user clicks on history (forward/back) button
user clicks on a link
It's not called when url is changed by $browser.url() method
It says this part also:
NOTE: this api is intended for use only by the $location service. Please use the {#link ng.$location $location service} to monitor url changes in angular apps.
I don't know how to 'use $location service to monitory url changes', so I am listening to the $locationChangeStart event:
$rootScope.$on('$locationChangeStart', function(event, next, current) {
// Breakpoint hit here on page load only
var x = 0;
}
Why doesn't the onUrlChange/$locationChangeStart functions run for me?
thanks!

AngularJS Paging with $location.path but no ngView reload

My single page application loads a home page and I want to display a series of ideas. Each of the ideas is displayed in an animated flash container, with animations displayed to cycle between the ideas.
Ideas are loaded using $http:
$scope.flash = new FlashInterface scope:$scope,location:$location
$http.get("/competition.json")
.success (data) ->
$scope.flash._init data
However, to benefit from history navigation and UX I wish to update the address bar to display the correct url for each idea using $location:
$location.path "/i/#{idea.code}"
$scope.$apply()
I am calling $apply here because this event comes from outwith the AngularJS context ie Flash. I would like for the current controller/view to remain and for the view to not reload. This is very bad because reloading the view results in the whole flash object being thrown away and the preloader cycle beginning again.
I've tried listening for $routeChangeStart to do a preventDefault:
$scope.$on "$routeChangeStart", (ev,next,current) ->
ev.preventDefault()
$scope.$on "$routeChangeSuccess", (ev,current) ->
ev.preventDefault()
but to no avail. The whole thing would be hunky dory if I could figure out a way of overriding the view reload when I change the $location.path.
I'm still very much feeling my way around AngularJS so I'd be glad of any pointers on how to structure the app to achieve my goal!
Instead of updating the path, just update query param with a page number.
set your route to ignore query param changes:
....
$routeProvider.when('/foo', {..., reloadOnSearch: false})
....
and in your app update $location with:
...
$location.search('page', pageNumber);
...
From this blog post:
by default all location changes go through the routing process, which
updates the angular view.
There’s a simple way to short-circuit this, however. Angular watches
for a location change (whether it’s accomplished through typing in the
location bar, clicking a link or setting the location through
$location.path()). When it senses this change, it broadcasts an
event, $locationChangeSuccess, and begins the routing process. What
we do is capture the event and reset the route to what it was
previously.
function MyCtrl($route, $scope) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
$route.current = lastRoute;
});
}
My solution was to use the $routeChangeStart because that gives you the "next" and "last" routes, you can compare them without the need of an extra variable like on $locationChangeSuccess.
The benefit is being able to access the "params" property on both "next" and "last" routes like next.params.yourproperty when you are using the "/property/value" URL style and of course use $location.url or $location.path to change the URL instead of $location.search() that depends on "?property=value" URL style.
In my case I used it not only for that but also to prevent the route to change is the controller did not change:
$scope.$on('$routeChangeStart',function(e,next,last){
if(next.$$route.controller === last.$$route.controller){
e.preventDefault();
$route.current = last.$$route;
//do whatever you want in here!
}
});
Personally I feel like AngularJS should provide a way to control it, right now they assume that whenever you change the browser's location you want to change the route.
You should be loading $location via Dependency Injection and using the following:
$scope.apply(function () {
$location.path("yourPath");
}
Keep in mind that you should not use hashtags(#) while using $location.path. This is for compability for HTML5 mode.
The $locationChangeSuccess event is a bit of a brute force approach, but I found that checking the path allows us to avoid page reloads when the route path template is unchanged, but reloads the page when switching to a different route template:
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function (event) {
if (lastRoute.$$route.originalPath === $route.current.$$route.originalPath) {
$route.current = lastRoute;
}
});
Adding that code to a particular controller makes the reloading more intelligent.
Edit: While this makes it a bit easier, I ultimately didn't like the complexity of the code I was writing to keep friendly looking URL's. In the end, I just switched to a search parameter and angular handles it much better.
I needed to do this but after fussing around trying to get the $locationChange~ events to get it to work I learned that you can actually do this on the route using resolve.
$routeProvider.when(
'/page',
{
templateUrl : 'partial.html',
controller : 'PageCtrl',
resolve : {
load : ['$q', function($q) {
var defer = $q.defer();
if (/*you only changed the idea thingo*/)
//dont reload the view
defer.reject('');
//otherwise, load the view
else
defer.resolve();
return defer.promise;
}]
}
}
);
With AngularJS V1.7.1, $route adds support for the reloadOnUrl configuration option.
If route /foo/:id has reloadOnUrl = false set, then moving from /foo/id1 to /foo/id2 only broadcasts a $routeUpdate event, and does not reload the view and re-instantiate the controller.

Resources