I have this angular service which on startup contains an empty array. In the getter, I log what it is returning. I would like avoid having to write logic around a logging statement, and I would like the logging statement to stay, for now.
here's the provider:
appInstance.service('ActivityNavigationDataService',
['$log', '$rootScope', 'UIManager',
function($log, $rootScope, UIManager) {
$log.debug('ActivityNavigationDataService constructor');
$rootScope.activities = [];
UIManager.registerActivityListener($rootScope, activitiesUpdated);
function activitiesUpdated(event, activities) {
$rootScope.activities = activities;
}
this.getActivities = function() {
$log.debug('returning ' + $rootScope.activities.length + ' activities');
return $rootScope.activities;
};
}]);
here's the error:
TypeError: Cannot read property 'length' of undefined
at Object.getActivities (ActivityNavigationDataService.js:28)
at new ActivityNavigationServiceController (ActivityNavigationServiceController.js:31)
at Object.instantiate (angular.js:5055)
at $controller (angular.js:11015)
at UIManager.js:40
at processQueue (angular.js:17051)
at angular.js:17095
at Scope.$digest (angular.js:18233)
at angular.js:18462
at completeOutstandingRequest (angular.js:6362) "Possibly unhandled rejection: {}"
thoughts?
Thnx, Matt
edit:
I think the problem is the ActivityNavigationDataService is not finished loaded and constructed by the time activitiesUpdated is called.
I am loading the service via define[] like this:
define(['services/ActivityNavigationDataService',
// theres others here
'controllers/UIManager'],
function() {
'use strict';
var appInstance = angular.module('obdPortletApp');
appInstance.controller('obdMasterController', ['$rootScope','$scope', '$log', 'UIManager',
function($rootScope, $scope, $log, UIManager) {
UIManager.initialize();
UIManager.buildUI();
}]);
}
);
The UIManager.buildUI() method uses ocLazyLoad to load additional resources, which in turn push activities to the ActivityNavigationService through a broadcasted message, not direct reference to the service.
So I think something is off on timing.
Your activitiesUpdated() handler is getting undefined for activities. If you are the one returning the activities make sure you return an empty array. However, if you don't, you could do something like this:
function activitiesUpdated(event, activities) {
$rootScope.activities = activities || [];
}
Related
I'm using ruby on rails with angularjs one, and testing it with teaspoon-jasmine for the first time and am running into issues. Basically, I have a controller that creates an empty array and upon load calls a factory method to populate that array. The Factory makes an http request and returns the data. Right now, i'm trying to test the controller, and i'm trying to test that 1) the factory method is called upon loading the controller, and 2) that the controller correctly assigns the returned data through it's callback. For a while I was having trouble getting a mocked factory to pass a test, but once I did, I realized I wasn't actually testing my controller anymore, but the code below passes. Any tips on how I can still get it to pass with mock, promises/callbacks, but accurately test my controller functionality. Or should I even test the this at all in my controller since it calls a factory method and just gives it a callback? My 3 files are below. Can anyone help here? It would be greatly appreciated
mainController.js
'use strict';
myApp.controller('mainController', [ 'mainFactory', '$scope', '$resource', function(factory, scope, resource){
//hits the /games server route upon page load via the factory to grab the list of video games
scope.games = [];
factory.populateTable(function(data){
scope.games = data;
});
}]);
mainFactory.js
'use strict';
myApp.factory('mainFactory', ['$http', '$routeParams', '$location', function(http, routeParams, location) {
var factory = {};
factory.populateTable = function(callback) {
http.get('/games')
.then(function(response){
callback(response.data);
})
};
return factory;
}]);
And finally my mainController_spec.js file
'use strict';
describe("mainController", function() {
var scope,
ctrl,
deferred,
mainFactoryMock;
var gamesArray = [
{name: 'Mario World', manufacturer: 'Nintendo'},
{name: 'Sonic', manufacturer: 'Sega'}
];
var ngInject = angular.mock.inject;
var ngModule = angular.mock.module;
var setupController = function() {
ngInject( function($rootScope, $controller, $q) {
deferred = $q.defer();
deferred.resolve(gamesArray);
mainFactoryMock = {
populateTable: function() {}
};
spyOn(mainFactoryMock, 'populateTable').and.returnValue(deferred.promise);
scope = $rootScope.$new();
ctrl = $controller('mainController', {
mainFactory: mainFactoryMock,
$scope: scope
});
})
}
beforeEach(ngModule("angularApp"));
beforeEach(function(){
setupController();
});
it('should start with an empty games array and populate the array upon load via a factory method', function(){
expect(scope.games).toEqual([])
mainFactoryMock.populateTable();
expect(mainFactoryMock.populateTable).toHaveBeenCalled();
mainFactoryMock.populateTable().then(function(d) {
scope.games = d;
});
scope.$apply(); // resolve promise
expect(scope.games).toEqual(gamesArray)
})
});
Your code looks "non-standard" e.g still using scope.
If you are just starting with angular I hardly recommend you to read and follow this:
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
Angular controllers cannot be tested, extract the logic into factories/services and test from there.
(function (angular) {
'use strict';
angular
.module('app')
.controller('ListSharedContentCtrl', ['$scope','$log','UserService', 'ContractService','ContentOwnerService','PlatformPartnerService',function ($scope, $log, userService, contractService,contentOwnerService,platformPartnerService) {
$scope.init = function () {
var contractIds =[];
var contentOwnerId;
var platformPartnerId;
var user=userService.getCurrentUser('true');
var contentOwner=userService.isCurrentUserIsContentOwner();
var platformPartner=userService.isCurrentUserIsPlatformPartner();
if(contentOwner == "true")
{
contentOwnerService.getContentOwnerId().then(function(savedContentOwnerId) {
contentOwnerId=savedContentOwnerId;
console.log(contentOwnerId);//here I can log the value
},function(error) {
$log.error("Error fetching contract id:" + error.message);
});
}
console.log(contentOwnerId); //but Here I cant log the value..Its showing undefined
} $scope.init();
}]);
})(angular);
Now my question is how to make the scope of the variable "contentOwnerId" available to whole function?Please anyone help me I cant figure it out..Thanks in advance..!
You are declaring contentOwnerId twice; once inside the if block, once outside it. The one outside is declared, but never assigned a value. It should be null where it is showing null.
The reason why you are getting the contentOwnerId undefined is that it is undefined until when the getcontentOwnerId() promise succeed!
if you do
var contentOwnerId = "before resolving the promise";
at the beginning of your init() you will probably have that string logged to console, instead of undefined
use $scope.contentOwnerId instead of var contentOwnerId.
See $scope.contentOwnerId is available to whole controller i.e ListSharedContentCtrl.
normal javascript variable are limited to functions where as $scope is available to entire CONTROLLER
The .then() method belongs to a promise.
This means it won't be executed immediately, but later in the angular life cycle.
Your console.log() will always log 'undefined' because your variable won't be initialized there.
You can try this to log the value:
$scope.$watch(
function() { return contentOwnerId; },
function(newContentOwnerId, oldContentOwnerId) {
console.log(newContentOwnerId);
}
);
I want to capture the url of my query in an AngularJS service as this
var mortgageloanService = angular.module('loanstreetIpadAppApp', []);
mortgageloanService.factory('updateTable', function($http) {
return {
getParams: function() {
var url = 'https://api.mongolab.com/api/1/databases/angularjs-intro/collections/users?apiKey=terrPcifZzn01_ImGsFOIZ96SwvSXgN9';
console.log('in service mode');
console.log(url);
return $http.get(url);
}
};
});
This is my controller.js code
angular.module('loanstreetIpadAppApp')
.controller('Mortgage_LoanCtrl', function ($location, $scope) {
$scope.update_result = function(updateTable) {
updateTable.getParams().success(function(loan){$scope.loan = loan});
console.log($scope.resulttable);
};
});
On my view page, i have a button which onclick shud call the update_result function. But whenever i click on the button i get the following error
TypeError: Cannot read property 'getParams' of undefined
at Scope.$scope.update_result (http://localhost:9003/scripts/controllers/mortgage_loan.js:22:16)
at http://localhost:9003/bower_components/angular/angular.js:10567:21
at http://localhost:9003/bower_components/angular/angular.js:18627:17
at Scope.$eval (http://localhost:9003/bower_components/angular/angular.js:12412:28)
at Scope.$apply (http://localhost:9003/bower_components/angular/angular.js:12510:23)
at HTMLButtonElement.<anonymous> (http://localhost:9003/bower_components/angular/angular.js:18626:21)
at HTMLButtonElement.jQuery.event.dispatch (http://localhost:9003/bower_components/jquery/dist/jquery.js:4430:9)
at HTMLButtonElement.elemData.handle (http://localhost:9003/bower_components/jquery/dist/jquery.js:4116:28)
Anyone knows how to solve this issue?
In order to use your updateTable's factory inside your controller, you need to inject it. So, your controller should look like this.
angular.module('loanstreetIpadAppApp')
.controller('Mortgage_LoanCtrl', function ($location, $scope, updateTable) {
$scope.update_result = function() {
updateTable.getParams().success(function(loan){$scope.loan = loan});
console.log($scope.resulttable);
};
});
Notice that I've removed "updateTable" as "$scope.update_result"'s parameter since it would overwrite your updateTable object inside that closure.
I'd like to do simple notifications in angular. Here is the code I've written.
http://pastebin.com/zYZtntu8
The question is:
Why if I add a new alert in hasAlerts() method it works, but if I add a new alert in NoteController it doesn't. I've tried something with $scope.$watch but it also doesn't work or I've done something wrong.
How can I do that?
Check out this plnkr I made a while back
http://plnkr.co/edit/ABQsAxz1bNi34ehmPRsF?p=preview
I show a couple of ways controllers can use data from services, in particular the first two show how to do it without a watch which is generally a more efficient way to go:
// Code goes here
angular.module("myApp", []).service("MyService", function($q) {
var serviceDef = {};
//It's important that you use an object or an array here a string or other
//primitive type can't be updated with angular.copy and changes to those
//primitives can't be watched.
serviceDef.someServiceData = {
label: 'aValue'
};
serviceDef.doSomething = function() {
var deferred = $q.defer();
angular.copy({
label: 'an updated value'
}, serviceDef.someServiceData);
deferred.resolve(serviceDef.someServiceData);
return deferred.promise;
}
return serviceDef;
}).controller("MyCtrl", function($scope, MyService) {
//Using a data object from the service that has it's properties updated async
$scope.sharedData = MyService.someServiceData;
}).controller("MyCtrl2", function($scope, MyService) {
//Same as above just has a function to modify the value as well
$scope.sharedData = MyService.someServiceData;
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl3", function($scope, MyService) {
//Shows using a watch to see if the service data has changed during a digest
//if so updates the local scope
$scope.$watch(function(){ return MyService.someServiceData }, function(newVal){
$scope.sharedData = newVal;
})
$scope.updateValue = function() {
MyService.doSomething();
}
}).controller("MyCtrl4", function($scope, MyService) {
//This option relies on the promise returned from the service to update the local
//scope, also since the properties of the object are being updated not the object
//itself this still stays "in sync" with the other controllers and service since
//really they are all referring to the same object.
MyService.doSomething().then(function(newVal) {
$scope.sharedData = newVal;
});
});
The notable thing here I guess is that I use angular.copy to re-use the same object that's created in the service instead of assigning a new object or array to that property. Since it's the same object if you reference that object from your controllers and use it in any data-binding situation (watches or {{}} interpolation in the view) will see the changes to the object.
When I receive a html content in angular app, I inject it in the html document, but then can't get it. Example minimized code on plunker and in javascript
I have next controller:
class ReadCtrl
constructor: (#$scope, #$rootScope, #$compile, #$sce, #$window, #webService) ->
#getData()
getData: ->
promise = #webService.getBookText()
promise.then #success, #error
success: (response) =>
#$scope.html = response.data
#$scope.trustedHtml = #$sce.trustAsHtml(#$scope.html)
console.log $('.book_contents').first().children()
ReadCtrl.$inject = ["$scope", "$rootScope", "$compile", '$sce', "$window", "webService"]
angular.module("bookReader").controller "ReadCtrl", ReadCtrl
Method success save html to variable trustedHtml, and then it binds to view:
<div class="br_container">
<div class="br_source_container" ng-bind-html="trustedHtml">
</div>
</div>
Content shows, but this console.log $('.book_contents').first().children() has zero elements. And when i try to insert something like #$rootScope.$digest() the error throws:
Error: [$rootScope:inprog] $digest already in progress
That because of => in method, which compiles to:
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
this.success = __bind(this.success, this);
So if I change => to -> there is another error:
TypeError: Cannot read property '$scope' of undefined
In the first line of success function. So this is undefined in success function, perhaps because it is called with promise.
Another method with $('.br_source_container').html(#$compile(#$scope.trustedHtml)(scope)) occurs the same errors.
So, I need to get DOM html of the inserted part.
So, the problem solves with timeout respectful for charlietfl
This how it wors