Configure value using a service constant - angularjs

In my latest project I'm making some API requests.
As far as I need to setup different endpoints (local development, remote production), I set the value of the used endpoint when defining the app. Everything works just fine.
var app = angular.module('app', ['lelylan.dashboards.device', 'ngMockE2E'])
app.value('client.config', { endpoint: 'http://localhost:8000' });
The only problem is that I want to set the endpoint http://localhost:8000 from a service that defines some constants [1]. I tried using the run and config block, but the value is not set. I was doing something like this, without any result.
var app = angular.module('app', ['lelylan.dashboards.device', 'ngMockE2E'])
.config(function(ENV) {
app.value('lelylan.client.config', { endpoint: ENV.endpoint });
})
This looks quite terrible to me, but I've no idea on how to solve this issue.
Thanks a lot.
[1] grunt-ng-constant

Please see here : http://jsbin.com/foqar/1/
var app = angular.module('app', []);
var client = {
endpoint : 'http://localhost:8000'
}
app.value('config', client);
app.controller('firstCtrl', function($scope, config){
$scope.endpoint = config.endpoint;
});
or: http://jsbin.com/foqar/2/edit
var app = angular.module('app', []);
app.value('config',{client :{endpoint : 'http://localhost:8000'}});
app.controller('firstCtrl', function($scope, config){
$scope.endpoint = config.client.endpoint;
});

It looks like the grunt-ng-constant creates a new module file to define the constants. You do not have to bother about it on your app JS file, other than declare the module containing those constants as a dependency.
The below is taken as is on the example config of the documentation of grunt-ng-constant
grunt.initConfig({
ngconstant: {
options: {
name: 'config',
dest: 'config.js',
constants: {
title: 'grunt-ng-constant',
debug: true
}
},
dev: {
constants: {
title: 'grunt-ng-constant-beta'
}
},
prod: {
constants: {
debug: false
}
},
}
});
In the options section, you specify the name of the module, file for the module to be written to and a general set of constants. It works like this,
options: {
name: 'config',
dest: 'config.js',
constants: {
title: 'grunt-ng-constant',
debug: true
}
}
The above code will become,
/*File: config.js*/
angular.module('config', [])
.constant('title', 'grunt-ng-constant')
.constant('debug', true);
To change the constants based on your scenario (development / production) you would be using different task sets. Here is where the dev and prod section comes into play
Considering you are using ng-boilerplate, in the gruntfile.js you have tasks build and compile. The build task is used during development, and the compile gets your app ready to be pushed to production.
In the build task you would add ngconstant:dev and in the compile task you would add ngconstant:prod.
grunt.registerTask('build', ['clean', 'html2js', 'otherTasksComeHere', 'ngconstant:dev']);
grunt.registerTask('compile', ['clean', 'html2js', 'otherTasksComeHere', 'ngconstant:prod']);
For your scenario the code would be as below:
/*gruntfile.js*/
grunt.initConfig({
ngconstant: {
options: {
name: 'lelylan.client.config',
dest: 'config.js',
values: {
endpoint : 'http://localhost:8000'
}
},
dev: {
debug: true
},
prod: {
endpoint: 'http://www.production-server.com/',
debug: false
}
},
});
grunt.registerTask('build', ["ngconstant:dev"]);
grunt.registerTask('compile', ["ngconstant:prod"]);
grunt.registerTask.('default', ["build", "compile"]);
/*app.js*/
var app = angular.module('app', ['lelylan.dashboards.device', 'leylan.client.config', 'ngMockE2E']);
app.controller("appCtrl", ["$scope", "$http", "endpoint",
function($scope, $http, endpoint) {
$scope.submit = function(formData) {
$http.post(endpoint+'/processform.php', formData);
}
}]);
Now it all depends on whether you run grunt build or grunt compile. The default task is run when you use the grunt command.

The solution was to use providers. As the docs states:
during application bootstrap, before Angular goes off creating all
services, it configures and instantiates all providers. We call this
the configuration phase of the application life-cycle. During this
phase services aren't accessible because they haven't been created
yet.
For this reason I created a provider to set the needed configurations.
<script>
angular.module('app', ['lelylan.dashboards.device'])
angular.module('app').config(function(lelylanClientConfigProvider, ENV) {
lelylanClientConfigProvider.configure({ endpoint: ENV.endpoint });
});
</script>
In this way all services will then be able to use the new endpoint. Using .value this was not possible.
Thanks everyone for your help.

Related

Angularjs application: tip to send log message to server

we have an application client-server working with nodejs server side and angularjs client side.
We use requirejs to load resources.
We need to send log messages to server. Which is the best way to do this?
Server side we use log4js-node library to log our application (documentation: https://github.com/nomiddlename/log4js-node)
Client side we already test multiple libraries to extend $log angular module, amoung with: Log4js, stacktrace, log4javascript
The last test we did it using log4javascript library (documentation: http://log4javascript.org/docs/index.html)
Below our code snippets:
requirejs configuration
require.config({
baseUrl: 'app',
paths: {
//...
'log4javascript': '../vendor/log4javascript', // tip to log4javascript.js
'logging': 'modules/logging' // our logging.js angular module
},
shim: {
//...
'logging': ['log4javascript']
},
deps: ['app']
});
logging.js module
define(['app'], function (app) {
var myApp = angular.module('logging', []);
myApp.factory('$log', function () {
var logger = log4javascript.getLogger('logging');
var ajaxAppender = new log4javascript.AjaxAppender('http://myserver.com/api/log');
logger.addAppender(ajaxAppender);
return logger;
});
});
app.js (our main)
require([...,"log4javascript"], function (...,log4javascript) {
window.log4javascript = log4javascript;
// ...
});
We use an AjaxAppender to an API that retrieve messages from client and format them through log4js-node library.
What do you think about? Could you suggest us a better way to accomplish our purpose?

How can I have a test and production AngularJS app in an Azure Website?

Can someone please suggest how I can set this up. Here is the scenerio.
I am developing an angular app with an Azure Mobile Services back end.
In angular I have resources that point to the Mobile Service API path. I have a config factory that returns the base service path. I want the dev/staging slot to point to the -dev api and the production slot to point to the production API path.
I can create two config factories, but I'm not sure how to tell it to use the dev one in the staging slot and the production one in the production slot. For a .net app with server code I would use the config stuff, or maybe environmental variables, but this is all client side code.
I thought to just have two web sites, once deployed from a dev branch and one deployed from the master and have a different config... but once I merge dev to master then the config change merges too.
I'm not sure if it matters, but I am deploying from Visual Studio Online as part of the build.
How can I accomplish this?
I was able to solve this very same problem. I have development, testing and production environments and they each need to connect to different API end points. I was able to accomplish this using a grunt plugin called grunt-ng-constant
Basically once you install the plugin, modify your Gruntfile and inside the grunt.initConfig add something like this:
ngconstant: {
// Options for all targets
options: {
space: ' ',
wrap: '"use strict";\n\n {%= __ngModule %}',
name: 'config',
},
// Environment targets
development: {
options: {
dest: '<%= yeoman.app %>/scripts/config.js'
},
constants: {
ENV: {
name: 'development',
apiEndpoint: 'http://your-development.api.endpoint:3000'
}
}
},
production: {
options: {
dest: '<%= yeoman.dist %>/scripts/config.js'
},
constants: {
ENV: {
name: 'production',
apiEndpoint: 'http://api.livesite.com'
}
}
}
},
Register the Grunt task like so:
grunt.registerTask('serve', function (target) {
if (target === 'dist') {
return grunt.task.run(['build', 'connect:dist:keepalive']);
}
grunt.task.run([
'clean:server',
'ngconstant:development', // ADD THIS
'bower-install',
'concurrent:server',
'autoprefixer',
'connect:livereload',
'watch'
]);
});
Now every time you run grunt serve it will generate a config.js file that holds your development constants. You can configure different tasks such as grunt testing or grunt production to generate the testing or production constants instead.
Finally you add the config.js to your index.html like so:
<script src="/scripts/config.js" />
And register the config module in your app:
var app = angular.module('myApp', [ 'config' ]);
In your controller you can get your "environment" variables like this:
angular.module('myApp')
.controller('MainCtrl', function ($scope, $http, ENV) { // ENV is injected
$scope.login = function() {
$http.post(
ENV.apiEndPoint, // Our environmental var :)
$scope.yourData
).success(function() {
console.log('Cows');
});
};
});
With this approach you can easily automate the entire deployment pipeline. You can have a CI server push your changes to the appropriate servers and build the correct version of your app.
Here's a very helpful resource for you to read and from which I took the code samples: http://mindthecode.com/how-to-use-environment-variables-in-your-angular-application
Hope this helps you!
If you can key off the client hostname of the production versus the development servers ( or not the production server), then you can inject an $http interceptor to rewrite request URLs. In my app.js, I have something like:
var DEV_SRVR = 'http://my-machine.example.com:8000';
var PRODUCTION_FRONT_END_CLIENT_SUFFIX = 'DEPLOYMENT';
app.factory('REST_Interceptor',[
function() {
var request = function(config) {
if (RegExp(PRODUCTION_FRONT_END_CLIENT_SUFFIX,'i').test(window.location.host)) {
return config;
}
var rest_request_regex = new RegExp('^.*?/rest/(.*)$');
config.url = config.url.replace(rest_request_regex, DEV_SRVR+'/$1');
return config;
}
return {
request: request
}
}])
app.config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('REST_Interceptor');
}])
Thus, requests from AngularJS like:
$http.get('/rest/foo/')
Go to http://my-machine.example.com:8000/foo/ if the client is not the production client interface, otherwise it goes to /rest/foo/ of the production host.
You might be interested in looking how David Ebbo and I setup this scenario. In David's talk at Build he discusses how to configure two websites to communication with each other. The source code for his talk is available on GitHub called ToDoApp. If you prefer to read over watch, there was also an article written about David's talk which is available in the Azure Documentation called Deploy a Complex Application predictably in Azure
You can set App Settings for you site and slot from the Azure Portal, and set separate settings for the dev and production slots.
See these docs for more details: https://azure.microsoft.com/en-us/documentation/articles/web-sites-staged-publishing/#configuration-for-deployment-slots

How can I pass argument with parameter to gulp?

I want to do something like
gulp build --param1=abc
and I want this param to be available in Angular Service/Controller.
Is there a way I can do that?
Yes. I would recommend in your Gulp config, you create an Angular constant that can be injected into any part of the application.
var ngConstant = require('gulp-ng-constant');
var args = require('yargs').argv;
gulp.task('constant', function () {
var selectedConstant = args.env || 'dev'; // if --env is not declared, assume dev
var constants = {
dev: {
ENV: {
'api': 'api.someUrl.com',
'url': 'dev.url.com'
}
},
prod: {
ENV: {
'api': 'api.someUrl.com',
'url': 'dev.url.com'
}
},
};
return ngConstant({
name: 'app', // name of your module
constants: constants[selectedConstant],
stream: true
})
.pipe(gulp.dest('src'));
});
gulp.task('build', function () {
runSequence(
['constant']
)
});
Now, you can run gulp build --env=prod and that will create a file called constants.js in your /src/ folder (or wherever you specify). Now include this file in your index.html.
You can now inject the constant ENV anywhere in your services or controllers and reference the variables within.
angular.module('app')
.service('SomeService', function(ENV) {
// access ENV vars here
});
.controller('SomeCtrl', function($scope, ENV) {
// access ENV vars here
});

how to activate debugInfoEnabled only for Unit tests?

In my angularjs 1.3 app, i disabled debug info to increase performance :
$compileProvider.debugInfoEnabled(false);
But when i launch my Jasmine tests with Karma, i got error with isolateScope :
var isoScope = element.isolateScope();
I know that it is completely normal,
but i'm looking for a way to reactivate the debug just for testing.
Can i do it programmatically ?
Can i define that in the karma-unit.conf.js ?
My approach to this was to create a set of config files, the load specific files via grunt or gulp, depending on the task or environment variables.
example config:
// configFileLocal.js
angular.module('myapp.config')
.constant('apiUrl', 'https://myapi.com/api')
.constant('debugInfoState', true);
an excerpt from Gruntfile:
copy: {
test: {
src: (function () {
var filename;
if (process.env.REMOTE_TESTS) {
filename = './config-files/configFileTestsRemote.js';
} else {
filename = './config-files/configFileTestsLocal.js';
}
return filename;
})(),
dest: '<%= yeoman.app %>/scripts/root/config.js',
},
dev: {
src: './config-files/configFileLocal.js',
dest: '<%= yeoman.app %>/scripts/root/config.js',
},
...
then of course you need to load it in your app like this:
angular.module('myapp')
.config(function ($compileProvider, debugInfoState) {
$compileProvider.debugInfoEnabled(debugInfoState);
});
so /app/scripts/root/config.js is overwritten every time.
in index.html, the only file that is loaded is /app/scripts/root/config.js
this task should be run before any concatenation tasks.
Answer from wiherek is good but it isn't necessary to use your grun/gulp to manage this. Just transform snippet from Angular documentation to something like this
beforeEach(function() {
module('myApp', function (_$compileProvider_) {
_$compileProvider_.debugInfoEnabled(true);
});
});
Here's how I do it:
$compileProvider.debugInfoEnabled(!!window.__karma__);
To debug an application after disabling debugInfo, you should open your console and run this code directly from your console:
angular.reloadWithDebugInfo();
This will reload your page with all debugging info.

Unit Testing AngularJS directive with templateUrl

Using AngularJS.
Have a directive.
Directive defines templateUrl.
Directive needs unit testing.
Currently unit testing with Jasmine.
This recommends code like:
describe('module: my.module', function () {
beforeEach(module('my.module'));
describe('my-directive directive', function () {
var scope, $compile;
beforeEach(inject(function (_$rootScope_, _$compile_, $injector) {
scope = _$rootScope_;
$compile = _$compile_;
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET('path/to/template.html').passThrough();
}));
describe('test', function () {
var element;
beforeEach(function () {
element = $compile(
'<my-directive></my-directive>')(scope);
angular.element(document.body).append(element);
});
afterEach(function () {
element.remove();
});
it('test', function () {
expect(element.html()).toBe('asdf');
});
});
});
});
Running code in Jasmine.
Getting error:
TypeError: Object #<Object> has no method 'passThrough'
templateUrl needs loading as-is
Cannot use respond
May be related to ngMock use rather than ngMockE2E use.
You're correct that it's related to ngMock. The ngMock module is automatically loaded for every Angular test, and it initializes the mock $httpBackend to handle any use of the $http service, which includes template fetching. The template system tries to load the template through $http and it becomes an "unexpected request" to the mock.
What you need a way to pre-load the templates into the $templateCache so that they're already available when Angular asks for them, without using $http.
The Preferred Solution: Karma
If you're using Karma to run your tests (and you should be), you can configure it to load the templates for you with the ng-html2js preprocessor. Ng-html2js reads the HTML files you specify and converts them into an Angular module that pre-loads the $templateCache.
Step 1: Enable and configure the preprocessor in your karma.conf.js
// karma.conf.js
preprocessors: {
"path/to/templates/**/*.html": ["ng-html2js"]
},
ngHtml2JsPreprocessor: {
// If your build process changes the path to your templates,
// use stripPrefix and prependPrefix to adjust it.
stripPrefix: "source/path/to/templates/.*/",
prependPrefix: "web/path/to/templates/",
// the name of the Angular module to create
moduleName: "my.templates"
},
If you are using Yeoman to scaffold your app this config will work
plugins: [
'karma-phantomjs-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor'
],
preprocessors: {
'app/views/*.html': ['ng-html2js']
},
ngHtml2JsPreprocessor: {
stripPrefix: 'app/',
moduleName: 'my.templates'
},
Step 2: Use the module in your tests
// my-test.js
beforeEach(module("my.templates")); // load new module containing templates
For a complete example, look at this canonical example from Angular test guru Vojta Jina. It includes an entire setup: karma config, templates, and tests.
A Non-Karma Solution
If you do not use Karma for whatever reason (I had an inflexible build process in legacy app) and are just testing in a browser, I have found that you can get around ngMock's takeover of $httpBackend by using a raw XHR to fetch the template for real and insert it into the $templateCache. This solution is much less flexible, but it gets the job done for now.
// my-test.js
// Make template available to unit tests without Karma
//
// Disclaimer: Not using Karma may result in bad karma.
beforeEach(inject(function($templateCache) {
var directiveTemplate = null;
var req = new XMLHttpRequest();
req.onload = function() {
directiveTemplate = this.responseText;
};
// Note that the relative path may be different from your unit test HTML file.
// Using `false` as the third parameter to open() makes the operation synchronous.
// Gentle reminder that boolean parameters are not the best API choice.
req.open("get", "../../partials/directiveTemplate.html", false);
req.send();
$templateCache.put("partials/directiveTemplate.html", directiveTemplate);
}));
Seriously, though. Use Karma. It takes a little work to set up, but it lets you run all your tests, in multiple browsers at once, from the command line. So you can have it as part of your continuous integration system, and/or you can make it a shortcut key from your editor. Much better than alt-tab-refresh-ad-infinitum.
What I ended up doing was getting the template cache and putting the view in there. I don't have control over not using ngMock, it turns out:
beforeEach(inject(function(_$rootScope_, _$compile_, $templateCache) {
$scope = _$rootScope_;
$compile = _$compile_;
$templateCache.put('path/to/template.html', '<div>Here goes the template</div>');
}));
This initial problem can be solved by adding this:
beforeEach(angular.mock.module('ngMockE2E'));
That's because it tries to find $httpBackend in ngMock module by default and it's not full.
The solution I reached needs jasmine-jquery.js and a proxy server.
I followed these steps:
In karma.conf:
add jasmine-jquery.js to your files
files = [
JASMINE,
JASMINE_ADAPTER,
...,
jasmine-jquery-1.3.1,
...
]
add a proxy server that will server your fixtures
proxies = {
'/' : 'http://localhost:3502/'
};
In your spec
describe('MySpec', function() {
var $scope, template;
jasmine.getFixtures().fixturesPath = 'public/partials/'; //custom path so you can serve the real template you use on the app
beforeEach(function() {
template = angular.element('');
module('project');
inject(function($injector, $controller, $rootScope, $compile, $templateCache) {
$templateCache.put('partials/resources-list.html', jasmine.getFixtures().getFixtureHtml_('resources-list.html')); //loadFixture function doesn't return a string
$scope = $rootScope.$new();
$compile(template)($scope);
$scope.$apply();
})
});
});
Run a server on your app's root directory
python -m SimpleHTTPServer 3502
Run karma.
It took my a while to figure this out, having to search many posts, I think the documentation about this should be clearer, as it is such an important issue.
My solution:
test/karma-utils.js:
function httpGetSync(filePath) {
var xhr = new XMLHttpRequest();
xhr.open("GET", "/base/app/" + filePath, false);
xhr.send();
return xhr.responseText;
}
function preloadTemplate(path) {
return inject(function ($templateCache) {
var response = httpGetSync(path);
$templateCache.put(path, response);
});
}
karma.config.js:
files: [
//(...)
'test/karma-utils.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
the test:
'use strict';
describe('Directive: gowiliEvent', function () {
// load the directive's module
beforeEach(module('frontendSrcApp'));
var element,
scope;
beforeEach(preloadTemplate('views/directives/event.html'));
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should exist', inject(function ($compile) {
element = angular.element('<event></-event>');
element = $compile(element)(scope);
scope.$digest();
expect(element.html()).toContain('div');
}));
});
If you are using Grunt, you can use grunt-angular-templates. It loads your templates in the templateCache and it's tranparent to your specs configuration.
My sample config:
module.exports = function(grunt) {
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
ngtemplates: {
myapp: {
options: {
base: 'public/partials',
prepend: 'partials/',
module: 'project'
},
src: 'public/partials/*.html',
dest: 'spec/javascripts/angular/helpers/templates.js'
}
},
watch: {
templates: {
files: ['public/partials/*.html'],
tasks: ['ngtemplates']
}
}
});
grunt.loadNpmTasks('grunt-angular-templates');
grunt.loadNpmTasks('grunt-contrib-watch');
};
I solved the same problem in a slightly different way than the chosen solution.
First, I installed and configured the ng-html2js plugin for
karma. In the karma.conf.js file :
preprocessors: {
'path/to/templates/**/*.html': 'ng-html2js'
},
ngHtml2JsPreprocessor: {
// you might need to strip the main directory prefix in the URL request
stripPrefix: 'path/'
}
Then I loaded the module created in the beforeEach.
In your Spec.js file :
beforeEach(module('myApp', 'to/templates/myTemplate.html'));
Then I used $templateCache.get to store it into a variable.
In your Spec.js file :
var element,
$scope,
template;
beforeEach(inject(function($rootScope, $compile, $templateCache) {
$scope = $rootScope.$new();
element = $compile('<div my-directive></div>')($scope);
template = $templateCache.get('to/templates/myTemplate.html');
$scope.$digest();
}));
Finally, I tested it this way.
In your Spec.js file:
describe('element', function() {
it('should contain the template', function() {
expect(element.html()).toMatch(template);
});
});
To load the template html dynamically into $templateCache you could just use html2js karma pre-processor, as explained here
this boils down to adding templates '.html' to your files in the conf.js file
as well
preprocessors = {
'.html': 'html2js'
};
and use
beforeEach(module('..'));
beforeEach(module('...html', '...html'));
into your js testing file
if you're using Karma, consider using karma-ng-html2js-preprocessor to pre-compile your external HTML templates and avoid having Angular try to HTTP GET them during test execution. I struggled with this for a couple of ours - in my case templateUrl's partial paths resolved during normal app execution but not during tests - due to differences in app vs. test dir structures.
If you are using the jasmine-maven-plugin together with RequireJS you can use the text plugin to load the template content into a variable and then put it in the template cache.
define(['angular', 'text!path/to/template.html', 'angular-route', 'angular-mocks'], function(ng, directiveTemplate) {
"use strict";
describe('Directive TestSuite', function () {
beforeEach(inject(function( $templateCache) {
$templateCache.put("path/to/template.html", directiveTemplate);
}));
});
});
If you use requirejs in your tests then you can use the 'text' plugin to pull in the html template and put it in the $templateCache.
require(["text!template.html", "module-file"], function (templateHtml){
describe("Thing", function () {
var element, scope;
beforeEach(module('module'));
beforeEach(inject(function($templateCache, $rootScope, $compile){
// VOILA!
$templateCache.put('/path/to/the/template.html', templateHtml);
element = angular.element('<my-thing></my-thing>');
scope = $rootScope;
$compile(element)(scope);
scope.$digest();
}));
});
});
I resolve this issue with compiling all templates to templatecache.
I'm using gulp, you can find similar solution for grunt too.
My templateUrls in directives, modals looks like
`templateUrl: '/templates/directives/sidebar/tree.html'`
Add a new npm package in my package.json
"gulp-angular-templatecache": "1.*"
In gulp file add templatecache and a new task:
var templateCache = require('gulp-angular-templatecache');
...
...
gulp.task('compileTemplates', function () {
gulp.src([
'./app/templates/**/*.html'
]).pipe(templateCache('templates.js', {
transformUrl: function (url) {
return '/templates/' + url;
}
}))
.pipe(gulp.dest('wwwroot/assets/js'));
});
Add all js files in index.html
<script src="/assets/js/lib.js"></script>
<script src="/assets/js/app.js"></script>
<script src="/assets/js/templates.js"></script>
Enjoy!

Resources