I just want to ask for some help with converting the following code from jQuery to jqLite (angular jQuery):
$(window).on("load", function() {
setTimeout(function(){
#some funcs
}, 100)
});
Thanks in advance.
Use this:
angular.element(document).ready(function () {
// your code here
});
The answer to that question depends on the context and use case and how it relates to the AngularJS framework and the phases of the app.
To start something in the AngularJS run phase:
app.run(function($timeout) {
$timeout(function() {
//Startup code
},100);
});
To start something in an AngularJS service:
app.service("something", function($timeout) {
$timeout(function() {
//Startup code
},100);
});
Of course the $timeout may not be necessary.
Or to start third-party code before bootstrapping AngularJS:
angular.element(function() {
//Third-party startup code
angular.bootstrap(document,['myApp']);
});
The choice really depends on the context and how the third-party code interacts with the AngularJS framework.
Related
I'm trying to migrate to ui-router 1.0.5 and have done most of the work but there are no examples of how to test new transition hooks that replaced $stateChangeXXX events listeners.
Code before:
scope.$on('$stateChangeSuccess', this.hideSpinner_.bind(this));
After:
this.transitions_ is a $transitions service exposed by ui-router
this.transitions_.onSuccess({}, this.hideSpinner_.bind(this));
Before I was able to test it by using scope.$broadcast($stateChangeSuccess) and then scope.$apply(). This worked with ui-router 0.x:
expect(ctrl.loading).toBe(true);
expect(ctrl.showLoadingSpinner).toBe(true);
// when
scope.$broadcast('$stateChangeSuccess');
scope.$apply();
// then
expect(ctrl.loading).toBe(false);
expect(ctrl.showLoadingSpinner).toBe(false);
Any idea how to rewrite tests to work with new version of ui-router?
Well,
I faced exactly the same problem migrating from ui-router 0.3.x to 1.0.5
App
Before :
scope.$on('$stateChangeSuccess', someFunction);
After :
$transitions.onSuccess( {}, someFunction);
Tests
Before :
scope.$broadcast('$stateChangeSuccess');
scope.$apply();
After :
I call directly the callback function with a mocked transition because I just want to test what the callback does (and here it only needs trans.$to().name and trans.$from().name) :
var mockTransition = {
$to: function() { return {name: 'foo'}; },
$from: function() { return {name: 'bar'}; }
};
service.someFunction(mockTransition);
$scope.$digest();
And in some places in my tests, I want to simulate the whole transition process so I do a real one, that way the events are properly called :
it('should handle transitions error properly when trying to make transition to an abstract state', function (done) {
spyOn(console, 'error');
spyOn(transitions, 'onError');
transitions.onError({}, function(transition) {});
$stateTest.transitionTo("c3.app.offre").then(function() {
}, function() {
expect(console.error).toHaveBeenCalled();
expect(transitions.onError).toHaveBeenCalled();
done();
});
scope.$apply();
});
I've got the same question. And I chose a stupid solution that is broadcasting the 'stateChangeStart' event in a onStart hook:
$transitions.onStart({}, function($transition$){
$scope.$broadcast('$myStateChangeStart', $transition$.to(), $transition$.params('to'), $transition$.from(), $transition$.params('from'), $transition$.options(), $transition$)
})
then I do not need to change so much of the legacy code, just handling some 'concurrency' logic.
Hope it makes sense. If you've got a good practice, pls tell me.
Really have no idea why this doesn't work. I must be doing something incredibly stupid.
Here is a controller:
angular.module('nightlifeApp')
.controller('TestCtrl', function($scope) {
$scope.testvar = 'before';
setTimeout(function() {
$scope.testvar = 'after';
}, 2000);
});
and here is the view that has this as the controller:
h1(ng-bind='testvar')
h1 {{testvar}}
But neither h1 element ever changes! Any thoughts?
If you're using setTimeout then you manually need to trigger apply. Like
$apply() is used to execute an expression in angular from outside of
the angular framework. (For example from browser DOM events,
setTimeout, XHR or third party libraries). Because we are calling into
the angular framework we need to perform proper scope life cycle of
exception handling, executing watches.
setTimeout(function() {
$scope.$apply(function() {
$scope.testvar = 'after';
});
}, 2000);
In my opinion you should use $timeout service. So it will trigger $apply() automatically. Your code will look like
$timeout(function () {
$scope.testvar = 'after';
}, 2000);
Make sure you've injected $timeout service in your controller. In your HTML you don't need to use ng-bind. You're doing same in controller. Only
<h1> {{testvar}} </h1>
While it is fairly easy to unit test services/controllers in angular it seems very tricky to test decorators.
Here is a basic scenario and an approach I am trying but failing to get any results:
I defined a separate module (used in the main app), that is decorating $log service.
(function() {
'use strict';
angular
.module('SpecialLogger', []);
angular
.module('SpecialLogger')
.config(configureLogger);
configureLogger.$inject = ['$provide'];
function configureLogger($provide) {
$provide.decorator('$log', logDecorator);
logDecorator.$inject = ['$delegate'];
function logDecorator($delegate) {
var errorFn = $delegate.error;
$delegate.error = function(e) {
/*global UglyGlobalFunction: true*/
UglyGlobalFunction.notify(e);
errorFn.apply(null, arguments);
};
return $delegate;
}
}
}());
Now comes a testing time and I am having a really hard time getting it working. Here is what I have come up with so far:
(function() {
describe('SpecialLogger module', function() {
var loggerModule,
mockLog;
beforeEach(function() {
UglyGlobalFunction = jasmine.createSpyObj('UglyGlobalFunctionMock', ['notify']);
mockLog = jasmine.createSpyObj('mockLog', ['error']);
});
beforeEach(function() {
loggerModule = angular.module('SpecialLogger');
module(function($provide){
$provide.value('$log', mockLog);
});
});
it('should initialize the logger module', function() {
expect(loggerModule).toBeDefined();
});
it('should monkey patch native logger with additional UglyGlobalFunction call', function() {
mockLog.error('test error');
expect(mockLog.error).toHaveBeenCalledWith('test error');
expect(UglyGlobalFunction.notify).toHaveBeenCalledWith('test error');
});
});
}());
After debugging for a while I have noticed that SpecialLogger config section is not even fired.. Any suggestions on how to properly test this kind of scenario?
You're missing the module('SpecialLogger'); call in your beforeEach function.
You shouldn't need this part: loggerModule = angular.module('JGM.Logger');
Just include the module and inject the $log. Then check if your decorator function exists and behaves as expected.
After some digging I came up with a solution. I had to create and inject my own mocked $log instance and only then I was able to check weather or not calling error function also triggers call to the global function I was decorating $log with.
The details can be found on a blog post I wrote to explain this problem in detail. Plus I open sourced an anuglar module that makes use of this functionality available here
I have a project using AngularAMD/RequireJS/Karma/Jasmine, that I have the basic configuration all working, most unit tests run and pass successfully.
I cannot get a mocked service injected correctly using either angular.mock.module or angularAMD.value().
I have:
// service definition in services/MyService.js
define(['app'],
function(app) {
app.factory('myService', [ '$document', function($document) {
function add(html) {
$document.find('body').append(html);
}
return { add: add };
}]);
}
);
// test
define(['angularAMD', 'angular-mocks', 'app', 'services/MyService'],
function(aamd, mocks, app) {
describe('MyService', function() {
var myBodyMock = {
append: function() {}
};
var myDocumentMock = {
find: function(sel) {
// this never gets called
console.log('selector: ' + sel);
return myBodyMock;
}
};
var svc;
beforeEach(function() {
// try standard way to mock a service through ng-mock
mocks.module(function($provide) {
$provide.value('$document', myDocumentMock);
});
// hedge my bets - try overriding in aamd as well as ng-mock
aamd.value('$document', myDocumentMock);
});
beforeEach(function() {
aamd.inject(['myService',
function(myService) {
svc = myService;
}]);
});
it('should work', function() {
// use svc expecting it to have injected mock of $document.
spyOn(myDocumentMock, 'find').andCallThrough();
spyOn(myBodyMock, 'append');
svc.add('<p></p>');
expect(myDocumentMock.find).toHaveBeenCalledWith('body');
expect(myBockMock.append).toHaveBeenCalledWith('<p></p>');
});
});
}
);
Does anyone know where I'm going wrong ? Any help would be much appreciated.
Angular isn't asynchronous, I think is not a good ideia use both. If you're trying to reach to a good modularization method, okay, but use the RequireJS optimizer to build everything before you put this on your browser, and about the tests, I think you can just use RequireJS optimizer to build your modules before, it will let you free from "CommonJS environment even in tests".
Looks like it'll be an issue with variable scopes, karma is very finicky about that. I think you should initialize your mock objects globally, then set them in the beforeEach.
The top line of my test files always looks something like:
var bodyMock, svcMock, foo, bar
Then in the beforeEach'es I set the values
Edit: Since bodyMock is only a scope variable, at the point where the tests are actually running and the browser is looking for an object 'bodyMock', it can't find anything.
I'm writing a script which generates png images from every page of my frontend.
I'm using angular for the UI and capturing the pages with phantom.
The view take a while until angular finish rendering it so I have to wait a little before capturing:
var page = require('webpage').create();
page.open('http://localhost:9000/', function () {
window.setTimeout(function () {
page.render('snapshot.png');
phantom.exit();
}, 2000);
});
I wonder if there is a better way to achieve this. I found angular can emit an event when the page is fully rendered:
$scope.$on('$viewContentLoaded', function () {
// do something
});
And found a way for communicate to phantom with onCallback so I could write something like:
$scope.$on('$viewContentLoaded', function () {
window.callPhantom({ hello: 'world' });
});
Then in other place in phantom script:
page.onCallback = function() {
page.render('snapshot.png');
phantom.exit();
};
But I'm lost in how to inject the angular $viewContentLoaded handle from the phantom script.
I don't know if evaluate/evalueateAsyn are the way to go ...
page.evaluateAsync(function () {
$scope.$on('$viewContentLoaded', function () {
window.callPhantom({ hello: 'world' });
});
});
Maybe I could access the right $scope in some way.
Any ideas?
The associated PhantomJS API is onCallback; you can find the API doc on the wiki.
// in angular
$scope.$on('$viewContentLoaded', function () {
window.callPhantom();
});
// in the phantomjs script
var page = require('webpage').create();
page.onCallback = function() {
page.render('snapshot.png');
phantom.exit();
};
page.open('http://localhost:9000/');
You can get access to the $rootScope by accessing the injector; for example, if you're using the ng-app directive, you can find the element with the directive and call .injector().get("$rootScope") on it. However, I'm not sure if the $viewContentLoaded event will already have fired by then.