Can not figure out how to store $rootScope in angular.bootstrap - angularjs

I'm trying to call a web service in AngularJS bootstrap method such that when my controller is finally executed, it has the necessary information to bring up the correct page. The problem with the code below is that of course $rootScope is not defined in my $http.post(..).then(...
My response is coming back with the data I want and the MultiHome Controller would work if $rootScope were set at the point. How can I access $rootScope in my angular document ready method or is there a better way to do this?
angular.module('baseApp')
.controller('MultihomeController', MultihomeController);
function MultihomeController($state, $rootScope) {
if ($rootScope.codeCampType === 'svcc') {
$state.transitionTo('svcc.home');
} else if ($rootScope.codeCampType === 'conf') {
$state.transitionTo('conf.home');
} else if ($rootScope.codeCampType === 'angu') {
$state.transitionTo('angu.home');
}
}
MultihomeController.$inject = ['$state', '$rootScope'];
angular.element(document).ready(function () {
var initInjector = angular.injector(["ng"]);
var $http = initInjector.get("$http");
$http.post('/rpc/Account/IsLoggedIn').then(function (response) {
$rootScope.codeCampType = response.data
angular.bootstrap(document, ['baseApp']);
}, function (errorResponse) {
// Handle error case
});
});

$scope (and $rootScope for that matter) is suppose to act as the glue between your controllers and views. I wouldn't use it to store application type information such as user, identity or security. For that I'd use the constant method or a factory (if you need to encapsulate more logic).
Example using constant:
var app = angular.module('myApp',[]);
app.controller('MainCtrl', ['$scope','user',
function ($scope, user) {
$scope.user = user;
}]);
angular.element(document).ready(function () {
var user = {};
user.codeCampType = "svcc";
app.constant('user', user);
angular.bootstrap(document, ['myApp']);
});
Note Because we're bootstrapping the app, you'll need to get rid of the ng-app directive on your view.
Here's a working fiddle

You could set it in a run() block that will get executed during bootstrapping:
baseApp.run(function($rootScope) {
$rootScope.codeCampType = response.data;
});
angular.bootstrap(document, ['baseApp']);
I don't think you can use the injector because the scope isn't created before bootstrapping. A config() block might work as well that would let you inject the data where you needed it.

Related

how to use $route.reload() commonly for all controllers in angular js

In order to retain a $rootScope value on refresh[F5] we can use $route.reload in controller as below:
$scope.reloadCtrl = function(){ console.log('reloading...'); $route.reload(); }
As i am using so many controllers, is there any way to use commonly in app.config()?
By refreshing the page you will wipe your $rootscope from memory. Your application restarts.
You can use some kind of storage. That way you can save a users preference and use it again when he comes back to you application.
You can use for example $cookies or sessionStorage / localStorage.
If you want to detect refresh on your app.run you can do by this way:
In the app.run() block inject '$window' dependency and add:
app.run(['$rootScope', '$location', '$window',function($rootScope,$location, $window) {
window.onbeforeunload = function() {
// handle the exit event
};
// you can detect change in route
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (!current) {
// insert segment you want here
}
});
}]);`
You can use a angular factory instead to have all the values across controllers
Use the below code
var app = angular.module('myApp', []);
app.factory('myService', function() {
var v1;
var v2;
var v3;
return{
v1:v1,
v2:v2,
v3:v3
});
app.controller('Ctrl1', function($scope,myService) {
});
app.controller('Ctrl2', function($scope,myService) {
});
If your using constants in $rootscope u can even use this
app.constant('myConfig',
{
v1:v1,
v2:v2,
v3:v3
});

Angularjs cascading dropdowns

I need to use angular service to create cascading drop-downs.The commented code I created for testing purpose and it is working fine. I need to create two services to call two methods from the MVC controller : GetCompanies() and GetDocTypes()
My questions are: Is my first service correct and how can I call the services from the controller?
Thank you.
/// <reference path="angular.js" />
//var myApp = angular
// .module("myApp", [])
// .controller("companyController", function ($scope, $http) {
// $http.post('CurrentSettings/GetCompanies')
// .then(function (response) {
// var response = $.parseJSON(response.data);
// $scope.currentSettings = response;
// });
// });
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', function () {
$http.post('CurrentSettings/GetCompanies')
.then(function (response) {
var response = $.parseJSON(response.data);
$scope.currentSettings = response;
});
});
myApp.controller("companyController", function ($scope, getCompanies, $http) {
});
The problem with your service is two-fold:
Firstly, there is no way to call the service. You injected it fine, but now what? Think of your service as an API; it's no good just having a reference to it somewhere, you need to be able to use it. I would change to the following:
var myApp = angular.module("myApp", []);
myApp.service('getCompanies', ["$http", function($http) {
this.currentSettings = "Hello";
$http.post('CurrentSettings/GetCompanies')
.then(function(response) {
var response = $.parseJSON(response.data);
this.currentSettings = response;
});
}]);
myApp.controller("companyController", ["$scope", "getCompanies",
function($scope, getCompanies) {
$scope.currentSettings = getCompanies.currentSettings;
}]);
Note a few things:
You need to explicitly inject $http into your service.
I specify the names of the services that I'm injecting as part of an array that includes the function. This actually allows you to name the parameters anything you want, and is considered a best practice.
The service doesn't use $scope directly. Instead, it makes a field available to clients of the service. That client (the controller in this case) can then do with the value whatever it wants, including assigning it to a $scope field.
The controller reads this field from the service. It could also call any functions you specified - making the service an API, as I mentioned before.
The second problem is one of timing. Notice that I used the super-original value of "Hello" to initialize the service field.
The value you receive from the service will depend on whether or not the controller reads the value after your call to the MVC controller returns.
To fix this, the service could expose a second field to indicate that the company list is fully loaded, but that really shifts the problem around instead of fixing it.
What you need is a function that returns a promise. If the value has already been loaded, the promise resolves immediately. If not, it returns a promise that will return once the $http call is done.
Here is the modified code:
var myApp = angular.module("myApp", []);
myApp.service('companiesService', ['$http', '$q', function($http, $q) {
var currentSettings = null;
this.getList = function() {
var def = $q.defer()
if (currentSettings) {
def.resolve(currentSettings);
} else {
$http.get('CurrentSettings/GetCompanies')
.then(function(response) {
var response = response.data;
currentSettings = response;
def.resolve(currentSettings);
});
}
return def.promise;
}
}]);
myApp.controller('companyController', ['$scope', 'companiesService',
function($scope, companiesService) {
$scope.currentSettings = '';
companiesService.getList().then(function(value) {
$scope.currentSettings = value;
});
}
]);
It becomes a bit more complicated because you have to use promises, but these are the things to note:
I changed the name of the service to make it more generic. It can now offer a number of company-related features.
currentSettings is no longer added to this on the service, but instead becomes a normal (private) variable. The calling code can only read it by calling the getList function.
getList returns a promise. The promise is resolved immediately if currentSettings has been assigned. If not, it only resolves once the value is received from the web service.
The controller calls getList and assigns the value to the $scope field in the then function.

Issues injecting Angular factories and services

I don't know what it is about injecting factories, but I am having the most difficult time.
I've simulated what I'm attempting to do via this sample plunk http://plnkr.co/edit/I6MJRx?p=preview, which creates a kendo treelist - it works fine.
I have an onChange event in script.js which just writes to the console. That's also working.
My plunk loads the following:
1) Inits the app module, and creates the main controller myCtrl (script.js)
2) Injects widgetLinkingFactory int myCtrl
3) Injects MyService into widgetLinkingFactory
The order in which I load the files in index.html appears to be VERY important.
Again, the above plunk is NOT the real application. It demonstrates how I'm injecting factories and services.
My actual code is giving me grief. I'm having much trouble inject factories/services into other factories.
For example,
when debugging inside function linking() below, I can see neither 'CalculatorService' nor 'MyService' services. However, I can see the 'reportsContext' service.
(function () {
// ******************************
// Factory: 'widgetLinkingFactory'
// ******************************
'use strict';
app.factory('widgetLinkingFactory', ['reportsContext', 'MyService', linking]);
function linking(reportsContext, MyService) {
var service = {
linkCharts: linkCharts
};
return service;
function linkCharts(parId, widgets, parentWidgetData) {
// *** WHEN DEBUGGING HERE, ***
// I CANNOT SEE 'CalculatorService' AND 'MyService'
// HOWEVER I CAN SEE 'reportsContext'
if (parentWidgetData.parentObj === undefined) {
// user clicked on root node of grid/treelist
}
_.each(widgets, function (wid) {
if (wid.dataModelOptions.linkedParentWidget) {
// REFRESH HERE...
}
});
}
}
})();
A snippet of reportsContext'service :
(function () {
'use strict';
var app = angular.module('rage');
app.service('reportsContext', ['$http', reportsContext]);
function reportsContext($http) {
this.encodeRageURL = function (sourceURL) {
var encodedURL = sourceURL.replace(/ /g, "%20");
encodedURL = encodedURL.replace(/</g, "%3C");
encodedURL = encodedURL.replace(/>/g, "%3E");
return encodedURL;
}
// SAVE CHART DATA TO LOCAL CACHE
this.saveChartCategoryAxisToLocalStorage = function (data) {
window.localStorage.setItem("chartCategoryAxis", JSON.stringify(data));
}
}
})();
One other point is that in my main directive code, I can a $broadcast event which calls the WidgetLinking factory :
Notice how I'm passing in the widgetLinkingFactory in scope.$on. Is this a problem ?
// Called from my DataModel factory :
$rootScope.$broadcast('refreshLinkedWidgets', id, widgetLinkingFactory, dataModelOptions);
// Watcher setup in my directive code :
scope.$on('refreshLinkedWidgets', function (event, parentWidgetId, widgetLinkingFactory, dataModelOptions) {
widgetLinkingFactory.linkCharts(parentWidgetId, scope.widgets, dataModelOptions);
});
I am wasting a lot of time with these injections, and it's driving me crazy.
Thanks ahead of time for your assistance.
regards,
Bob
I think you might want to read up on factories/services, but the following will work:
var app = angular.module('rage')
app.factory('hi', [function(){
var service = {};
service.sayHi = function(){return 'hi'}
return service;
}];
app.factory('bye', [function(){
var service = {};
service.sayBye = function(){return 'bye'}
return service;
}];
app.factory('combine', ['hi', 'bye', function(hi, bye){
var service = {};
service.sayHi = hi.sayHi;
service.sayBye = bye.sayBye;
return service;
}];
And in controller...
app.controller('test', ['combine', function(combine){
console.log(combine.sayHi());
console.log(combine.sayBye());
}];
So it would be most helpful if you created a plunk or something where we could fork your code and test a fix. Looking over your services it doen't seem that they are returning anything. I typically set up all of my services using the "factory" method as shown below
var app = angular.module('Bret.ApiM', ['ngRoute', 'angularFileUpload']);
app.factory('Bret.Api', ['$http', function ($http: ng.IHttpService) {
var adminService = new Bret.Api($http);
return adminService;
}]);
As you can see I give it a name and define what services it needs and then I create an object that is my service and return it to be consumed by something else. The above syntax is TypeScript which plays very nice with Angular as that is what the Angular team uses.

How to pass dynamic data from one module controller to another module controller

I'm new to AngularJS, I want to pass dynamic value (username) from one controller in one module to another controller in another module. Routing and other things are working fine.
This is my code
loginModule.js
(function() {
var app = angular.module("waleteros", ["ngRoute","ui.bootstrap","ngCookies"]);
app.config(function($routeProvider) {
$routeProvider
.when("/",{
templateUrl:"views/login.html",
controller:"LoginCtrl"
})
}
})
app.js
(function() {
var app = angular.module("waleterosAdmin", ["ngRoute","ngGrid","ui.bootstrap","ngCookies"]);
app.config(function($routeProvider) {
$routeProvider
.when("/home",{
templateUrl:"homepage.html",
controller:"HomeCtrl"
})
}
})
loginCtrl.js
(function(){
var app = angular.module("waleteros");
var LoginCtrl= function($scope,$location)
{
$scope.signIn=function(email,pin)
{
//Some authentication code...
//Here i want to pass the username to homectrl.js
window.location.href="views/home.html"
}
}
app.controller("LoginCtrl", LoginCtrl);
})
HomeCtrl.js
(function(){
var app = angular.module("waleterosAdmin");
var HomeCtrl=function($scope)
{
//Here i want to get the username
}
app.controller("HomeCtrl", HomeCtrl);
})
you can share service between modules ,and thus pass value between modules,
please have a look here Share a single service between multiple angular.js apps ,and here sharing between modules with AngularJS?
You would use a service to persist the data, and then inject the service into your controllers:
app.service("SharedProperties", function () {
var _userName = null;
return {
getUser: function () {
return _userName
},
setUser: function(user) {
_userName = user;
}
}
});
Now inject SharedProperties and use the getter/setter
var LoginCtrl= function($scope,$location, SharedProperties)
{
$scope.signIn=function(email,pin)
{
//Some authentication code...
SharedProperties.setUser(user);
//Here i want to pass the username to homectrl.js
window.location.href="views/home.html"
}
}
var app = angular.module("waleterosAdmin");
var HomeCtrl=function($scope, SharedProperties)
{
//Here i want to get the username
var user = SharedProperties.getUser();
}
One word of warning about services is that they persist for the lifetime of the application, i.e. they are only instantiated once. I have run into scenarios, especially once routing is implemented, where I want to wipe the data off of the service to save space and replace it with new data (you don't want to keep adding to this service every time you look at a different view). To do this, you could either write a "wipe" method that you call to clean the service on the route changes, or stick the data into a directive (on its controller), put the controllers into their own directives, and have these require the first directive, so that the data is accesible from the controller's with the added benefit of being wiped once the DOM element is declared on is destroy (on a view change, for instance).

Angularjs and Jasmine: Testing a controller with a service making ajax call

I am very new to testing javascript. My application is using angularjs. I am using jasmine as a testing framework.
Here is the controller I am testing:
angular.module('logonController', ["ngval", "accountFactory"])
.controller("logonController", function logOnController(accountFactory, $scope, $window) {
$scope.hasServerError = false;
$scope.Logon = function () {
accountFactory.Logon($scope.data.LogOnModel)
.then(function (data) {
$window.location.href = "/";
},
function (data) {
$scope.hasServerError = true;
});
}
})
where accountFactory.Logon is making a Post request to the server.
What I want to test is when calling accountFactory.Logon:
On success - window.location.href is called
On error $scope.hasServerError is set to true
So far I have managed to do this:
"use strict";
describe("Logon Controller", function () {
var $scope, $location, $rootScope, $httpBackend, $controller, $window, createController;
beforeEach(function () {
module("logonController");
});
beforeEach(inject(function ($injector) {
$rootScope = $injector.get("$rootScope");
$scope = $rootScope.$new();
$location = $injector.get("$location");
$httpBackend = $injector.get("$httpBackend");
$controller = $injector.get("$controller");
$window = $injector.get("$window");
}));
beforeEach(function () {
createController = function () {
return $controller("logonController", {
"$scope": $scope,
});
};
$scope.data = {
LogOnModel: { username: "user", password: "pass" }
};
$window = { location: { href: jasmine.createSpy() } };
});
it("should redirect on successfull login", function () {
var controller = createController();
$httpBackend.whenPOST("/Account/Logon").respond(function (method, url, data, headers) {
return [200, {}, {}];
});
$scope.Logon();
$httpBackend.flush();
expect($window.location.href).toHaveBeenCalled();
});
});
My idea is to create a spy on $window.location.href and only check if it is called. But I am getting
Expected spy unknown to have been called.
As I said I am very new to testing javascript, so any help will be appreciated.
Sten Muchow's Answer is wrong in several aspects:
you can't specify a compound property name ("location.href") as 2nd parameter to spyOn. You have to give a real property name.
And even if you would do the spyOn correctly, andCallThrough() would still raise an exception, as $window.location.href is not a function which could be called through.
But he is still right in saying that you should not intermingle your controller test with the service test.
To answer the question:
The reason, that your expectation is not met (that even the spy still exists*) is, that you're doing the $window.location.href assignment inside a promise's then() function. That means, it will be executed asynchronously, namely AFTER your expect() call. To go around this, you would need to make your test work asynchronously (for how to do this I would like to advise you to the Jasmine documentation: http://jasmine.github.io/2.0/introduction.html).
* In accountFactory.Logon, by doing $window.location.href = (i.e. assignment) you will effectively overwrite your spy.
Even better solution:
Instead of manipulating $window.location.href, you should use $location.url().
$location is an Angular core service. You will benefit from the integration within the Angular application lifecycle (i.e. watchers will be automatically processed when the url changes) + it is seamlessly integrated with existing HTML5 APIs like History API: https://docs.angularjs.org/guide/$location
Then, you can spy on $location.url() as you would have spied on $window.location.href (if it had been a function).
You need to create a spy:
spyOn($window, 'location.href').andCallThrough();
But on a bigger note though, you shouldnt be testing the functionality of your service in the controller test.

Resources