MVC bundling with minification and require.js - javascript

I am building a MVC application and using angular and require.js in it.
I have defined my JS files as below.
First JS File:
define(function (require) {
'use strict';
var angular = require('angular');
var app = angular.module('App');
app.service('Service1', function () {
});
});
Second JS File:
define(function (require) {
'use strict';
var angular = require('angular');
var app = angular.module('App');
app.service('Service2', function () {
});
});
In the require js configuration , I gave the path of the script bundle and could see JS files are bundled and minified and is loaded. But we are getting an error "Service2 is not defined".
When i merge the two files or remove "define(function (require)" from the individual files then everything is working fine.
Merged File:
define(function (require) {
'use strict';
var angular = require('angular');
var app = angular.module('App');
app.service('Service1', function () {
});
app.service('Service2', function () {
});
});
First JS File:
'use strict';
var angular = require('angular');
var app = angular.module('App');
app.service('Service1', function () {
});
Second JS File:
'use strict';
var angular = require('angular');
var app = angular.module('App');
app.service('Service2', function () {
});
I cannot merge the files as above since i have huge list of JS files.
I am not 100% sure what would be the impact if i remove "define(function (require)".
What would be best approach to use MVC's bundling and minification feature in my scenario?
Thanks.

The function passed to define does not run until it is required by something else. So your service 2 is not defined error is probably because you haven't explicitly required it.
require(['First', 'Second'], function () {
//Use Service1 and Service2
});

Related

CommonJS require not working when the module name is dynamic

I have the following code which is working fine
var angular = require("angular");
module.exports = angular
.module('controllers', [])
.controller('Topbar', require('../controllers/Topbar'));
But when I change it to the following code such that the require is done by a separate 'utility' function called r which automatically appends the folder name, I am getting errors.
var angular = require("angular");
var r = function (a) {
return require('../controllers/'+ a)
}
module.exports = angular
.module('controllers', [])
.controller('Topbar', r('Topbar'));

Using requireJS with AngularJS

I am following a tutorial about how to use AngularJS with RequireJs. I don't seem to understand how this works.
The author creates a file app.js and inside the file adds;
define(function (require) {
'use strict';
var angular = require('angular');
var services = require('./services/services');
var controllers = require('./controllers/controllers');
var directives = require('./directives/directives');
var app = angular.module('App', ['services', 'controllers', 'directives']);
app.init = function () {
angular.bootstrap(document, ['App']);
};
...
})
But does not show how the files services.js, controllers.js, and directives.js are structured.
For example in the services file I assume I would have multiple definitions of services. This is a nice structure because it means I can create multiple services or controllers in a single script. The problem I have is to get the services from; var = services into the App.
Nesting or grouping files with require is quite common - so the services/services.js file (which is being required in your app above) could look like:
define(function (require) {
var services = {};
services.service1 = require('services/service1');
services.service2 = require('services/service2');
services.service3 = require('services/service3');
services.service4 = require('services/service4');
services.service5 = require('services/service5');
return services;
});
and then services.service1 would become available within the app.

Setting global variables in jasmine for angularjs

I have an angular application with some global environment variables defined in an env.js file:
(function(sp) {
'use strict';
pk.env = pk.env || {};
// localhost
pk.env.baseUrl = 'http://localhost:8080/';
})(typeof exports === 'undefined' ? (this.pk = this.pk || {}) : exports);
These variables are used in multiple factories to make REST API calls:
'use strict';
angular.module('pkApp').factory('pkFactory', PKFactory);
function PKFactory($http) {
var urlBase = pk.env.baseUrl;
var apiUrl = 'v1/data';
var _pkFactory = {};
_pkFactory.getData = function() {
return $http.get(urlBase + apiUrl);
};
return _pkFactory;
}
I am writing unit tests for this factory using Jasmine and I keep getting the error:
ReferenceError: Can't find variable: pk
If I remove this variable reference from the factory, the tests run fine.
'use strict';
console.log('=== In pk.factory.spec');
describe('Unit: pkFactory', function() {
beforeEach(module("pkApp"));
var $httpBackend, $rootScope, pkFactory;
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
$httpBackend.when('GET', 'v1/data').respond('Not found');
pkFactory = $injector.get('pkFactory');
}));
afterEach(function() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('expects getData method to be defined', function(){
expect(pkFactory.getData()).toBeDefined();
$httpBackend.flush();
});
})
How do I inject value of 'pk.env.baseUrl' into the factory? I have tried using $window, but it didn't work.
As pretty much already answered here, you can also declare a global variable within your test file
var globalVar = "something";
describe('Your test suit', function() {
...
});
and if you are using Karma you can edit the karma.conf.js file to define it
// list of files / patterns to load in the browser
files: [
...,
'file-containing-the-global-variable.js'
],
You should avoid using the globals in Angular completely.
Convert the file to an angular value or constant:
angular.module('pkApp').value('pk', pk);
now you can change pkFactory to get the pk object injected
function PKFactory($http, pk) {
// pk is no longer from global scope, but injected from angular as an argument
var urlBase = pk.env.baseUrl;
var apiUrl = 'v1/data';
var _pkFactory = {};
_pkFactory.getData = function() {
return $http.get(urlBase + apiUrl);
};
return _pkFactory;
}
and in tests, you can now mock the pk to a different value (or not do anything and use the one from code)

using the mock folder for karma test in yeoman angularjs

I have a angularjs application, which I generated with yeoman. In the karma.conf.js is a reference to test/mock/**/*.js. I have troubles to find out, how I use this folder. Currently I have a simple Service:
'use strict';
angular.module('tvcalApp')
.factory('Series', function ($resource) {
return $resource('/search/:search');
});
and a Test
'use strict';
var $httpBackend;
describe('Service: Series', function () {
// load the service's module
beforeEach(module('tvcalApp'));
// instantiate service
var Series;
beforeEach(inject(function (_Series_) {
Series = _Series_;
}));
beforeEach(inject(function ($injector) {
var url_get = '/search/The%20Simpsons';
var response_get = [{"seriesid": "71663"}];
$httpBackend = $injector.get('$httpBackend');
$httpBackend.whenGET(url_get).respond(response_get);
}));
it('should return a list if search for The Simpsons', function () {
var res = Series.query({search: 'The Simpsons'});
$httpBackend.flush();
expect(res[0].seriesid === 71663);
});
});
This is working. But I wonder If I could use the mock folder from the karma.conf.js for the mocking function. Is it possible to move the mock part into the mock folder and use it for all unit test?
I could not find any example or documentation for this folder. Can someone please point me to to an example or documentation how to use the mock folder.
Basically i have done something like this looking at angular-mocks.js:
Let's say may app is called ql. and i have a loginService that i want to mock:
mocks/servicesMock.js looks like this:
'use strict';
var ql = {};
ql.mock = {};
ql.mock.$loginServiceMockProvider = function() {
this.$get = function() {
var $service = {
login: function() { }
};
return $service;
};
};
angular.module('qlMock', ['ng']).provider({
$loginServiceMock: ql.mock.$loginServiceMockProvider
});
Then in my tests i can injeck $loginServiceMock:
'use strict';
describe('LoginController tests', function () {
// load the controller's module
beforeEach(module('ql'));
// load our mocks module
beforeEach(angular.mock.module('qlMock'));
var loginController,
loginServiceMock,
scope;
// Initialize the controller and a mock scope
// $loginSericeMock will be injected from serviceMocks.js file
beforeEach(inject(function ($controller, $rootScope, $loginServiceMock) {
scope = $rootScope.$new();
loginServiceMock = $loginServiceMock;
loginController = $controller('LoginController', {
$scope: scope,
loginService: loginServiceMock
});
}));
});
The example by #gerasalus is useful, but to answer the question:
mocks is just a folder to put your code in to keep your project organized and the code in tests short and to the point. By keeping all your mocks in one place, it is easier to reuse them in tests... copying them from one test to another would be bad practice from a DRY perspective.
So, for example, you might have a service called 'Foo'
app/service/foo.js
Then you might create a mock of that service, called 'FooMock'
test/mocks/service/foo.js
And then you would create tests and inject whatever mocks you need, as is shown in gerasulus's answer.

Inject dependencies in "run" method of the module in Angularjs

I trying to understand how do I work with Angularjs. It looks like nice framework, but I stuck with a little problem with DI...
How I can inject dependecies in "run" method of the module? I mean I able to do it, but it works only if I have service/factory/value with as same name as "run" parameter name.
I build a simple application do illustrate what I mean:
var CONFIGURATION = "Configuration"; //I would like to have App.Configuration
var LOG_SERVICE = "LogService"; //I would like to have App.Services.LogService
var LOGIN_CONTROLLER = "LoginController";
var App = {};
App.Services = {};
App.Controllers = {};
App = angular.extend(App, angular.module("App", [])
.run(function ($rootScope, $location, Configuration, LogService) {
//How to force LogService to be the logger in params?
//not var = logger = LogService :)
LogService.log("app run");
}));
//App.$inject = [CONFIGURATION, LOG_SERVICE]; /* NOT WORKS */
App.Services.LogService = function (config) {
this.log = function (message) {
config.hasConsole ? console.log(message) : alert(message);
};
};
App.Services.LogService.$inject = [CONFIGURATION];
App.service(LOG_SERVICE, App.Services.LogService);
App.Controllers.LoginController = function (config, logger) {
logger.log("Controller constructed");
}
//The line below, required only because of problem described
App.Controllers.LoginController.$inject = [CONFIGURATION, LOG_SERVICE];
App.factory(CONFIGURATION, function () { return { hasConsole: console && console.log }; });
Why I need it may you ask :) But in my mind, first off all to have meaningful namespaces to organize the code. It will also minimize name collision and in the last, when minifing the JS, the things breaks down, since it renamed to more shorten names.
I think that the reason
App.$inject = [CONFIGURATION, LOG_SERVICE];
doesn't work, is because you have 2 other parameters $rootScope & $location that you need to inject in the $inject. So it needs to be:
App.$inject = ["$rootScope", "$location", CONFIGURATION, LOG_SERVICE];
Another way you can inject your service is to use this version:
app.run(["$rootScope", "$location", CONFIGURATION, LOG_SERVICE,
function ($rootScope, $location, Configuration, LogService) {
}] );

Categories