I'm working on my first Angular project.
Basically, I have two modules which have too much in common, module A, and module B which just have extended features of module A. So for clarity sake, module A is the parent, B the child.
What I'm trying to do :
var modA = angular.module('modA', [...]);
// module A controllers, directives, services and stuff
var modB = angular.module('modB', ['modA']);
// some other directives and services depending on module A
modA.controller('main', function(scope){
// Here it should execute modA controller logic
// #override
var someFunctionInModA = function(){
parent.someFunctionInModA(); // also not sure how this is done
// other stuff
}
});
What I found so far :
angular.extend(this, $controller('modA', {$scope: $scope}));
Similar things exists for services, some uses $injector, I'm missing some clear (and simple) ressources about the subject.
Related
My use case is: we have several helper classes, A and B, that are services, A depends on B, and I wanted to make them providers so that they can be used in .config phase.
I followed this SO answer to load a provider inside a provider.
As you can see here, it works:
http://plnkr.co/edit/SIvujHt7bprFumhxwJqD?p=preview
var coreModule = angular.module('CoreModule', []);
coreModule.provider('Car', function() {
//CarProvider.engine
this.engine = 'big engine';
//Car
this.$get = function() {
return {
color: 'red'
};
};
});
coreModule.provider('ParameterService', ['$injector', function($injector) {
try {
var CarProvider = $injector.get('CarProvider');
this.deepEngine = CarProvider.engine;
console.log('deepEngine = ' + this.deepEngine);
} catch (e) {
console.log("nope!")
}
// ParameterService
this.$get = function() {
return {};
};
}]);
coreModule.config(function(CarProvider) {
console.log('configEngine = ' + CarProvider.engine); // big engine
});
This works if I have Car and ParameterService in one file in this order.
However when I split Car and ParameterService into multiple files on disk, or I define ParameterService before Car in the same file, $injector.get('CarProvider') inside ParameterService fails.
How do I fix the issue?
I want to have one provider/service per file and I don't understand what is missing.
The order in which the services are defined doesn't matter during run phase, where service instances are injected. But it does matter during configuration phase, where service providers are injected, i.e. in provider constructors and config blocks.
Providers and config blocks are executed in the order in which they are defined. If Car provider is defined after ParameterService provider or config block, CarProvider doesn't exist at the moment when those two are executed.
To avoid potential race conditions, one module per file pattern should be followed. This allows to keep the app highly modular (also beneficial for testing) and never care about the order in which the files are loaded. E.g.:
angular.module('app', ['app.carService', 'app.parameterService']).config(...);
angular.module('app.carService', []).provider('Car', ...);
angular.module('app.parameterService', []).provider('ParameterService', ...);
Module parts are executed in the order in which the modules are defined in angular.module array hierarchy, from children to parents.
The decision if config block needs its own module depends on what it does (mostly for testing reasons).
It is possible to have providers in different files. You just need to attach them to the first module that you created.
If your markup looks like this:
<script src="coreModule.js"></script>
<script src="parameterService.js"></script>
Then, in coreModule.js, define your module:
angular.module('CoreModule', [])
.provider('Car', function() {
...
}
Remember, the second parameter ([]) tells angular to create a new module.
Then, declare your other provider in a different file, and attach it to your existing 'CoreModule' module:
angular.module('CoreModule')
.provider('ParameterService', ['$injector', function($injector) {
...
}
Notice that we are only passing one parameter to .module(). This tells angular to add your provider to an existing module.
Plunkr Demo
I'm having some trouble mocking a factory belonging to one of my modules. The factory I would like to mock has 2 dependencies:
Factory class:
angular.module('serviceapp')
.factory('claims.service', ['applicationSettings', 'localStorageService', function (applicationSettings, localStorageService) {
//Factory code here
}]);
Test class:
//Instantiate some values
var mockAppSettings = {};
var mockStorageService = {};
var $factory; //Will hold my factory
//Targeting my module for mocking
beforeEach(angular.mock.module('serviceapp'));
//Providing some values for the dependencies of my module
beforeEach(module('serviceapp', function ($provide) {
$provide.value('applicationSettings', mockAppSettings);
$provide.value('localStorageService', mockStorageService);
}));
//Problems start here
beforeEach(inject(function ($injector) {
$factory = $injector.get('claims.service');
}));
I get an error message
Failed to instantiate module serviceapp due to:
Failed to instantiate module accountModule due to:
Module 'accountModule' is not available!
When investigating I see that accountModule is listed as a dependency for the serviceApp module.
App.module class:
angular.module('serviceapp', [accountModule])
However I'm having some trouble mocking this module to pass to serviceapp. I have tried to mock the accountModule in the same way I have mocked the serviceapp in the beginning however this is still bring up the same error message. How can I mock and pass one module to another?
angular.mock.module('serviceapp') shouldn't be read literally. It doesn't mock a module. It is the same thing as module('serviceapp') and is used in modular environments where module is reserved.
So, all that
beforeEach(angular.mock.module('serviceapp'));
beforeEach(module('serviceapp', ...));
does is loading serviceapp twice (doesn't hurt but doesn't help either).
To avoid Module 'accountModule' is not available!, it should be (re)defined:
beforeAll(() => {
angular.module('accountModule', [])
});
The problem with this approach is that even if it was defined, it will be overridden to the end of test run. If real accountModule needs to be used in other tests, this won't be possible.
The appropriate solution for similar design issues (this also applies to dependencies that aren't desirable in tests, e.g. router modules) is
angular.module('serviceapp', ['accountModule']);
angular.module('serviceapp.internal', [])
.factory('claims.service',...);
Here serviceapp serves as a shallow wrapper for serviceapp.internal, while the latter can be safely tested. If serviceapp is top-level module that is used for bootstrapping, this indicates that the application wasn't modularized enough, this hurts testing.
I'm doing some studies using the Pixijs library, which I find amazing. I'll also have a look into Fabricjs, that seems to have a smaller footprint.
I've been working with Angularjs for some time now and I like conventions, instead of taking time in each project doing configuration and organizing code differently every time.
I would like to hear from some body who experienced Pixijs (or similar) with a framework to organise the code.
I understand that Angularjs is MVVM, but let me know about any tips or suggestion that you may think of?
I did some research this far and a few things came to my mind, such as Browserify (I do believe in convention instead of configuration like I've mentioned though and maybe this wouldn't be the best tool for me).
Kinda old question, but this is something I was looking for myself when starting out with PIXI, so I hope it could be of help to someone to get started.
I use the Revealing module pattern and separate the application into separate files/modules, and then use Browserify to create the application bundle. The HTML loads the app.js bundle which stems from the app.js source below.
index.html: Load your libs (PIXI et al) in <head> and then your app.js in the <body>.
app.js source example:
(function() {
// App.js is the "bootstrap" that loads dependencies, takes care of pre-loading etc.
// I have a template of this which I copy into any new project and use as a checklist.
var core = require("./core.js"); // Use a dummy module as application-wide namespace for easy access
// Any external modules (f eg node modules) could go here
core.utilityLib = require("node-lib");
// Application modules here
core.myModule = require("./myModule.js");
// core.myModule2 = require("./myModule2.js"); // .. you get the idea
// Our main application module
core.main = require("./main.js");
// Init function to run when DOM/scripts have loaded
var init = function() {
// I have a generic screen module which sets up PIXI renderer depending on device compatibility using Modernizr (or weapon of choice). To keep it simple for the sake of the example, lets just create our renderer directly:
core.renderer = PIXI.autoDetectRenderer(screen.innerWidth,screen.innerHeight,{resolution:window.devicePixelRatio});
// I also use a generic loader module that wraps PIXI.loader, taking a list of assets from a config file. Let's just call PIXI.loader directly for now:
PIXI.loader
.add({name:"myasset",url:"/myasset.png"})
.on('progress', loadProgressFunction)
.once('complete',loadCompleteFunction)
})
.load();
}
window.onload = init; // Tell browser to call init function when loaded
// Optional loading progress bar
var function = loadProgressCallback(e) {
}
// Call when mandatory assets has been loaded
var loadCompleteFunction = function() {
myModule.init(); // Init any mandatory modules, f eg to instantiate a player
main.init(); // Tell our main application/game module that we're ready to do fancy stuff
}
// Method to make things move
var animate = function() {
// Send juice to modules that needs to be juiced (or use a ticker module on per-module basis).
// core.main.animate();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate); // See comment below
}());
Comment: PIXI has an built-in requestAnimationFrame alias that takes care of fallback. If not using PIXI, you could use Paul Irish' gist.
core.js:
module.exports = {}; // Just a dummy object to create a module scope that all the modules
// can use to communicate with each other, without running into circular reference problems
main.js:
// Main application/game module
module.exports = (function() {
// Dependencies
var core = require("./core.js"); // This way we can easily access all the necessary modules
// Exports
var exports = {}; // Everything put into this object will be "public"
// Vars
var stuff = 1; // Module vars
exports.init = function() {
// Application magic starts here :)
}
// Some other public method ...
exports.publicMethod = function() {
}
// Some private method
var privateMethod = function() {
}
return exports; // Expose public functions to other modules
}());
Any additional modules can be organized in pretty much the same way as main.js.
Run browserify dev/app.js > html_root/app.js each time you want to "compile" your bundle (or create a Makefile, gulp-, node-, or webpack-script - whichever you prefer).
Today I found, that $injector injected to config or provider is different from $injector injected to service, factory or controller.
And get() function from this $injectors works differently.
$injector from config or provider, can't get() any service! $injector.get('myService') throws Error: [$injector:unpr] Unknown provider: myService, but $injector.has('myService') return true. That's very very strange.
$injector from service or controller works normally.
Here is a code sample for better understanding:
angular.module('app', [])
.provider('myProvider', function ($injector) {
this.$get = ['$injector', function (serviceInjector) {
return {
providerInjector: $injector,
serviceInjector: serviceInjector
};
}];
})
.service('myService', function () {})
.controller('myCtrl', function ($scope, myProvider) {
var providerInjector = myProvider.providerInjector;
var serviceInjector = myProvider.serviceInjector;
console.log(providerInjector === serviceInjector); // -> false
console.log(serviceInjector.has('myService')); // `serviceInjector` has `myService`
console.log(getMyService(serviceInjector)); // `serviceInjector` can get `myService`
console.log(providerInjector.has('myService')); // `providerInjector` has `myService` too!
console.log(getMyService(providerInjector)); // but `providerInjector` can't get `myService`! =(
function getMyService(injector) {
try {
injector.get('myService');
return "OK";
} catch (e) {
return e.toString();
}
}
});
Here is a plunker to play
Can anybody explain why there is two different injectors?
And how can I use $injector from provider/config to inject service(after service was initialized, of course)?
P.S. I use angular 1.3.13
I found this issue on github: https://github.com/angular/angular.js/issues/5559
In the config function, $injector is the provider injector, where in the run function, $injector is the instance injector.
One's the $injector at the config stage (only providers and constants accessible), and one's the $injector at the run stage. The confusion may be that you're thinking the $injector modifies itself to include the new stuff as it crosses the line from config to run, but that's not true. They're two separate (although related) objects, with their own caches of instances.
A more in-depth reason for this dichotomy will probably come from a deep learning of the $injector internals, but it seems like it's been DRY-ed pretty hardcore, and the two types of injectors share almost all the same behavior, except in how they deal with "cache misses" in their instance caches.
We are going to overhaul the injector in v2, so this will get fixed there (getting rid of the config phase is one of the objectives of the injector v2).
Seems like there is really two different injectors, and angular developers will not fix that behavior(in versions <2.0). And nobody added a note about that aspect to $injector docs for some reason.
I was unable to find a way how to really get instance injector inside a configuration block without hacky tricks. So, I write a cute provider to solve that kind of problems.
.provider('instanceInjector', function () {
var instanceInjector;
function get() {
return instanceInjector;
}
function exists() {
return !!instanceInjector;
}
angular.extend(this, {
get: get,
exists: exists
});
this.$get = function ($injector) {
instanceInjector = $injector;
return {
get: get,
exists: exists
};
}
})
// We need to inject service somewhere.
// Otherwise $get function will be never executed
.run(['instanceInjector', function(instanceInjector){}])
Ok. After reading your comments, here is my answer.
I edited code in plunk to make it work, when invoking the providerInjector.get() the code should be as follows:
$scope.getMyServiceFromProviderInjector = function () {
try {
myProvider.providerInjector.get('myServiceProvider');//here is change in provider name
return "OK";
} catch (e) {
return e.toString();
}
};
According to angular docs the following is quoted for config and run blocks:
Configuration blocks - get executed during the provider registrations and configuration phase. Only providers and constants
can be injected into configuration blocks. This is to prevent
accidental instantiation of services before they have been fully
configured.
Run blocks - get executed after the injector is created and are used to kickstart the application. Only instances and constants can be
injected into run blocks. This is to prevent further system
configuration during application run time.
This simply means, you cannot get instances of services inside config blocks.
I wrote this a while back, which explains the lifecycle of both injectors of AngularJS, i.e providerInjector and instanceInjector.
http://agiliq.com/blog/2017/04/angularjs-injectors-internals/
I am using angular-translate on a rather large Angular project. I am breaking the project into multiple modules to make it more manageable, but I am unable to break up my translation strings per module.
For example, I have modules A and B, where B is a submodule of A. There are strings that pertain to the HTML covered by module A, which are placed in '/json/localization/A/en.json'. Likewise, there are strings pertaining to B that I place in '/json/localization/B/en.json'. First I load B's en.json in module B using angular-translate's $translationProvider. Then I load module A's en.json, also using $translationProvider. The problem is that loading A's strings overrides B's strings and they are lost.
Using angular-translate, is there a way to load strings per module, without overriding, or does the parent module have to load all of the strings from a single en.json?
Here is an example (in coffeescript) of how I am loading the translation strings:
my_module.config(['$translateProvider', ($translateProvider) ->
$translateProvider.useStaticFilesLoader
prefix: '/json/localization/A/'
suffix: '.json'
$translateProvider.preferredLanguage 'en'
])
angular-translate supports async loading of partial language files. All partials are merged into one dictionary per language.
The official documentation can be found here: http://angular-translate.github.io/docs/#/guide/12_asynchronous-loading
It supports applying a template for url templates that point to the modularised language files:
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: '/i18n/{part}/{lang}.json'
});
From within your controllers, you can add language modules and refresh the data bindings like this:
angular.module('contact')
.controller('ContactCtrl',
function ($scope, $translatePartialLoader, $translate) {
$translatePartialLoader.addPart('contact');
$translate.refresh();
});
Of course, loading the partials can also be covered in a route's resolve phase
Alternatively, you can also look into building your own custom loader function. http://angular-translate.github.io/docs/#/guide/13_custom-loaders
This provides all the flexibility you need to combine required language modules in one shot. E.g. you could do something like this:
app.factory('customLoader', function ($http, $q) {
// return loaderFn
return function (options) {
var deferred = $q.defer();
var data = {
'TEXT': 'Fooooo'
};
$http.get('nls/moduleA/en.json').success(function(moduleA){
angular.extend(data, moduleA);
$http.get('nls/moduleB/en.json').success(function(moduleB){
angular.extend(data, moduleB);
deferred.resolve(data);
});
});
return deferred.promise;
};
});