I'm trying to show a no internet access button when my app user is not connected to the internet and the object in question is not available from the cache.
HTML:
<div class="VideoPlayerController video-player-controller">
<span class="icon-stack disconnected" ng-show="connectionError">
<icon type="signal" class="signal-icon"></icon>
<icon type="plus" class="plus-icon icon-stack-base"></icon>
</span>
<video autoplay controls></video>
JS:
(function() {
'use strict';
angular.module('core-viewer')
.controller('VideoPlayerController', ['$scope', 'ApiService', '$routeParams', 'NavbarService',
function($scope, ApiService, $routeParams, NavbarService) {
$scope.connectionError = false;
$scope.videoLoaded = function(video) {
NavbarService.header = video.title;
$('.video-player-controller video').bind("error", function(event) {
loadFail();
});
$('.video-player-controller video').attr('src', video.file.downloadURL);
};
function loadFail() {
if (!navigator.onLine) {
$scope.connectionError = true;
}
}
ApiService.getVideo($routeParams.uuid).then($scope.videoLoaded);
}]);
})();
Whenever connectionError gets set to true nothing happens back on the HTML view. It's obviously connecting in some way because if the default value of $scope.connectionError is false then it will hide the item, and if the default is true then it will show the item, but when the value is actually changed I see no response.
Am I missing something simple?
Whenever you make changes from outside of the angular framework, e.g. from browser DOM events, setTimeout, XHR or third party libraries, you need to use $apply as per the documentation.
function loadFail() {
if (!navigator.onLine) {
$scope.$apply(function() {
$scope.connectionError = true;
});
}
}
Edit:
You are doing a number of things considered bad practice, e.g. doing DOM manipulation inside your controller. I recommend you start from this answer.
Related
When working on a project, as these things tend to happen, we came across a situation where we were stumped on how to update certain UI elements when other things were done. For example, the navigation contains a counter of how many pending activities are due today. At any point in time during usage of the app, a user might schedule an activity for later today, and the count section would need to call the API to generate a count and the drop-down items associated with it.
How can I make a navigation controller pull the new list of activities when the main controller makes a change?
See this code for an example.
<div ng-app="TestApp">
<nav ng-controller="navigationController">
<p>The navigation count is: {{items.length}}</p>
</nav>
<div ng-controller="mainController">
<p>The main count is: {{items.length}}</p>
<p>
<button ng-click="addItem()" type="button">Add item.</button>
</p>
</div>
</div>
<script>
var app = angular.module('TestApp', []);
app.factory("api", function() {
return {
update: function() {
return ['a', 'b', 'c', 'd', 'e'];
}
};
});
app.factory("sharedFactory", function(api) {
var obj = {};
obj.items = ["a"];
obj.update = function() {
obj.items = api.update();
};
return obj;
});
app.controller("mainController", function(sharedFactory, $scope) {
$scope.items = sharedFactory.items;
$scope.addItem = function() {
sharedFactory.update();
};
});
app.controller("navigationController", function(sharedFactory, $scope) {
$scope.items = sharedFactory.items;
});
</script>
Our current solution was to create a callback service that other controllers could subscribe to, and then when an activity was created have those callbacks run as needed. This works nicely, but I'm nervous that I'm "doing it wrong".
We're switching to the Angular UI Router, now, so I'm curious if there's a better way of doing so in it. Right now our navigation handler is a stateless controller that hooks into our callback service still.
A nice way to handle this could be to use $scope.$on to listen for events, and $scope.$emit to fire an event going up the scope or $scope.$broadcast to fire an even going down the scope.
In each piece of the UI that needs to be updated can be listening with $scope.$on and update itself when an event is fired, like your user scheduling an event for later today.
Angular docs for $on, $emit and $broadcast
Though I generally think that registering scope values on a controller with a service is the best way to accomplish another option would be to use a factory and set a property of that on scope.
angular.module('app').factory('myService', function() {
var myService = {};
service.count = 0;
/// other service functions
return myService;
}
angular.module('app').controller('myController', function(myService) {
this.count = myService.count;
}
However you feel about MVC, you could use angular's internals to automatically do this:
https://jsfiddle.net/gkmtkxpm/
var myApp = angular.module('myApp', []);
myApp.factory('counter', function() {
return {
count: 0
};
});
myApp.controller('CounterController', function (counter) {
var vm = this;
vm.counter = counter;
vm.increment = function() {
vm.counter.count = vm.counter.count + 1;
};
});
edit:
Concerning your updated question, see the updated fiddle:
https://jsfiddle.net/gkmtkxpm/1/
I've got a potentially really dumb question, but how do I modify variables up in $rootScope in Angular? I've got a slide-in sidebar that I want to change the content on whenever someone clicks on a thumbnail, and I figured the easiest way to handle where the data in the sidebar comes from/the sidebar visibility would either be in global values, or in $rootScope. I'm trying to keep everything as simple as possible, but I just don't know how to handle modifying global variables.
My angular code surrounding this is:
app.run(function($rootScope) {
$rootScope.currentUrl = { value: 'visual/design/1/' };
$rootScope.detail_visible = { value: true };
});
app.controller('navController', ['$scope', '$rootScope',
function ($scope, $rootScope) {
$scope.isDetail = $rootScope.detail_visible.value;
$scope.url = $rootScope.currentUrl.value;
$scope.hide = function($rootScope) {
$rootScope.detail_visible.value = false;
};
}]);
and the connecting HTML is
<div id="detail_box" ng-class="{d_show: isDetail, d_hide: !isDetail}">
<div ng-include="url + 'detail.html'"></div>
</div>
In essence, I'm trying to make it so that when you click on a thumbnail, it changes the currentUrl value from 'visual/design/1/' to whatever they've clicked on (like, 'music/solo/2' or whatever) then changes the value of detail_visible to false, so that the classes on my sidebar switch and I get a nice little slide-in, with fresh content loaded via ng-include which I kind of love a thousand times more than I thought I would. I've been banging my head against this for about three hours now, breaking everything else on this app whenever I get the chance. What am I screwing up here? Alternatively, is there a better way of doing this?
My reason for using global variables is that I have multiple thumbnails in multiple controllers, and I want each one to be able to dynamically change the URL in my ng-include.
For your question, you change the $rootScope variable simple by referencing it with
$rootScope.detail_visible.value = newValue;
but you dont need to inject $rootScope to your function:
$scope.hide = function() { //without $rootScope
$rootScope.detail_visible.value = false;
};
But, I would suggest you to implement a service and not to pollute the rootscope for such task.
https://docs.angularjs.org/guide/services
Object properties of scopes are inherited -- in your controller, you should be able to modify $scope.detail_visible.value and see it affect the $rootScope. You still have to initialize it on the $rootScope in .run() though.
app.run(function($rootScope) {
$rootScope.currentUrl = { value: 'visual/design/1/' };
$rootScope.detail_visible = { value: true };
});
app.controller('navController', ['$scope', function ($scope, $rootScope) {
$scope.hide = function() { // don't need to pass an argument
$scope.detail_visible.value = false;
};
}]);
view:
<div id="detail_box" ng-class="{d_show: currentUrl.value, d_hide: !currentUrl.value}">
<div ng-include="currentUrl.value + 'detail.html'"></div>
</div>
I have a single page app that opens a gallery. I want to bind document level keyup event (for keyboard gallery controlls) only when the gallery is open, ie. when route matches
.when('/reference/:galleryId/:imageId/', { templateUrl: '/partials/gallery.htm', controller: 'galleryController', controllerAs: 'reference' })
and I want to unbind it when I leave this route.
One thing that might be a problem is, I block reloading the view between images within the same gallery with this:
.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])
Demo (Click on "Fotografie" to open the gallery)
http://tr.tomasreichmann.cz/
Angular wiz to the rescue?
Thank you for your time and effort
You could bind a keyup event to $document in your controller's constructor and then unbind the event when the controller's $scope is destroyed. For example:
.controller('galleryController', function ($scope, $document) {
var galleryCtrl = this;
function keyupHandler(keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
$scope.$apply(); // remove this line if not need
}
$document.on('keyup', keyupHandler);
$scope.$on('$destroy', function () {
$document.off('keyup', keyupHandler);
});
...
});
There will be nothing left behind when the view become inactive.
If you feel it isn't right to do this in the controller, you could move this into a custom directive and place it in a template of the view.
Finally I stuck with
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml"
lang="cz"
ng-app="profileApp"
ng-keyup="$broadcast('my:keyup', $event)" >
Not sure if this is good practice, but it registers within my controller
$scope.$on('my:keyup', function(event, keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
});
And doesn't do anything when the current route is not active
I found this answer here: https://groups.google.com/forum/#!searchin/angular/document$20level$20events/angular/vXqVOKcwA7M/RK29o3oT9GAJ
There is another way to bind it globally which wasn't my goal, the original code in question did what I needed.
Not able to figure out what the bug in this code is.I've tried to only post the relevant parts of the code here.
Controller
myApp.controller('MessageCtrl', function ($scope, notificationService, $rootScope) {
$scope.notificationService = notificationService;
$scope.msgCount = 0;
$scope.notificationService.subscribe({channel : 'my_channel'});
$rootScope.$on('pubnub:msg',function(event,message){
$scope.msgCount = $scope.msgCount + 1;
//$scope.$digest();
});
});
My Notification Angular Service
myApp.factory('notificationService',['$rootScope', function($rootScope) {
var pubnub = PUBNUB.init({
publish_key : '..',
subscribe_key : '..'
});
var notificationService = {
subscribe : function(subscription) {
pubnub.subscribe({
channel : subscription.channel,
message : function(m){
$rootScope.$broadcast('pubnub:msg', m);
}
});
}
};
return notificationService;
}]);
And the template :
<div>
Count = {{msgCount}}
</div>
The problem :
Using console logs & using karma tests I have confirmed that the $rootScope.$on method in MessageCtrl is getting called when I do a $broadcast from Notification Service. And that the msgCount variable is getting incremented. However, I don't see the updated value being reflected in the template without running a $scope.$digest() . I am pretty sure I shouldn't be needing to have to call $scope.$digest , ie Angular should be providing me this binding.
Interestingly, when I tried a $rootScope.$broadcast from another controller, the msgCount in the template got incremented without having to call $scope.$digest().
Can anyone kindly help me here. Thank you.
Update
Thanks to Peter and looking at the google group discussion, wrapping the $broadcast in an $apply did the trick.
$rootScope.$apply(function(){
$rootScope.$broadcast('pubnub:question', m);
});
It seems that your $broadcast happens outside AngularJS and you need to notify your app about it with calling $apply(), but better do it in the notificationService.
As for $broadcast and $on trigger a apply/digest you can read in this post. Brief overview of AngularJs source files make me sure that $broadcast does not auto-apply changes (look here ). $broadcast just calling listeners and nothing else.
Please, take a look at this simple example on jsFiddle .
The template
<div ng-controller="myCtrl">
<p>Count: {{ count }}</p>
<button ng-click="fireEvent()">Fire Event</button>
</div>
The controller
angular.module("app", [])
.controller('myCtrl', function($scope, $rootScope, notificationService) {
$scope.count = 0;
notificationService.subscribe();
$rootScope.$on('event', function() {
console.log("event listener");
$scope.count++;
});
$scope.fireEvent = function() {
// it is ok here due to ngClick directve
$rootScope.$broadcast('event', true);
};
})
And factory
.factory('notificationService',['$rootScope', function($rootScope) {
var notificationService = {
subscribe : function() {
setInterval(function(){
console.log("some event happend and broadcasted");
$rootScope.$broadcast('event', true);
// angular does not know about this
//$rootScope.$apply();
}, 5000);
}
};
return notificationService;
}]);
Of course in both cases you will see that event listener fires, but ngClick fires $digest and your notificationService does not.
Also you can get some info about sources that will start the digest cicle in this nice answer https://stackoverflow.com/a/12491335/1274503
i am new in angular js. i done with div hide and show. just i want to know how to hide or show div for 3 seconds only.
here i attaching my code which i used.
html code:
<div ng-hide="loginAlertMessage">Dynamic user feedback message comes here.</div>
<a ng-click="forgotPassword()">Forgot Password?</a>
angular js code:
$scope.loginAlertMessage = true;
$scope.forgotPassword = function () {
$scope.loginAlertMessage=false;
};
Inject the $timeout service in the controller and use it to unset loginAlertMessage.
app.controller('MyController', ['$scope', '$timeout', function($scope, $timeout) {
$scope.loginAlertMessage = true;
$scope.forgotPassword = function() {
$scope.loginAlertMessage = false;
$timeout(function() {
$scope.loginAlertMessage = true;
}, 3000);
};
// ...
}]);
inject $timeout service (derived from setTimeout(function() {"action",time(in ms) }) in controller and if wish to display make the div true and if wish to hide make use this service to stop.