Today, I have an config for the translateProvider looking like this:
App.config(['$translateProvider', function ($translateProvider) {
$translateProvider.preferredLanguage('en-US');
$translateProvider.useLoader('TranslationLoader', { versionIdentifier : 127} );
$translateProvider.useMissingTranslationHandler('MissingTranslationHandler');
}]);
The problem is that I don't know the value of the formIdentifier-option at configuration time. I get this value after resolving the first state in ui-route. I've tried to set the translationProvides loader in the state's controller, but realized that that's not possible :)
Any ideas?
angular-translate allows you to use any service as a loader as long as it meets a desired interface. But it doesn't restrict you in ways of how you pass additional parameters to the loader. So, you may pass them just like you want.
For example, you can set additional parameters directly to the loader. Just implement setters for them on top of your loader:
module.factory('Loader', [
'$q',
function($q) {
var myParam;
var loader = function(options) {
var allParams = angular.extend({}, { myParam: myParam }, options);
var deferred = $q.defer();
// load stuff
return deferred.promise;
};
loader.setMyParam = function(param) {
myParam = param;
};
return loader;
}])
Also, you may try to set these parameters with some helper service (either sync or async:
module.factory('SyncLoader', [
'$q', '$injector',
function($q, $injector) {
var loader = function(options) {
var helper = $injector.get(options.helper);
var myParam = helper.getMyParam();
var deferred = $q.defer();
// load stuff
return deferred.promise;
};
return loader;
}]);
or
module.factory('AsyncLoader', [
'$q', '$injector',
function($q, $injector) {
var loader = function(options) {
var helper = $injector.get(options.helper);
var deferred = $q.defer();
helper.getMyParam()
.then(function success(myParam) {
// load stuff
}, function error() {
// fail, probably
});
return deferred.promise;
};
return loader;
}]);
Also, it might be possible to use events somehow. Or, maybe, there are some other ways possible. It depends on a specific architecture.
Related
I have a directive that uses a service function like so:
angular.module('testModule',
['serviceBeingUsed'])
.directive('testDirective', function(serviceBeingUsed) {
return {
restrict: 'AE',
templateUrl: 'testTemplate.tpl.html',
scope: {
boundVar1: "="
},
link: function(scope) {
scope.getRequiredData = function(data){
//gether data using service
serviceBeingUsed.fetchRequiredData(data).then(
function(result){
scope.requiredData = result;
}
);
};
}
};
});
In the above directive I inject the service I wish to use and this service function gets used within the scope function "getRequiredData()" which is inside the "link" of this directive.
I have my test suite set up like so:
describe('test suite', function () {
var scope,
$rootScope,
$compile,
$q,
element,
isoScope,
serviceBeingUsed;
beforeEach(module('testModule'));
beforeEach( inject( function(_$rootScope_,
_$q_,
_$compile_,
_serviceBeingUsed_) {
$rootScope = _$rootScope_;
$compile = _$compile_;
serviceBeingUsed = _serviceBeingUsed_;
$q = _$q_;
//This is where we create the directive and it's options.
element = angular.element('<test-directive bound-var1="blabla"></test-directive>');
//We create a new scope from the rootScope.
scope = $rootScope.$new();
//Now we compile the HTML with the rootscope
$compile(element)(scope);
//digest the changes
scope.$digest();
//We retrieve the isolated scope scope of the directive
isoScope = element.isolateScope();
}));
Now I have a test which runs and passes wherby I can spyOn the isolated scope function "getRequiredData()", this test looks like so:
it('getRequiredData runs', inject(function () {
spyOn(isoScope,"getRequiredData");
isoScope.getRequiredData();
expect(isoScope.getRequiredData).toHaveBeenCalled();
}));
This proves that the link functions CAN be tested however when trying to test if the service function is called the test fails and I have no idea why, the test for the service looks like this:
it('serviceFunction runs', inject(function () {
spyOn(serviceBeingUsed, "serviceFunction").and.callFake(function() {
var deferred = $q.defer();
var data = "returnedDataDummy";
deferred.resolve(data);
return deferred.promise;
});
isoScope.getRequiredData();
expect(serviceBeingUsed.serviceFunction).toHaveBeenCalled();
}));
How can I successfully test if the service function has been called here?
In writing this example I have solved my issue. In my actual code, inside the test "serviceFunction runs" I had also included a spyOn(isoScope,"getRequiredData)"
This has the effect of blocking the inner functionality of the function
getRequiredData()
which meant the the service function inside getRequiredData could never run.
To resolve this issue I needed to edit the spy for the outer function
from:
spyOn(isoScope,"getRequiredData");
to:
spyOn(isoScope,"getRequiredData").and.callThrough();
this simple change means that the function being spied on will also run its inner code and not just register that it has been called.
However one important lesson that I have learned is to not do too much inside each test and to separate the tests as much as possible.
So just to clarify, my original test which failed looked like this:
it('getRequiredData runs', inject(function () {
spyOn(serviceBeingUsed, "serviceFunction").and.callFake(function() {
var deferred = $q.defer();
var data = "returnedDataDummy";
deferred.resolve(data);
return deferred.promise;
});
spyOn(isoScope,"getRequiredData");
isoScope.getRequiredData();
expect(serviceBeingUsed.fetchRequiredData).toHaveBeenCalled();
expect(isoScope.getRequiredData).toHaveBeenCalled();
}));
the fix for this test which passes:
it('getRequiredData runs', inject(function () {
spyOn(serviceBeingUsed, "serviceFunction").and.callFake(function() {
var deferred = $q.defer();
var data = "returnedDataDummy";
deferred.resolve(data);
return deferred.promise;
});
spyOn(isoScope,"getRequiredData").and.callThrough();
isoScope.getRequiredData();
expect(serviceBeingUsed.fetchRequiredData).toHaveBeenCalled();
expect(isoScope.getRequiredData).toHaveBeenCalled();
}));
I am trying to use AngularJS with RequireJS currently, but I do not know how to make the test work with injection.
Without RequireJS we could,
Impl
PhoneListCtrl.$inject = ['$scope', '$http'];
var PhoneListCtrl = ['$scope', '$http', function($scope, $http) {
/* constructor body */
}];
Test
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller(PhoneListCtrl, {$scope: scope});
}));
However, when we use RequireJS we may define the controller as following,
demoController.js
define(["dependency"], function() {
/* constructor body */
});
When using this controller, we add it as one of the dependencies and do not have a variable declaration.(Let me just use "Controller" as an example since we'd better call it "Service")
someJS.js
define(["demoController"], function(controller) {
controller.method();
});
My Question
How can we inject the $http, $scope(or something else) to the target controller or service for testing when using RequireJS(AMD)?
Any help would be highly appreciated.
I've done something similar:
/*global define, document */
define(['angular', 'jquery'], function (angular, $) {
'use strict';
return function () {
var $injector = angular.bootstrap(document, ['myApp']);
var $controller = $injector.get('$controller');
var myController = $controller('myController');
};
});
The idea is that angular.bootstrap returns an injector, which allows you to fetch a service.
I finally made it work by following.
angular.module('app').controller('MyController', ['$scope', 'dep2', function ($scope, dep2) {
$scope.method = function () {//do something};
}]);
We can use this controller in test cases like this:
inject(function($controller, $rootScope, dep2) {
scope = $rootScope.$new();
myController = $controller("MyController",
{
$scope : scope,
dep2: dep2
});
);
In my ExtJS 4.0.7 app I have some 3rd party javascripts that I need to dynamically load to render certain panel contents (some fancy charting/visualization widgets).
I run in to the age-old problem that the script doesn't finish loading before I try to use it. I thought ExtJS might have an elegant solution for this (much like the class loader: Ext.Loader).
I've looked at both Ext.Loader and Ext.ComponentLoader, but neither seem to provide what I'm looking for. Do I have to just "roll my own" and setup a timer to wait for a marker variable to exist?
Here's an example of how it's done in ExtJS 4.1.x:
Ext.Loader.loadScript({
url: '...', // URL of script
scope: this, // scope of callbacks
onLoad: function() { // callback fn when script is loaded
// ...
},
onError: function() { // callback fn if load fails
// ...
}
});
I've looked at both Ext.Loader and Ext.ComponentLoader, but neither
seem to provide what I'm looking for
Really looks like it's true. The only thing that can help you here, I think, is Loader's injectScriptElement method (which, however, is private):
var onError = function() {
// run this code on error
};
var onLoad = function() {
// run this code when script is loaded
};
Ext.Loader.injectScriptElement('/path/to/file.js', onLoad, onError);
Seems like this method would do what you want (here is example). But the only problem is that , ... you know, the method is marked as private.
This is exactly what newest Ext.Loader.loadScript from Ext.4-1 can be used for.
See http://docs.sencha.com/ext-js/4-1/#!/api/Ext.Loader-method-loadScript
For all you googlers out there, I ended up rolling my own by borrowing some Ext code:
var injectScriptElement = function(id, url, onLoad, onError, scope) {
var script = document.createElement('script'),
documentHead = typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
cleanupScriptElement = function(script) {
script.id = id;
script.onload = null;
script.onreadystatechange = null;
script.onerror = null;
return this;
},
onLoadFn = function() {
cleanupScriptElement(script);
onLoad.call(scope);
},
onErrorFn = function() {
cleanupScriptElement(script);
onError.call(scope);
};
// if the script is already loaded, don't load it again
if (document.getElementById(id) !== null) {
onLoadFn();
return;
}
script.type = 'text/javascript';
script.src = url;
script.onload = onLoadFn;
script.onerror = onErrorFn;
script.onreadystatechange = function() {
if (this.readyState === 'loaded' || this.readyState === 'complete') {
onLoadFn();
}
};
documentHead.appendChild(script);
return script;
}
var error = function() {
console.log('error occurred');
}
var init = function() {
console.log('should not get run till the script is fully loaded');
}
injectScriptElement('myScriptElem', 'http://www.example.com/script.js', init, error, this);
From looking at the source it seems to me that you could do it in a bit of a hackish way. Try using Ext.Loader.setPath() to map a bogus namespace to your third party javascript files, and then use Ext.Loader.require() to try to load them. It doesn't look like ExtJS actually checks if required class is defined in the file included.
I'm trying to figure out how ExtJS4 passes around config objects.
I want to do the equivalent of...
store = function(config){
if ( typeof config.call !== 'unndefined' ){
config.url = "server.php?c=" + config.call || config.url;
};
Sketch.Data.AutoSaveStore.superclass.constructor.call(this,config);
};
Ext.extend(store, Ext.data.Store{})
I am probably missing something obvious here, but having dug around in the sandbox file, the closest I have come is....
Ext.define('My.awesome.Class', {
// what i would like to pass.
config:{},
constructor: function(config) {
this.initConfig(config);
return this;
}
});
which doesn't seem to work if you do something like...
var awesome = Ext.create('My.awesome.Class',{
name="Super awesome"
});
alert(awesome.getName()); // 'awesome.getName is not a function'
However
Ext.define('My.awesome.Class', {
// The default config
config: {
name: 'Awesome',
isAwesome: true
},
constructor: function(config) {
this.initConfig(config);
return this;
}
});
var awesome = Ext.create('My.awesome.Class',{
name="Super awesome"
});
alert(awesome.getName()); // 'Super Awesome'
This is biting me in the rear end when trying to do complex store extensions.
Anyone have any idea how I pass a bunch of random params to the prototype?
You should not be using new operator to create new instance on your class. In ExtJS4, you should use Ext.create() method.
Try doing:
var awesome = Ext.create('My.awesome.Class');
alert(awesome.getName());
And if you want to pass some param when creating an instance, you can do the following
var awesome = Ext.create('My.awesome.Class',{name:'New Awesome'});
I want to migrate the javascript in my site from YU2 to YUI3, but I am only a poor amateur programer and I am stuck at the first pitfall.
I have the following code:
MyApp.Core = function() {
return {
init: function(e, MyAppConfig) {
if (MyAppConfig.tabpanels) {
MyApp.Core.prepareTabpanels(MyAppConfig.tabpanels);
}
},
prepareTabpanels: function(tabpanels) {
// Code here
}
}
}();
var MyAppConfig = {
"tabpanels":{"ids":["navigation"]}
};
YAHOO.util.Event.addListener(window, "load", MyApp.Core.init, MyAppConfig);
How can I pass the MyAppConfig object to the MyApp.Core.init function by using YUI3 "domready" event listener?
Thanks in advance!
You should be able to do something like:
var MyApp = {};
MyApp.Core = function(){ return {
init: function(MyAppConfig) {
console.log(MyAppConfig);
},
prepareTabpanels: function(tabpanels) {
// Code here
}
}
}();
var MyAppConfig = {
"tabpanels":{"ids":["navigation"]}
};
YUI().use('node', 'event', function(Y){
Y.on('domready', MyApp.Core.init, this, MyAppConfig);
});
Note that the event is not passed in as the first parameter, it is the config.
Y.on accepts parameters as <event_type>, <callback_function>, <context>, <params>..
any parameter after the third item is passed through to the callback function so MyAppConfig becomes the first parameter in your init.
EDIT
See the YUI3 API documentation here: http://developer.yahoo.com/yui/3/api/YUI.html#method_on