Update $scope collection upon closing modal - javascript

I'm having the most difficult time trying to find a way to make sure the parent scope's collection is updated with the saved information from a modal.
The parent has a collection of event speakers, and the modal adds one speaker to the event. I want for the new speaker to be added to the page once the modal OK button is clicked.
The data is saved and the modal closes, but the model isn't updated unless I refresh the page.
Here's my controller method that updates the collection of speakers. $scope.speakers gets bound to a repeating object in the page.
$scope.updateSpeakersList = function () {
factory.callGetService("GetSpeakers?eventId=" + $scope.event.eventId)
.then(function (response) {
var fullResult = angular.fromJson(response);
var serviceResponse = JSON.parse(fullResult.data);
$scope.speakers = serviceResponse.Content;
LogErrors(serviceResponse.Errors);
},
function (data) {
console.log("Unknown error occurred calling GetSpeakers");
console.log(data);
});
}
Here's the promise where the modal should be calling the previous method, and therefore updating the page.
$scope.openModal = function (size) {
var modalInstance = $modal.open({
templateUrl: "AddSpeakerModal.html",
controller: "AddSpeakerModalController",
size: size,
backdrop: "static",
scope: $scope,
resolve: {
userId: function() {
return $scope.currentUserId;
},
currentSpeaker: function () {
return ($scope.currentSpeaker) ? $scope.currentSpeaker : null;
},
currentSessions: function () {
return ($scope.currentSpeakerSessions) ? $scope.currentSpeakerSessions : null;
},
event: function () {
return $scope.event;
},
registration: function() {
return $scope.currentUserRegistration;
}
}
});
modalInstance.result.then(function (savedSpeaker) {
$scope.savedSpeaker = savedSpeaker;
$scope.updateSpeakersList();
}, function () {
console.log("Modal dismissed at: " + new Date());
});
};
Why is the model not updating?

It's hard to know for sure without having a minimal example that reproduces the problem, but your problem might be that you are updating a 'shadow scope'. This is a pecularity in Javascript which causes the object to be copied instead of modified.
Try adding $scope.$apply() after doing the change in updateSpeakerList().

Check this post and then play around with it.
AngularJS - Refresh after POST
What worked for me was pushing the data to my scope on success then setting the location, so after posting new data mine looked like:
$scope.look.push(data);
$location.path('/main');

The issue here isn't that my data wasn't updating necessarily... the real issue is that the moments that I was attempting to update it were the wrong moments for how my app is put together. I was attempting to update the underlying model BEFORE the updates were available, without realizing it. The AJAX calls were still in progress.
Within my modal, when OK is clicked, it runs through a few different GETs and POSTs. I was calling the update outside of these calls, assuming that it would be called sequentially once the AJAX calls were done. (This was wrong.)
Once I moved the $scope.updateSpeakersList() call to be within the final AJAX success call to my factory, everything appears to be working as desired.

Related

Angular $scope confusion with $watch

I'm still in the beginning stages of my Angular 1.0 journey. I'm learning to like it, but I'm still scratching my head in a few places.
Lately, I've run into something while using $watch that's left me confounded. Take a look:
$scope.$watch('cookies', function() {
if ($cookies.getAll().redditSession) {
$scope.$emit('cookiesChanged')
// $scope.userWelcome = cookieService.decodeCookie($cookies.get('redditSession'))
}
})
$scope.$on('cookiesChanged', function() {
$scope.userWelcome = cookieService.decodeCookie($cookies.get('redditSession'))
})
This code works. If my cookies change, I emit an event thereby triggering the event listener, which changes the value of $scope.userWelcome to some value stored in the cookie. I see this change if I navigate to another route in my app.
However, I'm wondering why I had to use an event emitter here? Notice the line I commented out. I tried this first, but it doesn't change value of $scope.userWelcome, even if I move to another page in my app. I have to reload the page in order to see that I'm logged in.
What's going on here?
Try watching the cookie directly:
$scope.$watch(
function () {
return $cookies.get('redditSession');
},
function (newValue) {
if (newValue) {
$scope.userWelcome = cookieService.decodeCookie(newValue);
};
}
);
Your mistake is that you try to get the new value with the standard method. The way you can actually get the new value is adding it to the parameters of the function. Here it goes:
$scope.$watch('cookies', function(newValue, oldValue) {
if (newValue.getAll().redditSession) {
$scope.userWelcome = cookieService.decodeCookie(newValue.get('redditSession'))
}
// also try this
console.log(oldvalue === $cookies);
});
Cheers!

on enter controller call function angularJS

The problem is that i need to call a function every single time that a route change to a specific state, lets say i have chatController and i have to fire a() every second but if i exit the controller i have to stop a() and when i'm back to chatController i have to restart a()
My code:
$scope.stop = $interval(yourOperation, 1000);
var dereg = $rootScope.$on('$locationChangeSuccess', function() {
$interval.cancel($scope.stop);
dereg();
});
function yourOperation() {
console.log('$location', $location.$$url)
console.log('test');
}
Works fine executing every single and stops when the controller change, but it doesn't work anymore if i go back, i tried with ng-init() function but only fires the first time that the controller start, i need it always when i'm on a specifict controller.
1] If it is state then you can use following event to call function every time when you back to state
$scope.$on('$ionicView.enter',function(){
$scope.callingFunctionName();
});
Here you may need to add following attribute in app.js state declaration
cache: false
2] In case you are using modal then controller will automatically get initialize.
just need to call function like following -
$scope.callingFunctionName();
hi this code must be in the controller
// .... controller
$scope.stop = $interval(yourOperation, 1000);
function yourOperation() {
console.log('hi')
}
$scope.$on('$destroy',function () {
$interval.cancel($scope.stop);
})

Keep element in focus after state changed

In my AngularJS/Ui-Router application I have a series of input box like this:
<input ng-model="vm.filter" ng-keyup="vm.onKeyUp($event)" type="text" class="form-control" id="filter">
Then I have a controller, in which I have put the onKeyUp function, that looks like this:
var vm = this;
vm.onKeyUp = function (e) {
var val = e.currentTarget.value;
$state.go('aState', { 'filter': val });
}
This will fire the state change (really just state parameter filter is changed) that will call a resolve behind the scene and at the end of the flow, my data set is filtered and the subset is showed. Amazing.
But there is a little problem: on the state changed the HTML input lost its focus, and I can't figure out how can set it on focus at the end of the flow.
This is necessary since the user start to write in the input box and then will be surprised when his focus get "misteriously" lost. Bad UI experience.
Ok for what can i read you just call the same state with different parameters on the url (:filter), what happens even if the state is the same is that the stateChange force a reload of the view and therefore the lost of the focused element.
And that is the behaviour unless you make a event.preventDefault() on $stateChangeStart but im pretty sure your data will not be updated.
So possible solutions are imho:
Rethink your flow so it doenst depend on state change. (No need to code here :P)
Find a way to save your active element and set it on reload, like.
//sessionStorage to prevent data stored after window is closed;
$scope.$on('$stateChangeStart', function(){
$window.sessionStorage.setItem('focusedId', '#' + document.activeElement.id);
});
$scope.$on('$viewContentLoaded', function(){
if((var id = $window.sessionStorage.getItem('focusedId'))) {
$DOM.focus(id);
//Use a service for DOM manipulation, you know manipulation in controllers is bad :(
}
});
`
the service could be like this:
.factory('$DOM', ['$window', '$timeout', function ($window, $timeout) {
return {
__constructor: function (selector, event) {
$timeout(function () {
var element = (typeof selector == 'object') ?
selector :
$window.document.querySelector(selector);
(!!element && !!element[event]) ? element[event]() : null;
});
},
focus: function (selector) {
this.__constructor(selector, 'focus');
},
blur: function (selector) {
this.__constructor(selector, 'blur');
}, //and so....
};
}])
comment if you find a better way, maybe you can refresh the data preventing the refresh of the view and my understanding of ui.router is wrong :)

Angular.js doesn't refresh the repeater after $scope.var changes but only after refresh

I thought 2 ways binding was angular thing:
I can post from a form to the controller, and if I refresh the page I see my input on the page:
$scope.loadInput = function () {
$scope.me.getList('api') //Restangular - this part works
.then(function(userinput) {
$scope.Input = $scope.Input.concat(userinput);
// scope.input is being referenced by ng-repeater in the page
// so after here a refresh should be triggered.
},function(response){
/*#alon TODO: better error handling than console.log..*/
console.log('Error with response:', response.status);
});
};
In the html page the ng-repeater iterates over the array of for i in input. but the new input from the form isn't shown unless I refresh the page.
I'll be glad for help with this - thanks!
Try $scope.$apply:
$scope.loadInput = function () {
$scope.me.getList('api') //Restangular - this part works
.then(function(userinput) {
$scope.$apply(function(){ //let angular know the changes
$scope.Input = $scope.Input.concat(userinput);
});
},function(response){
/*#alon TODO: better error handling than console.log..*/
console.log('Error with response:', response.status);
});
};
The reason why: Your ajax is async, it will execute in the next turn, but at this time, you already leaves angular cycle. Angular is not aware of the changes, we have to use $scope.$apply here to enter angular cycle. This case is a little bit different from using services from angular like $http, when you use $http service and handle your response inside .success, angular is aware of the changes.
DEMO that does not work. You would notice that the first click does not refresh the view.
setTimeout(function(){
$scope.checkboxes = $scope.checkboxes.concat([{"text": "text10", checked:true}]);
},1);
DEMO that works by using $scope.$apply
setTimeout(function(){
$scope.$apply(function(){
$scope.checkboxes = $scope.checkboxes.concat([{"text": "text10", checked:true}]);
});
},1);

AngularJS : service $broadcast and $watch not triggering on receiving controller

AngularJS noob here, on my path to the Angular Enlightenment :)
Here's the situation:
I have implemented a service 'AudioPlayer' inside my module 'app' and registered like so:
app.service('AudioPlayer', function($rootScope) {
// ...
this.next = function () {
// loads the next track in the playlist
this.loadTrack(playlist[++playIndex]);
};
this.loadTrack = function(track) {
// ... loads the track and plays it
// broadcast 'trackLoaded' event when done
$rootScope.$broadcast('trackLoaded', track);
};
}
and here's the 'receiver' controller (mostly for UI / presentation logic)
app.controller('PlayerCtrl', function PlayerCtrl($scope, AudioPlayer) {
// AudioPlayer broadcasts the event when the track is loaded
$scope.$on('trackLoaded', function(event, track) {
// assign the loaded track as the 'current'
$scope.current = track;
});
$scope.next = function() {
AudioPlayer.next();
};
}
in my views I show the current track info like so:
<div ng-controller="PlayerCtrl">
<button ng-click="next()"></button>
// ...
<p id="info">{{current.title}} by {{current.author}}</p>
</div>
the next() method is defined in the PlayerCtrl, and it simply invokes the same method on the AudioPlayer service.
The problem
This works fine when there is a manual interaction (ie when I click on the next() button) - the flow is the following:
PlayerCtrl intercepts the click and fires its own next() method
which in turn fires the AudioPlayer.next() method
which seeks the next track in the playlist and calls the loadTrack() method
loadTrack() $broadcasts the 'trackLoaded' event (sending out the track itself with it)
the PlayerCtrl listens the broadcast event and assigns the track to the current object
the view updates correctly, showing the current.title and current.author info
However, when the next() method is called from within the AudioService in the 'background' (ie, when the track is over), all the steps from 1 to 5 do happen, but the view doesn't get notified of the change in the PlayerCtrl's 'current' object.
I can see clearly the new track object being assigned in the PlayerCtrl, but it's as if the view doesn't get notified of the change. I'm a noob, and I'm not sure if this is of any help, but what I've tried is adding a $watch expression in the PlayerCtrl
$scope.$watch('current', function(newVal, oldVal) {
console.log('Current changed');
})
which gets printed out only during the 'manual' interactions...
Again, like I said, if I add a console.log(current) in the $on listener like so:
$scope.$on('trackLoaded', function(event, track) {
$scope.current = track;
console.log($scope.current);
});
this gets printed correctly at all times.
What am I doing wrong?
(ps I'm using AudioJS for the HTML5 audio player but I don't think this is the one to blame here...)
When you have a click event the $scope is updated, without the event you'll need to use $apply
$scope.$apply(function () {
$scope.current = track;
});
As it's not safe to peek into the the digest internals, the easiest way is to use $timeout:
$timeout(function () {
$scope.current = track;
}, 0);
The callback is executed always in the good environment.
EDIT: In fact, the function that should be wrapped in the apply phase is
this.loadTrack = function(track) {
// ... loads the track and plays it
// broadcast 'trackLoaded' event when done
$timeout(function() { $rootScope.$broadcast('trackLoaded', track); });
};
Otherwise the broadcast will get missed.
~~~~~~
Actually, an alternative might be better (at least from a semantic point of view) and it will work equally inside or outside a digest cycle:
$scope.$evalAsync(function (scope) {
scope.current = track;
});
Advantage with respect to $scope.$apply: you don't have to know whether you are in a digest cycle.
Advantage with respect to $timeout: you are not really wanting a timeout, and you get the simpler syntax without the extra 0 parameter.
// apply changes
$scope.current = track;
try {
if (!$scope.$$phase) {
$scope.$apply($scope.current);
}
} catch (err) {
console.log(err);
}
Tried everything, it worked for me with $rootScope.$applyAsync(function() {});

Categories

Resources