is it possible to bind the date from an serviceProvider with $watch?
My question is can i bind data from an provider service to my view?
I try to bind the $get object from a provide in my view because the provider is loaded in the init phase, without an controller.
I try it on this way but i get no updates.
http://plnkr.co/edit/t3PrE6lX1EDIMuqKyTW0?p=preview
<body ng-app="ServiceNotification">
<div style="border-style:dotted" ng-controller="TimerCtrl1">
TimerCtrl1<br/>
Last Updated: {{lastUpdated}}<br/>
Last Updated: {{calls}}<br/>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.5/angular.js"></script>
<script type="text/javascript">
var app = angular.module("ServiceNotification", []);
function TimerCtrl1($scope, Timer) {
$scope.$watch(function () { return Timer.data; }, function (data){
console.log("In $watch - data:" + data);
$scope.lastUpdated = data.lastUpdated;
$scope.calls = data.calls;
}, true); // <-- don't forgt the true
};
app.provider("Timer", function () {
this.$get = function() {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
window.setTimeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
}
}
});
</script>
Thanks Thomas
You get no updates cause setTimeout callback is called out of the digest cycle. There is a special $timeout wrapper in angular (http://docs.angularjs.org/api/ng.$timeout), which you can inject into you $get provider method.
app.provider("Timer", function () {
this.$get = function($timeout) {
var data = { lastUpdated: new Date(), calls: 0 };
var updateTimer = function () {
data.lastUpdated = new Date();
data.calls += 1;
console.log("updateTimer: " + data.lastUpdated);
$timeout(updateTimer, 5000);
};
updateTimer();
return {
data: data
}
}
});
Demo: http://plnkr.co/edit/QUVuSnikIwVd3TocU7Vn?p=preview
Related
I am writing an app with Angular 1.5, Ionic 1.3, and Cordova. Right now I am working on a part where the user will push a button, the app will store the time and geolocation in the localStorage and then post the data back to the server and update the view. The issue I am having is that in one situation the view is not updating after a model change.
I have tried a few things: $timeout, $rootScope, $q and nothing seems to fix the issue. It only happens on iOS and not on Android. I am also using this library to help with the geolocation process: https://github.com/dpa99c/cordova-diagnostic-plugin
I am aware of the 3rd party library issue where it may not be a part of the Angular digest cycle but I have wrapped it with $q and no luck: https://www.searchenginepeople.com/blog/top-5-technical-issues-large-sites-angularjs.html
I am posting the pseudo code here.
view.html
<ion-view cache-view="false">
<ion-content>
<span>{{ data.text }}</span>
<span>{{ data.date }}</span>
<span>{{ data.isSync }}</span>
<button ng-click="showPopup()">Click</button>
</ion-content>
</ion-view>
controller.js
(function () {
'use strict';
angular
.module('myApp')
.controller('ViewController', ViewController);
ViewController.$inject = ['$scope', 'GeoDataModelService'];
function ViewController($scope, GeoDataModelService) {
$scope.data = GeoDataModelService.value;
$scope.showPopup = GeoDataModelService.showPopup();
}
})();
service.js
(function () {
'use strict';
angular
.module('myApp')
.factory('GeoDataModelService', GeoDataModelService);
GeoDataModelService.$inject = [
...
];
function GeoDataModelService(
...
) {
//data model
var dataModel = {
isSync: true,
text: null,
date: null
};
return {
value: dataModel,
showPopup: showPopup
};
function showPopup() { //gets called
$ionicPopup.confirm({
title: 'clock now ?',
buttons: [{
text: 'CONTINUE',
type: 'button-positive',
onTap: function () {
geoData();
}
}, {
text: 'CANCEL',
type: 'button-default'
}]
});
};
function geoData() {
getLocationServicesStatus()
.then(geoServiceSuccessful)
.catch(function(err) {});
};
function geoServiceSuccessful() { //gets called
DataModelService.createRecord();
sendDataToServerAfterGeoData();
}
function getLocationServicesStatus() {
console.log(' getLocationServicesStatus');
var deferred = $q.defer();
//this is outside of angular
cordova.plugins.diagnostic.isLocationAvailable(
function (available) {
if (available) {
deferred.resolve(true);
} else {
deferred.reject(false);
}
}, function (error) {
deferred.reject(false);
}
);
return deferred.promise;
}
function updateDataModel(source) {
console.log('source ', source); //this one is not null
if (source != null) {
dataModel.text = source.text;
dataModel.date = source.date;
console.log(JSON.stringify(dataModel)); //correct
}
}
function sendDataToServerAfterGeoData() {
//if offline just skip the server post
if (!navigator.onLine) {
// trigger digest cycle
$timeout(function () {
updateModelAfterRecord(); //this one works fine
}, 0);
return;
}
var clockins = DataModelService.load(); //load from local storage
console.log(' * * * * * HERE WE GO * * * * * ');
//this service returns an http promise
DataModelService
.sendLocalDataToService(clockins)
.then(sendDataToServerAfterGeoDataSuccess)
.then(getClockDataToServerAfterGeoSuccess)
.catch(handleSendDeviceDataToServerFail);
};
function sendDataToServerAfterGeoDataSuccess() {
console.log(' sendDataToServerAfterGeoDataSuccess ');
//this service returns an http promise
return DataModelService.getDataModelFromServer();
}
function getClockDataToServerAfterGeoSuccess(response) {
console.log(' getClockDataToServerAfterGeoSuccess ', response);
console.log('1 dataModel: ', dataModel);
// $timeout not working here
// $rootScope.asyncEval not working either
// $rootScope.$apply threw an error
console.log(' 2 dataModel: ', dataModel); //correct
dataModel.isSync = true;
updateDataModel(response); //goes through this code
console.log('3 dataModel: ', dataModel); //correct
console.log(' 4 dataModel: ', dataModel); //correct
console.log('5 dataModel: ', dataModel); //correct
return response; //tried to leave this out - no effect
}
function handleSendDeviceDataToServerFail(error) {
console.log('handleSendDeviceDataToServerFail ', error);
var clockins = DataModelService.load();
dataModel.isSync = false;
updateDataModel(clockins); //this works
}
function updateModelAfterRecord() {
dataModel.isSync = false;
var data = DataModelService.load();
updateDataModel(data);
}
}
})();
I added a watcher to see if the data is changing:
$scope.$watch('data.text', function(newVal, oldVal) {
console.log(' new val', newVal); //this is correct
});
I am still learning the ropes when it comes to unit testing with angular. I have an angular service that I use to create HTML5 notifications. Code is similar to the following:
(function() {
'use strict';
angular
.module('blah')
.factory('OffPageNotification', offPageNotificationFactory);
function offPageNotificationFactory($window) {
//Request permission for HTML5 notifications as soon as we can
if($window.Notification && $window.Notification.permission !== 'denied') {
$window.Notification.requestPermission(function (status) { });
}
function OffPageNotification () {
var self = Object.create(OffPageNotification.prototype);
self.visibleNotification = null;
return self;
}
OffPageNotification.prototype.startNotification = function (options) {
var self = this;
self.options = options;
if(self.options.showHtml5Notification && (!self.options.onlyShowIfPageIsHidden || $window.document.hidden)) {
if($window.Notification && $window.Notification.permission !== 'denied') {
self.visibleNotification = new $window.Notification('Notification', {
body: self.options.notificationText,
icon: self.options.notificationIcon
});
}
}
};
.
.
.
return new OffPageNotification();
}
})();
I am attempting to write unit tests for this but am unsure how to mock $window.Notification so it can be used as both a constructor...
self.visibleNotification = new $window.Notification(....)
and also contain properties
if($window.Notification && $window.Notification.permission !== 'denied')
and methods....
$window.Notification.requestPermission(
An example of something I have tried is:
describe('startNotification', function() {
beforeEach(function() {
var mockNotification = function (title, options) {
this.title = title;
this.options = options;
this.requestPermission = sinon.stub();
};
mockNotification.prototype.permission = 'granted';
mockWindow = {
Notification: new mockNotification('blah', {}),
document: {hidden: true}
};
inject(function (_OffPageNotification_) {
OffPageNotification = _OffPageNotification_;
});
});
it('should display a html5 notification if the relevant value is true in the options, and permission has been granted', function(){
var options = {
showHtml5Notification: true,
onlyShowIfPageIsHidden: true
};
OffPageNotification.startNotification(options);
});
});
I get an error saying '$window.Notification is not a constructor' with this setup and I understand why (I am passing in an instantiated version of the mockNotification). But if I set mockWindow.Notification = mockNotification then I get an error when it calls requestPermission since this is undefined.
Any help is appreciated
Notification should be a constructor. And it should have static properties and methods.
All of the relevant properties of mockNotification are instance properties, while they should be static:
function MockNotification() {}
MockNotification.title = title;
MockNotification.options = options;
MockNotification.requestPermission = sinon.stub();
mockWindow = {
Notification: MockNotification,
document: {hidden: true}
};
I would like to use two different $firebaseArrays on one view with one controller. But only one of them works and the other only works if i put him in his own controller.
from my factory file:
.factory("AlphaFactory", ["$firebaseArray",
function($firebaseArray) {
var ref = firebase.database().ref('alpha/');
return $firebaseArray(ref);
}
])
.factory("BetaFactory", ["$firebaseArray",
function($firebaseArray) {
var ref = firebase.database().ref('beta/');
return $firebaseArray(ref);
}
])
and my controller:
.controller('DemoCtrl', function($scope, AlphaFactory, BetaFactory) {
$scope.alphaJobs = AlphaFactory;
$scope.addalphaJob = function() {
$scope.alphaJobs.$add({
Testentry: $scope.loremipsum,
timestamp: Date()
});
$scope.alphaJob = "";
};
$scope.betaJobs = BetaFactory;
$scope.addbetaJob = function() {
$scope.betaJobs.$add({
Testentry2: $scope.dolorest,
timestamp: Date()
});
$scope.betaJob = "";
};
)}
Are you sure it is not a simple matter of a promise has not finished?
var alphaJobs = AlphaFactory;
alphaJobs.$loaded().then(function() {
// Do something with data if needed
$scope.alphaJobs = alphaJobs;
});
var betaJobs = BetaFactory;
betaJobs.$loaded().then(function() {
// Do something with data if needed
$scope.betaJobs = betaJobs;
});
I am trying to display an object (songTitle) from my service. The initial state (tmp) is displayed. If I am changing the object in the service, the view doesnt get updated.
Js:
var party = angular.module("party", []);
party.run(function () {
var tag = document.createElement('script');
tag.src = "http://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
});
party.service('PlayerService', function ($window) {
this.playlist = [
"https://www.youtube.com/watch?v=fnW2uLwHAas",
"https://www.youtube.com/watch?v=iPT8DA32U6U",
"https://www.youtube.com/watch?v=eGjEnfQl37s",
"https://www.youtube.com/watch?v=nFtTY2S20mI",
"https://www.youtube.com/watch?v=UmXQiPLoLTk",
"https://www.youtube.com/watch?v=PbVx85DS9zc",
"https://www.youtube.com/watch?v=ciidn3nEoiE",
"https://www.youtube.com/watch?v=sm0DgkBEnUI",
"https://www.youtube.com/watch?v=J2OCSWF7sAw",
"https://www.youtube.com/watch?v=y_-giRHtuv8",
"https://www.youtube.com/watch?v=iPT8DA32U6U",
"https://www.youtube.com/watch?v=eGjEnfQl37s",
"https://www.youtube.com/watch?v=nFtTY2S20mI",
"https://www.youtube.com/watch?v=UmXQiPLoLTk",
"https://www.youtube.com/watch?v=PbVx85DS9zc"
];
this.player = {};
this.pbTimer = null;
this.songTitle = "tmp";
$window.onYouTubeIframeAPIReady = function () {
this.player = new YT.Player('ytplayer', {
height: '100',
width: '100',
videoId: 'ciidn3nEoiE',
events: {
'onReady': onPlayerReady
}
});
}
function onPlayerReady() {
console.log("db ready");
songTitle = player.getVideoData().title;
console.log(songTitle);
}
this.playVideo = function (url) {
console.log("db playVideo " + url);
player.loadVideoById(url.split("watch\?v=")[1], 0, "large");
console.log(player);
}
});
party.controller("FrontController", function ($scope) {
$scope.front = {};
$scope.front.title = "PARTY";
});
party.controller("PartyController", ['$scope', 'PlayerService', function ($scope, PlayerService) {
$scope.party = {};
$scope.party.title = "PARTY";
Sortable.create(playlist, { /* options */ });
$scope.playlist = PlayerService.playlist;
$scope.playVideo = function (url) {
PlayerService.playVideo(url);
}
$scope.songTitle = PlayerService.songTitle;
}]);
HTML
<body ng-app="party">
<div ng-controller="PartyController" class="container-fluid">
...
<p id="playertitle">{{songTitle}}</p>
...
Log:
db ready
Blackmill Feat. Veela - Life (Full Version)
The problem is in your onPlayerReady function. The line songTitle = player.getVideoData().title; doesn't set songTitle on your service, but rather on the global scope, which is the window object. Simply using this.songTitle won't help either, because this doesn't refer to your service too in the scope of onPlayerReady.
The easiest solution would be to save a reference to your service outside of onPlayerReady and then use it to assign songTitle:
var self = this;
function onPlayerReady() {
console.log("db ready");
self.songTitle = player.getVideoData().title;
console.log(self.songTitle);
}
Still, this is not enough. Because you change songTitle from outside the Angular world (the Youtube player callbacks), you need to call $scope.$apply to notify Angular something has changed.
For that, you need to inject $rootScope into your service:
party.service('PlayerService', function ($window, $rootScope)
and change songTitle using $rootScope.$apply:
var self = this;
function onPlayerReady() {
console.log("db ready");
$rootScope.$apply(function() {
self.songTitle = player.getVideoData().title;
console.log(self.songTitle);
});
}
I am having one chart directive created, and I am bootstrpping the app after loading google api. In following code, a simple data table is working fine. But when I load data from server in async manner, chart is not being displayed.
Controller
'use strict';
myNetaInfoApp.controller('allCandidatesController', [
'$scope','allCandidates2009Svc', '$timeout',
function ($scope, allCandidates2009Svc, $timeout) {
$scope.data1 = {};
$scope.data1.dataTable = new google.visualization.DataTable();
$scope.data1.dataTable.addColumn("string", "Party");
$scope.data1.dataTable.addColumn("number", "qty");
$scope.data1.dataTable.title = "ASDF";
$timeout( function (oldval, newval) {
allCandidates2009Svc.GetPartyCriminalCount().then(function(netasParty) {
var i = 0;
for (var key in netasParty) {
$scope.data1.dataTable.addRow([key.toString(), netasParty[key]]);
i++;
if (i > 20) break;
}
});
});
$scope.dataAll = $scope.data1;
//sample data
$scope.data2 = {};
$scope.data2.dataTable = new google.visualization.DataTable();
$scope.data2.dataTable.addColumn("string", "Name");
$scope.data2.dataTable.addColumn("number", "Qty");
$scope.data2.dataTable.addRow(["Test", 1]);
$scope.data2.dataTable.addRow(["Test2", 2]);
$scope.data2.dataTable.addRow(["Test3", 3]);
}
]);
Service
'use strict';
myNetaInfoApp.factory('allCandidates2009Svc', ['$http', '$q',
function ($http, $q) {
var netas;
return {
GetPartyCriminalCount: function () {
var deferred = $q.defer();
$http.get('../../data/AllCandidates2009.json')
.then(function (res) {
netas = res;
if (netas) {
var finalObj = {};
_.each(netas.data, function(neta) {
finalObj[neta.pty] = finalObj[neta.pty] ? finalObj[neta.pty] + 1 : 1;
});
deferred.resolve(finalObj);
}
});
return deferred.promise;
}
};
}]);
Directive
"use strict";
var googleChart = googleChart || angular.module("googleChart", []);
googleChart.directive("googleChart", function () {
return {
restrict: "A",
link: function ($scope, $elem, $attr) {
var dt = $scope[$attr.ngModel].dataTable;
var options = {};
if ($scope[$attr.ngModel].title)
options.title = $scope[$attr.ngModel].title;
var googleChart = new google.visualization[$attr.googleChart]($elem[0]);
$scope.$watch($attr.ngModel, function (oldval, newval) {
googleChart.draw(dt, options);
});
}
};
});
HTML
<div ng-controller="allCandidatesController">
<div class="col-lg-6">
<h2>Parties and Candidates with Criminal Charges</h2>
<div google-chart="PieChart" ng-model="dataAll" class="bigGraph"></div>
<!--<p><a class="btn btn-primary" href="#" role="button">View details ยป</a></p>-->
</div>
<div class="col-lg-6">
<h2>Heading</h2>
<div google-chart="BarChart" ng-model="data2" class="bigGraph"></div>
</div>
</div>
I think you need to wrap your function body in allCandidates2009Svc factory with scope.$apply(). But the return deferred.resolve() will be outside scope.$apply().
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
// since this fn executes async in a future turn of the event loop, we need to wrap
// our code into an $apply call so that the model changes are properly observed.
scope.$apply(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
Read the docs here
http://docs.angularjs.org/api/ng.$q