refactor(services): migrate angular.service -> module

This commit is contained in:
Misko Hevery 2011-11-02 16:32:46 -07:00
parent ed36b9da3b
commit bd04316a89
43 changed files with 2250 additions and 2220 deletions

2
angularFiles.js vendored
View file

@ -70,7 +70,6 @@ angularFiles = {
'src/jstd-scenario-adapter/*.js', 'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js', 'src/scenario/*.js',
'src/angular-mocks.js', 'src/angular-mocks.js',
'test/mocks.js',
'test/scenario/*.js', 'test/scenario/*.js',
'test/scenario/output/*.js', 'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js', 'test/jstd-scenario-adapter/*.js',
@ -133,7 +132,6 @@ angularFiles = {
'src/jstd-scenario-adapter/*.js', 'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js', 'src/scenario/*.js',
'src/angular-mocks.js', 'src/angular-mocks.js',
'test/mocks.js',
'test/scenario/*.js', 'test/scenario/*.js',
'test/scenario/output/*.js', 'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js', 'test/jstd-scenario-adapter/*.js',

View file

@ -148,9 +148,12 @@ function TutorialInstructionsCtrl($cookieStore) {
}; };
} }
angular.service('$locationConfig', function() { window.angular = window.angular || {};
return { angular.module = angular.module || {};
angular.module.ngdocs = function($provide) {
$provide.value('$locationConfig', {
html5Mode: true, html5Mode: true,
hashPrefix: '!' hashPrefix: '!'
}; });
}); };

View file

@ -2,7 +2,7 @@ describe('example.personalLog.LogCtrl', function() {
var logCtrl; var logCtrl;
function createNotesCtrl() { function createNotesCtrl() {
var injector = angular.injector('NG'); var injector = angular.injector('NG', 'NG_MOCK');
var scope = injector('$rootScope'); var scope = injector('$rootScope');
scope.$cookies = injector('$cookies'); scope.$cookies = injector('$cookies');
return scope.$new(example.personalLog.LogCtrl); return scope.$new(example.personalLog.LogCtrl);

View file

@ -99,8 +99,8 @@ var _undefined = undefined,
: noop, : noop,
/** @name angular */ /** @name angular */
angular = window[$angular] || (window[$angular] = {}), angular = window.angular || (window.angular = {}),
angularModules = angular.modules || (angular.modules = {}), angularModule = angular.module || (angular.module = {}),
/** @name angular.markup */ /** @name angular.markup */
angularTextMarkup = extensionMap(angular, 'markup'), angularTextMarkup = extensionMap(angular, 'markup'),
/** @name angular.attrMarkup */ /** @name angular.attrMarkup */
@ -114,7 +114,6 @@ var _undefined = undefined,
/** @name angular.service */ /** @name angular.service */
angularInputType = extensionMap(angular, 'inputType', lowercase), angularInputType = extensionMap(angular, 'inputType', lowercase),
/** @name angular.service */ /** @name angular.service */
angularService = extensionMap(angular, 'service'),
angularCallbacks = extensionMap(angular, 'callbacks'), angularCallbacks = extensionMap(angular, 'callbacks'),
nodeName_, nodeName_,
uid = ['0', '0', '0'], uid = ['0', '0', '0'],
@ -188,18 +187,6 @@ function forEachSorted(obj, iterator, context) {
} }
function formatError(arg) {
if (arg instanceof Error) {
if (arg.stack) {
arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ?
'Error: ' + arg.message + '\n' + arg.stack : arg.stack;
} else if (arg.sourceURL) {
arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
}
}
return arg;
}
/** /**
* A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric * A consistent way of creating unique IDs in angular. The ID is a sequence of alpha numeric
* characters such as '012ABC'. The reason why we are not using simply a number counter is that * characters such as '012ABC'. The reason why we are not using simply a number counter is that
@ -957,7 +944,7 @@ function angularInit(config, document){
modules.push(module); modules.push(module);
} }
}); });
createInjector(modules, angularModules)(['$rootScope', '$compile', function(scope, compile){ createInjector(modules, angularModule)(['$rootScope', '$compile', '$injector', function(scope, compile, injector){
scope.$apply(function(){ scope.$apply(function(){
compile(isString(autobind) ? document.getElementById(autobind) : document)(scope); compile(isString(autobind) ? document.getElementById(autobind) : document)(scope);
}); });
@ -1030,7 +1017,7 @@ function publishExternalAPI(angular){
'extend': extend, 'extend': extend,
'equals': equals, 'equals': equals,
'forEach': forEach, 'forEach': forEach,
'injector': function(){ return createInjector(arguments, angularModules); }, 'injector': function(){ return createInjector(arguments, angularModule); },
'noop':noop, 'noop':noop,
'bind':bind, 'bind':bind,
'toJson': toJson, 'toJson': toJson,
@ -1049,14 +1036,39 @@ function publishExternalAPI(angular){
'uppercase': uppercase 'uppercase': uppercase
}); });
angularModules.NG = ngModule; angularModule.NG = ngModule;
} }
ngModule.$inject = ['$provide']; ngModule.$inject = ['$provide', '$injector'];
function ngModule($provide) { function ngModule($provide, $injector) {
forEach(angularService, function(factory, name){ // TODO(misko): temporary services to get the compiler working;
$provide.factory(name, factory); $provide.value('$textMarkup', angularTextMarkup);
}); $provide.value('$attrMarkup', angularAttrMarkup);
$provide.value('$directive', angularDirective);
$provide.value('$widget', angularWidget);
$provide.service('$browser', $BrowserProvider);
$provide.service('$compile', $CompileProvider);
$provide.service('$cookies', $CookiesProvider);
$provide.service('$cookieStore', $CookieStoreProvider);
$provide.service('$defer', $DeferProvider);
$provide.service('$document', $DocumentProvider);
$provide.service('$exceptionHandler', $ExceptionHandlerProvider);
$provide.service('$formFactory', $FormFactoryProvider);
$provide.service('$locale', $LocaleProvider);
$provide.service('$location', $LocationProvider);
$provide.service('$locationConfig', $LocationConfigProvider);
$provide.service('$log', $LogProvider);
$provide.service('$resource', $ResourceProvider);
$provide.service('$route', $RouteProvider);
$provide.service('$routeParams', $RouteParamsProvider);
$provide.service('$rootScope', $RootScopeProvider);
$provide.service('$sniffer', $SnifferProvider);
$provide.service('$window', $WindowProvider);
$provide.service('$xhr.bulk', $XhrBulkProvider);
$provide.service('$xhr.cache', $XhrCacheProvider);
$provide.service('$xhr.error', $XhrErrorProvider);
$provide.service('$xhr', $XhrProvider);
} }

View file

@ -1,16 +1,5 @@
'use strict'; 'use strict';
var browserSingleton;
angularService('$browser', function($log, $sniffer) {
if (!browserSingleton) {
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
XHR, $log, $sniffer);
}
return browserSingleton;
}, {$inject: ['$log', '$sniffer']});
publishExternalAPI(angular); publishExternalAPI(angular);
//try to bind to jquery now so that one can write angular.element().read() //try to bind to jquery now so that one can write angular.element().read()

View file

@ -473,3 +473,10 @@ function Browser(window, document, body, XHR, $log, $sniffer) {
return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href; return href ? href.replace(/^https?\:\/\/[^\/]*/, '') : href;
}; };
} }
function $BrowserProvider(){
this.$get = ['$window', '$log', '$sniffer', '$document',
function( $window, $log, $sniffer, $document){
return new Browser($window, $document, $document.find('body'), XHR, $log, $sniffer);
}];
}

View file

@ -33,10 +33,6 @@
* `injector.eager()` * `injector.eager()`
*/ */
function angularServiceInject(name, fn, inject, eager) {
angularService(name, fn, {$inject:inject, $eager:eager});
}
/** /**
* @returns the $inject property of function. If not found the * @returns the $inject property of function. If not found the
@ -177,7 +173,11 @@ function createInjector(modulesToLoad, moduleRegistry) {
forEach(modulesToLoad, function(module){ forEach(modulesToLoad, function(module){
if (isString(module)) { if (isString(module)) {
module = moduleRegistry[module]; if (moduleRegistry[module]) {
module = moduleRegistry[module];
} else {
throw Error("Module '" + module + "' is not defined!");
}
} }
if (isFunction(module) || isArray(module)) { if (isFunction(module) || isArray(module)) {
$injector(module); $injector(module);

127
src/angular-mocks.js vendored
View file

@ -1,4 +1,3 @@
'use strict';
/** /**
* @license AngularJS v"NG_VERSION_FULL" * @license AngularJS v"NG_VERSION_FULL"
@ -8,30 +7,6 @@
/* /*
NUGGGGGH MUST TONGUE WANGS
\
.....
C C /
/< /
___ __________/_#__=o
/(- /(\_\________ \
\ ) \ )_ \o \
/|\ /|\ |' |
| _|
/o __\
/ ' |
/ / |
/_/\______|
( _( <
\ \ \
\ \ |
\____\____\
____\_\__\_\
/` /` o\
|___ |_______|.. . b'ger
IN THE FINAL BUILD THIS FILE DOESN'T HAVE DIRECT ACCESS TO GLOBAL FUNCTIONS IN THE FINAL BUILD THIS FILE DOESN'T HAVE DIRECT ACCESS TO GLOBAL FUNCTIONS
DEFINED IN Angular.js YOU *MUST* REFER TO THEM VIA angular OBJECT DEFINED IN Angular.js YOU *MUST* REFER TO THEM VIA angular OBJECT
(e.g. angular.forEach(...)) AND MAKE SURE THAT THE GIVEN FUNCTION IS EXPORTED (e.g. angular.forEach(...)) AND MAKE SURE THAT THE GIVEN FUNCTION IS EXPORTED
@ -56,8 +31,15 @@
* the angular service exception handler. * the angular service exception handler.
* * {@link angular.mock.service.$log $log } - A mock implementation of the angular service log. * * {@link angular.mock.service.$log $log } - A mock implementation of the angular service log.
*/ */
angular.mock = {}; window.angular = window.angular || {};
angular.module = angular.module || {};
angular.mock = angular.mock || {};
angular.module.NG_MOCK = ['$provide', function($provide){
$provide.service('$browser', angular.mock.$BrowserProvider);
$provide.service('$exceptionHandler', angular.mock.$ExceptionHandlerProvider);
$provide.service('$log', angular.mock.$LogProvider);
}];
/** /**
* @ngdoc service * @ngdoc service
@ -81,7 +63,12 @@ angular.mock = {};
* - $browser.defer enables testing of code that uses * - $browser.defer enables testing of code that uses
* {@link angular.service.$defer $defer service} for executing functions via the `setTimeout` api. * {@link angular.service.$defer $defer service} for executing functions via the `setTimeout` api.
*/ */
function MockBrowser() { angular.mock.$BrowserProvider = function(){
this.$get = function(){
return new angular.mock.$Browser();
};
};
angular.mock.$Browser = function() {
var self = this, var self = this,
expectations = {}, expectations = {},
requests = []; requests = [];
@ -309,7 +296,7 @@ function MockBrowser() {
return this.$$baseHref; return this.$$baseHref;
}; };
} }
MockBrowser.prototype = { angular.mock.$Browser.prototype = {
/** /**
* @name angular.mock.service.$browser#poll * @name angular.mock.service.$browser#poll
@ -360,10 +347,6 @@ MockBrowser.prototype = {
addJs: function() {} addJs: function() {}
}; };
angular.service('$browser', function() {
return new MockBrowser();
});
/** /**
* @ngdoc service * @ngdoc service
@ -376,9 +359,29 @@ angular.service('$browser', function() {
* *
* See {@link angular.mock} for more info on angular mocks. * See {@link angular.mock} for more info on angular mocks.
*/ */
angular.service('$exceptionHandler', function() { angular.mock.$ExceptionHandlerProvider = function(){
return function(e) { throw e; }; var handler;
});
this.mode = function(mode){
handler = {
rethrow: function(e) {
throw e;
},
log: angular.extend(function log(e) {
log.errors.push(e);
}, {errors:[]})
}[mode];
if (!handler) {
throw Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!");
}
};
this.$get = function(){
return handler;
};
this.mode('rethrow');
};
/** /**
@ -392,23 +395,43 @@ angular.service('$exceptionHandler', function() {
* *
* See {@link angular.mock} for more info on angular mocks. * See {@link angular.mock} for more info on angular mocks.
*/ */
angular.service('$log', MockLogFactory); angular.mock.$LogProvider = function(){
this.$get = function () {
var $log = {
log: function() { $log.log.logs.push(concat([], arguments, 0)); },
warn: function() { $log.warn.logs.push(concat([], arguments, 0)); },
info: function() { $log.info.logs.push(concat([], arguments, 0)); },
error: function() { $log.error.logs.push(concat([], arguments, 0)); }
};
function MockLogFactory() { $log.reset = function (){
var $log = { $log.log.logs = [];
log: function() { $log.log.logs.push(arguments); }, $log.warn.logs = [];
warn: function() { $log.warn.logs.push(arguments); }, $log.info.logs = [];
info: function() { $log.info.logs.push(arguments); }, $log.error.logs = [];
error: function() { $log.error.logs.push(arguments); } };
$log.assertEmpty = function(){
var errors = [];
angular.forEach(['error', 'warn', 'info', 'log'], function(logLevel) {
angular.forEach($log[logLevel].logs, function(log) {
angular.forEach(log, function (logItem) {
errors.push('MOCK $log (' + logLevel + '): ' + (logItem.stack || logItem));
});
});
});
if (errors.length) {
errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or an expected " +
"log message was not checked and removed:");
errors.push('')
throw new Error(errors.join('\n---------\n'));
}
};
$log.reset();
return $log;
}; };
};
$log.log.logs = [];
$log.warn.logs = [];
$log.info.logs = [];
$log.error.logs = [];
return $log;
}
/** /**
@ -441,7 +464,7 @@ function MockLogFactory() {
* </pre> * </pre>
* *
*/ */
function TzDate(offset, timestamp) { angular.mock.TzDate = function (offset, timestamp) {
if (angular.isString(timestamp)) { if (angular.isString(timestamp)) {
var tsStr = timestamp; var tsStr = timestamp;
@ -545,4 +568,4 @@ function TzDate(offset, timestamp) {
} }
//make "tzDateInstance instanceof Date" return true //make "tzDateInstance instanceof Date" return true
TzDate.prototype = Date.prototype; angular.mock.TzDate.prototype = Date.prototype;

View file

@ -1,325 +1,322 @@
'use strict'; 'use strict';
// TODO(misko): temporary services to get the compiler working;
angularService('$textMarkup', valueFn(angularTextMarkup));
angularService('$attrMarkup', valueFn(angularAttrMarkup));
angularService('$directive', valueFn(angularDirective));
angularService('$widget', valueFn(angularWidget));
angularServiceInject('$compile', function($injector, $exceptionHandler, $textMarkup, $attrMarkup, $directive, $widget){ function $CompileProvider(){
/** this.$get = ['$injector', '$exceptionHandler', '$textMarkup', '$attrMarkup', '$directive', '$widget',
* Template provides directions an how to bind to a given element. function( $injector, $exceptionHandler, $textMarkup, $attrMarkup, $directive, $widget){
* It contains a list of init functions which need to be called to /**
* bind to a new instance of elements. It also provides a list * Template provides directions an how to bind to a given element.
* of child paths which contain child templates * It contains a list of init functions which need to be called to
*/ * bind to a new instance of elements. It also provides a list
function Template() { * of child paths which contain child templates
this.paths = []; */
this.children = []; function Template() {
this.linkFns = []; this.paths = [];
this.newScope = false; this.children = [];
} this.linkFns = [];
this.newScope = false;
Template.prototype = {
link: function(element, scope) {
var childScope = scope,
locals = {$element: element};
if (this.newScope) {
childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new();
element.data($$scope, childScope);
} }
forEach(this.linkFns, function(fn) {
try {
$injector.invoke(childScope, fn, locals);
} catch (e) {
$exceptionHandler(e);
}
});
var i,
childNodes = element[0].childNodes,
children = this.children,
paths = this.paths,
length = paths.length;
for (i = 0; i < length; i++) {
// sometimes `element` can be modified by one of the linker functions in `this.linkFns`
// and childNodes may be added or removed
// TODO: element structure needs to be re-evaluated if new children added
// if the childNode still exists
if (childNodes[paths[i]])
children[i].link(jqLite(childNodes[paths[i]]), childScope);
else
delete paths[i]; // if child no longer available, delete path
}
},
Template.prototype = {
addLinkFn:function(linkingFn) { link: function(element, scope) {
if (linkingFn) { var childScope = scope,
//TODO(misko): temporary hack. locals = {$element: element};
if (isFunction(linkingFn) && !linkingFn.$inject) { if (this.newScope) {
linkingFn.$inject = ['$element']; childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new();
} element.data($$scope, childScope);
this.linkFns.push(linkingFn);
}
},
addChild: function(index, template) {
if (template) {
this.paths.push(index);
this.children.push(template);
}
},
empty: function() {
return this.linkFns.length === 0 && this.paths.length === 0;
}
};
///////////////////////////////////
//Compiler
//////////////////////////////////
/**
* @ngdoc function
* @name angular.compile
* @function
*
* @description
* Compiles a piece of HTML string or DOM into a template and produces a template function, which
* can then be used to link {@link angular.scope scope} and the template together.
*
* The compilation is a process of walking the DOM tree and trying to match DOM elements to
* {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup},
* {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it
* executes corresponding markup, attrMarkup, widget or directive template function and collects the
* instance functions into a single template function which is then returned.
*
* The template function can then be used once to produce the view or as it is the case with
* {@link angular.widget.@ng:repeat repeater} many-times, in which case each call results in a view
* that is a DOM clone of the original template.
*
<pre>
// compile the entire window.document and give me the scope bound to this template.
var rootScope = angular.compile(window.document)();
// compile a piece of html
var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')();
// compile a piece of html and retain reference to both the dom and scope
var template = angular.element('<div ng:click="clicked = true">click me</div>'),
scope = angular.compile(template)();
// at this point template was transformed into a view
</pre>
*
*
* @param {string|DOMElement} element Element or HTML to compile into a template function.
* @returns {function(scope[, cloneAttachFn])} a template function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link angular.scope Scope} to bind to.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
* called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
* Calling the template function returns the element of the template. It is either the original element
* passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
* It is important to understand that the returned scope is "linked" to the view DOM, but no linking
* (instance) functions registered by {@link angular.directive directives} or
* {@link angular.widget widgets} found in the template have been executed yet. This means that the
* view is likely empty and doesn't contain any values that result from evaluation on the scope. To
* bring the view to life, the scope needs to run through a $digest phase which typically is done by
* Angular automatically, except for the case when an application is being
* {@link guide/dev_guide.bootstrap.manual_bootstrap} manually bootstrapped, in which case the
* $digest phase must be invoked by calling {@link angular.scope.$apply}.
*
* If you need access to the bound view, there are two ways to do it:
*
* - If you are not asking the linking function to clone the template, create the DOM element(s)
* before you send them to the compiler and keep this reference around.
* <pre>
* var scope = angular.injector()('$rootScope');
* var element = angular.compile('<p>{{total}}</p>')(scope);
* </pre>
*
* - if on the other hand, you need the element to be cloned, the view reference from the original
* example would not point to the clone, but rather to the original template that was cloned. In
* this case, you can access the clone via the cloneAttachFn:
* <pre>
* var original = angular.element('<p>{{total}}</p>'),
* scope = someParentScope.$new(),
* clone;
*
* angular.compile(original)(scope, function(clonedElement, scope) {
* clone = clonedElement;
* //attach the clone to DOM document at the right place
* });
*
* //now we have reference to the cloned DOM via `clone`
* </pre>
*
*
* Compiler Methods For Widgets and Directives:
*
* The following methods are available for use when you write your own widgets, directives,
* and markup. (Recall that the compile function's this is a reference to the compiler.)
*
* `compile(element)` - returns linker -
* Invoke a new instance of the compiler to compile a DOM element and return a linker function.
* You can apply the linker function to the original element or a clone of the original element.
* The linker function returns a scope.
*
* * `comment(commentText)` - returns element - Create a comment element.
*
* * `element(elementName)` - returns element - Create an element by name.
*
* * `text(text)` - returns element - Create a text element.
*
* * `descend([set])` - returns descend state (true or false). Get or set the current descend
* state. If true the compiler will descend to children elements.
*
* * `directives([set])` - returns directive state (true or false). Get or set the current
* directives processing state. The compiler will process directives only when directives set to
* true.
*
* For information on how the compiler works, see the
* {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide.
*/
function Compiler(markup, attrMarkup, directives, widgets){
this.markup = markup;
this.attrMarkup = attrMarkup;
this.directives = directives;
this.widgets = widgets;
}
Compiler.prototype = {
compile: function(templateElement) {
templateElement = jqLite(templateElement);
var index = 0,
template,
parent = templateElement.parent();
if (templateElement.length > 1) {
// https://github.com/angular/angular.js/issues/338
throw Error("Cannot compile multiple element roots: " +
jqLite('<div>').append(templateElement.clone()).html());
}
if (parent && parent[0]) {
parent = parent[0];
for(var i = 0; i < parent.childNodes.length; i++) {
if (parent.childNodes[i] == templateElement[0]) {
index = i;
} }
forEach(this.linkFns, function(fn) {
try {
$injector.invoke(childScope, fn, locals);
} catch (e) {
$exceptionHandler(e);
}
});
var i,
childNodes = element[0].childNodes,
children = this.children,
paths = this.paths,
length = paths.length;
for (i = 0; i < length; i++) {
// sometimes `element` can be modified by one of the linker functions in `this.linkFns`
// and childNodes may be added or removed
// TODO: element structure needs to be re-evaluated if new children added
// if the childNode still exists
if (childNodes[paths[i]])
children[i].link(jqLite(childNodes[paths[i]]), childScope);
else
delete paths[i]; // if child no longer available, delete path
}
},
addLinkFn:function(linkingFn) {
if (linkingFn) {
//TODO(misko): temporary hack.
if (isFunction(linkingFn) && !linkingFn.$inject) {
linkingFn.$inject = ['$element'];
}
this.linkFns.push(linkingFn);
}
},
addChild: function(index, template) {
if (template) {
this.paths.push(index);
this.children.push(template);
}
},
empty: function() {
return this.linkFns.length === 0 && this.paths.length === 0;
} }
}
template = this.templatize(templateElement, index) || new Template();
return function(scope, cloneConnectFn){
assertArg(scope, 'scope');
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
var element = cloneConnectFn
? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!!
: templateElement;
element.data($$scope, scope);
scope.$element = element;
(cloneConnectFn||noop)(element, scope);
template.link(element, scope);
return element;
}; };
},
templatize: function(element, elementIndex){ ///////////////////////////////////
var self = this, //Compiler
widget, //////////////////////////////////
fn,
directiveFns = self.directives, /**
descend = true, * @ngdoc function
directives = true, * @name angular.compile
elementName = nodeName_(element), * @function
elementNamespace = elementName.indexOf(':') > 0 ? lowercase(elementName).replace(':', '-') : '', *
template, * @description
selfApi = { * Compiles a piece of HTML string or DOM into a template and produces a template function, which
compile: bind(self, self.compile), * can then be used to link {@link angular.scope scope} and the template together.
descend: function(value){ if(isDefined(value)) descend = value; return descend;}, *
directives: function(value){ if(isDefined(value)) directives = value; return directives;}, * The compilation is a process of walking the DOM tree and trying to match DOM elements to
scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;} * {@link angular.markup markup}, {@link angular.attrMarkup attrMarkup},
}; * {@link angular.widget widgets}, and {@link angular.directive directives}. For each match it
element.addClass(elementNamespace); * executes corresponding markup, attrMarkup, widget or directive template function and collects the
template = new Template(); * instance functions into a single template function which is then returned.
eachAttribute(element, function(value, name){ *
if (!widget) { * The template function can then be used once to produce the view or as it is the case with
if ((widget = self.widgets('@' + name))) { * {@link angular.widget.@ng:repeat repeater} many-times, in which case each call results in a view
element.addClass('ng-attr-widget'); * that is a DOM clone of the original template.
widget = bind(selfApi, widget, value, element); *
<pre>
// compile the entire window.document and give me the scope bound to this template.
var rootScope = angular.compile(window.document)();
// compile a piece of html
var rootScope2 = angular.compile('<div ng:click="clicked = true">click me</div>')();
// compile a piece of html and retain reference to both the dom and scope
var template = angular.element('<div ng:click="clicked = true">click me</div>'),
scope = angular.compile(template)();
// at this point template was transformed into a view
</pre>
*
*
* @param {string|DOMElement} element Element or HTML to compile into a template function.
* @returns {function(scope[, cloneAttachFn])} a template function which is used to bind template
* (a DOM element/tree) to a scope. Where:
*
* * `scope` - A {@link angular.scope Scope} to bind to.
* * `cloneAttachFn` - If `cloneAttachFn` is provided, then the link function will clone the
* `template` and call the `cloneAttachFn` function allowing the caller to attach the
* cloned elements to the DOM document at the appropriate place. The `cloneAttachFn` is
* called as: <br/> `cloneAttachFn(clonedElement, scope)` where:
*
* * `clonedElement` - is a clone of the original `element` passed into the compiler.
* * `scope` - is the current scope with which the linking function is working with.
*
* Calling the template function returns the element of the template. It is either the original element
* passed in, or the clone of the element if the `cloneAttachFn` is provided.
*
* It is important to understand that the returned scope is "linked" to the view DOM, but no linking
* (instance) functions registered by {@link angular.directive directives} or
* {@link angular.widget widgets} found in the template have been executed yet. This means that the
* view is likely empty and doesn't contain any values that result from evaluation on the scope. To
* bring the view to life, the scope needs to run through a $digest phase which typically is done by
* Angular automatically, except for the case when an application is being
* {@link guide/dev_guide.bootstrap.manual_bootstrap} manually bootstrapped, in which case the
* $digest phase must be invoked by calling {@link angular.scope.$apply}.
*
* If you need access to the bound view, there are two ways to do it:
*
* - If you are not asking the linking function to clone the template, create the DOM element(s)
* before you send them to the compiler and keep this reference around.
* <pre>
* var scope = angular.injector()('$rootScope');
* var element = angular.compile('<p>{{total}}</p>')(scope);
* </pre>
*
* - if on the other hand, you need the element to be cloned, the view reference from the original
* example would not point to the clone, but rather to the original template that was cloned. In
* this case, you can access the clone via the cloneAttachFn:
* <pre>
* var original = angular.element('<p>{{total}}</p>'),
* scope = someParentScope.$new(),
* clone;
*
* angular.compile(original)(scope, function(clonedElement, scope) {
* clone = clonedElement;
* //attach the clone to DOM document at the right place
* });
*
* //now we have reference to the cloned DOM via `clone`
* </pre>
*
*
* Compiler Methods For Widgets and Directives:
*
* The following methods are available for use when you write your own widgets, directives,
* and markup. (Recall that the compile function's this is a reference to the compiler.)
*
* `compile(element)` - returns linker -
* Invoke a new instance of the compiler to compile a DOM element and return a linker function.
* You can apply the linker function to the original element or a clone of the original element.
* The linker function returns a scope.
*
* * `comment(commentText)` - returns element - Create a comment element.
*
* * `element(elementName)` - returns element - Create an element by name.
*
* * `text(text)` - returns element - Create a text element.
*
* * `descend([set])` - returns descend state (true or false). Get or set the current descend
* state. If true the compiler will descend to children elements.
*
* * `directives([set])` - returns directive state (true or false). Get or set the current
* directives processing state. The compiler will process directives only when directives set to
* true.
*
* For information on how the compiler works, see the
* {@link guide/dev_guide.compiler Angular HTML Compiler} section of the Developer Guide.
*/
function Compiler(markup, attrMarkup, directives, widgets){
this.markup = markup;
this.attrMarkup = attrMarkup;
this.directives = directives;
this.widgets = widgets;
}
Compiler.prototype = {
compile: function(templateElement) {
templateElement = jqLite(templateElement);
var index = 0,
template,
parent = templateElement.parent();
if (templateElement.length > 1) {
// https://github.com/angular/angular.js/issues/338
throw Error("Cannot compile multiple element roots: " +
jqLite('<div>').append(templateElement.clone()).html());
} }
} if (parent && parent[0]) {
}); parent = parent[0];
if (!widget) { for(var i = 0; i < parent.childNodes.length; i++) {
if ((widget = self.widgets(elementName))) { if (parent.childNodes[i] == templateElement[0]) {
if (elementNamespace) index = i;
element.addClass('ng-widget'); }
widget = bind(selfApi, widget, element); }
} }
} template = this.templatize(templateElement, index) || new Template();
if (widget) { return function(scope, cloneConnectFn){
descend = false; assertArg(scope, 'scope');
directives = false; // important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
var parent = element.parent(); // and sometimes changes the structure of the DOM.
template.addLinkFn(widget.call(selfApi, element)); var element = cloneConnectFn
if (parent && parent[0]) { ? JQLitePrototype.clone.call(templateElement) // IMPORTANT!!!
element = jqLite(parent[0].childNodes[elementIndex]); : templateElement;
} element.data($$scope, scope);
} scope.$element = element;
if (descend){ (cloneConnectFn||noop)(element, scope);
// process markup for text nodes only template.link(element, scope);
for(var i=0, child=element[0].childNodes; return element;
i<child.length; i++) { };
if (isTextNode(child[i])) { },
forEach(self.markup, function(markup){
if (i<child.length) { templatize: function(element, elementIndex){
var textNode = jqLite(child[i]); var self = this,
markup.call(selfApi, textNode.text(), textNode, element); widget,
fn,
directiveFns = self.directives,
descend = true,
directives = true,
elementName = nodeName_(element),
elementNamespace = elementName.indexOf(':') > 0 ? lowercase(elementName).replace(':', '-') : '',
template,
selfApi = {
compile: bind(self, self.compile),
descend: function(value){ if(isDefined(value)) descend = value; return descend;},
directives: function(value){ if(isDefined(value)) directives = value; return directives;},
scope: function(value){ if(isDefined(value)) template.newScope = template.newScope || value; return template.newScope;}
};
element.addClass(elementNamespace);
template = new Template();
eachAttribute(element, function(value, name){
if (!widget) {
if ((widget = self.widgets('@' + name))) {
element.addClass('ng-attr-widget');
widget = bind(selfApi, widget, value, element);
}
}
});
if (!widget) {
if ((widget = self.widgets(elementName))) {
if (elementNamespace)
element.addClass('ng-widget');
widget = bind(selfApi, widget, element);
}
}
if (widget) {
descend = false;
directives = false;
var parent = element.parent();
template.addLinkFn(widget.call(selfApi, element));
if (parent && parent[0]) {
element = jqLite(parent[0].childNodes[elementIndex]);
}
}
if (descend){
// process markup for text nodes only
for(var i=0, child=element[0].childNodes;
i<child.length; i++) {
if (isTextNode(child[i])) {
forEach(self.markup, function(markup){
if (i<child.length) {
var textNode = jqLite(child[i]);
markup.call(selfApi, textNode.text(), textNode, element);
}
});
}
}
}
if (directives) {
// Process attributes/directives
eachAttribute(element, function(value, name){
forEach(self.attrMarkup, function(markup){
markup.call(selfApi, value, name, element);
});
});
eachAttribute(element, function(value, name){
name = lowercase(name);
fn = directiveFns[name];
if (fn) {
element.addClass('ng-directive');
template.addLinkFn((directiveFns[name]).call(selfApi, value, element));
} }
}); });
} }
} // Process non text child nodes
} if (descend) {
eachNode(element, function(child, i){
if (directives) { template.addChild(i, self.templatize(child, i));
// Process attributes/directives });
eachAttribute(element, function(value, name){
forEach(self.attrMarkup, function(markup){
markup.call(selfApi, value, name, element);
});
});
eachAttribute(element, function(value, name){
name = lowercase(name);
fn = directiveFns[name];
if (fn) {
element.addClass('ng-directive');
template.addLinkFn((directiveFns[name]).call(selfApi, value, element));
} }
}); return template.empty() ? null : template;
} }
// Process non text child nodes };
if (descend) {
eachNode(element, function(child, i){
template.addChild(i, self.templatize(child, i));
});
}
return template.empty() ? null : template;
}
};
///////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////
var compiler = new Compiler($textMarkup, $attrMarkup, $directive, $widget); var compiler = new Compiler($textMarkup, $attrMarkup, $directive, $widget);
return bind(compiler, compiler.compile); return bind(compiler, compiler.compile);
}, ['$injector', '$exceptionHandler', '$textMarkup', '$attrMarkup', '$directive', '$widget']); }];
};
function eachNode(element, fn){ function eachNode(element, fn){

View file

@ -11,52 +11,54 @@
* deserialized by angular's toJson/fromJson. * deserialized by angular's toJson/fromJson.
* @example * @example
*/ */
angularServiceInject('$cookieStore', function($store) { function $CookieStoreProvider(){
this.$get = ['$cookies', function($cookies) {
return { return {
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$cookieStore#get * @name angular.service.$cookieStore#get
* @methodOf angular.service.$cookieStore * @methodOf angular.service.$cookieStore
* *
* @description * @description
* Returns the value of given cookie key * Returns the value of given cookie key
* *
* @param {string} key Id to use for lookup. * @param {string} key Id to use for lookup.
* @returns {Object} Deserialized cookie value. * @returns {Object} Deserialized cookie value.
*/ */
get: function(key) { get: function(key) {
return fromJson($store[key]); return fromJson($cookies[key]);
}, },
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$cookieStore#put * @name angular.service.$cookieStore#put
* @methodOf angular.service.$cookieStore * @methodOf angular.service.$cookieStore
* *
* @description * @description
* Sets a value for given cookie key * Sets a value for given cookie key
* *
* @param {string} key Id for the `value`. * @param {string} key Id for the `value`.
* @param {Object} value Value to be stored. * @param {Object} value Value to be stored.
*/ */
put: function(key, value) { put: function(key, value) {
$store[key] = toJson(value); $cookies[key] = toJson(value);
}, },
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$cookieStore#remove * @name angular.service.$cookieStore#remove
* @methodOf angular.service.$cookieStore * @methodOf angular.service.$cookieStore
* *
* @description * @description
* Remove given cookie * Remove given cookie
* *
* @param {string} key Id of the key-value pair to delete. * @param {string} key Id of the key-value pair to delete.
*/ */
remove: function(key) { remove: function(key) {
delete $store[key]; delete $cookies[key];
} }
}; };
}, ['$cookies']); }];
}

View file

@ -13,80 +13,82 @@
* *
* @example * @example
*/ */
angularServiceInject('$cookies', function($rootScope, $browser) { function $CookiesProvider() {
var cookies = {}, this.$get = ['$rootScope', '$browser', function ($rootScope, $browser) {
lastCookies = {}, var cookies = {},
lastBrowserCookies, lastCookies = {},
runEval = false; lastBrowserCookies,
runEval = false;
//creates a poller fn that copies all cookies from the $browser to service & inits the service //creates a poller fn that copies all cookies from the $browser to service & inits the service
$browser.addPollFn(function() { $browser.addPollFn(function() {
var currentCookies = $browser.cookies(); var currentCookies = $browser.cookies();
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
lastBrowserCookies = currentCookies; lastBrowserCookies = currentCookies;
copy(currentCookies, lastCookies); copy(currentCookies, lastCookies);
copy(currentCookies, cookies); copy(currentCookies, cookies);
if (runEval) $rootScope.$apply(); if (runEval) $rootScope.$apply();
}
})();
runEval = true;
//at the end of each eval, push cookies
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
// strings or browser refuses to store some cookies, we update the model in the push fn.
$rootScope.$watch(push);
return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
} }
} })();
//update all cookies updated in $cookies runEval = true;
for(name in cookies) {
value = cookies[name]; //at the end of each eval, push cookies
if (!isString(value)) { //TODO: this should happen before the "delayed" watches fire, because if some cookies are not
if (isDefined(lastCookies[name])) { // strings or browser refuses to store some cookies, we update the model in the push fn.
cookies[name] = lastCookies[name]; $rootScope.$watch(push);
} else {
delete cookies[name]; return cookies;
/**
* Pushes all the cookies from the service to the browser and verifies if all cookies were stored.
*/
function push() {
var name,
value,
browserCookies,
updated;
//delete any cookies deleted in $cookies
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
} }
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true;
} }
}
//verify what was actually stored //update all cookies updated in $cookies
if (updated){ for(name in cookies) {
updated = false; value = cookies[name];
browserCookies = $browser.cookies(); if (!isString(value)) {
if (isDefined(lastCookies[name])) {
for (name in cookies) { cookies[name] = lastCookies[name];
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else { } else {
cookies[name] = browserCookies[name]; delete cookies[name];
} }
} else if (value !== lastCookies[name]) {
$browser.cookies(name, value);
updated = true; updated = true;
} }
} }
//verify what was actually stored
if (updated){
updated = false;
browserCookies = $browser.cookies();
for (name in cookies) {
if (cookies[name] !== browserCookies[name]) {
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
} else {
cookies[name] = browserCookies[name];
}
updated = true;
}
}
}
} }
} }];
}, ['$rootScope', '$browser']); }

View file

@ -28,16 +28,18 @@
* @param {*} deferId Token returned by the `$defer` function. * @param {*} deferId Token returned by the `$defer` function.
* @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled. * @returns {boolean} Returns `true` if the task hasn't executed yet and was successfuly canceled.
*/ */
angularServiceInject('$defer', function($rootScope, $browser) { function $DeferProvider(){
function defer(fn, delay) { this.$get = ['$rootScope', '$browser', function($rootScope, $browser) {
return $browser.defer(function() { function defer(fn, delay) {
$rootScope.$apply(fn); return $browser.defer(function() {
}, delay); $rootScope.$apply(fn);
} }, delay);
}
defer.cancel = function(deferId) { defer.cancel = function(deferId) {
return $browser.defer.cancel(deferId); return $browser.defer.cancel(deferId);
}; };
return defer; return defer;
}, ['$rootScope', '$browser']); }];
}

View file

@ -9,6 +9,8 @@
* A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document` * A {@link angular.element jQuery (lite)}-wrapped reference to the browser's `window.document`
* element. * element.
*/ */
angularServiceInject("$document", function(window){ function $DocumentProvider(){
return jqLite(window.document); this.$get = ['$window', function(window){
}, ['$window']); return jqLite(window.document);
}];
}

View file

@ -15,9 +15,10 @@
* *
* @example * @example
*/ */
var $exceptionHandlerFactory; //reference to be used only in tests function $ExceptionHandlerProvider(){
angularServiceInject('$exceptionHandler', $exceptionHandlerFactory = function($log){ this.$get = ['$log', function($log){
return function(e) { return function(e) {
$log.error(e); $log.error(e);
}; };
}, ['$log']); }];
}

View file

@ -96,297 +96,299 @@
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularServiceInject('$formFactory', function($rootScope) {
function $FormFactoryProvider() {
this.$get = ['$rootScope', function($rootScope) {
/**
* @ngdoc proprety
* @name rootForm
* @propertyOf angular.service.$formFactory
* @description
* Static property on `$formFactory`
*
* Each application ({@link guide/dev_guide.scopes.internals root scope}) gets a root form which
* is the top-level parent of all forms.
*/
formFactory.rootForm = formFactory($rootScope);
/** /**
* @ngdoc proprety * @ngdoc method
* @name rootForm * @name forElement
* @propertyOf angular.service.$formFactory * @methodOf angular.service.$formFactory
* @description * @description
* Static property on `$formFactory` * Static method on `$formFactory` service.
* *
* Each application ({@link guide/dev_guide.scopes.internals root scope}) gets a root form which * Retrieve the closest form for a given element or defaults to the `root` form. Used by the
* is the top-level parent of all forms. * {@link angular.widget.form form} element.
*/ * @param {Element} element The element where the search for form should initiate.
formFactory.rootForm = formFactory($rootScope); */
formFactory.forElement = function(element) {
return element.inheritedData('$form') || formFactory.rootForm;
};
return formFactory;
function formFactory(parent) {
return (parent || formFactory.rootForm).$new(FormController);
}
/** }];
* @ngdoc method
* @name forElement
* @methodOf angular.service.$formFactory
* @description
* Static method on `$formFactory` service.
*
* Retrieve the closest form for a given element or defaults to the `root` form. Used by the
* {@link angular.widget.form form} element.
* @param {Element} element The element where the search for form should initiate.
*/
formFactory.forElement = function(element) {
return element.inheritedData('$form') || formFactory.rootForm;
};
return formFactory;
function formFactory(parent) { function propertiesUpdate(widget) {
return (parent || formFactory.rootForm).$new(FormController); widget.$valid = !(widget.$invalid =
!(widget.$readonly || widget.$disabled || equals(widget.$error, {})));
} }
}, ['$rootScope']); /**
* @ngdoc property
* @name $error
* @propertyOf angular.service.$formFactory
* @description
* Property of the form and widget instance.
*
* Summary of all of the errors on the page. If a widget emits `$invalid` with `REQUIRED` key,
* then the `$error` object will have a `REQUIRED` key with an array of widgets which have
* emitted this key. `form.$error.REQUIRED == [ widget ]`.
*/
function propertiesUpdate(widget) { /**
widget.$valid = !(widget.$invalid = * @ngdoc property
!(widget.$readonly || widget.$disabled || equals(widget.$error, {}))); * @name $invalid
} * @propertyOf angular.service.$formFactory
* @description
* Property of the form and widget instance.
*
* True if any of the widgets of the form are invalid.
*/
/** /**
* @ngdoc property * @ngdoc property
* @name $error * @name $valid
* @propertyOf angular.service.$formFactory * @propertyOf angular.service.$formFactory
* @description * @description
* Property of the form and widget instance. * Property of the form and widget instance.
* *
* Summary of all of the errors on the page. If a widget emits `$invalid` with `REQUIRED` key, * True if all of the widgets of the form are valid.
* then the `$error` object will have a `REQUIRED` key with an array of widgets which have */
* emitted this key. `form.$error.REQUIRED == [ widget ]`.
*/
/** /**
* @ngdoc property * @ngdoc event
* @name $invalid * @name angular.service.$formFactory#$valid
* @propertyOf angular.service.$formFactory * @eventOf angular.service.$formFactory
* @description * @eventType listen on form
* Property of the form and widget instance. * @description
* * Upon receiving the `$valid` event from the widget update the `$error`, `$valid` and `$invalid`
* True if any of the widgets of the form are invalid. * properties of both the widget as well as the from.
*/ *
* @param {String} validationKey The validation key to be used when updating the `$error` object.
* The validation key is what will allow the template to bind to a specific validation error
* such as `<div ng:show="form.$error.KEY">error for key</div>`.
*/
/** /**
* @ngdoc property * @ngdoc event
* @name $valid * @name angular.service.$formFactory#$invalid
* @propertyOf angular.service.$formFactory * @eventOf angular.service.$formFactory
* @description * @eventType listen on form
* Property of the form and widget instance. * @description
* * Upon receiving the `$invalid` event from the widget update the `$error`, `$valid` and `$invalid`
* True if all of the widgets of the form are valid. * properties of both the widget as well as the from.
*/ *
* @param {String} validationKey The validation key to be used when updating the `$error` object.
* The validation key is what will allow the template to bind to a specific validation error
* such as `<div ng:show="form.$error.KEY">error for key</div>`.
*/
/** /**
* @ngdoc event * @ngdoc event
* @name angular.service.$formFactory#$valid * @name angular.service.$formFactory#$validate
* @eventOf angular.service.$formFactory * @eventOf angular.service.$formFactory
* @eventType listen on form * @eventType emit on widget
* @description * @description
* Upon receiving the `$valid` event from the widget update the `$error`, `$valid` and `$invalid` * Emit the `$validate` event on the widget, giving a widget a chance to emit a
* properties of both the widget as well as the from. * `$valid` / `$invalid` event base on its state. The `$validate` event is triggered when the
* * model or the view changes.
* @param {String} validationKey The validation key to be used when updating the `$error` object. */
* The validation key is what will allow the template to bind to a specific validation error
* such as `<div ng:show="form.$error.KEY">error for key</div>`.
*/
/** /**
* @ngdoc event * @ngdoc event
* @name angular.service.$formFactory#$invalid * @name angular.service.$formFactory#$viewChange
* @eventOf angular.service.$formFactory * @eventOf angular.service.$formFactory
* @eventType listen on form * @eventType listen on widget
* @description * @description
* Upon receiving the `$invalid` event from the widget update the `$error`, `$valid` and `$invalid` * A widget is responsible for emitting this event whenever the view changes do to user interaction.
* properties of both the widget as well as the from. * The event takes a `$viewValue` parameter, which is the new value of the view. This
* * event triggers a call to `$parseView()` as well as `$validate` event on widget.
* @param {String} validationKey The validation key to be used when updating the `$error` object. *
* The validation key is what will allow the template to bind to a specific validation error * @param {*} viewValue The new value for the view which will be assigned to `widget.$viewValue`.
* such as `<div ng:show="form.$error.KEY">error for key</div>`. */
*/
/** function FormController() {
* @ngdoc event var form = this,
* @name angular.service.$formFactory#$validate $error = form.$error = {};
* @eventOf angular.service.$formFactory
* @eventType emit on widget
* @description
* Emit the `$validate` event on the widget, giving a widget a chance to emit a
* `$valid` / `$invalid` event base on its state. The `$validate` event is triggered when the
* model or the view changes.
*/
/** form.$on('$destroy', function(event){
* @ngdoc event var widget = event.targetScope;
* @name angular.service.$formFactory#$viewChange if (widget.$widgetId) {
* @eventOf angular.service.$formFactory delete form[widget.$widgetId];
* @eventType listen on widget }
* @description forEach($error, removeWidget, widget);
* A widget is responsible for emitting this event whenever the view changes do to user interaction. });
* The event takes a `$viewValue` parameter, which is the new value of the view. This
* event triggers a call to `$parseView()` as well as `$validate` event on widget.
*
* @param {*} viewValue The new value for the view which will be assigned to `widget.$viewValue`.
*/
function FormController() { form.$on('$valid', function(event, error){
var form = this, var widget = event.targetScope;
$error = form.$error = {}; delete widget.$error[error];
propertiesUpdate(widget);
removeWidget($error[error], error, widget);
});
form.$on('$destroy', function(event){ form.$on('$invalid', function(event, error){
var widget = event.targetScope; var widget = event.targetScope;
if (widget.$widgetId) { addWidget(error, widget);
delete form[widget.$widgetId]; widget.$error[error] = true;
} propertiesUpdate(widget);
forEach($error, removeWidget, widget); });
});
form.$on('$valid', function(event, error){ propertiesUpdate(form);
var widget = event.targetScope;
delete widget.$error[error];
propertiesUpdate(widget);
removeWidget($error[error], error, widget);
});
form.$on('$invalid', function(event, error){ function removeWidget(queue, errorKey, widget) {
var widget = event.targetScope; if (queue) {
addWidget(error, widget); widget = widget || this; // so that we can be used in forEach;
widget.$error[error] = true; for (var i = 0, length = queue.length; i < length; i++) {
propertiesUpdate(widget); if (queue[i] === widget) {
}); queue.splice(i, 1);
if (!queue.length) {
propertiesUpdate(form); delete $error[errorKey];
}
function removeWidget(queue, errorKey, widget) {
if (queue) {
widget = widget || this; // so that we can be used in forEach;
for (var i = 0, length = queue.length; i < length; i++) {
if (queue[i] === widget) {
queue.splice(i, 1);
if (!queue.length) {
delete $error[errorKey];
} }
} }
propertiesUpdate(form);
} }
}
function addWidget(errorKey, widget) {
var queue = $error[errorKey];
if (queue) {
for (var i = 0, length = queue.length; i < length; i++) {
if (queue[i] === widget) {
return;
}
}
} else {
$error[errorKey] = queue = [];
}
queue.push(widget);
propertiesUpdate(form); propertiesUpdate(form);
} }
} }
function addWidget(errorKey, widget) {
var queue = $error[errorKey]; /**
if (queue) { * @ngdoc method
for (var i = 0, length = queue.length; i < length; i++) { * @name $createWidget
if (queue[i] === widget) { * @methodOf angular.service.$formFactory
return; * @description
} *
* Use form's `$createWidget` instance method to create new widgets. The widgets can be created
* using an alias which makes the accessible from the form and available for data-binding,
* useful for displaying validation error messages.
*
* The creation of a widget sets up:
*
* - `$watch` of `expression` on `model` scope. This code path syncs the model to the view.
* The `$watch` listener will:
*
* - assign the new model value of `expression` to `widget.$modelValue`.
* - call `widget.$parseModel` method if present. The `$parseModel` is responsible for copying
* the `widget.$modelValue` to `widget.$viewValue` and optionally converting the data.
* (For example to convert a number into string)
* - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid`
* event.
* - call `widget.$render()` method on widget. The `$render` method is responsible for
* reading the `widget.$viewValue` and updating the DOM.
*
* - Listen on `$viewChange` event from the `widget`. This code path syncs the view to the model.
* The `$viewChange` listener will:
*
* - assign the value to `widget.$viewValue`.
* - call `widget.$parseView` method if present. The `$parseView` is responsible for copying
* the `widget.$viewValue` to `widget.$modelValue` and optionally converting the data.
* (For example to convert a string into number)
* - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid`
* event.
* - Assign the `widget.$modelValue` to the `expression` on the `model` scope.
*
* - Creates these set of properties on the `widget` which are updated as a response to the
* `$valid` / `$invalid` events:
*
* - `$error` - object - validation errors will be published as keys on this object.
* Data-binding to this property is useful for displaying the validation errors.
* - `$valid` - boolean - true if there are no validation errors
* - `$invalid` - boolean - opposite of `$valid`.
* @param {Object} params Named parameters:
*
* - `scope` - `{Scope}` - The scope to which the model for this widget is attached.
* - `model` - `{string}` - The name of the model property on model scope.
* - `controller` - {WidgetController} - The controller constructor function.
* The controller constructor should create these instance methods.
* - `$parseView()`: optional method responsible for copying `$viewVale` to `$modelValue`.
* The method may fire `$valid`/`$invalid` events.
* - `$parseModel()`: optional method responsible for copying `$modelVale` to `$viewValue`.
* The method may fire `$valid`/`$invalid` events.
* - `$render()`: required method which needs to update the DOM of the widget to match the
* `$viewValue`.
*
* - `controllerArgs` - `{Array}` (Optional) - Any extra arguments will be curried to the
* WidgetController constructor.
* - `onChange` - `{(string|function())}` (Optional) - Expression to execute when user changes the
* value.
* - `alias` - `{string}` (Optional) - The name of the form property under which the widget
* instance should be published. The name should be unique for each form.
* @returns {Widget} Instance of a widget scope.
*/
FormController.prototype.$createWidget = function(params) {
var form = this,
modelScope = params.scope,
onChange = params.onChange,
alias = params.alias,
scopeGet = parser(params.model).assignable(),
scopeSet = scopeGet.assign,
widget = this.$new(params.controller, params.controllerArgs);
widget.$error = {};
// Set the state to something we know will change to get the process going.
widget.$modelValue = Number.NaN;
// watch for scope changes and update the view appropriately
modelScope.$watch(scopeGet, function(scope, value) {
if (!equals(widget.$modelValue, value)) {
widget.$modelValue = value;
widget.$parseModel ? widget.$parseModel() : (widget.$viewValue = value);
widget.$emit('$validate');
widget.$render && widget.$render();
} }
});
widget.$on('$viewChange', function(event, viewValue){
if (!equals(widget.$viewValue, viewValue)) {
widget.$viewValue = viewValue;
widget.$parseView ? widget.$parseView() : (widget.$modelValue = widget.$viewValue);
scopeSet(modelScope, widget.$modelValue);
if (onChange) modelScope.$eval(onChange);
widget.$emit('$validate');
}
});
propertiesUpdate(widget);
// assign the widgetModel to the form
if (alias && !form.hasOwnProperty(alias)) {
form[alias] = widget;
widget.$widgetId = alias;
} else { } else {
$error[errorKey] = queue = []; alias = null;
} }
queue.push(widget);
propertiesUpdate(form); return widget;
} };
} }
/**
* @ngdoc method
* @name $createWidget
* @methodOf angular.service.$formFactory
* @description
*
* Use form's `$createWidget` instance method to create new widgets. The widgets can be created
* using an alias which makes the accessible from the form and available for data-binding,
* useful for displaying validation error messages.
*
* The creation of a widget sets up:
*
* - `$watch` of `expression` on `model` scope. This code path syncs the model to the view.
* The `$watch` listener will:
*
* - assign the new model value of `expression` to `widget.$modelValue`.
* - call `widget.$parseModel` method if present. The `$parseModel` is responsible for copying
* the `widget.$modelValue` to `widget.$viewValue` and optionally converting the data.
* (For example to convert a number into string)
* - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid`
* event.
* - call `widget.$render()` method on widget. The `$render` method is responsible for
* reading the `widget.$viewValue` and updating the DOM.
*
* - Listen on `$viewChange` event from the `widget`. This code path syncs the view to the model.
* The `$viewChange` listener will:
*
* - assign the value to `widget.$viewValue`.
* - call `widget.$parseView` method if present. The `$parseView` is responsible for copying
* the `widget.$viewValue` to `widget.$modelValue` and optionally converting the data.
* (For example to convert a string into number)
* - emits `$validate` event on widget giving a widget a chance to emit `$valid` / `$invalid`
* event.
* - Assign the `widget.$modelValue` to the `expression` on the `model` scope.
*
* - Creates these set of properties on the `widget` which are updated as a response to the
* `$valid` / `$invalid` events:
*
* - `$error` - object - validation errors will be published as keys on this object.
* Data-binding to this property is useful for displaying the validation errors.
* - `$valid` - boolean - true if there are no validation errors
* - `$invalid` - boolean - opposite of `$valid`.
* @param {Object} params Named parameters:
*
* - `scope` - `{Scope}` - The scope to which the model for this widget is attached.
* - `model` - `{string}` - The name of the model property on model scope.
* - `controller` - {WidgetController} - The controller constructor function.
* The controller constructor should create these instance methods.
* - `$parseView()`: optional method responsible for copying `$viewVale` to `$modelValue`.
* The method may fire `$valid`/`$invalid` events.
* - `$parseModel()`: optional method responsible for copying `$modelVale` to `$viewValue`.
* The method may fire `$valid`/`$invalid` events.
* - `$render()`: required method which needs to update the DOM of the widget to match the
* `$viewValue`.
*
* - `controllerArgs` - `{Array}` (Optional) - Any extra arguments will be curried to the
* WidgetController constructor.
* - `onChange` - `{(string|function())}` (Optional) - Expression to execute when user changes the
* value.
* - `alias` - `{string}` (Optional) - The name of the form property under which the widget
* instance should be published. The name should be unique for each form.
* @returns {Widget} Instance of a widget scope.
*/
FormController.prototype.$createWidget = function(params) {
var form = this,
modelScope = params.scope,
onChange = params.onChange,
alias = params.alias,
scopeGet = parser(params.model).assignable(),
scopeSet = scopeGet.assign,
widget = this.$new(params.controller, params.controllerArgs);
widget.$error = {};
// Set the state to something we know will change to get the process going.
widget.$modelValue = Number.NaN;
// watch for scope changes and update the view appropriately
modelScope.$watch(scopeGet, function(scope, value) {
if (!equals(widget.$modelValue, value)) {
widget.$modelValue = value;
widget.$parseModel ? widget.$parseModel() : (widget.$viewValue = value);
widget.$emit('$validate');
widget.$render && widget.$render();
}
});
widget.$on('$viewChange', function(event, viewValue){
if (!equals(widget.$viewValue, viewValue)) {
widget.$viewValue = viewValue;
widget.$parseView ? widget.$parseView() : (widget.$modelValue = widget.$viewValue);
scopeSet(modelScope, widget.$modelValue);
if (onChange) modelScope.$eval(onChange);
widget.$emit('$validate');
}
});
propertiesUpdate(widget);
// assign the widgetModel to the form
if (alias && !form.hasOwnProperty(alias)) {
form[alias] = widget;
widget.$widgetId = alias;
} else {
alias = null;
}
return widget;
};

View file

@ -10,61 +10,63 @@
* *
* * `id` `{string}` locale id formatted as `languageId-countryId` (e.g. `en-us`) * * `id` `{string}` locale id formatted as `languageId-countryId` (e.g. `en-us`)
*/ */
angularServiceInject('$locale', function() { function $LocaleProvider(){
return { this.$get = function() {
id: 'en-us', return {
id: 'en-us',
NUMBER_FORMATS: { NUMBER_FORMATS: {
DECIMAL_SEP: '.', DECIMAL_SEP: '.',
GROUP_SEP: ',', GROUP_SEP: ',',
PATTERNS: [ PATTERNS: [
{ // Decimal Pattern { // Decimal Pattern
minInt: 1, minInt: 1,
minFrac: 0, minFrac: 0,
maxFrac: 3, maxFrac: 3,
posPre: '', posPre: '',
posSuf: '', posSuf: '',
negPre: '-', negPre: '-',
negSuf: '', negSuf: '',
gSize: 3, gSize: 3,
lgSize: 3 lgSize: 3
},{ //Currency Pattern },{ //Currency Pattern
minInt: 1, minInt: 1,
minFrac: 2, minFrac: 2,
maxFrac: 2, maxFrac: 2,
posPre: '\u00A4', posPre: '\u00A4',
posSuf: '', posSuf: '',
negPre: '(\u00A4', negPre: '(\u00A4',
negSuf: ')', negSuf: ')',
gSize: 3, gSize: 3,
lgSize: 3 lgSize: 3
}
],
CURRENCY_SYM: '$'
},
DATETIME_FORMATS: {
MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
.split(','),
SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
AMPMS: ['AM','PM'],
medium: 'MMM d, y h:mm:ss a',
short: 'M/d/yy h:mm a',
fullDate: 'EEEE, MMMM d, y',
longDate: 'MMMM d, y',
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
shortTime: 'h:mm a'
},
pluralCat: function(num) {
if (num === 1) {
return 'one';
} }
], return 'other';
CURRENCY_SYM: '$'
},
DATETIME_FORMATS: {
MONTH: 'January,February,March,April,May,June,July,August,September,October,November,December'
.split(','),
SHORTMONTH: 'Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec'.split(','),
DAY: 'Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday'.split(','),
SHORTDAY: 'Sun,Mon,Tue,Wed,Thu,Fri,Sat'.split(','),
AMPMS: ['AM','PM'],
medium: 'MMM d, y h:mm:ss a',
short: 'M/d/yy h:mm a',
fullDate: 'EEEE, MMMM d, y',
longDate: 'MMMM d, y',
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
shortTime: 'h:mm a'
},
pluralCat: function(num) {
if (num === 1) {
return 'one';
} }
return 'other'; };
}
}; };
}); }

View file

@ -419,94 +419,99 @@ function locationGetterSetter(property, preprocess) {
* *
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location} * For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location}
*/ */
angularServiceInject('$location', function($rootScope, $browser, $sniffer, $locationConfig, $document) { function $LocationProvider(){
var currentUrl, this.$get = ['$rootScope', '$browser', '$sniffer', '$locationConfig', '$document',
basePath = $browser.baseHref() || '/', function( $rootScope, $browser, $sniffer, $locationConfig, $document) {
pathPrefix = pathPrefixFromBase(basePath), var currentUrl,
hashPrefix = $locationConfig.hashPrefix || '', basePath = $browser.baseHref() || '/',
initUrl = $browser.url(); pathPrefix = pathPrefixFromBase(basePath),
hashPrefix = $locationConfig.hashPrefix || '',
initUrl = $browser.url();
if ($locationConfig.html5Mode) { if ($locationConfig.html5Mode) {
if ($sniffer.history) { if ($sniffer.history) {
currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix); currentUrl = new LocationUrl(convertToHtml5Url(initUrl, basePath, hashPrefix), pathPrefix);
} else { } else {
currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix), currentUrl = new LocationHashbangUrl(convertToHashbangUrl(initUrl, basePath, hashPrefix),
hashPrefix); hashPrefix);
}
// link rewriting
var u = currentUrl,
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
$document.bind('click', function(event) {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
if (event.ctrlKey || event.metaKey || event.which == 2) return;
var elm = jqLite(event.target);
// traverse the DOM up to find first A tag
while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
elm = elm.parent();
} }
var href = elm.attr('href'); // link rewriting
if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return; var u = currentUrl,
absUrlPrefix = composeProtocolHostPort(u.protocol(), u.host(), u.port()) + pathPrefix;
// remove same domain from full url links (IE7 always returns full hrefs) $document.bind('click', function(event) {
href = href.replace(absUrlPrefix, ''); // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
// link to different domain (or base path) if (event.ctrlKey || event.metaKey || event.which == 2) return;
if (href.substr(0, 4) == 'http') return;
// remove pathPrefix from absolute links var elm = jqLite(event.target);
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
currentUrl.url(href); // traverse the DOM up to find first A tag
$rootScope.$apply(); while (elm.length && lowercase(elm[0].nodeName) !== 'a') {
event.preventDefault(); elm = elm.parent();
// hack to work around FF6 bug 684208 when scenario runner clicks on links }
window.angular['ff-684208-preventDefault'] = true;
});
} else {
currentUrl = new LocationHashbangUrl(initUrl, hashPrefix);
}
// rewrite hashbang url <> html5 url var href = elm.attr('href');
if (currentUrl.absUrl() != initUrl) { if (!href || isDefined(elm.attr('ng:ext-link')) || elm.attr('target')) return;
$browser.url(currentUrl.absUrl(), true);
}
// update $location when $browser url changes // remove same domain from full url links (IE7 always returns full hrefs)
$browser.onUrlChange(function(newUrl) { href = href.replace(absUrlPrefix, '');
if (currentUrl.absUrl() != newUrl) {
currentUrl.$$parse(newUrl);
$rootScope.$apply();
}
});
// update browser // link to different domain (or base path)
var changeCounter = 0; if (href.substr(0, 4) == 'http') return;
$rootScope.$watch(function() {
if ($browser.url() != currentUrl.absUrl()) { // remove pathPrefix from absolute links
changeCounter++; href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
$rootScope.$evalAsync(function() {
$browser.url(currentUrl.absUrl(), currentUrl.$$replace); currentUrl.url(href);
currentUrl.$$replace = false; $rootScope.$apply();
event.preventDefault();
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window.angular['ff-684208-preventDefault'] = true;
}); });
} else {
currentUrl = new LocationHashbangUrl(initUrl, hashPrefix);
} }
return changeCounter; // rewrite hashbang url <> html5 url
}); if (currentUrl.absUrl() != initUrl) {
$browser.url(currentUrl.absUrl(), true);
}
return currentUrl; // update $location when $browser url changes
}, ['$rootScope', '$browser', '$sniffer', '$locationConfig', '$document']); $browser.onUrlChange(function(newUrl) {
if (currentUrl.absUrl() != newUrl) {
currentUrl.$$parse(newUrl);
$rootScope.$apply();
}
});
// update browser
var changeCounter = 0;
$rootScope.$watch(function() {
if ($browser.url() != currentUrl.absUrl()) {
changeCounter++;
$rootScope.$evalAsync(function() {
$browser.url(currentUrl.absUrl(), currentUrl.$$replace);
currentUrl.$$replace = false;
});
}
angular.service('$locationConfig', function() { return changeCounter;
return { });
html5Mode: false,
hashPrefix: '' return currentUrl;
}];
}
//TODO(misko): refactor to service
function $LocationConfigProvider(){
this.$get = function() {
return {
html5Mode: false,
hashPrefix: ''
};
}; };
}); }

View file

@ -34,64 +34,78 @@
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
var $logFactory; //reference to be used only in tests
angularServiceInject("$log", $logFactory = function($window){
return {
/**
* @ngdoc method
* @name angular.service.$log#log
* @methodOf angular.service.$log
*
* @description
* Write a log message
*/
log: consoleLog('log'),
/** function $LogProvider(){
* @ngdoc method this.$get = ['$window', function($window){
* @name angular.service.$log#warn return {
* @methodOf angular.service.$log /**
* * @ngdoc method
* @description * @name angular.service.$log#log
* Write a warning message * @methodOf angular.service.$log
*/ *
warn: consoleLog('warn'), * @description
* Write a log message
*/
log: consoleLog('log'),
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$log#info * @name angular.service.$log#warn
* @methodOf angular.service.$log * @methodOf angular.service.$log
* *
* @description * @description
* Write an information message * Write a warning message
*/ */
info: consoleLog('info'), warn: consoleLog('warn'),
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$log#error * @name angular.service.$log#info
* @methodOf angular.service.$log * @methodOf angular.service.$log
* *
* @description * @description
* Write an error message * Write an information message
*/ */
error: consoleLog('error') info: consoleLog('info'),
};
function consoleLog(type) { /**
var console = $window.console || {}; * @ngdoc method
var logFn = console[type] || console.log || noop; * @name angular.service.$log#error
if (logFn.apply) { * @methodOf angular.service.$log
return function() { *
var args = []; * @description
forEach(arguments, function(arg){ * Write an error message
args.push(formatError(arg)); */
}); error: consoleLog('error')
return logFn.apply(console, args); };
};
} else { function formatError(arg) {
// we are IE, in which case there is nothing we can do if (arg instanceof Error) {
return logFn; if (arg.stack) {
arg = (arg.message && arg.stack.indexOf(arg.message) === -1) ?
'Error: ' + arg.message + '\n' + arg.stack : arg.stack;
} else if (arg.sourceURL) {
arg = arg.message + '\n' + arg.sourceURL + ':' + arg.line;
}
}
return arg;
} }
}
}, ['$window']); function consoleLog(type) {
var console = $window.console || {};
var logFn = console[type] || console.log || noop;
if (logFn.apply) {
return function() {
var args = [];
forEach(arguments, function(arg){
args.push(formatError(arg));
});
return logFn.apply(console, args);
};
} else {
// we are IE, in which case there is nothing we can do
return logFn;
}
}
}];
}

View file

@ -200,7 +200,9 @@
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularServiceInject('$resource', function($xhr){ function $ResourceProvider() {
var resource = new ResourceFactory($xhr); this.$get = ['$xhr.cache', function($xhr){
return bind(resource, resource.route); var resource = new ResourceFactory($xhr);
}, ['$xhr.cache']); return bind(resource, resource.route);
}];
}

View file

@ -62,264 +62,267 @@
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularServiceInject('$route', function($rootScope, $location, $routeParams) { function $RouteProvider(){
/** this.$get = ['$rootScope', '$location', '$routeParams',
* @ngdoc event function( $rootScope, $location, $routeParams) {
* @name angular.service.$route#$beforeRouteChange /**
* @eventOf angular.service.$route * @ngdoc event
* @eventType broadcast on root scope * @name angular.service.$route#$beforeRouteChange
* @description * @eventOf angular.service.$route
* Broadcasted before a route change. * @eventType broadcast on root scope
* * @description
* @param {Route} next Future route information. * Broadcasted before a route change.
* @param {Route} current Current route information. *
* * @param {Route} next Future route information.
* The `Route` object extends the route definition with the following properties. * @param {Route} current Current route information.
* *
* * `scope` - The instance of the route controller. * The `Route` object extends the route definition with the following properties.
* * `params` - The current {@link angular.service.$routeParams params}. *
* * * `scope` - The instance of the route controller.
*/ * * `params` - The current {@link angular.service.$routeParams params}.
*
*/
/** /**
* @ngdoc event * @ngdoc event
* @name angular.service.$route#$afterRouteChange * @name angular.service.$route#$afterRouteChange
* @eventOf angular.service.$route * @eventOf angular.service.$route
* @eventType broadcast on root scope * @eventType broadcast on root scope
* @description * @description
* Broadcasted after a route change. * Broadcasted after a route change.
* *
* @param {Route} current Current route information. * @param {Route} current Current route information.
* @param {Route} previous Previous route information. * @param {Route} previous Previous route information.
* *
* The `Route` object extends the route definition with the following properties. * The `Route` object extends the route definition with the following properties.
* *
* * `scope` - The instance of the route controller. * * `scope` - The instance of the route controller.
* * `params` - The current {@link angular.service.$routeParams params}. * * `params` - The current {@link angular.service.$routeParams params}.
* *
*/ */
/** /**
* @ngdoc event * @ngdoc event
* @name angular.service.$route#$routeUpdate * @name angular.service.$route#$routeUpdate
* @eventOf angular.service.$route * @eventOf angular.service.$route
* @eventType emit on the current route scope * @eventType emit on the current route scope
* @description * @description
* *
* The `reloadOnSearch` property has been set to false, and we are reusing the same * The `reloadOnSearch` property has been set to false, and we are reusing the same
* instance of the Controller. * instance of the Controller.
*/ */
var routes = {}, var routes = {},
matcher = switchRouteMatcher, matcher = switchRouteMatcher,
parentScope = $rootScope, parentScope = $rootScope,
dirty = 0, dirty = 0,
forceReload = false, forceReload = false,
$route = { $route = {
routes: routes, routes: routes,
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$route#parent * @name angular.service.$route#parent
* @methodOf angular.service.$route * @methodOf angular.service.$route
* *
* @param {Scope} [scope=rootScope] Scope to be used as parent for newly created * @param {Scope} [scope=rootScope] Scope to be used as parent for newly created
* `$route.current.scope` scopes. * `$route.current.scope` scopes.
* *
* @description * @description
* Sets a scope to be used as the parent scope for scopes created on route change. If not * Sets a scope to be used as the parent scope for scopes created on route change. If not
* set, defaults to the root scope. * set, defaults to the root scope.
*/ */
parent: function(scope) { parent: function(scope) {
if (scope) parentScope = scope; if (scope) parentScope = scope;
}, },
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$route#when * @name angular.service.$route#when
* @methodOf angular.service.$route * @methodOf angular.service.$route
* *
* @param {string} path Route path (matched against `$location.hash`) * @param {string} path Route path (matched against `$location.hash`)
* @param {Object} route Mapping information to be assigned to `$route.current` on route * @param {Object} route Mapping information to be assigned to `$route.current` on route
* match. * match.
* *
* Object properties: * Object properties:
* *
* - `controller` `{function()=}` Controller fn that should be associated with newly * - `controller` `{function()=}` Controller fn that should be associated with newly
* created scope. * created scope.
* - `template` `{string=}` path to an html template that should be used by * - `template` `{string=}` path to an html template that should be used by
* {@link angular.widget.ng:view ng:view} or * {@link angular.widget.ng:view ng:view} or
* {@link angular.widget.ng:include ng:include} widgets. * {@link angular.widget.ng:include ng:include} widgets.
* - `redirectTo` {(string|function())=} value to update * - `redirectTo` {(string|function())=} value to update
* {@link angular.service.$location $location} path with and trigger route redirection. * {@link angular.service.$location $location} path with and trigger route redirection.
* *
* If `redirectTo` is a function, it will be called with the following parameters: * If `redirectTo` is a function, it will be called with the following parameters:
* *
* - `{Object.<string>}` - route parameters extracted from the current * - `{Object.<string>}` - route parameters extracted from the current
* `$location.path()` by applying the current route template. * `$location.path()` by applying the current route template.
* - `{string}` - current `$location.path()` * - `{string}` - current `$location.path()`
* - `{Object}` - current `$location.search()` * - `{Object}` - current `$location.search()`
* *
* The custom `redirectTo` function is expected to return a string which will be used * The custom `redirectTo` function is expected to return a string which will be used
* to update `$location.path()` and `$location.search()`. * to update `$location.path()` and `$location.search()`.
* *
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search() * - `[reloadOnSearch=true]` - {boolean=} - reload route when only $location.search()
* changes. * changes.
* *
* If the option is set to false and url in the browser changes, then * If the option is set to false and url in the browser changes, then
* $routeUpdate event is emited on the current route scope. You can use this event to * $routeUpdate event is emited on the current route scope. You can use this event to
* react to {@link angular.service.$routeParams} changes: * react to {@link angular.service.$routeParams} changes:
* *
* function MyCtrl($route, $routeParams) { * function MyCtrl($route, $routeParams) {
* this.$on('$routeUpdate', function() { * this.$on('$routeUpdate', function() {
* // do stuff with $routeParams * // do stuff with $routeParams
* }); * });
* } * }
* *
* @returns {Object} route object * @returns {Object} route object
* *
* @description * @description
* Adds a new route definition to the `$route` service. * Adds a new route definition to the `$route` service.
*/ */
when: function(path, route) { when: function(path, route) {
var routeDef = routes[path]; var routeDef = routes[path];
if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true}; if (!routeDef) routeDef = routes[path] = {reloadOnSearch: true};
if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions? if (route) extend(routeDef, route); // TODO(im): what the heck? merge two route definitions?
dirty++; dirty++;
return routeDef; return routeDef;
}, },
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$route#otherwise * @name angular.service.$route#otherwise
* @methodOf angular.service.$route * @methodOf angular.service.$route
* *
* @description * @description
* Sets route definition that will be used on route change when no other route definition * Sets route definition that will be used on route change when no other route definition
* is matched. * is matched.
* *
* @param {Object} params Mapping information to be assigned to `$route.current`. * @param {Object} params Mapping information to be assigned to `$route.current`.
*/ */
otherwise: function(params) { otherwise: function(params) {
$route.when(null, params); $route.when(null, params);
}, },
/** /**
* @ngdoc method * @ngdoc method
* @name angular.service.$route#reload * @name angular.service.$route#reload
* @methodOf angular.service.$route * @methodOf angular.service.$route
* *
* @description * @description
* Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next * Causes `$route` service to reload (and recreate the `$route.current` scope) upon the next
* eval even if {@link angular.service.$location $location} hasn't changed. * eval even if {@link angular.service.$location $location} hasn't changed.
*/ */
reload: function() { reload: function() {
dirty++; dirty++;
forceReload = true; forceReload = true;
} }
}; };
$rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute); $rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute);
return $route; return $route;
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
function switchRouteMatcher(on, when) { function switchRouteMatcher(on, when) {
// TODO(i): this code is convoluted and inefficient, we should construct the route matching // TODO(i): this code is convoluted and inefficient, we should construct the route matching
// regex only once and then reuse it // regex only once and then reuse it
var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$', var regex = '^' + when.replace(/([\.\\\(\)\^\$])/g, "\\$1") + '$',
params = [], params = [],
dst = {}; dst = {};
forEach(when.split(/\W/), function(param) { forEach(when.split(/\W/), function(param) {
if (param) { if (param) {
var paramRegExp = new RegExp(":" + param + "([\\W])"); var paramRegExp = new RegExp(":" + param + "([\\W])");
if (regex.match(paramRegExp)) { if (regex.match(paramRegExp)) {
regex = regex.replace(paramRegExp, "([^\\/]*)$1"); regex = regex.replace(paramRegExp, "([^\\/]*)$1");
params.push(param); params.push(param);
}
}
});
var match = on.match(new RegExp(regex));
if (match) {
forEach(params, function(name, index) {
dst[name] = match[index + 1];
});
}
return match ? dst : null;
}
function updateRoute() {
var next = parseRoute(),
last = $route.current,
Controller;
if (next && last && next.$route === last.$route
&& equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
$route.current = next;
copy(next.params, $routeParams);
last.scope && last.scope.$emit('$routeUpdate');
} else {
forceReload = false;
$rootScope.$broadcast('$beforeRouteChange', next, last);
last && last.scope && last.scope.$destroy();
$route.current = next;
if (next) {
if (next.redirectTo) {
if (isString(next.redirectTo)) {
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
.replace();
} else {
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
.replace();
} }
} else {
copy(next.params, $routeParams);
(Controller = next.controller) && inferInjectionArgs(Controller);
next.scope = parentScope.$new(Controller);
} }
});
var match = on.match(new RegExp(regex));
if (match) {
forEach(params, function(name, index) {
dst[name] = match[index + 1];
});
} }
$rootScope.$broadcast('$afterRouteChange', next, last); return match ? dst : null;
} }
}
function updateRoute() {
var next = parseRoute(),
last = $route.current,
Controller;
/** if (next && last && next.$route === last.$route
* @returns the current active route, by matching it against the URL && equals(next.pathParams, last.pathParams) && !next.reloadOnSearch && !forceReload) {
*/ $route.current = next;
function parseRoute() { copy(next.params, $routeParams);
// Match a route last.scope && last.scope.$emit('$routeUpdate');
var params, match;
forEach(routes, function(route, path) {
if (!match && (params = matcher($location.path(), path))) {
match = inherit(route, {
params: extend({}, $location.search(), params),
pathParams: params});
match.$route = route;
}
});
// No route matched; fallback to "otherwise" route
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
}
/**
* @returns interpolation of the redirect path with the parametrs
*/
function interpolate(string, params) {
var result = [];
forEach((string||'').split(':'), function(segment, i) {
if (i == 0) {
result.push(segment);
} else { } else {
var segmentMatch = segment.match(/(\w+)(.*)/); forceReload = false;
var key = segmentMatch[1]; $rootScope.$broadcast('$beforeRouteChange', next, last);
result.push(params[key]); last && last.scope && last.scope.$destroy();
result.push(segmentMatch[2] || ''); $route.current = next;
delete params[key]; if (next) {
if (next.redirectTo) {
if (isString(next.redirectTo)) {
$location.path(interpolate(next.redirectTo, next.params)).search(next.params)
.replace();
} else {
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
.replace();
}
} else {
copy(next.params, $routeParams);
(Controller = next.controller) && inferInjectionArgs(Controller);
next.scope = parentScope.$new(Controller);
}
}
$rootScope.$broadcast('$afterRouteChange', next, last);
} }
}); }
return result.join('');
}
}, ['$rootScope', '$location', '$routeParams']); /**
* @returns the current active route, by matching it against the URL
*/
function parseRoute() {
// Match a route
var params, match;
forEach(routes, function(route, path) {
if (!match && (params = matcher($location.path(), path))) {
match = inherit(route, {
params: extend({}, $location.search(), params),
pathParams: params});
match.$route = route;
}
});
// No route matched; fallback to "otherwise" route
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
}
/**
* @returns interpolation of the redirect path with the parametrs
*/
function interpolate(string, params) {
var result = [];
forEach((string||'').split(':'), function(segment, i) {
if (i == 0) {
result.push(segment);
} else {
var segmentMatch = segment.match(/(\w+)(.*)/);
var key = segmentMatch[1];
result.push(params[key]);
result.push(segmentMatch[2] || '');
delete params[key];
}
});
return result.join('');
}
}];
}

View file

@ -25,6 +25,6 @@
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'} * $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
* </pre> * </pre>
*/ */
angularService('$routeParams', function() { function $RouteParamsProvider() {
return {}; this.$get = valueFn({});
}); }

File diff suppressed because it is too large Load diff

View file

@ -11,13 +11,15 @@
* @description * @description
* This is very simple implementation of testing browser's features. * This is very simple implementation of testing browser's features.
*/ */
angularServiceInject('$sniffer', function($window) { function $SnifferProvider(){
if ($window.Modernizr) return $window.Modernizr; this.$get = ['$window', function($window){
if ($window.Modernizr) return $window.Modernizr;
return { return {
history: !!($window.history && $window.history.pushState), history: !!($window.history && $window.history.pushState),
hashchange: 'onhashchange' in $window && hashchange: 'onhashchange' in $window &&
// IE8 compatible mode lies // IE8 compatible mode lies
(!$window.document.documentMode || $window.document.documentMode > 7) (!$window.document.documentMode || $window.document.documentMode > 7)
}; };
}, ['$window']); }];
}

View file

@ -23,4 +23,6 @@
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularServiceInject("$window", bind(window, identity, window)); function $WindowProvider(){
this.$get = valueFn(window);
}

View file

@ -11,76 +11,79 @@
* *
* @example * @example
*/ */
angularServiceInject('$xhr.bulk', function($rootScope, $xhr, $error, $log){ function $XhrBulkProvider() {
var requests = []; this.$get = ['$rootScope', '$xhr', '$xhr.error', '$log',
function bulkXHR(method, url, post, success, error) { function( $rootScope, $xhr, $error, $log) {
if (isFunction(post)) { var requests = [];
error = success; function bulkXHR(method, url, post, success, error) {
success = post; if (isFunction(post)) {
post = null; error = success;
} success = post;
var currentQueue; post = null;
forEach(bulkXHR.urls, function(queue){
if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
currentQueue = queue;
} }
}); var currentQueue;
if (currentQueue) { forEach(bulkXHR.urls, function(queue){
if (!currentQueue.requests) currentQueue.requests = []; if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
var request = { currentQueue = queue;
method: method, }
url: url, });
data: post, if (currentQueue) {
success: success}; if (!currentQueue.requests) currentQueue.requests = [];
if (error) request.error = error; var request = {
currentQueue.requests.push(request); method: method,
} else { url: url,
$xhr(method, url, post, success, error); data: post,
} success: success};
} if (error) request.error = error;
bulkXHR.urls = {}; currentQueue.requests.push(request);
bulkXHR.flush = function(success, errorback) { } else {
assertArgFn(success = success || noop, 0); $xhr(method, url, post, success, error);
assertArgFn(errorback = errorback || noop, 1);
forEach(bulkXHR.urls, function(queue, url) {
var currentRequests = queue.requests;
if (currentRequests && currentRequests.length) {
queue.requests = [];
queue.callbacks = [];
$xhr('POST', url, {requests: currentRequests},
function(code, response) {
forEach(response, function(response, i) {
try {
if (response.status == 200) {
(currentRequests[i].success || noop)(response.status, response.response);
} else if (isFunction(currentRequests[i].error)) {
currentRequests[i].error(response.status, response.response);
} else {
$error(currentRequests[i], response);
}
} catch(e) {
$log.error(e);
}
});
success();
},
function(code, response) {
forEach(currentRequests, function(request, i) {
try {
if (isFunction(request.error)) {
request.error(code, response);
} else {
$error(request, response);
}
} catch(e) {
$log.error(e);
}
});
noop();
});
} }
}); }
}; bulkXHR.urls = {};
$rootScope.$watch(function() { bulkXHR.flush(); }); bulkXHR.flush = function(success, errorback) {
return bulkXHR; assertArgFn(success = success || noop, 0);
}, ['$rootScope', '$xhr', '$xhr.error', '$log']); assertArgFn(errorback = errorback || noop, 1);
forEach(bulkXHR.urls, function(queue, url) {
var currentRequests = queue.requests;
if (currentRequests && currentRequests.length) {
queue.requests = [];
queue.callbacks = [];
$xhr('POST', url, {requests: currentRequests},
function(code, response) {
forEach(response, function(response, i) {
try {
if (response.status == 200) {
(currentRequests[i].success || noop)(response.status, response.response);
} else if (isFunction(currentRequests[i].error)) {
currentRequests[i].error(response.status, response.response);
} else {
$error(currentRequests[i], response);
}
} catch(e) {
$log.error(e);
}
});
success();
},
function(code, response) {
forEach(currentRequests, function(request, i) {
try {
if (isFunction(request.error)) {
request.error(code, response);
} else {
$error(request, response);
}
} catch(e) {
$log.error(e);
}
});
noop();
});
}
});
};
$rootScope.$watch(function() { bulkXHR.flush(); });
return bulkXHR;
}];
}

View file

@ -28,87 +28,91 @@
* cached entry. The `success` function will be called when the response is received. * cached entry. The `success` function will be called when the response is received.
* @param {boolean=} [sync=false] in case of cache hit execute `success` synchronously. * @param {boolean=} [sync=false] in case of cache hit execute `success` synchronously.
*/ */
angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) { function $XhrCacheProvider() {
var inflight = {}; this.$get = ['$xhr.bulk', '$defer', '$xhr.error', '$log',
function cache(method, url, post, success, error, verifyCache, sync) { function($xhr, $defer, $error, $log) {
if (isFunction(post)) { var inflight = {};
if (!isFunction(success)) { function cache(method, url, post, success, error, verifyCache, sync) {
verifyCache = success; if (isFunction(post)) {
sync = error; if (!isFunction(success)) {
error = null; verifyCache = success;
} else { sync = error;
error = null;
} else {
sync = verifyCache;
verifyCache = error;
error = success;
}
success = post;
post = null;
} else if (!isFunction(error)) {
sync = verifyCache; sync = verifyCache;
verifyCache = error; verifyCache = error;
error = success; error = null;
} }
success = post;
post = null;
} else if (!isFunction(error)) {
sync = verifyCache;
verifyCache = error;
error = null;
}
if (method == 'GET') { if (method == 'GET') {
var data, dataCached; var data, dataCached;
if ((dataCached = cache.data[url])) { if ((dataCached = cache.data[url])) {
if (sync) { if (sync) {
success(200, copy(dataCached.value)); success(200, copy(dataCached.value));
} else { } else {
$defer(function() { success(200, copy(dataCached.value)); }); $defer(function() { success(200, copy(dataCached.value)); });
}
if (!verifyCache)
return;
} }
if (!verifyCache) if ((data = inflight[url])) {
return; data.successes.push(success);
} data.errors.push(error);
} else {
if ((data = inflight[url])) { inflight[url] = {successes: [success], errors: [error]};
data.successes.push(success); cache.delegate(method, url, post,
data.errors.push(error); function(status, response) {
} else { if (status == 200)
inflight[url] = {successes: [success], errors: [error]}; cache.data[url] = {value: response};
cache.delegate(method, url, post, var successes = inflight[url].successes;
function(status, response) { delete inflight[url];
if (status == 200) forEach(successes, function(success) {
cache.data[url] = {value: response}; try {
var successes = inflight[url].successes; (success||noop)(status, copy(response));
delete inflight[url]; } catch(e) {
forEach(successes, function(success) { $log.error(e);
try {
(success||noop)(status, copy(response));
} catch(e) {
$log.error(e);
}
});
},
function(status, response) {
var errors = inflight[url].errors,
successes = inflight[url].successes;
delete inflight[url];
forEach(errors, function(error, i) {
try {
if (isFunction(error)) {
error(status, copy(response));
} else {
$error(
{method: method, url: url, data: post, success: successes[i]},
{status: status, body: response});
} }
} catch(e) { });
$log.error(e); },
} function(status, response) {
}); var errors = inflight[url].errors,
}); successes = inflight[url].successes;
} delete inflight[url];
} else { forEach(errors, function(error, i) {
cache.data = {}; try {
cache.delegate(method, url, post, success, error); if (isFunction(error)) {
error(status, copy(response));
} else {
$error(
{method: method, url: url, data: post, success: successes[i]},
{status: status, body: response});
}
} catch(e) {
$log.error(e);
}
});
});
}
} else {
cache.data = {};
cache.delegate(method, url, post, success, error);
}
} }
} cache.data = {};
cache.data = {}; cache.delegate = $xhr;
cache.delegate = $xhr; return cache;
return cache; }];
}, ['$xhr.bulk', '$defer', '$xhr.error', '$log']);
}

View file

@ -35,8 +35,10 @@
</doc:source> </doc:source>
</doc:example> </doc:example>
*/ */
angularServiceInject('$xhr.error', function($log){ function $XhrErrorProvider() {
return function(request, response){ this.$get = ['$log', function($log) {
$log.error('ERROR: XHR: ' + request.url, request, response); return function(request, response){
}; $log.error('ERROR: XHR: ' + request.url, request, response);
}, ['$log']); };
}];
}

View file

@ -171,58 +171,61 @@
</doc:scenario> </doc:scenario>
</doc:example> </doc:example>
*/ */
angularServiceInject('$xhr', function($rootScope, $browser, $error, $log){ function $XhrProvider() {
var xhrHeaderDefaults = { this.$get = ['$rootScope', '$browser', '$xhr.error', '$log',
common: { function( $rootScope, $browser, $error, $log){
"Accept": "application/json, text/plain, */*", var xhrHeaderDefaults = {
"X-Requested-With": "XMLHttpRequest" common: {
}, "Accept": "application/json, text/plain, */*",
post: {'Content-Type': 'application/x-www-form-urlencoded'}, "X-Requested-With": "XMLHttpRequest"
get: {}, // all these empty properties are needed so that client apps can just do: },
head: {}, // $xhr.defaults.headers.head.foo="bar" without having to create head object post: {'Content-Type': 'application/x-www-form-urlencoded'},
put: {}, // it also means that if we add a header for these methods in the future, it get: {}, // all these empty properties are needed so that client apps can just do:
'delete': {}, // won't be easily silently lost due to an object assignment. head: {}, // $xhr.defaults.headers.head.foo="bar" without having to create head object
patch: {} put: {}, // it also means that if we add a header for these methods in the future, it
}; 'delete': {}, // won't be easily silently lost due to an object assignment.
patch: {}
};
function xhr(method, url, post, success, error) { function xhr(method, url, post, success, error) {
if (isFunction(post)) { if (isFunction(post)) {
error = success; error = success;
success = post; success = post;
post = null; post = null;
} }
if (post && isObject(post)) { if (post && isObject(post)) {
post = toJson(post); post = toJson(post);
}
$browser.xhr(method, url, post, function(code, response){
try {
if (isString(response)) {
if (response.match(/^\)\]\}',\n/)) response=response.substr(6);
if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
response = fromJson(response, true);
}
}
$rootScope.$apply(function() {
if (200 <= code && code < 300) {
success(code, response);
} else if (isFunction(error)) {
error(code, response);
} else {
$error(
{method: method, url: url, data: post, success: success},
{status: code, body: response});
}
});
} catch (e) {
$log.error(e);
} }
}, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
xhrHeaderDefaults.common,
xhrHeaderDefaults[lowercase(method)]));
}
xhr.defaults = {headers: xhrHeaderDefaults}; $browser.xhr(method, url, post, function(code, response){
try {
if (isString(response)) {
if (response.match(/^\)\]\}',\n/)) response=response.substr(6);
if (/^\s*[\[\{]/.exec(response) && /[\}\]]\s*$/.exec(response)) {
response = fromJson(response, true);
}
}
$rootScope.$apply(function() {
if (200 <= code && code < 300) {
success(code, response);
} else if (isFunction(error)) {
error(code, response);
} else {
$error(
{method: method, url: url, data: post, success: success},
{status: code, body: response});
}
});
} catch (e) {
$log.error(e);
}
}, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
xhrHeaderDefaults.common,
xhrHeaderDefaults[lowercase(method)]));
}
return xhr; xhr.defaults = {headers: xhrHeaderDefaults};
}, ['$rootScope', '$browser', '$xhr.error', '$log']);
return xhr;
}];
}

View file

@ -382,25 +382,6 @@ describe('angular', function() {
expect(fake).toEqual('new'); expect(fake).toEqual('new');
})); }));
it('should not preserve properties on override', function() {
angular.service('fake', {$one: true}, {$two: true}, {three: true});
var result = angular.service('fake', {$four: true});
expect(result.$one).toBeUndefined();
expect(result.$two).toBeUndefined();
expect(result.three).toBeUndefined();
expect(result.$four).toBe(true);
});
it('should not preserve non-angular properties on override', function() {
angular.service('fake', {one: true}, {two: true});
var result = angular.service('fake', {third: true});
expect(result.one).not.toBeDefined();
expect(result.two).not.toBeDefined();
expect(result.third).toBeTruthy();
});
it('should inject dependencies specified by $inject and ignore function argument name', function() { it('should inject dependencies specified by $inject and ignore function argument name', function() {
expect(angular.injector(function($provide){ expect(angular.injector(function($provide){
$provide.factory('svc1', function() { return 'svc1'; }); $provide.factory('svc1', function() { return 'svc1'; });

View file

@ -222,8 +222,8 @@ describe('Binder', function() {
})); }));
it('IfTextBindingThrowsErrorDecorateTheSpan', inject( it('IfTextBindingThrowsErrorDecorateTheSpan', inject(
function($provide){ function($exceptionHandlerProvider){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory); $exceptionHandlerProvider.mode('log');
}, },
function($rootScope, $exceptionHandler, $compile) { function($rootScope, $exceptionHandler, $compile) {
$compile('<div>{{error.throw()}}</div>', null, true)($rootScope); $compile('<div>{{error.throw()}}</div>', null, true)($rootScope);
@ -245,8 +245,8 @@ describe('Binder', function() {
}) })
); );
it('IfAttrBindingThrowsErrorDecorateTheAttribute', inject(function($provide){ it('IfAttrBindingThrowsErrorDecorateTheAttribute', inject(function($exceptionHandlerProvider){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory); $exceptionHandlerProvider.mode('log');
}, function($rootScope, $exceptionHandler, $compile) { }, function($rootScope, $exceptionHandler, $compile) {
$compile('<div attr="before {{error.throw()}} after"></div>', null, true)($rootScope); $compile('<div attr="before {{error.throw()}} after"></div>', null, true)($rootScope);
var errorLogs = $exceptionHandler.errors; var errorLogs = $exceptionHandler.errors;
@ -387,8 +387,8 @@ describe('Binder', function() {
})); }));
it('ActionOnAHrefThrowsError', inject( it('ActionOnAHrefThrowsError', inject(
function($provide){ function($exceptionHandlerProvider){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory); $exceptionHandlerProvider.mode('log');
}, },
function($rootScope, $exceptionHandler, $compile) { function($rootScope, $exceptionHandler, $compile) {
var input = $compile('<a ng:click="action()">Add Phone</a>')($rootScope); var input = $compile('<a ng:click="action()">Add Phone</a>')($rootScope);
@ -471,8 +471,8 @@ describe('Binder', function() {
})); }));
it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorrect', inject( it('ItShouldDisplayErrorWhenActionIsSyntacticlyIncorrect', inject(
function($provide){ function($exceptionHandlerProvider){
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory); $exceptionHandlerProvider.mode('log');
}, },
function($rootScope, $exceptionHandler, $log, $compile) { function($rootScope, $exceptionHandler, $log, $compile) {
var element = $compile( var element = $compile(

View file

@ -200,10 +200,10 @@ describe('filter', function() {
describe('date', function() { describe('date', function() {
var morning = new TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am var morning = new angular.mock.TzDate(+5, '2010-09-03T12:05:08.000Z'); //7am
var noon = new TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm var noon = new angular.mock.TzDate(+5, '2010-09-03T17:05:08.000Z'); //12pm
var midnight = new TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am var midnight = new angular.mock.TzDate(+5, '2010-09-03T05:05:08.000Z'); //12am
var earlyDate = new TzDate(+5, '0001-09-03T05:05:08.000Z'); var earlyDate = new angular.mock.TzDate(+5, '0001-09-03T05:05:08.000Z');
var context, date; var context, date;

View file

@ -239,6 +239,12 @@ describe('injector', function() {
expect($injector('a')).toEqual('abc'); expect($injector('a')).toEqual('abc');
}); });
it('should error on invalid madule name', function(){
expect(function(){
createInjector(['IDontExist'], {});
}).toThrow("Module 'IDontExist' is not defined!");
});
describe('$provide', function() { describe('$provide', function() {
describe('value', function(){ describe('value', function(){

View file

@ -8,90 +8,90 @@ describe('mocks', function() {
} }
it('should take millis as constructor argument', function() { it('should take millis as constructor argument', function() {
expect(new TzDate(0, 0).getTime()).toBe(0); expect(new angular.mock.TzDate(0, 0).getTime()).toBe(0);
expect(new TzDate(0, 1283555108000).getTime()).toBe(1283555108000); expect(new angular.mock.TzDate(0, 1283555108000).getTime()).toBe(1283555108000);
}); });
it('should take dateString as constructor argument', function() { it('should take dateString as constructor argument', function() {
expect(new TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0); expect(new angular.mock.TzDate(0, '1970-01-01T00:00:00.000Z').getTime()).toBe(0);
expect(new TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023); expect(new angular.mock.TzDate(0, '2010-09-03T23:05:08.023Z').getTime()).toBe(1283555108023);
}); });
it('should fake getLocalDateString method', function() { it('should fake getLocalDateString method', function() {
//0 in -3h //0 in -3h
var t0 = new TzDate(-3, 0); var t0 = new angular.mock.TzDate(-3, 0);
expect(t0.toLocaleDateString()).toMatch('1970'); expect(t0.toLocaleDateString()).toMatch('1970');
//0 in +0h //0 in +0h
var t1 = new TzDate(0, 0); var t1 = new angular.mock.TzDate(0, 0);
expect(t1.toLocaleDateString()).toMatch('1970'); expect(t1.toLocaleDateString()).toMatch('1970');
//0 in +3h //0 in +3h
var t2 = new TzDate(3, 0); var t2 = new angular.mock.TzDate(3, 0);
expect(t2.toLocaleDateString()).toMatch('1969'); expect(t2.toLocaleDateString()).toMatch('1969');
}); });
it('should fake getHours method', function() { it('should fake getHours method', function() {
//0 in -3h //0 in -3h
var t0 = new TzDate(-3, 0); var t0 = new angular.mock.TzDate(-3, 0);
expect(t0.getHours()).toBe(3); expect(t0.getHours()).toBe(3);
//0 in +0h //0 in +0h
var t1 = new TzDate(0, 0); var t1 = new angular.mock.TzDate(0, 0);
expect(t1.getHours()).toBe(0); expect(t1.getHours()).toBe(0);
//0 in +3h //0 in +3h
var t2 = new TzDate(3, 0); var t2 = new angular.mock.TzDate(3, 0);
expect(t2.getHours()).toMatch(21); expect(t2.getHours()).toMatch(21);
}); });
it('should fake getMinutes method', function() { it('should fake getMinutes method', function() {
//0:15 in -3h //0:15 in -3h
var t0 = new TzDate(-3, minutes(15)); var t0 = new angular.mock.TzDate(-3, minutes(15));
expect(t0.getMinutes()).toBe(15); expect(t0.getMinutes()).toBe(15);
//0:15 in -3.25h //0:15 in -3.25h
var t0a = new TzDate(-3.25, minutes(15)); var t0a = new angular.mock.TzDate(-3.25, minutes(15));
expect(t0a.getMinutes()).toBe(30); expect(t0a.getMinutes()).toBe(30);
//0 in +0h //0 in +0h
var t1 = new TzDate(0, minutes(0)); var t1 = new angular.mock.TzDate(0, minutes(0));
expect(t1.getMinutes()).toBe(0); expect(t1.getMinutes()).toBe(0);
//0:15 in +0h //0:15 in +0h
var t1a = new TzDate(0, minutes(15)); var t1a = new angular.mock.TzDate(0, minutes(15));
expect(t1a.getMinutes()).toBe(15); expect(t1a.getMinutes()).toBe(15);
//0:15 in +3h //0:15 in +3h
var t2 = new TzDate(3, minutes(15)); var t2 = new angular.mock.TzDate(3, minutes(15));
expect(t2.getMinutes()).toMatch(15); expect(t2.getMinutes()).toMatch(15);
//0:15 in +3.25h //0:15 in +3.25h
var t2a = new TzDate(3.25, minutes(15)); var t2a = new angular.mock.TzDate(3.25, minutes(15));
expect(t2a.getMinutes()).toMatch(0); expect(t2a.getMinutes()).toMatch(0);
}); });
it('should fake getSeconds method', function() { it('should fake getSeconds method', function() {
//0 in -3h //0 in -3h
var t0 = new TzDate(-3, 0); var t0 = new angular.mock.TzDate(-3, 0);
expect(t0.getSeconds()).toBe(0); expect(t0.getSeconds()).toBe(0);
//0 in +0h //0 in +0h
var t1 = new TzDate(0, 0); var t1 = new angular.mock.TzDate(0, 0);
expect(t1.getSeconds()).toBe(0); expect(t1.getSeconds()).toBe(0);
//0 in +3h //0 in +3h
var t2 = new TzDate(3, 0); var t2 = new angular.mock.TzDate(3, 0);
expect(t2.getSeconds()).toMatch(0); expect(t2.getSeconds()).toMatch(0);
}); });
it('should create a date representing new year in Bratislava', function() { it('should create a date representing new year in Bratislava', function() {
var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00.000Z'); var newYearInBratislava = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
expect(newYearInBratislava.getTimezoneOffset()).toBe(-60); expect(newYearInBratislava.getTimezoneOffset()).toBe(-60);
expect(newYearInBratislava.getFullYear()).toBe(2010); expect(newYearInBratislava.getFullYear()).toBe(2010);
expect(newYearInBratislava.getMonth()).toBe(0); expect(newYearInBratislava.getMonth()).toBe(0);
@ -103,7 +103,7 @@ describe('mocks', function() {
it('should delegate all the UTC methods to the original UTC Date object', function() { it('should delegate all the UTC methods to the original UTC Date object', function() {
//from when created from string //from when created from string
var date1 = new TzDate(-1, '2009-12-31T23:00:00.000Z'); var date1 = new angular.mock.TzDate(-1, '2009-12-31T23:00:00.000Z');
expect(date1.getUTCFullYear()).toBe(2009); expect(date1.getUTCFullYear()).toBe(2009);
expect(date1.getUTCMonth()).toBe(11); expect(date1.getUTCMonth()).toBe(11);
expect(date1.getUTCDate()).toBe(31); expect(date1.getUTCDate()).toBe(31);
@ -113,7 +113,7 @@ describe('mocks', function() {
//from when created from millis //from when created from millis
var date2 = new TzDate(-1, angular.String.toDate('2009-12-31T23:00:00.000Z').getTime()); var date2 = new angular.mock.TzDate(-1, angular.String.toDate('2009-12-31T23:00:00.000Z').getTime());
expect(date2.getUTCFullYear()).toBe(2009); expect(date2.getUTCFullYear()).toBe(2009);
expect(date2.getUTCMonth()).toBe(11); expect(date2.getUTCMonth()).toBe(11);
expect(date2.getUTCDate()).toBe(31); expect(date2.getUTCDate()).toBe(31);
@ -124,16 +124,20 @@ describe('mocks', function() {
it('should throw error when no third param but toString called', function() { it('should throw error when no third param but toString called', function() {
expect(function() { new TzDate(0,0).toString(); }). expect(function() { new angular.mock.TzDate(0,0).toString(); }).
toThrow('Method \'toString\' is not implemented in the TzDate mock'); toThrow('Method \'toString\' is not implemented in the TzDate mock');
}); });
}); });
describe('$log mock', function() { describe('$log', function() {
var $log; var $log;
beforeEach(function() { beforeEach(inject(['$log', function(log) {
$log = MockLogFactory(); $log = log;
}); }]));
afterEach(inject(function($log){
$log.reset();
}));
it('should provide log method', function() { it('should provide log method', function() {
expect(function() { $log.log(''); }).not.toThrow(); expect(function() { $log.log(''); }).not.toThrow();
@ -170,14 +174,48 @@ describe('mocks', function() {
$log.error('fake log'); $log.error('fake log');
expect($log.error.logs).toContain(['fake log']); expect($log.error.logs).toContain(['fake log']);
}); });
it('should assertEmpty', function(){
try {
$log.error(Error('MyError'));
$log.warn(Error('MyWarn'));
$log.info(Error('MyInfo'));
$log.log(Error('MyLog'));
$log.assertEmpty();
} catch (error) {
error = error.message || error;
expect(error).toMatch(/Error: MyError/m);
expect(error).toMatch(/Error: MyWarn/m);
expect(error).toMatch(/Error: MyInfo/m);
expect(error).toMatch(/Error: MyLog/m);
} finally {
$log.reset();
}
});
it('should reset state', function(){
$log.error(Error('MyError'));
$log.warn(Error('MyWarn'));
$log.info(Error('MyInfo'));
$log.log(Error('MyLog'));
$log.reset();
var passed = false;
try {
$log.assertEmpty(); // should not throw error!
passed = true;
} catch (e) {
passed = e;
}
expect(passed).toBe(true);
});
}); });
describe('defer', function() { describe('defer', function() {
var browser, log; var browser, log;
beforeEach(function() { beforeEach(inject(function($browser) {
browser = new MockBrowser(); browser = $browser;
log = ''; log = '';
}); }));
function logFn(text){ return function() { function logFn(text){ return function() {
log += text +';'; log += text +';';
@ -229,5 +267,20 @@ describe('mocks', function() {
it('should rethrow exceptions', inject(function($exceptionHandler) { it('should rethrow exceptions', inject(function($exceptionHandler) {
expect(function() { $exceptionHandler('myException'); }).toThrow('myException'); expect(function() { $exceptionHandler('myException'); }).toThrow('myException');
})); }));
it('should log exceptions', inject(function($exceptionHandlerProvider){
$exceptionHandlerProvider.mode('log');
var $exceptionHandler = $exceptionHandlerProvider.$get();
$exceptionHandler('MyError');
expect($exceptionHandler.errors).toEqual(['MyError']);
}));
it('should thorw on wrong argument', inject(function($exceptionHandlerProvider) {
expect(function() {
$exceptionHandlerProvider.mode('XXX');
}).toThrow("Unknown mode 'XXX', only 'log'/'rethrow' modes are allowed!");
}));
}); });
}); });

View file

@ -1,69 +0,0 @@
'use strict';
/**
* Mock implementation of {@link angular.service.$log} that gathers all logged messages in arrays
* (one array per logging level). These arrays are exposed as `logs` property of each of the
* level-specific log function, e.g. for level `error` the array is exposed as
* `$logMock.error.logs`
*
* Please note that this is not a factory function, but rather the actual mock instance. This is
* important because it allows `beforeEach` and `afterEach` test hooks to clean up or check the
* state of `logs` arrays in between tests.
*
* Exposing the instance in this way makes this mock a singleton, which means that the instance
* becomes global state for tests. To mitigate the issue, each time the `$log` mock is registered
* with the injector, a check is performed to ensure that there are no pending logs in `logs`
* arrays. This means that if a message is logged via $log during a test, the `logs` array must be
* emptied before the test is finished. `Array#shift` method can be used for this purpose as
* follows:
*
* <pre>
* it('should do some good', function() {
* var scope = angular.scope(),
* $log = scope.$service('$log');
*
* //do something that triggers a message to be logged
* expect($log.error.logs.shift()).toEqual(['message', 'arg1', 'arg2']);
* });
* </pre>
*
* See {@link angular.mock} for more info on angular mocks.
*/
var $logMock = {
log: function() { $logMock.log.logs.push(concat([], arguments, 0)); },
warn: function() { $logMock.warn.logs.push(concat([], arguments, 0)); },
info: function() { $logMock.info.logs.push(concat([], arguments, 0)); },
error: function() { $logMock.error.logs.push(concat([], arguments, 0)); }
};
$logMock.log.logs = [];
$logMock.warn.logs = [];
$logMock.info.logs = [];
$logMock.error.logs = [];
angular.service('$log', function() {
return $logMock;
});
/**
* Factory that returns mock implementation of {@link angular.service.$exceptionHandler} that
* gathers all errors in an array. This array is exposed as `errors` property of the mock and can be
* accessed as `$exceptionHandler.errors`.
*
* Note that this factory is not registered with angular's injector by default (as opposed to
* `$logMock`). It is your responsibility to register this factory when you need it. Typically like
* this:
*
* <pre>
* var scope = angular.scope(null, {'$exceptionHandler': $exceptionHandlerMockFactory()});
* </pre>
*
*/
function $exceptionHandlerMockFactory() {
var mockHandler = function(e) {
mockHandler.errors.push(e);
};
mockHandler.errors = [];
return mockHandler;
}

View file

@ -3,7 +3,7 @@
describe('$cookies', function() { describe('$cookies', function() {
beforeEach(inject(function($provide) { beforeEach(inject(function($provide) {
$provide.factory('$browser', function(){ $provide.factory('$browser', function(){
return angular.extend(new MockBrowser(), {cookieHash: {preexisting:'oldCookie'}}); return angular.extend(new angular.mock.$Browser(), {cookieHash: {preexisting:'oldCookie'}});
}); });
})); }));

View file

@ -5,11 +5,9 @@ describe('$exceptionHandler', function() {
it('should log errors', inject( it('should log errors', inject(
function($provide){ function($provide){
$provide.factory('$exceptionHandler', $exceptionHandlerFactory); $provide.service('$exceptionHandler', $ExceptionHandlerProvider);
$provide.value('$log', $logMock);
}, },
function($log, $exceptionHandler) { function($log, $exceptionHandler) {
$log.error.rethrow = false;
$exceptionHandler('myError'); $exceptionHandler('myError');
expect($log.error.logs.shift()).toEqual(['myError']); expect($log.error.logs.shift()).toEqual(['myError']);
} }

View file

@ -2,7 +2,7 @@
describe('$locale', function() { describe('$locale', function() {
var $locale = angular.service('$locale')(); var $locale = new $LocaleProvider().$get();
it('should have locale id set to en-us', function() { it('should have locale id set to en-us', function() {
expect($locale.id).toBe('en-us'); expect($locale.id).toBe('en-us');

View file

@ -12,7 +12,7 @@ describe('$log', function() {
beforeEach(inject(function($provide){ beforeEach(inject(function($provide){
$window = {}; $window = {};
logger = ''; logger = '';
$provide.factory('$log', $logFactory); $provide.service('$log', $LogProvider);
$provide.value('$exceptionHandler', rethrow); $provide.value('$exceptionHandler', rethrow);
$provide.value('$window', $window); $provide.value('$window', $window);
})); }));
@ -68,7 +68,7 @@ describe('$log', function() {
e.line = undefined; e.line = undefined;
e.stack = undefined; e.stack = undefined;
$log = $logFactory({console:{error:function() { $log = new $LogProvider().$get[1]({console:{error:function() {
errorArgs = arguments; errorArgs = arguments;
}}}); }}});
}); });

View file

@ -2,8 +2,8 @@
describe('Scope', function() { describe('Scope', function() {
beforeEach(inject(function($provide) { beforeEach(inject(function($exceptionHandlerProvider) {
$provide.factory('$exceptionHandler', $exceptionHandlerMockFactory); $exceptionHandlerProvider.mode('log');
})); }));
@ -121,12 +121,12 @@ describe('Scope', function() {
expect(spy).wasCalled(); expect(spy).wasCalled();
})); }));
it('should delegate exceptions', inject(function($rootScope, $exceptionHandler) { it('should delegate exceptions', inject(function($rootScope, $exceptionHandler, $log) {
$rootScope.$watch('a', function() {throw new Error('abc');}); $rootScope.$watch('a', function() {throw new Error('abc');});
$rootScope.a = 1; $rootScope.a = 1;
$rootScope.$digest(); $rootScope.$digest();
expect($exceptionHandler.errors[0].message).toEqual('abc'); expect($exceptionHandler.errors[0].message).toEqual('abc');
$logMock.error.logs.length = 0; $log.assertEmpty();
})); }));
@ -422,7 +422,7 @@ describe('Scope', function() {
})); }));
it('should catch exceptions', inject(function($rootScope, $exceptionHandler) { it('should catch exceptions', inject(function($rootScope, $exceptionHandler, $log) {
var log = ''; var log = '';
var child = $rootScope.$new(); var child = $rootScope.$new();
$rootScope.$watch('a', function(scope, a){ log += '1'; }); $rootScope.$watch('a', function(scope, a){ log += '1'; });
@ -430,7 +430,7 @@ describe('Scope', function() {
child.$apply(function() { throw new Error('MyError'); }); child.$apply(function() { throw new Error('MyError'); });
expect(log).toEqual('1'); expect(log).toEqual('1');
expect($exceptionHandler.errors[0].message).toEqual('MyError'); expect($exceptionHandler.errors[0].message).toEqual('MyError');
$logMock.error.logs.shift(); $log.error.logs.shift();
})); }));

View file

@ -3,7 +3,7 @@
describe('$sniffer', function() { describe('$sniffer', function() {
function sniffer($window) { function sniffer($window) {
return angular.service('$sniffer')($window); return new $SnifferProvider().$get[1]($window);
} }
describe('history', function() { describe('history', function() {

View file

@ -77,18 +77,13 @@ beforeEach(function() {
} }
}); });
$logMock.log.logs = [];
$logMock.warn.logs = [];
$logMock.info.logs = [];
$logMock.error.logs = [];
}); });
function inject(){ function inject(){
var blockFns = sliceArgs(arguments); var blockFns = sliceArgs(arguments);
return function(){ return function(){
var spec = this; var spec = this;
spec.$injector = spec.$injector || angular.injector('NG'); spec.$injector = spec.$injector || angular.injector('NG', 'NG_MOCK');
angular.forEach(blockFns, function(fn){ angular.forEach(blockFns, function(fn){
spec.$injector.invoke(spec, fn); spec.$injector.invoke(spec, fn);
}); });
@ -96,30 +91,12 @@ function inject(){
} }
afterEach(inject(function($rootScope) { afterEach(inject(function($rootScope, $log) {
// release the injector // release the injector
dealoc($rootScope); dealoc($rootScope);
// check $log mock // check $log mock
forEach(['error', 'warn', 'info', 'log'], function(logLevel) { $log.assertEmpty && $log.assertEmpty();
if ($logMock[logLevel].logs.length) {
forEach($logMock[logLevel].logs, function(log) {
forEach(log, function deleteStack(logItem) {
if (logItem instanceof Error) {
dump(logItem.stack);
delete logItem.stack;
delete logItem.arguments;
} else {
dump(logItem);
}
});
});
throw new Error("Exprected $log." + logLevel + ".logs array to be empty. " +
"Either a message was logged unexpectedly, or an expected log message was not checked " +
"and removed. Array contents: " + toJson($logMock[logLevel].logs));
}
});
clearJqCache(); clearJqCache();
})); }));

View file

@ -448,12 +448,12 @@ describe('widget: input', function() {
}); });
it('should report error on assignment error', function() { it('should report error on assignment error', inject(function($log) {
expect(function() { expect(function() {
compile('<input type="text" ng:model="throw \'\'">'); compile('<input type="text" ng:model="throw \'\'">');
}).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at ['']."); }).toThrow("Syntax Error: Token '''' is an unexpected token at column 7 of the expression [throw ''] starting at [''].");
$logMock.error.logs.shift(); $log.error.logs.shift();
}); }));
}); });

View file

@ -223,12 +223,12 @@ describe("widget", function() {
expect(element.text()).toEqual('name:value;'); expect(element.text()).toEqual('name:value;');
})); }));
it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile) { it('should error on wrong parsing of ng:repeat', inject(function($rootScope, $compile, $log) {
expect(function() { expect(function() {
var element = $compile('<ul><li ng:repeat="i dont parse"></li></ul>')($rootScope); var element = $compile('<ul><li ng:repeat="i dont parse"></li></ul>')($rootScope);
}).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'."); }).toThrow("Expected ng:repeat in form of '_item_ in _collection_' but got 'i dont parse'.");
$logMock.error.logs.shift(); $log.error.logs.shift();
})); }));
it('should expose iterator offset as $index when iterating over arrays', inject(function($rootScope, $compile) { it('should expose iterator offset as $index when iterating over arrays', inject(function($rootScope, $compile) {
@ -487,9 +487,9 @@ describe("widget", function() {
})); }));
it('should be possible to nest ng:view in ng:include', inject(function() { it('should be possible to nest ng:view in ng:include', inject(function() {
var injector = angular.injector('NG'); var injector = angular.injector('NG', 'NG_MOCK');
var myApp = injector('$rootScope'); var myApp = injector('$rootScope');
var $browser = myApp.$service('$browser'); var $browser = injector('$browser');
$browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>'); $browser.xhr.expectGET('includePartial.html').respond('view: <ng:view></ng:view>');
injector('$location').path('/foo'); injector('$location').path('/foo');