We have a service handler that changes the location. The location does not change until something else triggers a digest. So, I used $apply, but getting errors from that.
$scope.myFunction = function () {
$scope.$apply(function () {
$location.url(path);
});
};
Above gives this error:
Uncaught TypeError: Cannot read property '$$nextSibling' of null
From what I found, it seems like the $scope is getting destroyed during a phase cycle...But how can you use apply() on a location change without this error?
EDIT:
Testing to see if this scenario is related to our current app and discovered that reloading the route works oddly enough, which seems like a hack?
$scope.myFunction = function () {
$location.url(path);
$route.reload()
};
Inject a $rootScope and use it instead of the $scope:
$location.url(path);
$rootScope.$apply();
The $rootScope will be available for the lifetime of the app.
Related
UPDATE 2:
I found out where the problem was coming from and I left out the important part.
I'm setting the property in the first controller INSIDE this
$rootScope.$on('anEvent', function (event, data) {
InjectedService.setToken(data.value);
})
But I can't grab it from outside that callback scope. The event that is listened for depends on an $http request so I'm guessing that is the problem. Are there any workarounds to this?
UPDATE:
The order of controllers are the other way around so that the code in secondController is actually being called first in my actual app. I have reordered them accordingly
Question:
I'm trying to grab the services specific property but when I try to grab the service property, I get undefined (using a getter function or not). But when I try to grab the full service, I get everything with the correct property and value.
main.js
angular.module('myApp', ['myModule', 'myServices'])
.controller('firstController', ['InjectedService', function(InjectedService) {
InjectedService.setProperty('Hello World')
}])
othermodule.js:
angular.module('myModule', [])
.controller('secondController', ['InjectedService',
function (InjectedService) {
console.log(InjectedService); // Full object with property == 'hello world'
console.log(InjectedService.property); // I get undefined
console.log(InjectedService.getProperty()); // I get undefined
// interesting to note:
InjectedService.setToken('A different string');
console.log(InjectedService.property); // I get 'A different string'
console.log(InjectedService); // Full object with property == 'Hello World' which was set in second controller
}])
services.js:
angular.module('myServices', function
.service('InjectedService', function(){
var Service = this;
Service.setProperty = function (value) {
Service.property = value;
}
Service.getProperty = function () {
return Service.property;
}
Service.unsetProperty = function () {
Service.property = null;
}
return Service;
})
It seems to me a scope problem, but the variable isn't a primitive type. Any suggestions?
I have a method in a controller which executes the following code:
this.StockService.GetByInvoicesID(this.SelectedInvoice.ID).success((StockItems) =>
{
this.StockItems = StockItems;
this.CreditNoteStockItems = new Array<ViewModels.CreditNoteStockItemViewModel>();
}
Before this service method is called, all members in the controller are defined. However, once the promise resolves, this.StockItems and this.CreditNoteStockItems are all undefined. Furthermore, the assignment of StockItems is not being reflected in the view. I'm guessing this is a scope issue and the promise is returning into a new scope. This has happened with other methods before, it almost seems to happen at random.
My questions are, can anyone tell me why exactly this is happening, and more importantly, how can I prevent it from occurring?
edit: This is a simplified version of my controller (all thats missing is several members and methods)
edit 2: more info about method in controller
export class CreditNoteController
{
static $inject = ['$scope', '$modalInstance', 'StockService'];
StockService: Services.StockService;
ModalInstance: ng.ui.bootstrap.IModalServiceInstance;
constructor($scope, $modalInstance, StockService: Services.StockService)
{
$scope.vm = this;
this.ModalInstance = $modalInstance;
this.StockService = StockService;
}
InvoicesSelectionChanged()
{
this.StockService.GetByInvoicesID(this.SelectedInvoice.ID).success((StockItems) =>
{
this.StockItems = StockItems;
this.CreditNoteStockItems = new Array<ViewModels.CreditNoteStockItemViewModel>();
});
}
}
The controller is injected through the angular UI modal service open method sitting in another controller:
this.ModalService.open(
{
templateUrl: URL,
controller: Controllers.CreditNoteController,
});
edit 2: The javascript that it generates
CreditNoteModalController.prototype.InvoicesSelectionChanged = function () {
var _this = this;
this.StockService.GetByInvoicesID(this.SelectedInvoice.ID).success(function (StockItems) {
_this.StockItems = StockItems;
_this.CreditNoteStockItems = new Array();
});
};
Thanks
After some back and forth in the comment thread, turns out there were 2 things going on here, so consolidating the info into the answer:
When inspecting 'this' in a debugger in a .ts file, know that you will be seeing the actual 'this' value and not the _this alias that is created in the .js file when you use arrow functions. This can sometimes make it look like variables aren't getting set when in reality, you are inspecting the wrong variable. Setting a watch on _this will show the right one. You also have the option of using console.log on 'this' in the TypeScript code itself, since it will compile to using the _this alias.
ng-options won't work without ng-model
I'm running a single test on my controller to determine if it's properly defined but I keep getting a TypeError: undefined on the controller object. Here's the complete error:
Search Controller
should have the controller defined <<< FAILURE!
* TypeError: 'undefined' is not an object (evaluating 'myMenuDataLoad.then')
* Expected undefined to be defined.
And here is the controller to be tested:
myAppControllers.controller('VisibilitySearchController', ['$scope', 'headerService', 'menuService', 'navigationService', function($scope, headerService, menuService, navigationService ){
headerService.setTitle('My title');
var myMenuDataLoad = menuService.loadData('partials/common/components/menu-bar/json/menu-bar.json');
myMenuDataLoad.then(function(dataResult){
menuService.setData(dataResult.data);
});
var myNavDataLoad = navigationService.loadData('partials/common/components/navigation-bar/json/navigation-bar.json');
myNavDataLoad.then(function(dataResult){
navigationService.setData(dataResult.data);
});
}]);
I've initialized the controller by passing it everything it needs in its parameters i.e. scope, headerService, menuService and navigationService - I mock these services using the jasmine.createSpyObj method and pass in all the relevant methods ( the ones used on the controller ):
// Mock our services
beforeEach(function() {
// Methods are accepted as the 2nd second parameter
headerService = jasmine.createSpyObj('headerService', ['setTitle']);
module(function($provide) {
$provide.value('headerService', headerService);
});
menuService = jasmine.createSpyObj('menuService', ['loadData', 'setData']);
module(function($provide) {
$provide.value('menuService', menuService);
});
navigationService = jasmine.createSpyObj('navigationService', ['loadData', 'setData']);
module(function($provide) {
$provide.value('navigationService', navigationService);
});
});
And the actual initialization of the controller happens here:
beforeEach(inject(function($rootScope, $injector, $controller, _headerService_, _menuService_, _navigationService_) {
scope = $rootScope.$new();
// Instantiate the controller
searchController = $controller('VisibilitySearchController', {
$scope : scope,
headerService : headerService,
menuService : menuService,
navigationService : navigationService
});
}));
So what am I doing wrong here? Why isn't the test (see below) passing?
it("should have the controller defined", function() {
expect(searchController).toBeDefined();
});
Have I mocked the services correctly? What action needs to be done on a local controller variable in order to properly initialize them and the methods they are used in?
Thanks!
UPDATE
I've looked further into this but am unfortunately still receiving the same undefined error. When you create a mock object of a service do you have to provide that service with all of its dependencies and methods you make use of? For example:
menuService = jasmine.createSpyObj('menuService', ['$parse','$q', 'dataService', 'loadData', 'then']);
module(function($provide) {
$provide.value('menuService', menuService);
});
Here when I create the mock object I provide it with all the dependencies it would expect plus I added in two functions that I make use of in the controller.
So how do I go about mocking a function in a mocked object? I tried this but I'm still getting the same error:
menuService.loadData = jasmine.createSpy( 'loadData()' ).andReturn( data );
As mentioned in the comment your menuService.loadData() will always return undefined so evaluating expression myMenuDataLoad.then will always fail as mentioned in the error. What you must do is to provide an implementation of menuService.loadData which will return a promise. You can do the mocking the way you did it in case you want these method to be called but you don't rely on any return value of it. If you need the method to return something you can do define menuService this way:
var menuService = {
loadData: function() {
var deferred = $q.defer();
var data = []; //put any data you need here to be returned within the promise
deferred.resolve{data);
return deferred.promise;
}
}
module(function($provide) {
$provide.value('menuService', menuService);
});
You will need instance of $q which you can get in your inject call similarly to $rootScope, $injector etc.
In case you wanted to spy on menuService.load function you can do it this way:
spyOn(menuService, "loadData").andCallThrough()
That will keep your mocked implementation of the method but still allow you to assert it was called etc. I don't think you need it.
jsfiddle: http://jsfiddle.net/3gd8a/1/
(function () {
var app = angular.module("index", []);
app.run(function ($log, $controller) {
$log_service = $log;
$controller_service = $controller;
});
app.controller("AlertsController", function () {
this.alerts = [ "first alert" ];
this.innerFunction = function() {
this.alerts.push("inner alert");
$log_service.debug(alerts.alerts);
};
});
})();
function outerFunction() {
var alerts = $controller_service("AlertsController");
alerts.alerts.push("outer alert");
$log_service.debug(alerts.alerts);
};
In this example I changed the property of controller instance in the outer js function,
now I don't known how can I apply it just like use $scope.apply().
I already read angular-tips watch-how-the-apply-runs-a-digest,
$scope has the $apply function, but controller instance didn't have one.
You may want to ask why I use controller instance not $scope, because I learn angular js from codeschool so I want use the same way I learned if possible.
And you may want to ask why not use ng-click, I known use ng-click will work but I want figure out how angular js watch properties of controller instance and how to apply the changes of them manually.
Edit:
I figured out controller instance is just a property named alerts under $scope when I use it by ng-controller="AlertsController as alerts".
Now I had another problem, If I access $scope from outerFunction will cause an error.
The first time is Error: [$injector:unpr] Unknown provider: $scopeProvider <- $scope
http://errors.angularjs.org/1.2.1/$injector/unpr?p0=%24scopeProvider%20%3C-%20%24scope angular.js:78
And the second time is
Error: [$injector:cdep] Circular dependency found:
http://errors.angularjs.org/1.2.1/$injector/cdep?p0=
Since nobody answer, I leave my answer here.
Controller instance is a object under $scope after initiazlied, in the example I alias AlertsController to alerts so it named alerts under $scope.
Then $scope.alerts and this are equivalent.
In the first comment (http://jsfiddle.net/3gd8a/5) I tried put $scope to global then access it from outerFunction but failed, I still need figure out why.
In the second comment (http://jsfiddle.net/3gd8a/6) I found $scope can get from the element inside of controller, and it's can be access normally.
So the way to update and apply property of controller instance from global function is
add id to any element inside controller
use angular.element find that element then call .scope() to get the scope.
use scope.{alias of controller}.{property name} to update property, then call $scope.apply to apply.
I am playing with Angular and SignalR, I have tried to create a service which will act as a manager.
dashboard.factory('notificationsHub', function ($scope) {
var connection;
var proxy;
var initialize = function () {
connection = $.hubConnection();
proxy = connection.createHubProxy('notification');
proxy.on('numberOfIncidents', function (numOfIncident) {
console.log(numOfIncident);
$scope.$emit('numberOfIncidents', numOfIncident);
});
connection.start()
.done(function() {
console.log('Connected');
})
.fail(function() { console.log('Failed to connect Connected'); });
};
return {
initialize: initialize
};
});
however I get the error Error: Unknown provider: $scopeProvider <- $scope <- notificationsHub.
How can I use pubsub to pass all the notifications to the controllers? jQuery maybe?
$scope does not exist in this context as that's something injected when a controller is created and a new child scope is made. However, $rootScope is available at the time you need.
Also, be aware $emit() goes upward and your controller scopes wont see it. You would either need to switch to $broadcast() so the event goes downwards or inject $rootScope as well to the controllers you want to be able to subscribe to 'numberOfIncidents'
Check out the angular docs and a useful wiki on scopes.
Here is a great example showing how to wrap the proxy in a service and use $rootScope for event pub/sub.
http://sravi-kiran.blogspot.com/2013/09/ABetterWayOfUsingAspNetSignalRWithAngularJs.html
As already noted in johlrich's answer, $scope is not avaliable inside proxy.on. However, just switching to $rootScope will most likely not work. The reason for this is because the event handlers regisrered with proxy.on are called by code outside the angular framework, and thus angular will not detect changes to variables. The same applies to $rootScope.$on event handlers that are triggered by events broadcasted from the SignalR event handlers. See https://docs.angularjs.org/error/$rootScope/inprog for some more details.
Thus you want to call $rootScope.$apply() from the SignalR event handler, either explicitly
proxy.on('numberOfIncidents', function (numOfIncident) {
console.log(numOfIncident);
$scope.$apply(function () {
$rootScope.$emit('numberOfIncidents', numOfIncident);
});
});
or possibly implicitly through $timeout
proxy.on('numberOfIncidents', function (numOfIncident) {
console.log(numOfIncident);
$timeout(function () {
$rootScope.$emit('numberOfIncidents', numOfIncident);
}, 0);
});
I tried to use $apply() after changing value, i tried to use $apply(functuin() {value = 3}), and also i tried to use $emit and $broadcast for changing value and it doesn't help.
But i found solution we need in html after in controller you can use
var scope2 = angular.element("#test").scope();
scope2.point.WarmData.push(result);
$scope.$apply();
P.s. I understand that it is very old question, but may by smb, as i, need this solution.