AngularJs - RequireJs - Socket.io : Not playing well together - angularjs

I have a 2 part issue:
I. --- When I load socket.io in my requirejs path file, while it may appear in browser's scripts collection, the moment I use it in a factory object, I receive the error, "io" is not defined. I do not get the error however, if I load socket.io.js from the HTML page directly. I found another person had the exact issue here but with no resolution.
II. --- I am using ocLazyLoad, and my app is modularized per function. I opted to place the $socket factory object I copied from Brian Ford's example directly in my root module ['app']. I know services in a lazyload scenario need to be loaded every time they are used, regardless of prior calls to them. But, this seems wrong. If I wish to make my app real time, that means I have to list it as a dependency in every state. There should be a more efficient way to do this but I cannot see how.
Issue I:
Establishing RequireJs dependency and adding to bootstrap
a. RequireJs's path file:
//socket.io
'socket.io' : '/vendor/socket.io-client/lib/socket.io'
//shim
'socketio': {exports: 'io'}
b. Bootstrap file
window.name = "NG_DEFER_BOOTSTRAP!";
define([
'require',
'jquery',
'socket.io',
'angular',
.....
Observation: This is all working as expected, as evidenced by socket.io.js now in my browser's scripts collection on page load.
$socket factory service
a. app.socket.js (factory file from Brian Ford) file:
define(['app'], function (app) {
app.factory('$socket',['$rootScope', function ($rootScope) {
/////////////////////
// PROBLEM IS HERE //
/////////////////////
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);
}
});
})
}
};
}]); });
b. Controller using $socket factory
define(['angular'], function(ng) {
'use strict';
/* #ngInject */
MyDashboardCtrl.$inject = ['$socket','MyDashboardSvc'];
ng.module('MyDashboard').controller('MyDashboardCtrl', MyDashboardCtrl);
function MyDashboardCtrl($socket, MyDashboardSvc) {
....
Obervation: With socket.io.js being loaded from require's path file, once the controller calls the factory as a dependency, I receive this error:
ReferenceError: io is not defined
If instead, I load socket.io.js straight from the HTML, the problem goes away:
<script src="vendor/socket.io-client/lib/socket.io.js"></script>
<script src="app/require/rconfig.js"></script>
<script src="vendor/requirejs/require.js" data-main="app/app.bootstrap.js"></script>
Issue II:
I would like to make my entire app real time. Does that mean, if I intend to utilize lazy loading, I will need to list the $socket factory in every BLESSED $state?

I hate my solution. I put the socket.io object on the windows inside of requirejs, and then find it there using the $window object in angular. It really is atrocious - but it will allow me to continue to code while I find a better solution. Here lie the relevant and irrelevant bits:
<script text="text/javascript">
require.config({
paths: {
angular: "<path to angular>"
socketIO: [
"/socket.io/socket.io"
],
myNgApplicationJs: "/client/my-angular-all"
},
shim: {
socketIO: { deps: [] },
angular: { deps: [
"jQuery",
"socketIO"
] },
myNgApplicationJs: {deps: [
"angular", "socketIO"
]}
}
});
require([
"myNgApplicationJs"
,"socketIO"
], function(noop, io) {
// a hack to make the socket.io available on the $window in angular
window.io = io
angular.bootstrap(document, ['myNgApplication']);
return {};
});
</script>
Then (based off of code poached from Brian Ford):
angular.module('myNgApplication').factory('MySocketIO', function ($log, $window, $rootScope) {
if(!$window.io) {
throw new Exception("assure the socket.io is added to the window object. e,g, window.io")
}
// Hack socket.io off of the $window object
var io = $window.io
var socket = io();
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);
}
});
})
}
};
});
This allows me to abstract away the hack - the best I can. Should I find a better solution I only need to change requirejs peice and the MySocketIO implementation.
I then code up business level factories like this:
angular.module('myNgApplication').factory('BlahProxy', function ($log, MyHttp, MySocketIO) {
var Blah = {}
Blah.onUpdate = function(callback) {
// don't expose socket-io message type to the controllers
MySocketIO.on('send:testIV', callback)
}
return Blah
})
Then leverage those factories in my controllers:
angular.module('myNgApplication').controller('myBlahGridCtrl', function ($scope, $log, BlahProxy) {
$scope.messages = []
// handle whatever message types explicitly
BlahProxy.onUpdate(function (message) {
$scope.messages.push(message);
})
})

I was facing the same problem, It solved by adding 'socket.io' in my socketfactory file dependencies. In your case, try the following change:
a. app.socket.js file:
define(['app', 'socket.io'], function (app, io) {
app.factory('$socket',['$rootScope', function ($rootScope) {
//........

Related

Angular-translate : How can I refresh content (api request) when I change language?

I'm new in AngularJS and I try to understand how to use it. I'm using angular-translate to translate my website and it works but I have a problem with the dynamic content from the database.
I get the dynamic content by an api request. I would like to "redo" the request with the good language to get the content reloaded in the good language.
I catch the "translateChangeSuccess" event but how can I "redo" the previous api request ?
Thank you very much for your help :)
ps : sorry for my english
Edit :
// my run block :
(function ()
{
'use strict';
angular
.module('fuse')
.run(runBlock);
function runBlock($rootScope, $timeout, $state, $cookieStore)
{
$rootScope.$on('$translateChangeSuccess', function () {
// catch translateChangeSuccess event
// redo the previous api request
});
}
})();
// my change language function
/**
* Change Language
*/
function changeLanguage(lang)
{
angular.forEach(vm.languages, function(value, key) {
if (lang.code == key)
$translate.use(lang.code); // launch translateChangeSuccess event
});
}
// my api service
function apiService($http, $resource, $translate, CONFIG_API)
{
// change header with good language
$http.defaults.headers.common["Accept-Language"] = $translate.proposedLanguage();
var api = {};
// Base Url
api.baseUrl = CONFIG_API.base_url;
// request to reload when user changes language
api.Documents = $resource(api.baseUrl + 'documents/:id',
{id: '#id'},
{update: {method: 'PUT'}}
);
...
}
On the translateChangeSuccess event do the request again with the current parameter for the desired language (implying that the server sends you different content depending on language)
I cannot give you a solid example since there are so many ways to manage it.
With some code you can have a better explanation.
Ok, so I found how to do that. I just ask data to the api again through a service (apiResolver)
test.module.js :
(function ()
{
'use strict';
angular
.module('app.test_module', [])
.config(config);
/** #ngInject */
function config($stateProvider, msApiProvider)
{
// State
$stateProvider.state('app.test_module', {
url : '/myurl',
views : {
'content#app': {
templateUrl: 'mytemplate.html',
controller : 'MyController as vm'
}
},
resolve : {
test : function (apiResolver)
{
return apiResolver.resolve('myquery#query');
}
}
});
}
})();
and test.controller.js :
(function ()
{
'use strict';
angular
.module('app.test_module')
.controller('testController', testController);
/** #ngInject */
function testController($rootScope, apiResolver, dataToDisplay)
{
var vm = this;
// Data
vm.dataToDisplay = dataToDisplay;
$rootScope.$on('$translateChangeSuccess', function () {
// reload my content
apiResolver.resolve('myquery#query')
.then(function(result) {
vm.dataToDisplay = result;
});
});
}
// more code here but not usefull in this example
})();
There is maybe a better way but it works, my data are translated when the user changes language :)

Refer to service in run immediately after definition?

I would like to attach a string from a service to every request. Attempt:
'use strict';
angular.module('main')
.service('Foo', function () {
self.bar = 'haz';
})
.run(function ($injector, Foo) {
$injector.get('$http').defaults.transformRequest = function (data, headersGetter) {
headersGetter()['X-Bar'] = Foo.bar;
};
});
Error: [$injector:nomod] Module 'Foo' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.
Note: I'm using ui-router and have multiple files; but one module; if that makes a difference.
Be sure to have ng-app="main" somewhere in your HTML because definitely the error message says that you are trying to load a module Foo...
Next, your approach is almost right. First, what should self be in the Foo service - use this. And also, why do you use the $injector? Just inject $http directly... your code then is as follows:
angular.module('main')
.service('Foo', function () {
this.bar = 'haz';
})
.run(function ($http, Foo) {
$http.defaults.transformRequest = function (data, headersGetter) {
headersGetter()['X-Bar'] = Foo.bar;
};
});
My $injector approach was taken from: http://engineering.talis.com/articles/elegant-api-auth-angular-js/
Looking at the docs: https://docs.angularjs.org/api/ng/service/$http, here's how I rewrote it:
angular.module('main')
.service('Foo', function () {
this.bar = 'haz';
})
.run(function ($injector, Foo) {
$injector.get('$http').defaults.headers.common['X-Bar'] = Foo.bar;
});
Which works fine. Also, looks like I had another bug in my code which was causing the issue… that's the problem with coding at midnight >.<

$rootScope in angular document ready

I have this piece of code:
.service('webSocket', function ($rootScope, socketFactory, CONFIG, $timeout) {
angular.element(document).ready(function () {
$rootScope.log('Waiting for connection...',CONSOLE_INFO);
});
And I am getting this error:
TypeError: $rootScope.log is not a function
This service is injected into this controller:
.controller('mainCtrl', function mainCtrl($scope, $rootScope, webSocket, myConsole ...
In which I have:
$rootScope.log = function (msg, type) { myConsole.log(msg,type); ... };
Can you tell me where is the problem? Or at least point me in the right direction? The reason I am using document ready function is because apart from logging messages to browser console (console.log) I use notifications for user (pNotify library) which needs to be called after DOM is loaded.
Sharing something between services using $rootScope should be considered generally as anti-pattern. If you don't have some different implementation of console for different controllers, you can do it Angular-way and perform all configurations in config block. Subscribing to document ready event in the service is also not a good idea (I would prefer to do it in run block), since in angular service is instantiated once it is first time required by any other service or controller or whatever. In order to have configurable service that may have different console implementation I would implement it using provider as follows:
angular.module('app',[]).
constant('console', console).
constant('PNotify', PNotify).
provider('myConsole', function() {
var log = angular.noop;
function MyConsoleFactory() {
return {
log: log,
debug: log
}
}
this.setLog = function(logImplementation) {
log = logImplementation
}
this.$get = [MyConsoleFactory];
}).
config(['myConsoleProvider', 'console', 'PNotify', function(myConsoleProvider, console, PNotify) {
myConsoleProvider.setLog(function(msg) {
console.log('[LOG] '+ Date.now() + ':\t' + msg);
new PNotify({
title: 'Title',
text: msg
});
});
}]).
run(['myConsole', '$document', function(myConsole, $document) {
$document.ready(function () {
myConsole.log('Waiting for connection...');
});
}]);
In this case you don't need any controller at all.
Plunker: http://plnkr.co/edit/aV9TIO07pnDs26xDBPtf?p=preview
That happens because service code runs before service was added to controller(where $rootScope.log method is defined). You can move $rootScope.log = function (msg, type) { myConsole.log(msg,type); ... }; into app.run(...) method and it will work.

AngularJS load config on app start

I need to load a config file (JSON format) upon my AngularJS app startup in order to load few parameters which will be used in all api calls. So I was wondering if it is possible to do so in AngularJS and if yes where / when I shall be loading the config file?
Note:
- I will need to save the config file parameters in a service, so I will need to load the json file content before any controller is loaded but with service units available
- Using an external json file is a must in my case here as the app client need to be able to update the app configuration easily from external file without the need to go through the app sources.
EDITED
It sounds like what you are trying to do is configure a service with parameters. In order to load the external config file asynchronously, you will have to bootstrap the angular application yourself inside of a data load complete callback instead of using the automatic boostrapping.
Consider this example for a service definition that does not actually have the service URL defined (this would be something like contact-service.js):
angular.module('myApp').provider('contactsService', function () {
var options = {
svcUrl: null,
apiKey: null,
};
this.config = function (opt) {
angular.extend(options, opt);
};
this.$get = ['$http', function ($http) {
if(!options.svcUrl || !options.apiKey) {
throw new Error('Service URL and API Key must be configured.');
}
function onContactsLoadComplete(data) {
svc.contacts = data.contacts;
svc.isAdmin = data.isAdmin || false;
}
var svc = {
isAdmin: false,
contacts: null,
loadData: function () {
return $http.get(options.svcUrl).success(onContactsLoadComplete);
}
};
return svc;
}];
});
Then, on document ready, you would make a call to load your config file (in this case, using jQuery). In the callback, you would then do your angular app .config using the loaded json data. After running the .config, you would then manually bootstrap the application. Very Important: do not use the ng-app directive if you are using this method or angular will bootstrap itself See this url for more details:
http://docs.angularjs.org/guide/bootstrap
Like so:
angular.element(document).ready(function () {
$.get('/js/config/myconfig.json', function (data) {
angular.module('myApp').config(['contactsServiceProvider', function (contactsServiceProvider) {
contactsServiceProvider.config({
svcUrl: data.svcUrl,
apiKey: data.apiKey
});
}]);
angular.bootstrap(document, ['myApp']);
});
});
UPDATE: Here is a JSFiddle example: http://jsfiddle.net/e8tEX/
I couldn't get the approach suggested my Keith Morris to work.
So I created a config.js file and included it in index.html before all the angular files
config.js
var configData = {
url:"http://api.mydomain-staging.com",
foo:"bar"
}
index.html
...
<script type="text/javascript" src="config.js"></script>
<!-- compiled JavaScript --><% scripts.forEach( function ( file ) { %>
<script type="text/javascript" src="<%= file %>"></script><% }); %>
then in my run function I set the config variables to $rootScope
.run( function run($rootScope) {
$rootScope.url = configData.url;
$rootScope.foo = configData.foo;
...
})
You can use constants for things like this:
angular.module('myApp', [])
// constants work
//.constant('API_BASE', 'http://localhost:3000/')
.constant('API_BASE', 'http://myapp.production.com/')
//or you can use services
.service('urls',function(productName){ this.apiUrl = API_BASE;})
//Controller calling
.controller('MainController',function($scope,urls, API_BASE) {
$scope.api_base = urls.apiUrl; // or API_BASE
});
//in html page call it
{{api_base}}
There are also several other options including .value and .config but they all have their limitations. .config is great if you need to reach the provider of a service to do some initial configuration. .value is like constant except you can use different types of values.
https://stackoverflow.com/a/13015756/580487
Solved by using constant.
Like providers you can configure it in .config phase.
Everything else like Keith Morris wrote before.
So the actual code would be look like this:
(function () {
var appConfig = {
};
angular.module('myApp').constant('appConfig', appConfig);
})();
then in app.bootstrap.js
(function () {
angular.element(document).ready(function () {
function handleBootstrapError(errMsg, e) {
console.error("bootstrapping error: " + errMsg, e);
}
$.getJSON('./config.json', function (dataApp) {
angular.module('myApp').config(function (appConfig) {
$.extend(true, appConfig, dataApp);
console.log(appConfig);
});
angular.bootstrap(document, ['myApp']);
}).fail(function (e) {
handleBootstrapError("fail to load config.json", e);
});
});
})();
To json config file, there is a practice example on Jaco Pretorius blog's. Basically:
angular.module('plunker', []);
angular.module('plunker').provider('configuration', function() {
let configurationData;
this.initialize = (data) => {
configurationData = data;
};
this.$get = () => {
return configurationData;
};
});
angular.module('plunker').controller('MainCtrl', ($scope, configuration) => {
$scope.externalServiceEnabled = configuration.external_service_enabled;
$scope.externalServiceApiKey = configuration.external_service_api_key;
});
angular.element(document).ready(() => {
$.get('server_configuration.json', (response) => {
angular.module('plunker').config((configurationProvider) => {
configurationProvider.initialize(response);
});
angular.bootstrap(document, ['plunker']);
});
});
Plunker: http://plnkr.co/edit/9QB6BqPkxprznIS1OMdd?p=preview
Ref: https://jacopretorius.net/2016/09/loading-configuration-data-on-startup-with-angular.html, last access on 13/03/2018

Inject a config in AngularJS

I have a config that I'd like to be able to inject into Angular services, directives, etc. What is the best way to go about doing this?
I was playing with the idea of making config module:
'use strict';
angular.module('config', [])
But I wasn't sure how to construct the object literal that would be the actual config object that gets injected:
angular.module('myModule', ['ngResource', 'config'])
.factory('myservice', function ($resource) {
return $resource(config.myservice,{}, {
// whatever
})
});
Would it be okay to just expose config as a service and inject that?
I would say, the strategy varies, depending on what sort of config you have, but you have several options:
Module wide constants
If you only need several constants, you can use .value(), like this:
var app;
app = angular.module("my.angular.module", []);
app.value("baseUrl", "http://myrestservice.com/api/v1");
//injecting the value
app.controller("MyCtrl", ['baseUrl', function (baseUrl) {
console.log(baseUrl); // => "http://myrestservice.com/api/v1"
}]);
See a more detailed answer here.
Fetching the config/config service
What i personally like to do is fetch my configuration from somewhere else via a service just as normal. It doesn't matter, if this is a remote location or just static information.
var app;
app = angular.module("my.angular.config", []);
app.service('Configuration', [function() {
return {
getbaseUrl: function() { return "http://myrestservice.com/api/v1" },
getConfig: function() {
return {
answer: 42,
question: "??"
}
}
}
}]):
EDIT: example with an external fetch:
var app;
app = angular.module('my.module.config', []);
app.factory('ConfigurationData', ['$http', '$q', function(http, q) {
var deferredConfig = q.defer();
//error handling ommited
http.get('http://remote.local/config.json').success(function(data) {
return deferredConfig.resolve(data);
});
return {
getConfig: function() {
return deferredConfig.promise;
}
};
}]);
With this service, you can inject your config into other services, however, you can run into timing issues, as you have to inject and resolve the promise given by the service before anything you want to do with the config:
var app;
app = angular.module("my.other.module", ['my.module.config']);
app.factory('MyAwesomeService', ['ConfigurationData', function(config) {
config.getConfig().then(function(config) {
//do something with your config.
});
}]);
You get a bit more fine control here, as you can react to different inputs. Again, it depends on your use case. You can use a factory here if you need additional logic for structuring the configuration.
Lastly, if you want even more control over configuration, you can make a
Custom Provider
Providers can be pretty useful, but i think they are a bit harder to design. Considering a baseUrl from the configuration needed for your app to work, you could write a provider for the service needing the value of the baseUrl like this:
var app;
app = angular.module('my.angular.module', []);
app.provider("RestServiceProvider", function(){
this.baseUrl = 'http://restservice.com';
this.$get = function() {
var baseUrl = this.baseUrl;
return {
getBaseUrl: function() { return this.baseUrl; }
}
};
this.setBaseUrl = function(url) {
this.baseUrl = url;
};
});
This lets you do cool stuff in the config phase of your application:
app.config(['RestserviceProvider', function(restServiceProvider) {
restServiceProvider.setBaseUrl('http://wwww.myotherrestservice.com');
}]);
Every instance you fetch in a service/controller/etc. of RestService will now have the baseUrl set from the config phase from the moment you inject it.
For a more detailed overview, i suggest this gist.
Creating a stand-alone module that only has a service or a directive (or both) is a great way to make application-independent angular code. If you do this you can easily just take that .js file and plop it into any project, and all you need to do is inject it into your applications and it just works.
So doing something like:
angular.module('config', [])
.factory('config', function() {
return {
theme : 'nighttime',
cursor : 'sword',
...
};
});
Then you can just inject it into any application like so:
angular.module('myModule', ['config'])
.factory('myservice', function (config) {
var theme = config.theme;
var cursor = config.cursor;
// do stuff with night-time swords!
});
This is actually how the angular-ui team package all their directives, each directive is contained within its own module, which makes it easy for others to just take that code and reuse it in all their apps.

Resources