Hope any angularjs gurus can help me with this.Here is my angularjs code
$scope.$on('$routeChangeStart', function(event, next, current) {
if ($scope.myForm.$dirty) {
if(!confirm("Unsaved, do u want to continue?")) {
event.preventDefault();
}
}
});
It alerts in browser back button click when data is dirty, but on clicking cancel or ok it still completes the route change.Seems like event.preventDefault() is not working.
Can any one point out what may be wrong
I had lots of trouble finding this one, but instead of the "$routeChangeStart" event, you can listen to the "$locationChangeStart" event, for which you can prevent default:
$scope.$on("$locationChangeStart", function(event, next, current) {
if (!confirm("You have unsaved changes, continue navigating to " + next + " ?")) {
event.preventDefault();
}
});
You could also always prevent default, store "next", and display a clean JS modal and decide asynchronously.
$locationChangeStart is currently undocumented but referenced here : https://github.com/angular/angular.js/issues/2109
Fixed exactly after Angular 1.3.7
https://code.angularjs.org/1.3.7/docs/api/ngRoute/service/$route
$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.
According to the AngularJS docs (see at $broadcast) you simply cannot cancel an event of type broadcast ($routeChangeStart is of that type):
The event life cycle starts at the scope on which $broadcast was
called. All listeners listening for name event on this scope get
notified. Afterwards, the event propagates to all direct and indirect
scopes of the current scope and calls all registered listeners along
the way. The event cannot be canceled.
This problem was fixed in the newest versions ( >= 1.3.8 ).
Since the arguments supplied to $routeChangeStart are more detailed (and often more useful), if possible, try to update your angular version ...
The problem might persist if you are using a $stateProvider.
In this case use:
$scope.$on('$stateChangeStart', function( event){
.....
event.preventDefault();
});
Related
I am facing a weird memory leak.
I am using uigrid appScopeProvider to be able to select a row in the grid and call an expression binding ("&") to validate my choice and close my component.
What I discovered this morning by using Chrome memory tools is that the parent of this component was never released and this component too.
With the tool I've found the reason was the uigrid.
I tried to remove the use of this expression binding inside the appScopeProvider and my memory leak disappeared.
That is why I am convinced the issue is here.
Here is a sample code
angular.module('myApp')
.component('myComp', {
templateUrl: '....',
controller: [MyCompCtrl],
bindings: {
onValidate: '&'
}
}
function MyCompCtrl() {
var ctrl = this
ctrl.myData = []
ctrl.gridOptions = {
data: '$ctrl.myData',
appScopeProvider: {
onClick: function(row) {
ctrl.onValidate({ $row: row.entity })
}
},
rowTemplate: '<div ng-click="grid.appScope.onClick(row)" ng-repeat="(colRenderIndex, col) in colContainer.renderedColumns track by col.colDef.name\" class="ui-grid-cell" ui-grid-cell ></div>',
columnDefs: [...]
}
}
When my row is clicked, the onValidate binding is called, the parent of the component receive the data and the component is removed.
I have a button too on this component which allow me to validate the current row without touching the grid.
If I validate with the button I don't have any memory leak.
It seems that the issue is clearly when onClick is called on grid appScope.
I tried many things:
use a declared function instead of a block
listen for $scope $destroy event and remove appScopeProvider, nullify bindings expression, nullify appScopeProvider.onClick
use events on rootScope inside onClick, and listen for event outside to call onValidate
use eval() to call a function from string to prevent this grid from keeping a reference of my binding
use what I've read here https://blog.meteor.com/an-interesting-kind-of-javascript-memory-leak-8b47d2e7f156 on this by nullifying lots of stuff in onClick and in $scope $destroy event
call ctrl.gridApi.grid.appScope.$destroy
Nothing worked and I am out of ideas. I've already searched google and stackoverflow for something similar but didn't find anything
I guess I have to do something specific on $scope $destroy event to clean my uigrid to allow it to free my component but what?
Has any one already faced something similar?
Alright so I am running into this issue, check it out
http://jsbin.com/rarubesuxutu/1/edit?html,css,js
I am trying to send an event right after init but the event does not get handled. Instead the action just errors out. Why does Ember handle it like this? When is it ok to send an event? Is there some callback so I can set my observers?
Thanks!
Ok so I figured it out. I had a misunderstanding of the order of the callbacks.
Here is my updated version. While it's not fixed it helps understand the order of the route and controller initializations. The IndexController init is called before the routes setupController. This triggers the event before the route was set.
Okay, so I'm not sure why you want to send an action right after init, as you should be able to define all of your variables in the setupController or in the controllers init phase, so I'll show you how I would go about it. JSBIN
Effectively what you can do is on init for the controller, you can do this:
theTruth: false,
setTheTruth: function(){
this.set('theTruth', true);
}.on('init'),
Then you can have an observer that watches for changes on this variable like so:
truthChanged:function(){
console.log("TRUTH HAS CHANGED");
}.observes('this.theTruth'),
Ideally though, you can forgo all of this by taking advantage of the routes setupController function like soJSBIN
setupController: function(controller, model) {
controller.set('model', model);
controller.set('theTruth', true);
},
This way you don't even deal with handling anything in the init event. However, say you have a requirement that makes you have to do anything with the init event, you should separate your observer from the .on('init') event, and let them coexist separately. Let me know if this works for you.
I'm trying to figure out if angular base automatically unbinds watchers and scope events bound with $scope.$on(...) or $scope.$watch(...) when scope is destroyed?
Suppose I have following code:
$scope.$on('someEvents', handleSomeEvent);
$scope.$watch('someProperty', handleSomePropertyChange);
Do I need to manually unbind these watchers and events when $destroy event is triggered on scope?
According to Angular documentation on $scope:
'$destroy()' must be called on a scope when it is desired for the scope and its child scopes to be permanently detached from the parent and thus stop participating in model change detection and listener notification by invoking.
Also
Removal also implies that the current scope is eligible for garbage collection.
So it seems when $destroy() is called all the watchers and listeners get removed and the object which represented the scope becomes eligible for garbage collection.
If we look at the destroy() source code we'll see a line :
forEach(this.$$listenerCount, bind(null, decrementListenerCount, this));
Which is supposed to remove all the listeners.
As mentioned by #glepretre it applies to the watchers and listeners in the controller. The same doc page listed above says that:
Note that, in AngularJS, there is also a $destroy jQuery event, which can be used to clean up DOM bindings before an element is removed from the DOM.
So if you have specific listeners in the directives you should listen to the $destroy event and do the necessary cleanup yourself
If the scope is in a controller, angular unbind for you. Else you can unbind your event by calling the returned function :
var myevent = $scope.$on('someEvents', handleSomeEvent);
myevent() ; // unbind the event
http://docs.angularjs.org/api/ng/function/angular.bind
As previously answered, Angular indeed takes care of cleaning things for you, whenever possible. So if you do $scope.$on('someEvents', handleSomeEvent);, once the scope is destroyed (eg when you go to another page/view in your app), the event is automatically removed.
One important thing to note though, is that $rootScope is of course never destroyed, unless you quit your app. So if you do $rootScope.$on('someEvents', handleSomeEvent);, you may have to remove the event yourself, depending on where you listen to the event:
if in a controller or directive, then you'll have to remove it manually, else each time you'll instantiate the controller, a new event will be attached, and so handleSomeEvent will be called many times
if in a service, then you do not need to remove it manually, as services are always singleton (note that in Angular service, factory, ... all end up being the same thing)
I'm trying to call view method from controller, but no idea how to do this. From view I can easily call controller method like this.get('controller').send('method');
How to do something like that from controller this.get('view').send('method');?
To give you better overview what I'm trying to do.
I have application controller Ember.Controller.extend({}) I have application view Ember.View.extend({}) and application template.
In application template is login form, when user submit it controller method is executed. In this method if login credentials are incorrect I need to call view method which is executing jQueryUI method on login form (shake method to be exact and showing some text).
This sounds like a good use for Ember.Evented. By using event subscription and dispatching you can avoid coupling your view and controller.
Simply mixin Ember.Evented:
Controller = Ember.Controller.extend(Ember.Evented)
Now you can call on and trigger methods on your controller, to subscribe to an event and then to kick off the event. So, in your view you might do:
didInsertElement: function () {
this.get('controller').on('loginDidFail', this, this.loginFail);
}
And then in your controller call this.trigger('loginDidFail') to kick off your loginFail view method.
Remember to remove the handler after the view is dismissed... see the answer below.
Just wanted to answer on this question to address the issue with properly removing the listener if the view is cleared (when the route changes). It's also not necessary to use a jquery proxy, since the on/off methods support a target, which is good because unsubscribing a proxy is definitely more complicated. Revising what Christopher provided:
didInsertElement: function()
{
this.get('controller').on('loginDidFail', this, this.loginFail);
},
willClearRender: function()
{
this.get('controller').off('loginDidFail', this, this.loginFail);
}
Without removing the subscription any subsequent visits to the login route (without reloading the page) will add additional listeners; i.e. memory leaks, errors, and unexpected behavior.
Using $routeProvider every time user clicks on a link, a new $scope is being generated. That means all the data is lost. How can i make Angular use the same controller/$scope?
Explanation:
http://jsfiddle.net/mpKBh/1/
(click on links)
<a href='#'>First controller</a>
<a href='#/view'>Second controller</a>
$routeProvider.
when('/', { template:"{{$id}}",controller: ContentListCtrl}).
when('/view', {template:"{{$id}}",controller: ContentDetailCtrl}).
P.s. is it possible to know which controller is currently active?
In AngularJS, $scope is not meant to hold data that persists across your application. For that, you want to use a service that is injected into both controllers. If you provide more detail on what data you're missing across routes, I would be happy to revise this answer to include something a little more actionable.
In re your PS: You can inject the $route service to get information about the current route; the $route.current.controller property will give you the constructor function of the current route.
For those researching how to "unbind" in AngularJS, he is a bit of info (related to the OP's last comment above)
When a view is destroyed, it's basically marked for garbage collection - but it's still there. That is why you are getting multiple requests when a scroll happens - because it's still listening for events.
So the easiest way to deal with this (that I have found, though I'd like to learn of other ways) is to listen for the $destroy event and react on it.
You can "unbind/unlisten" for an event by keeping a reference to what is returned by the $on method. Here is an example taken from a controller:
$scope.systemListener = $rootScope.$on("someEventYouListenTo", function (event, data) {
console.log('Event received by ' + $scope.name);
});
$scope.$on('$destroy', function () {
// Remove the listener
$scope.systemListener();
});
Now those old scopes/views won't react to events anymore.
Hope that helps someone!