I'm using grunt to concatenate my .js files for my Angular app.
I've just gone through and tidied up the codebase to follow the conventions discussed here, specifically, grouping my code into small modules that represent features.
However, I'm finding that the order of concatenation appears to break the app, if a module is consumed before it is declared.
eg:
|-- src/
| |-- app/
| | |-- userProfile/
| | | | userProfile.js
| | | |-- deposits/
| | | | |-- depositFormCtrl.js
Where:
// userProfile.js
var userProfile = angular.module('userProfile',[])
// depositFormCtrl.js
angular.module('userProfile')
.controller('DepositFormCtrl', function($scope) {...});
When grunt performs the concatenation, depositFormCtrl.js appears before userProfile.js. This causes the app to throw an error, complaining:
Uncaught Error: No module: userProfile
I see lots of talk about the possibilities of using RequireJS / AMD to manage the load order of the modules. However, often it's stated that this is overkill / not required, as Angular handles this for you.
E.g: Brian Ford of the Angular team mentioned:
My personal take is that RequireJS does too much; the only feature that AngularJS's DI system is really missing is the async loading.
He's also stated elsewhere that he doesn't recommend RequireJS with Angular.
I've also seen mentioned made to using angular-loader.js, as shown on the seed project. However, as I understand it, (there's little official documentation) the loader aims to solve the problem of loading modules out of order, rather than them being referenced before used.
Adding angular-loader.js to my project didn't resolve the issue.
Is there a declaration I should be using that prevents the errors I'm having?
What is the correct way to declare modules & controllers, so that the order files are concatenated doesn't impact the code at runtime?
One technique I sometimes use is to put declarations at the start of the concatenated file. I do this by putting them in a dedicated file that will be the first one to be picked up by the concatenation utility.
//app/_declarations.js
angular.module('userProfile',[]);
//app/userProfile/userProfile.js
angular.module('userProfile')
.config(['$routeProvider', function ($router) {...});
//app/userProfile/deposits/depositFormCtrl.js
angular.module('userProfile')
.controller('DepositFormCtrl', function($scope) {...});
May not be a good fit for all scenarios, but is simple to set up and understand.
I ran into the same issue. I split the concatenation step in my GruntFile.js to two tasks, so essentially my app.js, where my Angular application is defined, is 'prepended' to the intermediate concatenated file (_app.js), and the result is saved with the same name of the intermediate file, resulting into a final '_app.js'
app.js
var TestApp = angular.module('TestApp', [...]);
Sub tasks in my concat section of my GruntFile.js
....
// Concatenate all Angular application JS files into _app.js. Note the use
// of wildcards to walk the App folder subfolders to pick up all JS files.
jsCore: {
src: [
'<%= meta.appPath%>/**/*.js',
// Do not include app.js, since it needs to be prepended to the final concat file
'!<%= meta.appPath/app.js',
// Do not include config.js, since it will need to be tokenized by RM
'!<%= meta.appPath%>/config.js'
],
dest: '<%= meta.resPath %>/_app.js'
},
// Prepend app.js to the beginning of _app.js, and save result as _app.js.
// This keeps Angular happy ...
jsApp: {
src: [
'<%= meta.appPath%>/app.js',
'<%= meta.resPath%>/_app.js'
],
dest: '<%= meta.resPath %>/_app.js'
}
...
The resultant _app.js file has the source of app.js at the beginning i.e. the declaration of TestApp.
If you split a module in multiples js files and you don't want to manage manually in which order your build should concatenate files then you will need RequireJS.
Personally, I try to avoid splitting a module in several files for several reasons :
It can be hard to find all controllers/services/etc.. from a single module
If the module is becoming too big, then it probably means you should split in several modules
It's cumbersome enough to have to declare manually a list of modules dependencies and a list of injected dependencies for each module. Add to that a list of js dependency files needed by RequireJS and you will spend most of your time declaring boring stuff instead of resolving business problems
However, if you keep the 1 module / 1 file rule, you'll see your index.html growing very quickly. Personally, I don't consider that as a big issue.
use gulp and then use gulp-angular-sort
return gulp.src('./build/src/app/**/*.js')
.pipe(sort())
.pipe(plug.concat('concat.js'))
.pipe(gulp.dest('./output/'));
Related
My reactjs app consists of a bunch of typescript files with a clean separation of GUI and model. The webworker needs most of model files, so maybe half of all files. I could imagine loading the webworker from exactly the same URL as the app as the model does reference neither the GUI nor React nor other unavailable stuff (At least, it shouldn't, and if so, it'd easy to clean it up).
There seem to be some problems:
finding the correct javascript files
injecting proper start up code into them
and probably others I haven't thought about yet.
The communication to the webworker is not a problem as all I need is a single async call passing and receiving some simple data.
There may be more issues like e.g., https://github.com/microsoft/TypeScript/issues/20595.
Before I learnt what I really need, I tried e.g., ttps://www.npmjs.com/package/#koale/useworker, which is nice, but seems to be able to deal with plain javascript dependencies only.
Finding the correct javascript files
What I can see in index.html is
<script src="/myapp/static/js/bundle.js"></script>
<script src="/myapp/static/js/0.chunk.js"></script>
<script src="/myapp/static/js/main.chunk.js"></script>
<script src="/myapp/main.4e45e2b4b645351b7733.hot-update.js"></script>
I guess, I could live without hot updates, however the names of the other three files change in production to something like "/myapp/static/js/2.28cf00cf.chunk.js".
Injecting proper start up code into them
When the worker loads, it executes some webpack code generated code which most probably crashes it. I'd need to avoid it somehow.
The questions
Is this doable at all?
Does it make sense or is there a better approach?
For a seamless integration of worker code with main-thread code, I recommend using comlink-loader. For example, if you have a main.ts file and a thingy.worker.ts file, you could seamlessly load it as a worker by using TS imports:
// main.ts
import { getThing } from "./thingy.worker.ts"; // make sure the file name ends with .worker.ts
async function test() {
console.log(`the thingy is: ${await getThing()}`);
}
// thingy.worker.ts
export async function getThing() {
return 3;
}
You'll need to add it to your webpack config like this:
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.worker\.(js|ts)$/i,
use: [{
loader: 'comlink-loader',
options: {
singleton: true
}
}]
}
]
}
}
The best part is that your editor's intellisense will work across modules and type safety will not be compromised. More documentation is available here.
You need to compile a separate js file for your WebWorker. There is a React lib called react-webworker that makes this easier for you. It in turn uses WebPack’s worker-plugin.
This is the first time I use grunt, so I'm not sure if such thing can be done.
I have too many js files to write, and I don't wanna add one by one.
Most modules has a js folder, and in that folder (Alphabetically)
name.controller.js
name.module.js
name.service.js
For the concat task, I wrote:
src: ['app/**/*.js'],
dest: 'dist/app.js'
A problem occurs, because the controller is loaded before the module, that when I use: angular.module('name').controller... It does not recognize the module 'name'.
Are there rules I can give the concat task to know in which order to concat the files?
Or, is there a way to tell the controllers to wait until the module is loaded, and then load?
If you want to load the modules before the controllers and services, you can define that like this:
src: [
'app/**/*.module.js',
'app/**/*.controller.js',
'app/**/*.services.js'],
I understand that requirejs and browserify can load my files dependent on its current context, and that it is amazing. I would really prefer to use the #section sections syntax that the razor engine uses. Was just wondering if there is a way to implement this into a typescript / angularjs application.
for example
index.html
#renderSection scripts;
// which could turn into something like
<script data-render="scripts"></scripts>
// the app.run() could declare all the scripts that will be needed on every
// page view
view.html
<script ng-section-repeat="injected in injection"></script>
// the ng-section-repeat is basically taking all the items in the
// typescript constructor and then finding out which ones are needed for
// that view.
I like the idea injecting application file dependencies in the view , without a configuration file and all the added extras that comes with the loaders.
I just want to easily define what files are needed in the actual view and get them loaded, with angular's dependency injection handling the dependency itself.
If you are handling all your dependencies with $inject then , as far as i can tell, dependency is technically already setup in the controllers, all one would need, is to load this as it is called. Which could even eliminate the need for the #section scripts completely
Update:
What i have done to sort of replicate the module loaders is to just use gulp-concat and define the file order in my gulp.config.js and then pass it to the gulp-src before running $.concat .this allows me to have the files in the gulp steam , in dependent order . They are however loaded on the first load. With gulp-uglify the files are tiny ( its now at 566Kb with 16 external libraries loading in 69ms . To put that into perspective it takes 209ms to load one google font ).
I dont know maybe i am not understanding browserify correctly but i honestly struggle to see the need for it, its seems extremely convoluted for something so simple
It is possible using external modules and an injector to do what you asked for:
I just want to easily define what files are needed in the actual view
import {UserFactory} from 'models/userFactory';
import {UserValidator} from 'models/userValidator';
import {Inject} from 'angular2/di';
and get them loaded, with angular's dependency injection handling the dependency itself.
Note: My example uses angular 2.x because I less familiar with angular 1.x and I'm sure you can do something really similar...
class SomeComponent {
userName: string;
userRating: number;
rating: number;
constructor(
#Inject(UserFactory) UserFactory
#Inject(UserValidator) UserValidator
)
{
this.UserFactory = UserFactory;
this.UserValidator = UserValidator;
}
}
Then you can use Browserify to create a bundle.js file that can be executed in a web browser.
I am trying to set up tests for my Angular.js project and I keep getting "$injector:nomod, Module 'result' is not available! You either misspelled..." error. I am sure that I am including "result" module in the "files" array inside "karma.config.js", basically it looks like this:
files: [
'../javascripts/jquery-2.1.4.min.js',
'../jquery-ui/jquery-ui.min.js',
'../D3/d3.js',
'libs/angular.min.js',
'libs/angular-route.min.js',
'libs/angular-animate.min.js',
'libs/selectize.js',
'libs/angular-selectize.js',
'libs/angular-mocks.js',
'simulator.js',
'*.js',
'services/**/*.js',
'qa/tests-*.js'
],
...
I thought initially that the ordering of the main module: 'simulator' (defined inside 'simulator.js' file) is wrong, so I specifically moved it upwards, before
the other modules, like the following stackoverflow thread recommends:
Angular module not available in Karma Jasmine test run
It did not help. Then I tried to make sure that the files are imported in the same order as in my angular apps' main entry file (except for angular-mocks.js and qa/tests-*.js), importing each single file, instead of using wildcards, but no success.
Jasmine definitely goes inside the test files but stumbles upon the line where I am trying to import the module "result":
describe('simulator.chartService', function() {
var chartService;
var graphConfig;
console.log("instantiating module result");
beforeEach(module('result'));
console.log("finished instantiating");
beforeEach(inject(function($injector) {
graphConfig = $injector.get('graphConfig');
chartService = $injector.get('chartService');
}));
it('should be created', function() {
expect(chartService.calcColors(10)).not.toBeNull();
});
});
So, I see that the error happens in-between two console.log() statements.
I suspect that still something can be wrong with the ordering of my files inside the array "files" in "karma.config.js". I have main module "simulator" which is dependent on other modules:
angular.module('simulator', ['ngRoute','ngAnimate','selectize','newexp2','newexp','login','edit','exps', 'result','templates','commons'])
Modules 'newexp2', 'newexp', 'login', 'edit', 'exps', 'result', 'templates' are all dependent on the module 'commons'.
How to correctly import interdependent modules inside the "files" array?
Is it just enough to place "simulator.js", main module, above all others,
or I also need to place all other modules before "commons.js"?
Another my suspicion is that angular.js library version that I downloaded from the official angular website, "angular-mocks.js", can be incompatible with other modules that I am using. I had such an issue with "angular-animate.js" file before.
As long as I surround my test code with $(function(){...}) (and all other my modules ARE surrounded with it) it does not generate the error while importing the result module, so I start seeing two console.log() statements without an error in-between, however, this generates some unknown error which prevents me from invoking the it part at all, whereas when I do not surround it with $(function(){...}), the it test is invoked, but the module result import fails.
So far I am pretty much stuck and do not know where to move and what to try. Any suggestion would be greatly appreciated.
OK, I figured it out. The issue was that ALL of my angular code was enclosed inside $(function(){...}). The solution is to remove all of the $function(){...}), then reorder javascript imports inside the main entry .html file, and then all of the testing starts working good.
The question might be better to mark as duplicate with:
Angular document.ready() issue
I am working on a not-yet-huge angular app. I have got a problem with karma unit testing. To make a long story short, I am using angular with requirej. My tests run well if I keep my template embedded in my directive. But as this app will be huge, it will not scale. But when I move the template and load it in my directive with templateUrl, I can not get rid of one of the following errors (depending on how I modify the conf):
(initial error) "Unexpected request: GET path/to/mytpl.html"
"ReferenceError: Can't find variable: angular". This error occurs with different config:
when I put {pattern: 'path/to/mytpl.html', included: false} in my karma.conf and add a requirejs's define path/to/mytpl.html in my test file (this is the way I would like that to work).
when I put path/to/mytpl.html in my karma.conf then all my template return this error (whether I am using ng-html2-js preprocessor or not).
"Error: [$injector:modulerr] Failed to instantiate module templates due to:
Error: [$injector:nomod] Module 'templates' is not available!". I saw that karma-ng-html2js-preprocessor could be use to create a module that will contains all templates, but it never worked.
NB: I do not use FQN in karma.conf, I use it here to be consistent. I do not think that matters, but I prefer to precise it
I saw someone putting each 'path/to/mytpl.html': {deps: ['angular']} in the shim section of their test-main.js. But it does not work for me and that solution will not scale.
I had the same problem and found solution in the following article.
I guess your problem is in wrong karma or requireJS config, in particular base path setup.
In case your directive templateUrl is relative and that url can't be resolved correctly against karma base path or requireJS base path, then you need to work out this issue.
Let's assume your directive has 'app/directive/myDirective.html' value for templateUrl. Then:
Check if your html file is properly included into files section of karma config, pay attention to included:false setting:
files: [
//...
{ pattern: 'path_to_directive/myDirective.html', included: false },
//...
],
of course you can use a wild card chars like * or **, but for troubleshooting purposes I would start with a full path value. According to the value of basePath in karma config, 'path_to_directive' will or won't be the same as path in your app.
Ex: if karma base path is one level up from you app root, then your 'path_to_directive' = 'some-folder/app/directive/myDirective.html'
Make sure you can get your file from the browser. Go to http://localhost:karma_port/base/path_to_directive/myDirective.html.js
While doing this test pay attention to:
base prefix in the url (karma serves all files from that virtual path and adds this prefix to all urls)
'.js' at the end of your url
Once you get file in step 2, you'll see the actual module code, created by html2js preprocessor. It should look something like this:
angular.module('module_path/myDirective.html', []).run(function($templateCache) {
$templateCache.put('module_path/myDirective.html',
//...
Make sure module_path is 'app/directive/'. If not then you have 2 options:
module_path has some prefix => simply remove it by adding the following to karma config:
ngHtml2JsPreprocessor: { stripPrefix: 'modulePrefix' }
module_path is shorter => add missing part:
ngHtml2JsPreprocessor: { prependPrefix: 'missingPart' }
Restart karma and make sure 'module_path' in step 3 is 'app/directive/'
Make sure you have added angular dependency for your template module (check test-main.js which is basically config file for requireJS). Also keep in mind, that value in shim section will be resolved according to baseUrl value in your test-main.js file. For simplicity, I'll assume that requireJS can get the file from 'requirejs_path/myDirective.html' path.
shim: {
//..
'requirejs_path/myDirective.html': { deps: ['angular'] },
//..
}
Make sure you have included your template as a dependency to your test and also don't forget to load the module:
define(['requirejs_path/myDirective.html', 'angular-mocks', '...'], function () {
describe('test suite', function() {
//...
beforeEach(module('app/directive/myDirective.html'));
//...
});
});
I hope by completing these 6 step you'll get a working test. Good luck.
After having set aside this problem for a while and use this time to improve my knowledge and to widen my app, I found a solution.
Brief methodology recap: as karma-ng-html2js-preprocessor or karma-html2js-preprocessor seems to work on some other projects and as they are referenced in the official doc, I tried many (almost all, I think) possible configuration combinations to run one of them in my test environment. With no success, I came the clue : I have to drop them and find another way.
The solution came from grunt-html2js. By simply adding to grunt configuration the basic set-up and run grunt, it generate a module containing all my templates - maybe, I will split it later in more meaningful modules. But from here, work is almost done. Then I just had to :
tell karma to serve the file has other external tools file: [ {pattern: 'path/to/mytplmodule.js', included: false}, ...]
register the path in my require conf for tests path: [ 'templates' : 'path/to/mytplmodule', ...]
shim it, still in require conf for tests, to be sure it won't be loaded before angular shim: [ 'templates' : 'angular', ...]
load this module in my test file define(['templates',...], function(){...});
retrieve the tested directive's template beforeEach(module('path/to/mytpl.html'));
add a step in your grunt configuration to make it automatically generated before running tests.
That's all !
There is a drawback with this approach, it requires to retrieve or mock all other directives' templates used in the tested directive. It could be boring as number of directives grows. But I prefer that for now as it is not invasive for the application's code. Maybe later, especially when I will include javascript's compilation in my process, I will move it from test to app too.