Using requireJS with AngularJS - javascript

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.

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'));

MVC bundling with minification and require.js

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
});

multiple widget instances with require.js

We built a widget app with backbone and require.js. It works cool with one app instance on page. Now we have a new requirement. We need to run multiple widget instances on same page. Each of app will have its own configurations.
The following test code is not working as we expected:
for(var i=0;i<3;i++){
require([ "app" ], function(app) {
var bootstrap = {};
jQueryPB(function() {
app.testData = i;
app.startup();
});
});
}
I wonder how could I instantiate multiple apps and set different configs to them?
Project structure is similar to:
/main.js
require.config({
....
});
/*code to create multiple instances*/
require(["app"], function(app){
var instance = new app(color:"yellow");
var instance2 = new app(color:"red");
instance.render();
instance2.render();
/*want to create multiple instances here to same page*/
})
/*above code is not working, TypeError: app is not a constructor*/
/app.js
define([ "jQueryPB", "backbone", "underscore", "models/app", "views/app" ], function($jpb,
Backbone, _, appModel, appView) {
var appInfo = new appModel();
var app = new appView({
model : appInfo
});
return app;
});
/models/app.js
/views/app.js
/view/bags.js
/view/bag (it references app by var app = require("app") , so that it can access app.color)
/collection/bags
/model/bag
I use r.js to compile all js into one
node r.js -o build.js optimize=none
After main.js is fully downloaded, it would start to initialize different app instances.
=================================================updated code
cool. I tried it with similar way:
main.js
require(["app"], function(app){
var instance = new app({
testData : 1
});
instance.testData = "1";
instance.startup();
})
app.js
define([ "jQueryPB", "backbone", "underscore", "models/app", "views/app" ], function($jpb,
Backbone, _, appModel, appView) {
return function app(color) {
var appInfo = new appModel();
var app = new appView({
model : appInfo
});
console.log(">>"+color.testData);
app.testData = color.testData;
return app;
};
});
a problem is in bag.js, it needs to access the custom variable in app. I use var app = require("app"); console.log(app.testData); But the output is undefined. Is there a different way to access app instance?
In addition, if require("app"), will it cause a problem if there are multiple app instances?
The problem is, require being an asynchronous function, does not execute immediately, and when it finally does execute, the value of i will have changed. Ultimately, you will end up with all your instances sharing the same value of i, as the loop will have completed before the first require callback gets executed. See here.
You can solve this by creating a closure around each iteration of your loop. This way the original value of i is retained within the scope of the require callback.
for (var i = 0; i < 3; i++) {
(function (i) {
require(["app"], function (app) {
// etc
});
})(i);
}

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