angularjs / javascript how to update factory created singleton - javascript

I'm pretty new to angularjs and javascript so I'm hoping you can help me figure this out. I have a factory creating a service singleton and I want it to subscribe to some events and update itself when those occur.
However, I'm not sure how to get a reference to the object created by the factory in this context. See "My Problem" in a code comment.
I would also appreciate any and all feedback on the way I'm using angular/js and what I could be doing better.
(function () {
'use strict';
var coreMod = angular.module('CoreMod',['ng']);
coreMod.factory('accountService', accountService);
accountService.$inject=['$rootScope',
'$log',
'$http',
'$q',
'$localStorage',
'$sessionStorage',
'authService'];
function accountService($rootScope, $log, $http, $q, $localStorage, $sessionStorage, authService) {
var accountService = {
CurrentAccount: null,
logOut: logOut,
_logIn: userLoggedIn//anyway to hide this?
};
$rootScope.$on('userLoggedIn', accountService._logIn);
return accountService;
function userLoggedIn() {
$http({ method: 'get', url: msApiUrl + '/account/userinfo', timeout: 3000, warningAfter: 50 })//TODO: find a way to make these timinings default
.success(function (result) {
$log.info('User logs into api server successfully and gets response: ' + result);
handleLoginMessage(result);
}).error(function (result) {
$log.warn('Error logging into api server. Response: ' + result);
$rootScope.$broadcast('CriticalError', 'Error logging into api. Please clear your cache and try again. If this occurrs again please contact your system administrator. ');//TODO: configurable and localized message
});
};
function handleLoginMessage(message) {
var accountService = this; //my problem: this is undefined
accountService.CurrentAccount={};
accountService.CurrentAccount.emailHash = message.EmailHash;//TODO: are these case sensitive / can I control that
accountService.CurrentAccount.organizationId = message.OrganizationId;
accountService.CurrentAccount.username = message.Username;
$localStorage.userInfo = accountService;
};
function logOut() {
authService.logOut();
$sessionStorage.$reset();
$localStorage.userInfo = null;
};
}
}());

You don't need the code line at all. Just remove:
var accountService = this;
accountService is an object literal that you have created with var accountService = {...} and your function handleLoginMessage is in the same scope. So the accountService variable should be accessible with-in handleLoginMessage.
If you want to hide _logIn: userLoggedIn then remove it from accountService object but then it is not accessible from outside of your service. Everything that's inside of accountService will be exposed to other controllers, services or factories that are injecting your factory.

Related

How can I execute a function in an angularJS controller from a backbone controller

I am working on a application originally created with backbone and jQuery, however due to client requirement, new modules are built with angular. Routing of the application is handled with backbone route and we have successfully integrated angular modules.
The actual problem is, I need to retrieve the current instance of a module in angular and execute a function from the controller of that module based on actions handled by a backbone controller.
Here is what my angular module and controller looks like:
//In chat.module.js
( function () {
angular
.module( 'chat.module', [] );
})();
//In chat.controller.js
(function () {
angular
.module('chat.module')
.controller('chat.controller', ['profileFactory', '$filter', '$q', '$timeout', 'Position', 'Chat', chat]);
function chat(profileFactory, $filter, $q, $timeout, Position, Chat) {
var vm = this;
vm.initChatFlag = false;
vm.initChat = initChat;
vm.setInformation = setInformation;
function setInformation() {
//handle business logic here
}
...
In backbone, the module is created as follows:
chatmodule: function () {
var self = this;
var element = angular.element(document.querySelector('#modalCallback'));
var chat = angular.element(document.querySelector('#chatModule'));
var isInitializedChat = chat.injector();
var isInitialized = element.injector();
if (!isInitialized) {
angular.bootstrap($('#modalCallback'), ['app']);
}
if (!isInitializedChat) {
angular.bootstrap($('#chatModule'), ['app']);
}
//TODO: chat.controller.setInformation() get access to fields like chat.controller.initChatFlag etc
The main app module is defined thus:
(function(){
angular
.module('app',[
'callback',
'ui.bootstrap',
'720kb.datepicker',
'ngLocale',
'directives.module',
'interceptor',
'directive.loading',
'angularUtils.directives.dirPagination',
'blog.module',
'profile.module',
'filters.module',
'chat.module',
'ui.toggle',
]);
})();
The AngularJS $injector is where a lot of the magic happens, so if you expose that outside of the AngularJS code you can hook it up to non-AngularJS code like the following:
//A simple AngularJS service:
app.service('myService', function() {
this.message = "This is my default message.";
});
//Expose the injector outside the angular app.
app.run(function($injector, $window) {
$window.angularInjector = $injector;
});
//Then use the injector to get access to the service.
//Make sure to wrap the code in a `$apply()` so an
//AngularJS digest cycle will run
function nonAngularEventHandler() {
angularInjector.invoke(function(myService, $rootScope) {
$rootScope.$apply(function() {
myService.message = "Now this is my message."
});
});
}
Edit: Alternatively, simplify the call like so.
//Instead of exposing the $injector directly, wrap it in a function
//which will do the $apply() for you.
app.run(function($injector, $window, $rootScope) {
$window.callInMyAngularApp = function(func) {
$rootScope.$apply(function() {
$injector.invoke(func);
});
}
});
//Then call that function with an injectable function like so.
function nonAngularClick() {
callInMyAngularApp(function(myService) {
myService.message = "Now this is my message."
});
}
//And remember if you're minifying, you'll want the minify-safe
//version of the injectable function like this
function nonAngularClick() {
callInMyAngularApp(['myService', function(myService) {
myService.message = "Now this is my message."
}]);
}
Update: (last one I promise!)
The above will work fine, but you might want to consider exposing a well-defined API instead of a generic injectable interface. Consider the following.
//Now I have a limited API defined in a service
app.service("myExternalApi", function($rootScope, myService) {
this.changeMyMessage = function(message) {
$rootScope.$apply(function() {
myService.message = message;
});
};
});
//And I just expose that API
app.run(function($window, myExternalApi) {
$window.myExternalApi = myExternalApi;
});
//And the call from outside of angular is much cleaner.
function nonAngularClick() {
myExternalApi.changeMyMessage("Now this is my message.");
}
I was able to get access to the controller using answer from this post - https://stackoverflow.com/a/21997129/7411342
var Chat = angular.element(document.querySelector('#chatModule')).scope();
if(!Chat) return;
if(Chat.chatCtrl.initChatFlag) {
Chat.chatCtrl.setInformation();
}else{
console.log('Chat has not been initialized');
}

AngularJS module.controller vs $controllerProvider.register

I'm trying to add controller dynamically in my AngularJS application.
On sub-domain, I have anotherController.js file.
Here's anotherController.js content:
function anotherControllerWrapper() {
return ['$scope', '$state', function ($scope, $state) {
$scope.doWork = function () {
//...doing some work...
alert('work done');
};
$scope.doWork();
}];
};
Also I have wrote runtimeController provider to be able to use $controllerProvider in runtime:
app.provider('runtimeController', function () {
var controllerProvider = null;
this.setControllerProvider = function (cp) {
controllerProvider = cp;
};
this.$get = function () {
return {
registerController: function (controllerName, controllerConstructor) {
if (!controllerProvider.has(controllerName)) {
controllerProvider.register(controllerName, controllerConstructor);
}
}
};
};
});
Here's config section of application:
app.config(function($controllerProvider, runtimeControllerProvider) {
runtimeControllerProvider.setControllerProvider($controllerProvider);
});
I'm receiving controller's code over http (inside another controller), so it looks like this:
app.controller('testController', ['$scope', '$state', '$http', 'runtimeController',
function ($scope, $state, $http, runtimeController) {
$http.get('http://someUrl/anotherController.js')
.then(
function(sucess){
var evaluated = new Function('return ' + success.data)();
var ctrl = evaluated();
// routing to ui state with specified 'anotherController' works
// no 'anotherController' in app._invokeQueue
runtimeController.registerController('anotherController', ctrl);
// routing to ui state with specified 'anotherController' constanly fails
// 'anotherController' appears in app._invokeQueue
//app.controller('anotherController', ctrl);
//--registering new UI route with 'anotherController' as controller here
$state.go('anotherState');
},
function(error){ alert('something went wrong!'); },
);
}]);
Ui states are also added dymanically, after I'm adding controller.
Can someone explain me please, what's happening and what's difference between $controllerProvider.register and module.controller?
Module methods (controller, directive, etc) result in adding a config block (_configBlocks) that is executed on application initialization. Once the application has passed config phase, it won't execute newly added config blocks, so app.controller(...) has no effect during run phase.
As this example shows, runtimeController implementation can be simplified to
app.config(($provide, $controllerProvider) => {
$provide.value('$controllerProvider', $controllerProvider);
});
eval should be avoided for numerous reasons. Considering that the script is loaded from a domain that is allowed by CORS and doesn't require to be evaled, a suitable alternative is to load it as a script. This will require to patch AngularJS API to allow late component registrations, similarly to how ocLazyLoad does - or just use ocLazyLoad, because it already does that.

AngularJS call common controller function from outside controller

My basic premise is I want to call back to the server to get the logged in user in case someone comes to the site and is still logged in. On the page I want to call this method. Since I am passing the user service to all my controllers I don't know which controller will be in use since I won't know what page they're landing on.
I have the following User Service
app.factory('userService', function ($window) {
var root = {};
root.get_current_user = function(http){
var config = {
params: {}
};
http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
Here is an empty controller I'm trying to inject the service into
app.controller('myResourcesController', function($scope, $http, userService) {
});
So on the top of my index file I want to have something along the lines of
controller.get_current_user();
This will be called from all the pages though so I'm not sure the syntax here. All examples I found related to calling a specific controller, and usually from within another controller. Perhaps this needs to go into my angularjs somewhere and not simply within a script tag on my index page.
You could run factory initialization in run method of your angular application.
https://docs.angularjs.org/guide/module#module-loading-dependencies
E.g.
app.run(['userService', function(userService) {
userService.get_current_user();
}]);
And userService factory should store authenticated user object internaly.
...
if (data.success == true) {
root.user = data.user;
}
...
Then you will be able to use your factory in any controller
app.controller('myController', ['userService', function(userService) {
//alert(userService.user);
}]);
You need to inject $http through the factory constructor function, for firsts
app.factory('userService', function ($window, $http) {
var root = {};
root.get_current_user = function(){
var config = {
params: {}
};
$http.post("/api/user/show", null, config)
.success(function(data, status, headers, config) {
if(data.success == true) {
user = data.user;
show_authenticated();
}
});
};
return root;
});
in your controller you can say
$scope.get_current_user = UserService.get_current_user();
ng attributes in your html if needed. besides this, i am not sure what you need.

AngularJS Jasmine test: scoped variable always undefined

I am very new to testing in Javascript and am currenly trying to test a controller function.
The function calls a service method which retrieves data from a web sql db.
This is a part of my controller function (it contains 2 callbacks, one for success and another for error):
$scope.getLocations = function () {
LocationDbService.getAll(
//Success
function (tx, results) {
$scope.numberOfLocations = results.rows.length;
...
},
//Error
function () {
console.log("Error");
});
}
The test:
it('we should be able to retrieve all stored locations',
function () {
expect(scope.numberOfLocations).toBeUndefined();
scope.getLocations();
expect(scope.numberOfLocations).toBeDefined();
});
beforeEach test:
var ctrl, scope, location, locationDbService;
// inject the $controller and $rootScope services
// in the beforeEach block
beforeEach(inject(function ($controller, $rootScope, $location, LocationDbService) {
// Create a new scope that's a child of the $rootScope
scope = $rootScope.$new();
// Create the controller
ctrl = $controller('LocationsCtrl', {
$scope: scope
});
location = $location;
locationDbService = LocationDbService;
}));
Controller header:
.controller('LocationsCtrl', function ($scope, $location, LocationDbService) {
When I run the application in the browser (or on my smartphone, its a hybrid app) everything works but when I run the test I get the following:
Does somebody know why the scoped variable is still undefined?
Thanks in advance!
When instantiating your controller, you should also inject any other services it needs.
AngularJS has a cool trick btw where you can use underscores in names:
beforeEach(inject(function ($controller, $rootScope, _$location_, _LocationDbService_) {
// Create a new scope that's a child of the $rootScope
scope = $rootScope.$new();
// Create the controller
ctrl = $controller('LocationsCtrl', {
$scope: scope,
$location : _$location_,
LocationDbService : _LocationDbService_
});
location = _$location_; //thx to the underscores you could use '$location' as name instead of 'location'
locationDbService = _LocationDbService_;
}));
Next you should mock the service call:
it('should be able to retrieve all stored locations',
function () {
spyOn(locationDbService , 'getAll').andCallFake(function (success, fail) {
var results = {};
results.rows = new Array(5);
success(null, results);
});
expect(scope.numberOfLocations).toBeUndefined();
scope.getLocations();
expect(scope.numberOfLocations).toBe(5);
});
The service should have tests of its own.

Sharing dynamic data between two controllers with service AngularJS

I have two angular services that need to share models (a list of messages and an individual message), which they get from a call to our API. The service is as follows:
angular.module('CmServices', ['ngResource'])
.factory('Messages', function ($resource, $routeParams, $rootScope) {
var data = {};
data.rest = $resource(url, {}, {
query: {method:'GET', params: params},
post: {method:'POST', params: params}
});
// Trying to set this through a call to the API (needs to get param from route)
$rootScope.$on('$routeChangeSuccess', function(event, current, previous) {
var messages = data.rest.query({m_gid: $routeParams.gid}, function () {
data.messages = messages;
});
});
return data;
});
and the controllers are:
function MessagesCtrl ($scope, $http, $location, $routeParams, Messages) {
$scope.messages = Messages.messages;
}
function MessageCtrl ($scope, $http, $location, $routeParams, Messages) {
$scope.messages = Messages.messages[0];
}
But neither of the controllers update when the data loads from the REST API (I've logged the data coming back, and it definately does).
Instead of assigning a new array to data.messages like this:
data.messages = messages
use angular.copy() instead, which will populate the same array:
angular.copy(messages, data.messages)
That way, the controllers will see the update.
The problem is that you are returning a different version of data to each controller. I would place messages in $rootScope. So
data.rest.query({m_gid: $routeParams.gid}, function () {
$rootScope.messages = messages;
});
Incidentally, what is the purpose of setting the return value of data.rest.query to var messages? That variable gets blown as soon as you leave the function.

Categories

Resources