I have a service declared in my application.js file within my AngularJS project. It looks like this:
application.factory('interfaceService', function ($rootScope, $timeout) {
var interfaceService = {};
interfaceService.lang = "";
interfaceService.dev = ""
interfaceService.theme = "";
interfaceService.integ = "";
//For Integration Type
interfaceService.demo = function (dev, theme, integ, lang) {
this.dev = dev;
this.theme = theme;
this.integ= integ;
this.lang = lang;
this.broadcastItem();
};
interfaceService.broadcastItem = function () {
$timeout(function(){
$rootScope.$broadcast('handleBroadcast');
});
};
return interfaceService;
});
I am using the above service to pass variables between 2 of my Controllers. The controller which calls the service is here:
$scope.buildDemo = function () {
interfaceService.demo(device, template, integ, language);
$rootScope.template = template;
$rootScope.themeFolder = template;
$state.go("MainPage", {
"themeName": $rootScope.themeFolder
});
}
That function is triggered when the user clicks on a div on my view
<div id='build-btn' ng-click='buildDemo()'>Go</div>
The problem I am having is that my view is not updating when I click on the button. I have to click on it a second time to see any changes. Would anyone know what is causing this? The URL updates and the new view comes onto the page but the elements that should be showing based on the parameters set in the service are not visible until I click on the "Go" button a second time.
On first click handleBroadcast event gets broadcasted and but haven't received by anyone, because when you press "GO" button, at that time state transition occurs and it loads template and its controller. When controller instantiated, it register the listener event using $on on handleBroadcast event.
I'd suggest you that to wait to broadcast an event till controller & its underlying view gets render. This can be done by taking advantage of promise returned by $state.go method. Which completes when transition succeeds.
Code
$scope.buildDemo = function() {
//state transition started.
$state.go("MainPage", {
"themeName": $rootScope.themeFolder
}).then(function() {
//state transition completed
//controller instance is available & listener is ready to listen
interfaceService.demo(device, template, integ, language);
$rootScope.template = template;
$rootScope.themeFolder = template;
});
}
Related
Strange issue
<button ng-show="scene.audio" class="button icon {{scene.audioIcon}}"
ng-click="playAudio(scene)"/>
$scope.playAudio = function ($scene){
if($scene.audioIcon == "ion-ios-play-outline") {
$scene.audioIcon = "ion-ios-pause-outline";
media = new Media($scene.audio.src,function(){
**$scene.audioIcon = "ion-ios-play-outline";**
media.stop();
media.release();
},null);
media.scene = $scene;
media.play();
}
else if(media){
media.stop();
media.release();
$scene.audioIcon = "ion-ios-play-outline";
}
I can update the $scene.audioIcon on the 2 click functions, which updates the button in the UI. However in the onComplete function of new Media, this function is called when the audio is finished, and the $scene audio icon changes, however it doesn't get updated in the UI.
I assume because it comes later?
Is there a way I can trigger an update of the button?
angularjs doesn't know that it should check for changes because the completion event is called from native code. You should wrap your code in $scope.$apply().
Something like that:
media = new Media($scene.audio.src,function() {
$scope.$apply(function() {
$scene.audioIcon = "ion-ios-play-outline";
media.stop();
media.release();
});
},null);
This is undoubtedly a stupid problem where I'm just doing something simple wrong.
I have a page with several directives, loading their templates and controllers. All of which is working fine except for this one.
Using the controller as model, this. is the same as $scope.. So in my controller I have:
var self = this;
this.states = { showControls: false, showVideo: false }
this.showVideo = function() { self.states.showVideo = true; }
this.showControls = function() { self.states.showControls = true; }
$scope.$on(Constants.EVENT.START_WEBCAM, self.showVideo)
$scope.$on(Constants.EVENT.VIDEO_SUCCESS, self.showControls)
In the view I have a button to reveal this part of the view and subsequently request access to your webcam. Clicking the button broadcasts an event with $rootScope.$broadcast from the parent controller.
When the user grants access to the webcam (handled in the directive's link function) it broadcasts another event the same way.
Both methods are triggered by listening with $scope.$on, and both methods fire as they should. However, the showVideo method successfully updates its associated state property, and the showControls method does not. What am I doing wrong?
Using the debug tool it looks like states.showControls is being set to true, but this change isn't reflected in the view, and adding a watcher to the states object doesn't detect any change at this point either. It does when I set showVideo.
EDIT
This part is in the directive:
if (Modernizr && Modernizr.prefixed('getUserMedia', navigator)) {
userMedia = Modernizr.prefixed('getUserMedia', navigator);
}
var videoSuccess = function(stream) {
// Do some stuff
$rootScope.$broadcast(Constants.EVENT.VIDEO_SUCCESS);
}
scope.$on(Constants.EVENT.START_WEBCAM, function() {
if (MediaStreamTrack && MediaStreamTrack.getSources) {
MediaStreamTrack.getSources(function(sourceInfo) {
var audio = null;
var video = null;
_.each(sourceInfo, function(info, i) {
if (info.kind === "audio") {
audio = info.id;
} else if (info.kind === "video") {
video = info.id;
} else {
console.log("random unknown source: ", info);
}
});
if (userMedia) { userMedia(getReqs(), videoSuccess, error); }
});
}
});
So i'm learning AngularJS and i'm building a small web app that allows you to click through images randomly. Basically you click the next button and an image is downloaded and shown, when you click the back button it goes to the previous image in the stack.
I'd like to show a loading spinner and disable the back/forward buttons until the ajax request for the new image is complete, AND the image is completely loaded
My image controller is structured like so:
app.controller('ImageController', ['imageService', function(imageService) {
var that = this;
that.position = 0;
that.images = [];
that.loading = false;
that.isLoading = function() {
return that.loading;
}
that.setLoading = function(isLoading) {
that.loading = isLoading;
}
that.currentImage = function() {
if (that.images.length > 0) {
return that.images[that.position];
} else {
return {};
}
};
that.fetchSkin = function() {
that.setLoading(true);
imageService.fetchRandomSkin().success(function(data) {
// data is just a js object that contains, among other things, the URL for the image I want to display.
that.images.push(data);
that.imagesLoaded = imagesLoaded('.skin-preview-wrapper', function() {
console.log('images loaded');
that.setLoading(false);
});
});
};
that.nextImage = function() {
that.position++;
if (that.position === that.images.length) {
that.fetchSkin();
}
};
that.previousImage = function() {
if (that.position > 0) {
that.position--;
}
};
that.fetchSkin();
}]);
If you notice inside of the that.fetchSkin() function, i'm calling the imagesLoaded plugin then when the images are loaded I am setting that.loading to false. In my template I am using ng-show to show the images when the loading variable is set to false.
If I set loading to false outside of the imagesLoaded callback (like when the ajax request is complete) then everything works as expected, when I set it inside of the imagesLoaded function the template doesn't update with the new loading value. Note that the console.log('images loaded'); does print to the console once the images have loaded so I know the imagesLoaded plugin is working correctly.
As your imagesLoaded callback is invoked asynchronously once images are loaded, Angular does not know that values of that.isLoading() method calls changed. It is because of dirty checking that Angular uses to provide you with easy to use 2 way data binding.
If you have a template like so:
<div ng-show="isLoading()"></div>
it won't update after you change the values.
You need to manually tell angular about data changes and that can be done by invoking $digest manually.
$scope.$digest();
just after you do
console.log('images loaded');
that.setLoading(false);
Pseudo code that can work (copied and pasted from my directive):
//inside your controller
$scope.isLoading = false;
// just another way of using imagesLoaded. Yours is ok.
$element.imagesLoaded(function() {
$scope.isLoading = true;
$scope.$digest();
});
As long as you only change your controller $scope within async callback, there's no need to call $apply() to run $digest on $rootScope because your model changes are only local.
Description:
I have a modal where users can view their profile picture & upload a new one. This profile picture is shown in the navigation as well (next to their name).
The current process goes like this:
User goes to modal
User doesn't have photo, shows default stock avatar. User does, shows theirs
User uploads new photo, HTTP response returns the src attribute
Controller updates scope and sets $scope.photo equal to the response
Right now this works perfectly fine as it should. However, I have the profile photo in the layout view in the side navigation as well which is under command of another controller.
The scope variable is the same name and the HTML is the same, and they are set by the same service. But, the scope is set from different controllers.
Here is the code for the modal controller:
Files.profile_photo()
.success(function(data) {
$scope.photo = data;
})
$scope.onFileSelect = function($files) {
var path = $scope.file_name;
console.log(path);
for(var i = 0; i < $files.length; i++) {
var file = $files[i];
$scope.upload = $upload.upload({
url: '/smart2/api/files/profile',
file: file
}).success(function(data) {
$scope.photo = data;
});
}
}
And then in my controller for my side navigation where the photo also needs to be updated, I call the service Files and the function profile_photo and do the exact same thing to set the photo when the pages loads.
How can I also update it when it's changed from the modal? I can't do $scope.onFileSelect because that's in the modal inside of another controller.
I need to somehow notify Angular that the photo has been changed and update it in another controller.
What are my options?
Maybe you can handle this with broadcasting an event that the photo changed. I would store the photo in the service, not in the controller, and raise broadcast event when changed. In the controller I would listen to this event to update the image.
angular.factory('photo', function($rootScope) {
var photo = {};
var setPhoto = function (p) {
photo = p;
$rootScope.$broadcast('photoChanged', p);
};
var service = {};
service.setPhoto = setPhoto;
return service;
});
angular.controller('photoController', function($rootScope, $scope, photo) {
$scope.photo = {};
$rootScope.$on('photoChanged', function(p) {
$scope.photo = p;
});
$scope.changePhoto = function(p) {
photo.setPhoto(p);
};
});
I have the following functionin my controller
$scope.progress = function () {
var form = $scope.coverDetails;
for (i in form) {
if ($scope.coverDetails[i].hasOwnProperty('$valid') && !$scope.coverDetails[i].$valid) {
$location.hash(i + '-label');
break;
}
};
$scope.submitted = true;
$scope.validateForm();
if ($scope.coverDetails.$valid) {
$location.path('/zones');
}
$anchorScroll();
};
This is kind of working. But not really.
When I click on the submit button (which calls the above function) it successfully updates the hash, however, none of the ng-class or ng-show directives update, until the 2nd press of the button, assuming $location.hash hasn't changed between the first and second.
an example of one of the bits not working is
<label id="reg-label" for="reg" ng-class="{'error': coverDetails.reg.$invalid && submitted}">
Number plate
</label>
in this example, the class 'error' isn't applied, but it will scroll to the label if the field is invalid.
anyone able to help?
So I figured out what was going on,
changing the hash causes a tempalte reload, so one has to prevent that using the following code
angular.module("ScrollToErrorPrevention", []).factory('$preventErrorReload', ['$route',
function ($route) {
return function ($scope) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function () {
if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
$rootScope.$broadcast('locationChangedWithoutReload', $route.current);
$route.current = lastRoute;
}
});
};
}
])
and including it within the app thus:
angular.module('app', ['ScrollToErrorPrevention'])
and call the factory method from the controller
angular.module('app').controller('controller', ['$scope', $preventErrorReload,
function($scope, $preventErrorReload) {
$preventErrorReload($scope);
...
}
]);
after this, using $location.hash(...) along with $anchorScroll() works fine
The work is not entirely my own, but modified from one or 2 (more?) other sources