How to make an angularjs application 12 factor compliant regarding configuration - angularjs

I'm trying to make an angularjs app 12 factor compliant regarding config (http://12factor.net/config).
It should depend on the environment and I should not see the words development, test, production, etc. in the code.
Variables could be stored in bash env for example.
I could pass them to a webserver.
I already thought of an .erb template file to erb config.js.erb > config.js, but if I change a variable while the application is running I'd have to redo this.
I already found this article http://mindthecode.com/how-to-use-environment-variables-in-your-angular-application/
But it's a big lie and Grunt.js to do this, really... Anyway.
I know 12factor philosophy wasn't made for frontend application but my angular application could be deployed in many different servers across many environment and it won't harm anyone to try to do things properly :).
Thanks !
Edit:
The config parameters I'd like to use would be some stuff like :
app:
api:
url: "The url of the api server"
port: 8080
cdn:
images: "url of my images caching service"
google:
oauth:
"api_key": "The api key used for that deployment"
#other external frontend services
Other Edit:
This guy kinda went with an answer : http://bahmutov.calepin.co/inject-valid-constants-into-angular.html which I find kind of ugly and totally bound to angularjs; but it works !

Here is the solution I found, it's totally bound to angularjs but it works for me on Heroku and it's very simple. I just append my conf module to the generated code.
Everytime I restart the app a new version of the code is copied therefore the Append only happens once.
The Append just redefines an already existing configuration variable.
If someone find something more 'classy' I'd be happy to put it as the right solution !
var compression = require('compression');
var express = require('express');
var logfmt = require('logfmt');
var stdio = require('stdio');
var glob = require("glob");
var fs = require('fs');
// ------------------------
// Read config from args
var ops = stdio.getopt({
'url': {
key: 'u',
args: 1,
default: '',
description: 'Url of api server'
},
'port': {
key: 'p',
args: 1,
default: 5000,
description: 'Port on which to listen'
}
});
var port = ops.port || process.env.PORT;
ops.port = undefined;
// ------------------------
// Append config to js built file
var codeToAppend = 'angular.module("project.config",[]).constant("ApiConfig",' + JSON.stringify(ops) + ');';
glob(__dirname + '/dist/scripts/scripts.*.js', function(er, files) {
fs.appendFile(files[0], codeToAppend, function(err) {
if (err) {
throw err;
}
console.log('The "conf code" was appended to file!');
});
});
// ------------------------
// Start App :3
var app = express();
app.use(logfmt.requestLogger());
app.use(compression({
threshold: 512
}));
app.use(express.static(__dirname + '/dist'));
app.get('/config', function(req, res) {
res.json(ops);
});
app.listen(port);

I found a repository on github that hopefully help you Angular-Express-Train-Seed

Related

How to make an angular application take arguments from command line?

I have an AngularJS application in which my code looks something like this:
myApp = angular.module('myApp',
[
'ui.router',
'ngMaterial',
'ngMessages'
]
);
myApp.constant('CONSTANTS', (function() {
// Define your variable
return {
backend: {
baseURL: 'http://mybackend.com:3026'
}
};
})());
I run this application using http-server on port number 8000 like this:
% http-server -p 8000
I want to pass in a command-line argument for the backend.baseURL such that it over-rides the value specified in the code. How can I do it??
What you need is at least required http-server that supported dynamic content. while your http-server is supported only static content.
And in the comment you asking which server should you use. There are thousands of web-server that support dynamic content out there. but sinc you are currently using http-server I assumed you just want a small server for local-dev.
Unfortunately, I cannot find any server that support your need without modifying their code. So I suggest you to create your own server base on a library on npm.
This is and example server using live-server.
var liveServer = require("live-server");
var fs = require("fs")
var root = process.argv[2] || "."
var port = process.argv[3] || 8000
var replaceTextMiddleWare = function(req, res, next){
var file = process.argv[4]
var find = process.argv[5]
var replace = process.argv[6]
if(file && find){
if(req.url === file) {
fs.readFile( root + file, "utf-8", function(e, content){
res.end( content.replace(find, replace))
} )
return;
}
}
next();
}
var params = {
port: port, // Set the server port. Defaults to 8080.
host: "0.0.0.0", // Set the address to bind to. Defaults to 0.0.0.0 or process.env.IP.
root: root, // Set root directory that's being server. Defaults to cwd.
open: false, // When false, it won't load your browser by default.
ignore: 'scss,my/templates', // comma-separated string for paths to ignore
file: "index.html", // When set, serve this file for every 404 (useful for single-page applications)
wait: 1000, // Waits for all changes, before reloading. Defaults to 0 sec.
mount: [['/components', './node_modules']], // Mount a directory to a route.
logLevel: 2, // 0 = errors only, 1 = some, 2 = lots
middleware: [ replaceTextMiddleWare ] // Takes an array of Connect-compatible middleware that are injected into the server middleware stack
};
liveServer.start(params);
Then you can run your server by
nodejs myserver.js /mydocument/myproject/ 8000 config.js "http://mybackend.com:3026" "http://mydevserver.com:80"
The command accept parameters:
Path to serve content
Port
File name
Text to find
Text to replace
This server support only one dynamic file with simple find/replace.
From this point, I guess you can modify middleware to do whatever you want.
when Ive done this in production I use the build process for this, using gulp in this case,
var knownOptions = {
string: 'env',
default: { env: process.env.NODE_ENV || 'default' }
};
var options = minimist(process.argv.slice(2), knownOptions);
console.log("using config : " + chalk.blue(options.env));
we get an environment variable defaulting to default using minimist we can pass -env 'string'
then further in the code pushing a dynamic file onto app.js
//now we use options.env
appJS.push("env/"+options.env+".js");
env/[options.env].js here is an angular module that exports environment specific constants
looks like you are not using gulp but you are using node script from package.json. if you are using gulp then this should not be a problem you you use http-server via gulp.
one thing you can do in your current case is as part of your run command, set process.env.baseUrl="dynamic" and then roughly speaking, use this in your code like this
return {
backend: {
baseURL:process.env.baseUrl || 'http://fallbackurl'
}
Basically what I understand is that you want to customize http-server package for your won custom handling.
Here is small solution I found for you....
Go to node installation folder(In my case its local one)...
So path of file which we going to edit is like this...
../node_modules/http-server/lib/http-server.js
Import one package which serve you to get command line arguments...you don't need to install it, its already there.
var argv = require('optimist').boolean('cors').argv; // add this line at top after line number 7
Now
At line 60 and after code before.push(function (req, res) { add following code..
if( req.url === '/backend-url' ) {
return res.end(JSON.stringify(argv.x));
}
So before push function will look like::
before.push(function (req, res) {
if( req.url === '/backend-url' ) {
return res.end(JSON.stringify(argv.x));
}
if (options.logFn) {
options.logFn(req, res);
}
res.emit('next');
});
now we have configured our new command line argument x for http-server which will be return for url "/backend-url"
Now at front end
myApp.constant('CONSTANTS', ('$http', function($http) {
// Define your variable
**var backendURL = (function() {
return $http.get('/backend-url', function(bUrl) {
return bUrl;
})
})();**
return {
backend: {
baseURL: backendURL
}
};
})());
Done, now you add url like this: **http-server -p 8080 -x http://example.com**
I choose this approch as replacing file content i dont think good in you case
If you use only one instance of your app, you can start it with script, that accepts all necessary command line arguments, replaces appropriate placeholders (string between specific comments, for example) in your application js files and then launches http-server on necessary port.

Using different domain for development and production in angularjs

I'm working on AngularJS single page application that consume REST services. The front-end application is developed separately from the back-end and therefore during development we've to hardcode the domain name in the URLs for AJAX calls (we've enabled CORS). But in the case of production everything is running in the same domain and hence hardcoding the domain name looks little bad. Is there we can use the domain name in urls for ajax calls during development and in production don't hardcode the domain name? I'm using gulp.
An example of using gulp-ng-constant with an $http interceptor:
The following task in gulpfile.js will generate the file target/build/scripts/_config.js with the contents angular.module('globals', []).constant('apiContextPath', '...');:
gulp.task('app.ngconstant', [], function() {
var ngConstant = require('gulp-ng-constant'),
var rename = require('gulp-rename');
return
ngConstant({
constants: {
apiContextPath: '.' // TODO Devise a way to set per environment; eg command line
},
name: 'globals',
stream: true
})
.pipe(rename('_config.js'))
.pipe(gulp.dest('target/build/scripts'));
});
Obviously you need to include the generated file in your (packed/minified) code.
Something along the following code will configure the $httpProvider to prepend the apiContextPath to all requests that start with '/api/' (i.e. our REST endpoints):
angular.module(...).config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push(['globals', function(globals) {
return {
'request': function(config) {
if( config.url.indexOf('/api/') === 0 ) {
config.url = globals.apiContextPath + config.url;
}
return config;
}
};
}]);
}]);
(There are quite a few other configuration options, so this is just an example from an older project I worked on.)
If you don't already, you could pass your gulp build a parameter to build in production or development mode.
Based on that flag you can set a baseUrl property in your gulpfile that is used to include a script (using gulp-insert) into your javascript build before all of your angular scripts:
'window.baseUrl = ' + baseUrl
Then you could have a constant in your application that your services use to get the baseUrl:
angular.module('YourModule').constant('baseUrl', window.baseUrl);

Hosting nodeJS app with firebase

So I have this web-app using angularJS and nodeJS. I don't want to just use localhost to demo my project because it doesn't looks cool at all when I type "node server.js" and then go to localhost.....
Since I intend to use Firebase for the data, I have noticed that Firebase provides hosting. I tried it, but it seems to only host the index.html and not through/using server.js. I have customized files for the server to use/update. So, how can I tell Firebase Hosting to use my server and related files when hosting?
Is it possible to tell Firebase, hey, run "node server.js" to host my index.html?
I'm guessing by the way you are wording the question you want to see this site from "the internet".
Two routes you could go here.
a) Serve your index through Firebase hosting. Firebase only hosts assets. If your Angular app is being served through Node then you will need to change your architecture to be more SPA-ish
SPA-ish would be like an index bootstrap that interacts with the backend purely through API's.
You would host the API server on something more appropriate like through Nodejitsu.
b) Serve the whole thing through something like Nodejitsu (hosting platform) or your very own VM managed by a different kind of hosting company like BuyVM.net.
Another idea, is if your nodejs app is independent of the angularjs app (however they use shared data, and perform operations on that data model) you could separate the two and connect them only via firebase.
Firebase hosting -> index.html and necessary angularjs files.
Locally (your PC) -> server.js which just connects to firebase and trigger on changed data.
I have done this for a few projects and it's a handy way to access the outside world (internet) while maintaining some semblence of security by not opening ports blindly.
I was able to do this to control a chromecast at my house while at a friends house
Here's an example from my most recent project (I'm trying to make a DVR).
https://github.com/onaclov2000/webdvr/blob/master/app.js
var FB_URL = '';
var Firebase = require('firebase');
var os = require('os')
var myRootRef = new Firebase(FB_URL);
var interfaces = os.networkInterfaces();
var addresses = [];
for (k in interfaces) {
for (k2 in interfaces[k]) {
var address = interfaces[k][k2];
if (address.family == 'IPv4' && !address.internal) {
addresses.push(address.address)
}
}
}
// Push my IP to firebase
// Perhaps a common "devices" location would be handy
var ipRef = myRootRef.push({
"type": "local",
"ip": addresses[0]
});
myRootRef.on('child_changed', function(childSnapshot, prevChildName) {
// code to handle child data changes.
var data = childSnapshot.val();
var localref = childSnapshot.ref();
if (data["commanded"] == "new") {
console.log("New Schedule Added");
var schedule = require('node-schedule');
var date = new Date(data["year"], data["month"], data["day"], data["hh"], data["mm"], 0);
console.log(date);
var j = schedule.scheduleJob(date, function(channel, program, length){
console.log("Recording Channel " + channel + " and program " + program + " for " + length + "ms");
}.bind(null, data["channel"], data["program"], data["length"]));
localref.update({"commanded" : "waiting"});
}
});
When I change my "commanded" data at the FB_URL, to "new" (which can be accomplished by angularjs VERY Simply, using an ng-click operation for example) it'll schedule a recording for a particular date and time (not all actually functional at the moment).
I might be late but since 3 years have passed there is an solution available now from Firebase in the form of cloud functions
Its not straight forward but looks promising if one can refactor their code a bit

Mean.io framework with socket.io

How to use socket.io in Mean.io stack?
First of all, Mean.io changes their folder structure very regularly.. So my question is where is the best place to configure socket.io ? or is it better to use express.io ?
Second I am still not quite sure where to look for to find code that tells mean.io to listen for port, I have found a port is defined in config folder in all.js file, but real problem is as soon as I define server.listen(port) app doesn't load. and if I don't app loads but socket.io doesn't work.
Also I have another question about /socket.io/socket-io.js file? I have copied that in index folder, but my app can't find it or says 404 error. I know it's not an actual file sitting on any such location as far as I have understood, also people suggested to put that line as 127.0.0.1/socket.io/socket-io.js but none made the js file available for the app to be able to run socket.io.
What is the proper way of defining socket.io in mean.io framework?
I also faced the same issue and took me about a week to finally get it right. I'll try to explain what I did:
app.js
In this file, I just invoke the code that creates and sets up a socket.io object for me, which is then passed to the routes module.
'use strict';
/*
* Defining the Package
*/
var Module = require('meanio').Module;
var MeanSocket = new Module('chat');
/*
* All MEAN packages require registration
* Dependency injection is used to define required modules
*/
MeanSocket.register(function(app, http) {
var io = require('./server/config/socketio')(http);
//We enable routing. By default the Package Object is passed to the routes
MeanSocket.routes(io);
return MeanSocket;
});
server/config/socketio.js
This file simply configures the socket.io object. Please note that I had to upgrade meanio module to version 0.5.26 for this work, as http object (express server) is not available in older meanio versions. Moreover, in case you want to use ssl, you can inject https instead of http.
'use strict';
var config = require('meanio').loadConfig(),
cookie = require('cookie'),
cookieParser = require('cookie-parser'),
socketio = require('socket.io');
module.exports = function(http) {
var io = socketio.listen(http);
io.use(function(socket, next) {
var data = socket.request;
if (!data.headers.cookie) {
return next(new Error('No cookie transmitted.'));
}
var parsedCookie = cookie.parse(data.headers.cookie);
var sessionID = parsedCookie[config.sessionName];
var parsedSessionID = cookieParser.signedCookie(parsedCookie[config.sessionName], config.sessionSecret);
if (sessionID === parsedSessionID) {
return next(new Error('Cookie is invalid.'));
}
next();
});
return io;
};
routes/chat.js
Finally, use the routes file to define the socket events, etc.
'use strict';
// The Package is passed automatically as first parameter
module.exports = function(MeanSocket, io) {
io.on('connection', function(socket) {
console.log('Client Connected');
socket.on('authenticate', function(data, callback) {
});
});
};
Hope this helps!
The simplest way would be to install the socket package...
mean install socket

Can not access ANY directories in phonegap through the getDirectory() function

This should be quite simple but I just cannot get it to work
Szenario:
I want to iterate through my folders with the phonegap file API
Problem:
I can not get the getDirectory() function wo work
Very simple example: (to illustrate my problem)
var fileSystem, basePath;
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, doStuff, function(error) {
notificationservice.log('Failed to get local filesystem: ' + error.code);
});
function doStuff(fs) {
fileSystem = fs;
basePath = fileSystem.root.fullPath;
var directoryEntry = new DirectoryEntry('', basePath);
readDirectory(directoryEntry);
}
function readDirectory(directoryEntry) {
var directoryReader = directoryEntry.createReader();
directoryReader.readEntries(function(entries) {
for (var i = 0 ; i < entries.length ; i++) {
notificationservice.log(entries[i].fullPath);
fileSystem.root.getDirectory(entries[i].fullPath, {create: false}, function(dir) {
notificationservice.log('SUCCESS');
}, function (error) {
notificationservice.log('Failed to get directory');
});
}
});
}
I can access my folder with the new DirectoryEntry() but whenever I try the access a directory with the getDirectory() function I fail - if anyone could help me correct the above code so that the fileSystem.root.getDirectory() would not return an error I´d be very thanksfull !
Please note:
I use the eclipse editor for deployment and deploy to a nexus 7
(if possible the code should work an plattforms like iOS or win as well)
thanks,
matthias
by the way: I am sure there are a lot of questions which actually solve this issue - however, I haven´t been able to find ANYTHING working for me...
according to https://meta.stackexchange.com/questions/17845/etiquette-for-answering-your-own-question
Silly me - was hooked on script which I got from the web - I have to use the .name property (relative path from the current directory) like this fileSystem.root.getDirectory(name, {create: false}, function(dir) {... - THIS QUESTION IS SOLVED (and sorry if anyone wasted time on this)

Resources