In my angular application (using ES6) I need to make an ajax call after some custom event is fired from other directive with the code below:
class GridController {
constructor($scope, $rootScope, MyService) {
this.$scope = $scope;
this.$rootScope = $rootScope;
this.MyService = MyService
this.MyService.getItems(arg1, arg2).then((resp) => {
this.viewModel= resp;
});
this.$rootScope.$on('myEvent', (e, args) => {
this.MyService.getItems(arg1, args[1]).then((resp) => {
this.viewModel= resp;
});
}
});
}
The problem is that the view is not updated after the successful ajax call.
Is there any ideas why this is happening?
It may be model update problem try this.
$scope.$apply();
Maybe the ajax-call happens outside of angulars digest cycle. You have tried calling $scope.$digest()? This fires the watchers of the current scope; they should pick up the model change.
Related
In my $onInit() I pick up the propagation event:
$onInit() {
this.$rootScope.$on('CANCELLED', (event) => {
this.EventService.getEventsCurrentUser('own')
.then((result) => {
this.ownEvents = result
})
})
}
How can I stop the propagation at one time ?
You need to "unregister" $rootScope events manually by calling the return function. You can do it with the component lifecycle by using this.$onDestroy. $rootScope events getting binded again and again each time $rootScope.$on() is executed. Thats why your events getting called multiple times.
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl', function ($scope, $rootScope) {
var registerScope = null;
this.$onInit = function () {
//register rootScope event
registerScope = $rootScope.$on('CANCELLED', function(event) {
console.log("fired");
});
}
this.$onDestroy = function () {
//unregister rootScope event by calling the return function
registerScope();
}
});
Please also check this answers which will help you to understand the logic behind $rootScope and $scope events:
How to use component lifecycles
Difference between $rootScope.$on vs $scope.$on
I am trying to bind data to $scope within a callback function and display this in an html element.
Below is my angular code:
gAutocomplete.controller('geocoder', ['gdata', function($scope, gdata){
var geocoder = L.mapbox.geocoder('mapbox.places');
geocoder.query('New York', assign_geocode2());
function assign_geocode2() {
function assign_geocode(err, data) {
console.log(data);
$scope.lat = data.latlng[0];
$scope.lng = data.latlng[1];
console.log($scope.lat)
}
return assign_geocode;
};
}])
Below is HTML:
</div>
<div class="spacer50"></div>
<div class="center-block" style="width:600px" ng-cloak data-ng- controller='geocoder'>
{{"Chosen lat/long are"}} {{$scope.lat}} {{$scope.lng}}
</div>
I can see the controller gets executed, callback function is called and values are written to console.log. However, they are not propogated to HTML element. What could be happening?
Update
I am not using $timeout as below and getting errors that $timeout is not a function. i know I am using an intermediate tmp variable, but when I use $timeout in the closure, I still have the same issue.
gAutocomplete.controller('geocoder', ['$scope', 'gdata', '$timeout', function($scope, $timeout, gdata) {
var tmp = {}
var geocoder = L.mapbox.geocoder('mapbox.places');
geocoder.query('New York', assign_geocode2(tmp));
function assign_geocode2(tmp) {
function assign_geocode(err, data) {
tmp.lat = data.latlng[0],
tmp.lng = data.latlng[1]
}
return assign_geocode;
}
$timeout(function() {
$scope.lat = tmp.lat,
$scope.lng = tmp.lng,
console.log($scope)},0);
}
])
You're changing scope values from a non-angular event handler. This means you need to notify angular that, "hey, I've updated things, take note pls". AFAIK the ideal way of taking care of this is running the callback inside a $timeout call.
function assign_geocode(err, data) {
$timeout(() => {
console.log(data);
$scope.lat = data.latlng[0];
$scope.lng = data.latlng[1];
console.log($scope.lat)
});
}
Running this inside $timeout will cause angular to run a digest cycle and update scope values. You don't need to do this from events initiated by Angular, because it already knows its in a digest cycle. For example, services like $http take care of this for you.
Scope is the glue between application controller and the view. During the template linking phase the directives set up $watch expressions on the scope. The $watch allows the directives to be notified of property changes, which allows the directive to render the updated value to the DOM.
...
{{"Chosen lat/long are"}} {{lat}} {{lng}}
...
Example :
http://plnkr.co/edit/5TJJkYf21LlwPyyKjgTv?p=preview
https://docs.angularjs.org/guide/scope
I have got a page layout with two controllers at the same time, one holds the data displayed as kind of side navigation, based on data stored the browsers local storage and at least one other controller, which is bind to a route and view.
I created this little wire frame graphic below which show the page layout:
The second controller is used for manipulating the local stored data and performs actions like adding a new item or deleting an existing one. My goal is to keep the data in sync, if an item got added or deleted by the 'ManageListCrtl' the side navigation using the 'ListCtrl' should get updated immediately.
I archived this by separating the local storage logic into a service which performs a broadcast when the list got manipulated, each controller listens on this event and updates the scope's list.
This works fine, but I'm not sure if there is the best practice.
Here is a stripped down version of my code containing just the necessary parts:
angular.module('StoredListExample', ['LocalObjectStorage'])
.service('StoredList', function ($rootScope, LocalObjectStorage) {
this.add = function (url, title) {
// local storage add logic
$rootScope.$broadcast('StoredList', list);
};
this.delete = function (id) {
// local storage delete logic
$rootScope.$broadcast('StoredList', list);
};
this.get = function () {
// local storage get logic
};
});
angular.module('StoredListExample')
.controller('ListCtrl', function ($scope, StoredList) {
$scope.list = StoredList.get();
$scope.$on('StoredList', function (event, data) {
$scope.list = data;
});
});
angular.module('StoredListExample')
.controller('ManageListCtrl', function ($scope, $location, StoredList) {
$scope.list = StoredList.get();
$scope.add = function () {
StoredList.add($scope.url, $scope.title);
$location.path('/manage');
};
$scope.delete = function (id) {
StoredList.delete(id);
};
$scope.$on('StoredList', function (event, data) {
$scope.list = data;
});
});
I don't see anything wrong with doing it this way. Your other option of course is to just inject $rootScope into both controllers and pub/sub between them with a $rootScope.$broadcast and a $rootScope.$on.
angular.module('StoredListExample')
.controller('ListCtrl', function ($scope, $rootScope) {
$scope.list = [];
$rootScope.$on('StoredList', function (event, data) {
$scope.list = data;
});
});
angular.module('StoredListExample')
.controller('ManageListCtrl', function ($scope, $rootScope, $location) {
$scope.list = [];
$scope.add = function () {
//psuedo, not sure if this is what you'd be doing...
$scope.list.push({ url: $scope.url, title: $scope.title});
$scope.storedListUpdated();
$location.path('/manage');
};
$scope.delete = function (id) {
var index = $scope.list.indexOf(id);
$scope.list.splice(index, 1);
$scope.storedListUpdated();
};
$scope.storedListUpdated = function () {
$rootScope.$broadcast('StoredList', $scope.list);
};
});
Additionally, you can achieve this in a messy but fun way by having a common parent controller. Whereby you would $emit up a 'StoredListUpdated' event from 'ManageListCtrl' to the parent controller, then the parent controller would $broadcast the same event down to the 'ListCtrl'. This would allow you to avoid using $rootScope, but it would get pretty messy in terms of readability as you add more events in this way.
It is always a better practice to use a common service that is a singleton for sharing the data between the 2 controllers - just make sure you use only references and not creating a local object in one of the controllers that should actually be in the service
I'm using BreezeJS with AngularJS but I'm having a difficult time understanding how to get Promises to work with $scope. Whenever I try to submit my form its not showing the validation errors until I click it for a 2nd time. I realize I could call $scope.$apply() but I read its not best practice? Here is my code:
app.controller("MainController", ["$scope", "$q", "datacontext", function ($scope, $q, datacontext) {
datacontext.manager.fetchMetadata();
$scope.errors = [];
$scope.addDamp = function () {
var item = datacontext.manager.createEntity("Damp", {
name: $scope.newDamp
});
var tes = datacontext.manager.saveChanges()
.then(function () {
alert("yay");
})
.fail(function (error, a, b, c) {
var arr = [];
error.entitiesWithErrors.map(function (entity) {
entity.entityAspect.getValidationErrors().map(function (validationError) {
arr.push(validationError.errorMessage);
});
});
$scope.errors = arr;
datacontext.manager.rejectChanges();
});
};
}]);
What is the best way to go about handling scope changes that come from inside of a Promise?
Yes, you're going to need $scope.apply here, because the promise isn't coming out of a core Angular call (such as $http, which would have handled the .apply() itself behind the scenes). In fact, the Breeze/Angular example on the BreezeJS page (http://www.breezejs.com/samples/todo-angular) includes a $scope.apply() after its data retrieval:
datacontext.getAllTodos()
.then(success)
.fail(failed)
.fin(refreshView);
function refreshView() {
$scope.$apply();
}
It's a bad practice to toss $scope.apply() about where you don't need it. But when you're handling promises created outside of Angular itself, it's going to come up.
Now my task is to rewrite $exceptionHandler provider so that it will output modal dialog with message and stop default event.
What I do:
in project init I use method .provider:
.provider('$exceptionHandler', function(){
//and here I would like to have rootScope to make event broadcast
})
standart inject method does not work.
UPD: sandbox - http://jsfiddle.net/STEVER/PYpdM/
You can inject the injector and lookup the $rootScope.
Demo plunkr: http://plnkr.co/edit/0hpTkXx5WkvKN3Wn5EmY?p=preview
myApp.factory('$exceptionHandler',function($injector){
return function(exception, cause){
var rScope = $injector.get('$rootScope');
if(rScope){
rScope.$broadcast('exception',exception, cause);
}
};
})
Update: add .provider technique too:
app.provider('$exceptionHandler', function() {
// In the provider function, you cannot inject any
// service or factory. This can only be done at the
// "$get" method.
this.$get = function($injector) {
return function(exception,cause){
var rScope = $injector.get('$rootScope');
rScope.$broadcast('exception',exception, cause);
}
};
});
My way of doing this - using a decorator and reverting to the previous exception handler on unknown errors:
app.config(function ($provide) {
$provide.decorator('$exceptionHandler', function($delegate, $injector) {
return function (exception, cause) {
if (ICanHandleThisError) {
var rootScope= $injector.get('$rootScope');
// do something (can use rootScope)
} else
$delegate(exception, cause);
};
});
});
You need to inject the $rootScope:
.provider('$exceptionHandler', '$rootScope', function(){
//and here I would like to have rootScope to make event broadcast
})
Is this what you tried? And if so do you have an error message or a jsfillde/plnkr to see why it failed?