I need some help understanding the code below. It is taken from:
http://www.html5rocks.com/en/tutorials/frameworks/angular-websockets
Factory:
app.factory('socket', function ($rootScope) {
var socket = io.connect();
return {
on: function (eventName, callback) {
socket.on(eventName, function () {
var args = arguments;
$rootScope.$apply(function () {
callback.apply(socket, args);
});
});
},
emit: function (eventName, data, callback) {
socket.emit(eventName, data, function () {
var args = arguments;
$rootScope.$apply(function () {
if (callback) {
callback.apply(socket, args);
}
});
})
}
};
Controller:
function AppCtrl($scope, socket) {
// Socket listeners
// ================
socket.on('init', function (data) {
$scope.name = data.name;
$scope.users = data.users;
});
$scope.sendMessage = function () {
socket.emit('send:message', {
message: $scope.message
});
// add the message to our model locally
$scope.messages.push({
user: $scope.name,
text: $scope.message
});
// clear message box
$scope.message = '';
};
}
My doubt is:
What is the flow of control once the controller calls socket.on('init',function(data){.....});. In factory when socket.on is called it takes two parameters eventName and callback. What is this callback?
Why are we using $rootScope.apply
What is callback.apply?
1.What is the flow of control once the controller calls socket.on('init',function(data){.....});. In factory when socket.on is called it takes two parameters eventName and callback. What is this callback?
There is nothing special involved in calling socket.on from your controller. Doing that simply calls the on method in the factory, directly.
Once that's clear, it's easy to see that callback is simply the second parameter passed into that function. In the case of this example, it's the function function (data) { $scope.name = data.name; $scope.users = data.users; }
2.Why are we using $rootScope.apply
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply
To ensure that anything that happens in the function inside it is picked up in a digest cycle.
3.What is callback.apply?
It's the apply method that's present on any JavaScript function. This is being used here to call the callback with the socket as the this parameter and the event handler's arguments as the arguments.
Related
I'd like to create dynamically controllers responsible for view of data from REST API. My idea is to use ng-repeat directive with data from service and inside it create object with ng-controller directive with parameter from ng-repeat output (The most important condition is that each one question must have its own $scope). Unfortunatelly I don't know how to pass data from service.
AngularJS service code
(function () {
'use strict';
angular
.module('App')
.factory('questionsDataService', questionsDataService);
questionsDataService.$inject = ['$http'];
function questionsDataService($http) {
return {
getMetadata: function (taskId) {
var metaData = $http.get('api/toDo/taskVariables/' + taskId).then(
function (response) {
return response.data;
});
return metaData;
},
getQuestionsData: function (taskId) {
var questionsData = $http.get('api/toDo/getQuestions/' + taskId).then(
function (response) {
return response.data;
});
return questionsData;
}
}
}
})();
I'm not sure if I understood the question, your title is misleading, but I will show how to get the data from the service. I don't think you need a new controller for each item in an ng-repeat, but without more info on why you are trying to do this, I can't help there.
(function () {
'use strict';
angular
.module('App')
.controller('myController', myController);
// you are injecting your service here, this will be whatever the string is from the .factory() call
myController.$inject = ['$scope', 'questionsDataService'];
// again, we are passing the service in to the controller function
function myController($scope, questionsDataService) {
// your service calls are returning promises
// this will get run when the controller is initialized
questionsDataService.getMetadata(1).then(function(data){
// here is where you can access the returned metadata
// save it to the scope so you can access it in the DOM
console.log(data);
})
// if you want to call your service on a button click, or with some other function
$scope.getQuestions = function (id) {
questionsDataService.getQuestionsData(id).then(function (data) {
// here is where you can access the returned data
// save it to the scope so you can access it in the DOM
console.log(data);
})
}
// I added a service method that returns a string, rather than a promise
// this will get run when the controller is initialized
var str = questionsDataService.getServiceName();
console.log(str);
}
})();
(function () {
'use strict';
angular
.module('App')
.factory('questionsDataService', questionsDataService);
questionsDataService.$inject = ['$http'];
function questionsDataService($http) {
return {
getMetadata: function (taskId) {
var metaData = $http.get('api/toDo/taskVariables/' + taskId).then(
function (response) {
return response.data;
});
return metaData;
},
getQuestionsData: function (taskId) {
var questionsData = $http.get('api/toDo/getQuestions/' + taskId).then(
function (response) {
return response.data;
});
return questionsData;
},
// adding this just to show you how to access functions that don't return promises
getServiceName: function () {
return "questionsDataService";
}
}
}
})();
Hi I have a Angular service that uses another service that loads data from the local storage on init.
angular
.module('app')
.factory('localStorage', function ($window)
{
if (!$window.localStorage)
{
// throw Error
}
return $window.localStorage;
});
angular
.module('app')
.factory('session', function (localStorage)
{
var container = JSON.parse(localStorage.getItem('sessionContainer'));
return {
getUser: getUser
};
});
Now i want to test the session service.
describe('SessionService', function ()
{
var service;
var localStorageMock;
// Load the module.
beforeEach(module('appRegistration'));
// Create mocks.
beforeEach(function ()
{
logMock = {};
localStorageMock = jasmine.createSpyObj('localStorageServiceMockSpy', ['setItem', 'getItem']);
localStorageMock.getItem.and.returnValue('{}');
module(function ($provide)
{
$provide.value('localStorage', localStorageMock);
});
inject(function (_session_)
{
service = _session_;
});
});
it('should call `getItem` on the `localStorageService` service', function ()
{
expect(localStorageMock.getItem).toHaveBeenCalledWith('sessionContainer');
});
describe('getUser method', function ()
{
it('should return an empty object when the user is not set', function ()
{
var result = service.getUser();
expect(result).toEqual({});
});
it('should return the user data', function ()
{
// localStorageMock.getItem.and.returnValue('{"user":{"some":"data"}}');
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
});
});
As you can see in the should return the user data section.
I need a way to update the container so getUser returns the expected data.
I tried to update the getItem spy, but this does not work. The localStorageMock is already injected in the session service when i want to change the spy.
Any help?
The most simple way is to have a variable with mocked value that is common for both function scopes:
var getItemValue;
beforeEach({
localStorage: {
getItem: jasmine.createSpy().and.callFake(function () {
return getItemValue;
}),
setItem: jasmine.createSpy()
}
});
...
it('should return the user data', function ()
{
getItemValue = '{"user":{"some":"data"}}';
inject(function (_session_) {
service = _session_;
});
var result = service.getUser();
expect(result).toEqual({some: 'user data'});
});
Notice that inject should be moved from beforeEach to it for all specs (the specs that don't involve getItemValue may use shorter syntax, it('...', inject(function (session) { ... }))).
This reveals the flaw in service design that makes it test-unfriendly.
The solution is to make container lazily evaluated, so there is time to mock it after the app was bootstrapped with inject:
.factory('session', function (localStorage)
{
var containerCache;
function getUser() {
...
return this.container;
}
return {
get container() {
return (containerCache === undefined)
? (containerCache = JSON.parse(localStorage.getItem('sessionContainer')))
: containerCache;
},
getUser: getUser
};
});
Additionally, this makes possible to test session.container as well. In this case localStorageMock.getItem spy value may be redefined whenever needed.
I'm trying to get a history data from Pubnub.history(), store that data and update the views by using different controllers.
I've tried creating a service:
(function(){
'use strict';
angular.module('app')
.service('pubnubService', ['Pubnub',
pubnubService
]);
function pubnubService(Pubnub){
var history;
Pubnub.history({
channel : 'ParkFriend',
limit : 1,
callback : function(historyData) {
console.log("callback called");
history = historyData;
}
});
return {
getHistory : function() {
console.log("return from getHistory called");
return history;
}
};
}
})();
The problem is, getHistory() returns the data before Pubnub.history(). I need to make sure that history data is stored on history before returning it.
Since Pubnub.history is async, your getHistory function have to be an async function too.
Try the following:
function pubnubService(Pubnub) {
return {
getHistory: function(cb) { // cb is a callback function
Pubnub.history({
channel: 'ParkFriend',
limit: 1,
callback: function(historyData) {
console.log("callback called");
cb(historyData);
}
});
}
};
}
To use this service, you can't use it as a synchronous function (i.e., like var history = Pubnub.getHistory()), you need to pass a function as parameter to act like a callback.
Correct usage:
Pubnub.getHistory(function(history) { // here you have defined an anonym func as callback
console.log(history);
});
I am using the following pattern for my REST API, but vm.listing in my controller is always undefined? Probably my pattern is not right? Is there a different pattern to use here? I don't want to call the .get(..) in my controller code.
.factory("listingsResource", ["$resource", "$q", 'appSettings',
function ($resource, $q, appSettings) {
return $resource(appSettings.serverPath + "api/Listings/:id")
}]);
.factory("editService",
var _listing;
var _getListing = function (listingId) {
_listing = listingsResource.get({
id: listingId
});
}
return {
listing: _listing,
getListing: _getListing
};
Controller Code:
createService.getListing(listingId);
vm.listing = createService.listing;
The problem is that when you call listingsResource.get() it returns a promise. Not the data response.
You have to pass the get() a success callback and then set the listing variable inside this callback. I would do something like this:
.service("listingsService", ["$resource", "$q", 'appSettings',
function ($resource, $q, appSettings) {
var _this = this;
_this.listing = {};
var listingResource = $resource(appSettings.serverPath + "api/Listings/:id");
this.getListing = function(listingId){
listingResource.get({id: listingId},
function (data) {
// Success callback
// Set listing keys-value pairs
// do not do: _this.listing = data
_this.listing.id = data.id;
_this.listing.title = data.title;
},
function(err) {
// error callback
console.log(err);
}
)
}
}
])
This works fine with a factory aswell if you prefer. Then in the controller:
listingService.getListing(listingId);
vm.listing = listingService.listing;
I am trying to listen to changes in my injected service (self-updating) in the controller. In the below example you'll find two $watch cases - one that works but I don't know exactly why and one that was obvious to me, yet doesn't work. Is the second example the right way to do it? Isn't that code duplication? What is the right way to do it?
Service:
app.factory("StatsService", [
'$timeout', 'MockDataService',
function ($timeout, MockDataService) {
var service, timeout;
timeout = 5000;
service = {
fetch: function () {
// Getting sample data, irrelevant, however this is what updates the data
return this.data = MockDataService.shuffle();
},
grab: function () {
this.fetch();
return this.update();
},
update: function () {
var _this = this;
return $timeout(function () {
return _this.grab();
}, timeout);
}
};
service.grab();
return service;
}
]);
Controller:
app.controller("StatsController", [
'$scope', 'StatsService',
function ($scope, StatsService) {
var chart;
$scope.stats = StatsService;
$scope.test = function (newValue) {
if (arguments.length === 0) {
return StatsService.data;
}
return StatsService.data = newValue;
};
// This doesn't work
$scope.$watch('stats', function (stats) {
return console.log('meh');
});
// This works, don't know why
$scope.$watch('test()', function (stats) {
return console.log('changed');
});
}
]);
See the third parameter for $watch: objectEquality
Compare object for equality rather than for reference.
However if you're only interested in watching the returned data, then you should do:
$scope.$watch('stats.data', function (stats) {
return console.log('meh');
});
You could use $rootScope events. For example inside the service you could dispatch an event with $rootScope.$broadcast("somethingFetched", data) and catch it in the controller $scope.$on("somethingFetched", function(event, data) { $scope.data = data }).
More details you could find in the documentation http://docs.angularjs.org/api/ng.$rootScope.Scope