diff --git a/angularFiles.js b/angularFiles.js
index 3b4d91ee952a..453d9989d28f 100644
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -2,8 +2,6 @@ angularFiles = {
'angularSrc': [
'src/Angular.js',
'src/JSON.js',
- 'src/Compiler.js',
- 'src/Scope.js',
'src/Injector.js',
'src/parser.js',
'src/Resource.js',
@@ -12,6 +10,7 @@ angularFiles = {
'src/jqLite.js',
'src/apis.js',
'src/filters.js',
+ 'src/service/compiler.js',
'src/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
@@ -23,6 +22,7 @@ angularFiles = {
'src/service/resource.js',
'src/service/route.js',
'src/service/routeParams.js',
+ 'src/service/scope.js',
'src/service/sniffer.js',
'src/service/window.js',
'src/service/xhr.bulk.js',
diff --git a/example/personalLog/test/personalLogSpec.js b/example/personalLog/test/personalLogSpec.js
index 3e6935a3d9be..cf80a420a32b 100644
--- a/example/personalLog/test/personalLogSpec.js
+++ b/example/personalLog/test/personalLogSpec.js
@@ -2,8 +2,9 @@ describe('example.personalLog.LogCtrl', function() {
var logCtrl;
function createNotesCtrl() {
- var scope = angular.scope();
- scope.$cookies = scope.$service('$cookies');
+ var injector = angular.injector();
+ var scope = injector('$rootScope');
+ scope.$cookies = injector('$cookies');
return scope.$new(example.personalLog.LogCtrl);
}
diff --git a/src/Angular.js b/src/Angular.js
index 256a119c1308..8969d5ad617a 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -855,11 +855,6 @@ function toBoolean(value) {
}
-/** @name angular.compile */
-function compile(element) {
- return new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget)
- .compile(element);
-}
/////////////////////////////////////////////////
/**
@@ -954,11 +949,12 @@ function angularInit(config, document){
if (autobind) {
var element = isString(autobind) ? document.getElementById(autobind) : document,
- scope = compile(element)(createScope()),
- $browser = scope.$service('$browser');
+ injector = createInjector(),
+ scope = injector('$rootScope');
+ injector('$compile')(element)(scope);
if (config.css)
- $browser.addCss(config.base_url + config.css);
+ injector('$browser').addCss(config.base_url + config.css);
scope.$apply();
}
}
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index fc8a90fdf978..3ed0eab77694 100644
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -14,8 +14,6 @@ angularService('$browser', function($log, $sniffer) {
extend(angular, {
// disabled for now until we agree on public name
//'annotate': annotate,
- 'compile': compile,
- 'scope': createScope,
'copy': copy,
'extend': extend,
'equals': equals,
diff --git a/src/Compiler.js b/src/Compiler.js
deleted file mode 100644
index 12736db7adc1..000000000000
--- a/src/Compiler.js
+++ /dev/null
@@ -1,329 +0,0 @@
-'use strict';
-
-/**
- * Template provides directions an how to bind to a given element.
- * 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
- * of child paths which contain child templates
- */
-function Template() {
- this.paths = [];
- this.children = [];
- this.linkFns = [];
- this.newScope = false;
-}
-
-Template.prototype = {
- link: function(element, scope) {
- var childScope = scope;
- if (this.newScope) {
- childScope = isFunction(this.newScope) ? scope.$new(this.newScope(scope)) : scope.$new();
- element.data($$scope, childScope);
- }
- forEach(this.linkFns, function(fn) {
- try {
- childScope.$service.invoke(childScope, fn, [element]);
- } catch (e) {
- childScope.$service('$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) {
- 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.
- *
-
- // 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('
click me
')();
-
- // compile a piece of html and retain reference to both the dom and scope
- var template = angular.element('
click me
'),
- scope = angular.compile(template)();
- // at this point template was transformed into a view
-
- *
- *
- * @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. If none specified, then a new
- * root scope is created.
- * * `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: `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 scope to which the element is bound to. It is either
- * the same scope as the one passed into the template function, or if none were provided it's the
- * newly create scope.
- *
- * 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.
- *
- * var view = angular.element('
{{total}}
'),
- * scope = angular.compile(view)();
- *
- *
- * - 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:
- *
- * var original = angular.element('
{{total}}
'),
- * 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`
- *
- *
- *
- * 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('
').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;
- }
- }
- }
- template = this.templatize(templateElement, index) || new Template();
- return function(scope, cloneConnectFn){
- // 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;
- scope = scope || createScope();
- element.data($$scope, scope);
- scope.$element = element;
- (cloneConnectFn||noop)(element, scope);
- template.link(element, scope);
- return scope;
- };
- },
-
- templatize: function(element, elementIndex){
- var self = this,
- 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=} [factories=angular.service] Map of the service factory
* functions.
- * @param {Object.=} [instanceCache={}] Place where instances of services are
- * saved for reuse. Can also be used to override services specified by `serviceFactory`
- * (useful in tests).
* @returns {function()} Injector function:
*
* * `injector(serviceName)`:
@@ -38,40 +32,37 @@
* * An `eager` property which is used to initialize the eager services.
* `injector.eager()`
*/
-function createInjector(factoryScope, factories, instanceCache) {
+function createInjector(factories) {
+ var instanceCache = {$injector: injector};
factories = factories || angularService;
- instanceCache = instanceCache || {};
- factoryScope = factoryScope || {};
- injector.invoke = invoke;
- injector.eager = function() {
- forEach(factories, function(factory, name){
- if (factory.$eager)
- injector(name);
+ injector.invoke = invoke;
- if (factory.$creation)
- throw new Error("Failed to register service '" + name +
- "': $creation property is unsupported. Use $eager:true or see release notes.");
- });
- };
+ forEach(factories, function(factory, name){
+ if (factory.$eager)
+ injector(name);
+ });
return injector;
- function injector(value){
- if (!(value in instanceCache)) {
- var factory = factories[value];
- if (!factory) throw Error("Unknown provider for '"+value+"'.");
+ function injector(serviceId, path){
+ if (!(serviceId in instanceCache)) {
+ var factory = factories[serviceId];
+ path = path || [];
+ path.unshift(serviceId);
+ if (!factory) throw Error("Unknown provider for '" + path.join("' <- '") + "'.");
inferInjectionArgs(factory);
- instanceCache[value] = invoke(factoryScope, factory);
+ instanceCache[serviceId] = invoke(null, factory, [], path);
+ path.shift();
}
- return instanceCache[value];
+ return instanceCache[serviceId];
}
- function invoke(self, fn, args){
+ function invoke(self, fn, args, path){
args = args || [];
var injectNames = fn.$inject || [];
var i = injectNames.length;
while(i--) {
- args.unshift(injector(injectNames[i]));
+ args.unshift(injector(injectNames[i], path));
}
return fn.apply(self, args);
}
diff --git a/src/Scope.js b/src/Scope.js
deleted file mode 100644
index be5030cc2db8..000000000000
--- a/src/Scope.js
+++ /dev/null
@@ -1,668 +0,0 @@
-'use strict';
-
-/**
- * DESIGN NOTES
- *
- * The design decisions behind the scope ware heavily favored for speed and memory consumption.
- *
- * The typical use of scope is to watch the expressions, which most of the time return the same
- * value as last time so we optimize the operation.
- *
- * Closures construction is expensive from speed as well as memory:
- * - no closures, instead ups prototypical inheritance for API
- * - Internal state needs to be stored on scope directly, which means that private state is
- * exposed as $$____ properties
- *
- * Loop operations are optimized by using while(count--) { ... }
- * - this means that in order to keep the same order of execution as addition we have to add
- * items to the array at the begging (shift) instead of at the end (push)
- *
- * Child scopes are created and removed often
- * - Using array would be slow since inserts in meddle are expensive so we use linked list
- *
- * There are few watches then a lot of observers. This is why you don't want the observer to be
- * implemented in the same way as watch. Watch requires return of initialization function which
- * are expensive to construct.
- */
-
-
-function createScope(providers, instanceCache) {
- var scope = new Scope();
- (scope.$service = createInjector(scope, providers, instanceCache)).eager();
- return scope;
-}
-
-
-/**
- * @ngdoc function
- * @name angular.scope
- *
- * @description
- * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes
- * are created using the {@link angular.scope.$new $new()} method.
- * (Most scopes are created automatically when compiled HTML template is executed.)
- *
- * Here is a simple scope snippet to show how you can interact with the scope.
- *
- var scope = angular.scope();
- scope.salutation = 'Hello';
- scope.name = 'World';
-
- expect(scope.greeting).toEqual(undefined);
-
- scope.$watch('name', function() {
- this.greeting = this.salutation + ' ' + this.name + '!';
- }); // initialize the watch
-
- expect(scope.greeting).toEqual(undefined);
- scope.name = 'Misko';
- // still old value, since watches have not been called yet
- expect(scope.greeting).toEqual(undefined);
-
- scope.$digest(); // fire all the watches
- expect(scope.greeting).toEqual('Hello Misko!');
- *
- *
- * # Inheritance
- * A scope can inherit from a parent scope, as in this example:
- *
- *
- * # Dependency Injection
- * See {@link guide/dev_guide.di dependency injection}.
- *
- *
- * @param {Object.=} providers Map of service factory which need to be provided
- * for the current scope. Defaults to {@link angular.service}.
- * @param {Object.=} instanceCache Provides pre-instantiated services which should
- * append/override services provided by `providers`. This is handy when unit-testing and having
- * the need to override a default service.
- * @returns {Object} Newly created scope.
- *
- */
-function Scope() {
- this.$id = nextUid();
- this.$$phase = this.$parent = this.$$watchers =
- this.$$nextSibling = this.$$prevSibling =
- this.$$childHead = this.$$childTail = null;
- this.$destructor = noop;
- this['this'] = this.$root = this;
- this.$$asyncQueue = [];
- this.$$listeners = {};
-}
-
-/**
- * @ngdoc property
- * @name angular.scope.$id
- * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
- * debugging.
- */
-
-/**
- * @ngdoc property
- * @name angular.scope.$service
- * @function
- *
- * @description
- * Provides reference to an instance of {@link angular.injector injector} which can be used to
- * retrieve {@link angular.service services}. In general the use of this api is discouraged,
- * in favor of proper {@link guide/dev_guide.di dependency injection}.
- *
- * @returns {function} {@link angular.injector injector}
- */
-
-/**
- * @ngdoc property
- * @name angular.scope.$root
- * @returns {Scope} The root scope of the current scope hierarchy.
- */
-
-/**
- * @ngdoc property
- * @name angular.scope.$parent
- * @returns {Scope} The parent scope of the current scope.
- */
-
-
-Scope.prototype = {
- /**
- * @ngdoc function
- * @name angular.scope.$new
- * @function
- *
- * @description
- * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a
- * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and
- * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope
- * hierarchy using {@link angular.scope.$destroy $destroy()}.
- *
- * {@link angular.scope.$destroy $destroy()} must be called on a scope when it is desired for
- * the scope and its child scopes to be permanently detached from the parent and thus stop
- * participating in model change detection and listener notification by invoking.
- *
- * @param {function()=} Class Constructor function which the scope should be applied to the scope.
- * @param {...*} curryArguments Any additional arguments which are curried into the constructor.
- * See {@link guide/dev_guide.di dependency injection}.
- * @returns {Object} The newly created child scope.
- *
- */
- $new: function(Class, curryArguments) {
- var Child = function() {}; // should be anonymous; This is so that when the minifier munges
- // the name it does not become random set of chars. These will then show up as class
- // name in the debugger.
- var child;
- Child.prototype = this;
- child = new Child();
- child['this'] = child;
- child.$$listeners = {};
- child.$parent = this;
- child.$id = nextUid();
- child.$$asyncQueue = [];
- child.$$phase = child.$$watchers =
- child.$$nextSibling = child.$$childHead = child.$$childTail = null;
- child.$$prevSibling = this.$$childTail;
- if (this.$$childHead) {
- this.$$childTail.$$nextSibling = child;
- this.$$childTail = child;
- } else {
- this.$$childHead = this.$$childTail = child;
- }
- // short circuit if we have no class
- if (Class) {
- // can't use forEach, we need speed!
- var ClassPrototype = Class.prototype;
- for(var key in ClassPrototype) {
- child[key] = bind(child, ClassPrototype[key]);
- }
- this.$service.invoke(child, Class, curryArguments);
- }
- return child;
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$watch
- * @function
- *
- * @description
- * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
- *
- * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and
- * should return the value which will be watched. (Since {@link angular.scope.$digest $digest()}
- * reruns when it detects changes the `watchExpression` can execute multiple times per
- * {@link angular.scope.$digest $digest()} and should be idempotent.)
- * - The `listener` is called only when the value from the current `watchExpression` and the
- * previous call to `watchExpression' are not equal. The inequality is determined according to
- * {@link angular.equals} function. To save the value of the object for later comparison
- * {@link angular.copy} function is used. It also means that watching complex options will
- * have adverse memory and performance implications.
- * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
- * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
- * limit is 100 to prevent infinity loop deadlock.
- *
- *
- * If you want to be notified whenever {@link angular.scope.$digest $digest} is called,
- * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`,
- * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is
- * detected, be prepared for multiple calls to your listener.)
- *
- *
- * # Example
-
- *
- *
- *
- * @param {(function()|string)} watchExpression Expression that is evaluated on each
- * {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a
- * call to the `listener`.
- *
- * - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
- * - `function(scope)`: called with current `scope` as a parameter.
- * @param {(function()|string)=} listener Callback called whenever the return value of
- * the `watchExpression` changes.
- *
- * - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
- * - `function(scope, newValue, oldValue)`: called with current `scope` an previous and
- * current values as parameters.
- * @returns {function()} Returns a deregistration function for this listener.
- */
- $watch: function(watchExp, listener) {
- var scope = this,
- get = compileToFn(watchExp, 'watch'),
- listenFn = compileToFn(listener || noop, 'listener'),
- array = scope.$$watchers,
- watcher = {
- fn: listenFn,
- last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.
- get: get
- };
-
- if (!array) {
- array = scope.$$watchers = [];
- }
- // we use unshift since we use a while loop in $digest for speed.
- // the while loop reads in reverse order.
- array.unshift(watcher);
-
- return function() {
- angularArray.remove(array, watcher);
- };
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$digest
- * @function
- *
- * @description
- * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children.
- * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the
- * `$digest()` keeps calling the {@link angular.scope.$watch watchers} until no more listeners are
- * firing. This means that it is possible to get into an infinite loop. This function will throw
- * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100.
- *
- * Usually you don't call `$digest()` directly in
- * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}.
- * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a
- * {@link angular.directive directive}) will force a `$digest()`.
- *
- * If you want to be notified whenever `$digest()` is called,
- * you can register a `watchExpression` function with {@link angular.scope.$watch $watch()}
- * with no `listener`.
- *
- * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
- * life-cycle.
- *
- * # Example
-
- *
- */
- $digest: function() {
- var watch, value, last,
- watchers,
- asyncQueue,
- length,
- dirty, ttl = 100,
- next, current, target = this;
-
- if (target.$$phase) {
- throw Error(target.$$phase + ' already in progress');
- }
- do {
-
- dirty = false;
- current = target;
- do {
- current.$$phase = '$digest';
- asyncQueue = current.$$asyncQueue;
- while(asyncQueue.length) {
- try {
- current.$eval(asyncQueue.shift());
- } catch (e) {
- current.$service('$exceptionHandler')(e);
- }
- }
- if ((watchers = current.$$watchers)) {
- // process our watches
- length = watchers.length;
- while (length--) {
- try {
- watch = watchers[length];
- // Most common watches are on primitives, in which case we can short
- // circuit it with === operator, only when === fails do we use .equals
- if ((value = watch.get(current)) !== (last = watch.last) && !equals(value, last)) {
- dirty = true;
- watch.last = copy(value);
- watch.fn(current, value, last);
- }
- } catch (e) {
- current.$service('$exceptionHandler')(e);
- }
- }
- }
-
- current.$$phase = null;
-
- // Insanity Warning: scope depth-first traversal
- // yes, this code is a bit crazy, but it works and we have tests to prove it!
- // this piece should be kept in sync with the traversal in $broadcast
- if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
- while(current !== target && !(next = current.$$nextSibling)) {
- current = current.$parent;
- }
- }
- } while ((current = next));
-
- if(!(ttl--)) {
- throw Error('100 $digest() iterations reached. Aborting!');
- }
- } while (dirty);
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$destroy
- * @function
- *
- * @description
- * Remove the current scope (and all of its children) from the parent scope. Removal implies
- * that calls to {@link angular.scope.$digest $digest()} will no longer propagate to the current
- * scope and its children. Removal also implies that the current scope is eligible for garbage
- * collection.
- *
- * The destructing scope emits an `$destroy` {@link angular.scope.$emit event}.
- *
- * The `$destroy()` is usually used by directives such as
- * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop.
- *
- */
- $destroy: function() {
- if (this.$root == this) return; // we can't remove the root node;
- this.$emit('$destroy');
- var parent = this.$parent;
-
- if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
- if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
- if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
- if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$eval
- * @function
- *
- * @description
- * Executes the `expression` on the current scope returning the result. Any exceptions in the
- * expression are propagated (uncaught). This is useful when evaluating engular expressions.
- *
- * # Example
-
- *
- * @param {(string|function())=} expression An angular expression to be executed.
- *
- * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
- * - `function(scope)`: execute the function with the current `scope` parameter.
- *
- * @returns {*} The result of evaluating the expression.
- */
- $eval: function(expr) {
- var fn = isString(expr)
- ? expressionCompile(expr)
- : expr || noop;
- return fn(this);
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$evalAsync
- * @function
- *
- * @description
- * Executes the expression on the current scope at a later point in time.
- *
- * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
- *
- * - it will execute in the current script execution context (before any DOM rendering).
- * - at least one {@link angular.scope.$digest $digest cycle} will be performed after
- * `expression` execution.
- *
- * Any exceptions from the execution of the expression are forwarded to the
- * {@link angular.service.$exceptionHandler $exceptionHandler} service.
- *
- * @param {(string|function())=} expression An angular expression to be executed.
- *
- * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
- * - `function(scope)`: execute the function with the current `scope` parameter.
- *
- */
- $evalAsync: function(expr) {
- this.$$asyncQueue.push(expr);
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$apply
- * @function
- *
- * @description
- * `$apply()` is used to execute an expression in angular from outside of the angular framework.
- * (For example from browser DOM events, setTimeout, XHR or third party libraries).
- * Because we are calling into the angular framework we need to perform proper scope life-cycle
- * of {@link angular.service.$exceptionHandler exception handling},
- * {@link angular.scope.$digest executing watches}.
- *
- * ## Life cycle
- *
- * # Pseudo-Code of `$apply()`
- function $apply(expr) {
- try {
- return $eval(expr);
- } catch (e) {
- $exceptionHandler(e);
- } finally {
- $root.$digest();
- }
- }
- *
- *
- * Scope's `$apply()` method transitions through the following stages:
- *
- * 1. The {@link guide/dev_guide.expressions expression} is executed using the
- * {@link angular.scope.$eval $eval()} method.
- * 2. Any exceptions from the execution of the expression are forwarded to the
- * {@link angular.service.$exceptionHandler $exceptionHandler} service.
- * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression
- * was executed using the {@link angular.scope.$digest $digest()} method.
- *
- *
- * @param {(string|function())=} exp An angular expression to be executed.
- *
- * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
- * - `function(scope)`: execute the function with current `scope` parameter.
- *
- * @returns {*} The result of evaluating the expression.
- */
- $apply: function(expr) {
- try {
- return this.$eval(expr);
- } catch (e) {
- this.$service('$exceptionHandler')(e);
- } finally {
- this.$root.$digest();
- }
- },
-
- /**
- * @ngdoc function
- * @name angular.scope.$on
- * @function
- *
- * @description
- * Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of
- * event life cycle.
- *
- * @param {string} name Event name to listen on.
- * @param {function(event)} listener Function to call when the event is emitted.
- * @returns {function()} Returns a deregistration function for this listener.
- *
- * The event listener function format is: `function(event)`. The `event` object passed into the
- * listener has the following attributes
- * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
- * - `currentScope` - {Scope}: the current scope which is handling the event.
- * - `name` - {string}: Name of the event.
- * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation
- * (available only for events that were `$emit`-ed).
- */
- $on: function(name, listener) {
- var namedListeners = this.$$listeners[name];
- if (!namedListeners) {
- this.$$listeners[name] = namedListeners = [];
- }
- namedListeners.push(listener);
-
- return function() {
- angularArray.remove(namedListeners, listener);
- };
- },
-
-
- /**
- * @ngdoc function
- * @name angular.scope.$emit
- * @function
- *
- * @description
- * Dispatches an event `name` upwards through the scope hierarchy notifying the
- * registered {@link angular.scope.$on} listeners.
- *
- * The event life cycle starts at the scope on which `$emit` was called. All
- * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified.
- * Afterwards, the event traverses upwards toward the root scope and calls all registered
- * listeners along the way. The event will stop propagating if one of the listeners cancels it.
- *
- * Any exception emmited from the {@link angular.scope.$on listeners} will be passed
- * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service.
- *
- * @param {string} name Event name to emit.
- * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
- */
- $emit: function(name, args) {
- var empty = [],
- namedListeners,
- canceled = false,
- scope = this,
- event = {
- name: name,
- targetScope: scope,
- cancel: function() {canceled = true;}
- },
- listenerArgs = concat([event], arguments, 1),
- i, length;
-
- do {
- namedListeners = scope.$$listeners[name] || empty;
- event.currentScope = scope;
- for (i=0, length=namedListeners.length; i
+ // 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('
click me
')();
+
+ // compile a piece of html and retain reference to both the dom and scope
+ var template = angular.element('
click me
'),
+ scope = angular.compile(template)();
+ // at this point template was transformed into a view
+
+ *
+ *
+ * @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: `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.
+ *
+ * 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.
+ *
+ * var scope = angular.injector()('$rootScope');
+ * var element = angular.compile('
{{total}}
')(scope);
+ *
+ *
+ * - 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:
+ *
+ * var original = angular.element('
{{total}}
'),
+ * 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`
+ *
+ *
+ *
+ * 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('
').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;
+ }
+ }
+ }
+ 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,
+ 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
*/
-angularServiceInject('$formFactory', function() {
+angularServiceInject('$formFactory', function($rootScope) {
/**
@@ -109,7 +109,7 @@ angularServiceInject('$formFactory', function() {
* 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(this);
+ formFactory.rootForm = formFactory($rootScope);
/**
@@ -132,7 +132,7 @@ angularServiceInject('$formFactory', function() {
return (parent || formFactory.rootForm).$new(FormController);
}
-});
+}, ['$rootScope']);
function propertiesUpdate(widget) {
widget.$valid = !(widget.$invalid =
diff --git a/src/service/location.js b/src/service/location.js
index d1d34e6762be..c9b76122605d 100644
--- a/src/service/location.js
+++ b/src/service/location.js
@@ -419,8 +419,8 @@ function locationGetterSetter(property, preprocess) {
*
* For more information see {@link guide/dev_guide.services.$location Developer Guide: Angular Services: Using $location}
*/
-angularServiceInject('$location', function($browser, $sniffer, $locationConfig, $document) {
- var scope = this, currentUrl,
+angularServiceInject('$location', function($rootScope, $browser, $sniffer, $locationConfig, $document) {
+ var currentUrl,
basePath = $browser.baseHref() || '/',
pathPrefix = pathPrefixFromBase(basePath),
hashPrefix = $locationConfig.hashPrefix || '',
@@ -464,7 +464,7 @@ angularServiceInject('$location', function($browser, $sniffer, $locationConfig,
href = href.indexOf(pathPrefix) === 0 ? href.substr(pathPrefix.length) : href;
currentUrl.url(href);
- scope.$apply();
+ $rootScope.$apply();
event.preventDefault();
// hack to work around FF6 bug 684208 when scenario runner clicks on links
window.angular['ff-684208-preventDefault'] = true;
@@ -482,16 +482,16 @@ angularServiceInject('$location', function($browser, $sniffer, $locationConfig,
$browser.onUrlChange(function(newUrl) {
if (currentUrl.absUrl() != newUrl) {
currentUrl.$$parse(newUrl);
- scope.$apply();
+ $rootScope.$apply();
}
});
// update browser
var changeCounter = 0;
- scope.$watch(function() {
+ $rootScope.$watch(function() {
if ($browser.url() != currentUrl.absUrl()) {
changeCounter++;
- scope.$evalAsync(function() {
+ $rootScope.$evalAsync(function() {
$browser.url(currentUrl.absUrl(), currentUrl.$$replace);
currentUrl.$$replace = false;
});
@@ -501,7 +501,7 @@ angularServiceInject('$location', function($browser, $sniffer, $locationConfig,
});
return currentUrl;
-}, ['$browser', '$sniffer', '$locationConfig', '$document']);
+}, ['$rootScope', '$browser', '$sniffer', '$locationConfig', '$document']);
angular.service('$locationConfig', function() {
diff --git a/src/service/route.js b/src/service/route.js
index ddc3df493729..3918c251b5b0 100644
--- a/src/service/route.js
+++ b/src/service/route.js
@@ -62,7 +62,7 @@
*/
-angularServiceInject('$route', function($location, $routeParams) {
+angularServiceInject('$route', function($rootScope, $location, $routeParams) {
/**
* @ngdoc event
* @name angular.service.$route#$beforeRouteChange
@@ -112,8 +112,7 @@ angularServiceInject('$route', function($location, $routeParams) {
var routes = {},
matcher = switchRouteMatcher,
- parentScope = this,
- rootScope = this,
+ parentScope = $rootScope,
dirty = 0,
forceReload = false,
$route = {
@@ -220,7 +219,7 @@ angularServiceInject('$route', function($location, $routeParams) {
}
};
- this.$watch(function() { return dirty + $location.url(); }, updateRoute);
+ $rootScope.$watch(function() { return dirty + $location.url(); }, updateRoute);
return $route;
@@ -262,7 +261,7 @@ angularServiceInject('$route', function($location, $routeParams) {
last.scope && last.scope.$emit('$routeUpdate');
} else {
forceReload = false;
- rootScope.$broadcast('$beforeRouteChange', next, last);
+ $rootScope.$broadcast('$beforeRouteChange', next, last);
last && last.scope && last.scope.$destroy();
$route.current = next;
if (next) {
@@ -280,7 +279,7 @@ angularServiceInject('$route', function($location, $routeParams) {
next.scope = parentScope.$new(Controller);
}
}
- rootScope.$broadcast('$afterRouteChange', next, last);
+ $rootScope.$broadcast('$afterRouteChange', next, last);
}
}
@@ -323,4 +322,4 @@ angularServiceInject('$route', function($location, $routeParams) {
}
-}, ['$location', '$routeParams']);
+}, ['$rootScope', '$location', '$routeParams']);
diff --git a/src/service/scope.js b/src/service/scope.js
new file mode 100644
index 000000000000..f059208a74cc
--- /dev/null
+++ b/src/service/scope.js
@@ -0,0 +1,655 @@
+'use strict';
+
+/**
+ * DESIGN NOTES
+ *
+ * The design decisions behind the scope ware heavily favored for speed and memory consumption.
+ *
+ * The typical use of scope is to watch the expressions, which most of the time return the same
+ * value as last time so we optimize the operation.
+ *
+ * Closures construction is expensive from speed as well as memory:
+ * - no closures, instead ups prototypical inheritance for API
+ * - Internal state needs to be stored on scope directly, which means that private state is
+ * exposed as $$____ properties
+ *
+ * Loop operations are optimized by using while(count--) { ... }
+ * - this means that in order to keep the same order of execution as addition we have to add
+ * items to the array at the begging (shift) instead of at the end (push)
+ *
+ * Child scopes are created and removed often
+ * - Using array would be slow since inserts in meddle are expensive so we use linked list
+ *
+ * There are few watches then a lot of observers. This is why you don't want the observer to be
+ * implemented in the same way as watch. Watch requires return of initialization function which
+ * are expensive to construct.
+ */
+
+angularServiceInject('$rootScope', function($injector, $exceptionHandler){
+ /**
+ * @ngdoc function
+ * @name angular.scope
+ *
+ * @description
+ * A root scope can be created by calling {@link angular.scope angular.scope()}. Child scopes
+ * are created using the {@link angular.scope.$new $new()} method.
+ * (Most scopes are created automatically when compiled HTML template is executed.)
+ *
+ * Here is a simple scope snippet to show how you can interact with the scope.
+ *
+ var scope = angular.scope();
+ scope.salutation = 'Hello';
+ scope.name = 'World';
+
+ expect(scope.greeting).toEqual(undefined);
+
+ scope.$watch('name', function() {
+ this.greeting = this.salutation + ' ' + this.name + '!';
+ }); // initialize the watch
+
+ expect(scope.greeting).toEqual(undefined);
+ scope.name = 'Misko';
+ // still old value, since watches have not been called yet
+ expect(scope.greeting).toEqual(undefined);
+
+ scope.$digest(); // fire all the watches
+ expect(scope.greeting).toEqual('Hello Misko!');
+ *
+ *
+ * # Inheritance
+ * A scope can inherit from a parent scope, as in this example:
+ *
+ *
+ * # Dependency Injection
+ * See {@link guide/dev_guide.di dependency injection}.
+ *
+ *
+ * @param {Object.=} providers Map of service factory which need to be provided
+ * for the current scope. Defaults to {@link angular.service}.
+ * @param {Object.=} instanceCache Provides pre-instantiated services which should
+ * append/override services provided by `providers`. This is handy when unit-testing and having
+ * the need to override a default service.
+ * @returns {Object} Newly created scope.
+ *
+ */
+ function Scope() {
+ this.$id = nextUid();
+ this.$$phase = this.$parent = this.$$watchers =
+ this.$$nextSibling = this.$$prevSibling =
+ this.$$childHead = this.$$childTail = null;
+ this.$destructor = noop;
+ this['this'] = this.$root = this;
+ this.$$asyncQueue = [];
+ this.$$listeners = {};
+ }
+
+ /**
+ * @ngdoc property
+ * @name angular.scope.$id
+ * @returns {number} Unique scope ID (monotonically increasing alphanumeric sequence) useful for
+ * debugging.
+ */
+
+ /**
+ * @ngdoc property
+ * @name angular.scope.$root
+ * @returns {Scope} The root scope of the current scope hierarchy.
+ */
+
+ /**
+ * @ngdoc property
+ * @name angular.scope.$parent
+ * @returns {Scope} The parent scope of the current scope.
+ */
+
+
+ Scope.prototype = {
+ /**
+ * @ngdoc function
+ * @name angular.scope.$new
+ * @function
+ *
+ * @description
+ * Creates a new child {@link angular.scope scope}. The new scope can optionally behave as a
+ * controller. The parent scope will propagate the {@link angular.scope.$digest $digest()} and
+ * {@link angular.scope.$digest $digest()} events. The scope can be removed from the scope
+ * hierarchy using {@link angular.scope.$destroy $destroy()}.
+ *
+ * {@link angular.scope.$destroy $destroy()} must be called on a scope when it is desired for
+ * the scope and its child scopes to be permanently detached from the parent and thus stop
+ * participating in model change detection and listener notification by invoking.
+ *
+ * @param {function()=} Class Constructor function which the scope should be applied to the scope.
+ * @param {...*} curryArguments Any additional arguments which are curried into the constructor.
+ * See {@link guide/dev_guide.di dependency injection}.
+ * @returns {Object} The newly created child scope.
+ *
+ */
+ $new: function(Class, curryArguments) {
+ var Child = function() {}; // should be anonymous; This is so that when the minifier munges
+ // the name it does not become random set of chars. These will then show up as class
+ // name in the debugger.
+ var child;
+ Child.prototype = this;
+ child = new Child();
+ child['this'] = child;
+ child.$$listeners = {};
+ child.$parent = this;
+ child.$id = nextUid();
+ child.$$asyncQueue = [];
+ child.$$phase = child.$$watchers =
+ child.$$nextSibling = child.$$childHead = child.$$childTail = null;
+ child.$$prevSibling = this.$$childTail;
+ if (this.$$childHead) {
+ this.$$childTail.$$nextSibling = child;
+ this.$$childTail = child;
+ } else {
+ this.$$childHead = this.$$childTail = child;
+ }
+ // short circuit if we have no class
+ if (Class) {
+ // can't use forEach, we need speed!
+ var ClassPrototype = Class.prototype;
+ for(var key in ClassPrototype) {
+ child[key] = bind(child, ClassPrototype[key]);
+ }
+ $injector.invoke(child, Class, curryArguments);
+ }
+ return child;
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$watch
+ * @function
+ *
+ * @description
+ * Registers a `listener` callback to be executed whenever the `watchExpression` changes.
+ *
+ * - The `watchExpression` is called on every call to {@link angular.scope.$digest $digest()} and
+ * should return the value which will be watched. (Since {@link angular.scope.$digest $digest()}
+ * reruns when it detects changes the `watchExpression` can execute multiple times per
+ * {@link angular.scope.$digest $digest()} and should be idempotent.)
+ * - The `listener` is called only when the value from the current `watchExpression` and the
+ * previous call to `watchExpression' are not equal. The inequality is determined according to
+ * {@link angular.equals} function. To save the value of the object for later comparison
+ * {@link angular.copy} function is used. It also means that watching complex options will
+ * have adverse memory and performance implications.
+ * - The watch `listener` may change the model, which may trigger other `listener`s to fire. This
+ * is achieved by rerunning the watchers until no changes are detected. The rerun iteration
+ * limit is 100 to prevent infinity loop deadlock.
+ *
+ *
+ * If you want to be notified whenever {@link angular.scope.$digest $digest} is called,
+ * you can register an `watchExpression` function with no `listener`. (Since `watchExpression`,
+ * can execute multiple times per {@link angular.scope.$digest $digest} cycle when a change is
+ * detected, be prepared for multiple calls to your listener.)
+ *
+ *
+ * # Example
+
+ *
+ *
+ *
+ * @param {(function()|string)} watchExpression Expression that is evaluated on each
+ * {@link angular.scope.$digest $digest} cycle. A change in the return value triggers a
+ * call to the `listener`.
+ *
+ * - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
+ * - `function(scope)`: called with current `scope` as a parameter.
+ * @param {(function()|string)=} listener Callback called whenever the return value of
+ * the `watchExpression` changes.
+ *
+ * - `string`: Evaluated as {@link guide/dev_guide.expressions expression}
+ * - `function(scope, newValue, oldValue)`: called with current `scope` an previous and
+ * current values as parameters.
+ * @returns {function()} Returns a deregistration function for this listener.
+ */
+ $watch: function(watchExp, listener) {
+ var scope = this,
+ get = compileToFn(watchExp, 'watch'),
+ listenFn = compileToFn(listener || noop, 'listener'),
+ array = scope.$$watchers,
+ watcher = {
+ fn: listenFn,
+ last: Number.NaN, // NaN !== NaN. We used this to force $watch to fire on first run.
+ get: get
+ };
+
+ if (!array) {
+ array = scope.$$watchers = [];
+ }
+ // we use unshift since we use a while loop in $digest for speed.
+ // the while loop reads in reverse order.
+ array.unshift(watcher);
+
+ return function() {
+ angularArray.remove(array, watcher);
+ };
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$digest
+ * @function
+ *
+ * @description
+ * Process all of the {@link angular.scope.$watch watchers} of the current scope and its children.
+ * Because a {@link angular.scope.$watch watcher}'s listener can change the model, the
+ * `$digest()` keeps calling the {@link angular.scope.$watch watchers} until no more listeners are
+ * firing. This means that it is possible to get into an infinite loop. This function will throw
+ * `'Maximum iteration limit exceeded.'` if the number of iterations exceeds 100.
+ *
+ * Usually you don't call `$digest()` directly in
+ * {@link angular.directive.ng:controller controllers} or in {@link angular.directive directives}.
+ * Instead a call to {@link angular.scope.$apply $apply()} (typically from within a
+ * {@link angular.directive directive}) will force a `$digest()`.
+ *
+ * If you want to be notified whenever `$digest()` is called,
+ * you can register a `watchExpression` function with {@link angular.scope.$watch $watch()}
+ * with no `listener`.
+ *
+ * You may have a need to call `$digest()` from within unit-tests, to simulate the scope
+ * life-cycle.
+ *
+ * # Example
+
+ *
+ */
+ $digest: function() {
+ var watch, value, last,
+ watchers,
+ asyncQueue,
+ length,
+ dirty, ttl = 100,
+ next, current, target = this;
+
+ if (target.$$phase) {
+ throw Error(target.$$phase + ' already in progress');
+ }
+ do {
+
+ dirty = false;
+ current = target;
+ do {
+ current.$$phase = '$digest';
+ asyncQueue = current.$$asyncQueue;
+ while(asyncQueue.length) {
+ try {
+ current.$eval(asyncQueue.shift());
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+ if ((watchers = current.$$watchers)) {
+ // process our watches
+ length = watchers.length;
+ while (length--) {
+ try {
+ watch = watchers[length];
+ // Most common watches are on primitives, in which case we can short
+ // circuit it with === operator, only when === fails do we use .equals
+ if ((value = watch.get(current)) !== (last = watch.last) && !equals(value, last)) {
+ dirty = true;
+ watch.last = copy(value);
+ watch.fn(current, value, last);
+ }
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ }
+ }
+
+ current.$$phase = null;
+
+ // Insanity Warning: scope depth-first traversal
+ // yes, this code is a bit crazy, but it works and we have tests to prove it!
+ // this piece should be kept in sync with the traversal in $broadcast
+ if (!(next = (current.$$childHead || (current !== target && current.$$nextSibling)))) {
+ while(current !== target && !(next = current.$$nextSibling)) {
+ current = current.$parent;
+ }
+ }
+ } while ((current = next));
+
+ if(!(ttl--)) {
+ throw Error('100 $digest() iterations reached. Aborting!');
+ }
+ } while (dirty);
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$destroy
+ * @function
+ *
+ * @description
+ * Remove the current scope (and all of its children) from the parent scope. Removal implies
+ * that calls to {@link angular.scope.$digest $digest()} will no longer propagate to the current
+ * scope and its children. Removal also implies that the current scope is eligible for garbage
+ * collection.
+ *
+ * The destructing scope emits an `$destroy` {@link angular.scope.$emit event}.
+ *
+ * The `$destroy()` is usually used by directives such as
+ * {@link angular.widget.@ng:repeat ng:repeat} for managing the unrolling of the loop.
+ *
+ */
+ $destroy: function() {
+ if (this.$root == this) return; // we can't remove the root node;
+ this.$emit('$destroy');
+ var parent = this.$parent;
+
+ if (parent.$$childHead == this) parent.$$childHead = this.$$nextSibling;
+ if (parent.$$childTail == this) parent.$$childTail = this.$$prevSibling;
+ if (this.$$prevSibling) this.$$prevSibling.$$nextSibling = this.$$nextSibling;
+ if (this.$$nextSibling) this.$$nextSibling.$$prevSibling = this.$$prevSibling;
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$eval
+ * @function
+ *
+ * @description
+ * Executes the `expression` on the current scope returning the result. Any exceptions in the
+ * expression are propagated (uncaught). This is useful when evaluating engular expressions.
+ *
+ * # Example
+
+ *
+ * @param {(string|function())=} expression An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
+ * - `function(scope)`: execute the function with the current `scope` parameter.
+ *
+ * @returns {*} The result of evaluating the expression.
+ */
+ $eval: function(expr) {
+ var fn = isString(expr)
+ ? expressionCompile(expr)
+ : expr || noop;
+ return fn(this);
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$evalAsync
+ * @function
+ *
+ * @description
+ * Executes the expression on the current scope at a later point in time.
+ *
+ * The `$evalAsync` makes no guarantees as to when the `expression` will be executed, only that:
+ *
+ * - it will execute in the current script execution context (before any DOM rendering).
+ * - at least one {@link angular.scope.$digest $digest cycle} will be performed after
+ * `expression` execution.
+ *
+ * Any exceptions from the execution of the expression are forwarded to the
+ * {@link angular.service.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {(string|function())=} expression An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
+ * - `function(scope)`: execute the function with the current `scope` parameter.
+ *
+ */
+ $evalAsync: function(expr) {
+ this.$$asyncQueue.push(expr);
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$apply
+ * @function
+ *
+ * @description
+ * `$apply()` is used to execute an expression in angular from outside of the angular framework.
+ * (For example from browser DOM events, setTimeout, XHR or third party libraries).
+ * Because we are calling into the angular framework we need to perform proper scope life-cycle
+ * of {@link angular.service.$exceptionHandler exception handling},
+ * {@link angular.scope.$digest executing watches}.
+ *
+ * ## Life cycle
+ *
+ * # Pseudo-Code of `$apply()`
+ function $apply(expr) {
+ try {
+ return $eval(expr);
+ } catch (e) {
+ $exceptionHandler(e);
+ } finally {
+ $root.$digest();
+ }
+ }
+ *
+ *
+ * Scope's `$apply()` method transitions through the following stages:
+ *
+ * 1. The {@link guide/dev_guide.expressions expression} is executed using the
+ * {@link angular.scope.$eval $eval()} method.
+ * 2. Any exceptions from the execution of the expression are forwarded to the
+ * {@link angular.service.$exceptionHandler $exceptionHandler} service.
+ * 3. The {@link angular.scope.$watch watch} listeners are fired immediately after the expression
+ * was executed using the {@link angular.scope.$digest $digest()} method.
+ *
+ *
+ * @param {(string|function())=} exp An angular expression to be executed.
+ *
+ * - `string`: execute using the rules as defined in {@link guide/dev_guide.expressions expression}.
+ * - `function(scope)`: execute the function with current `scope` parameter.
+ *
+ * @returns {*} The result of evaluating the expression.
+ */
+ $apply: function(expr) {
+ try {
+ return this.$eval(expr);
+ } catch (e) {
+ $exceptionHandler(e);
+ } finally {
+ this.$root.$digest();
+ }
+ },
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$on
+ * @function
+ *
+ * @description
+ * Listen on events of a given type. See {@link angular.scope.$emit $emit} for discussion of
+ * event life cycle.
+ *
+ * @param {string} name Event name to listen on.
+ * @param {function(event)} listener Function to call when the event is emitted.
+ * @returns {function()} Returns a deregistration function for this listener.
+ *
+ * The event listener function format is: `function(event)`. The `event` object passed into the
+ * listener has the following attributes
+ * - `targetScope` - {Scope}: the scope on which the event was `$emit`-ed or `$broadcast`-ed.
+ * - `currentScope` - {Scope}: the current scope which is handling the event.
+ * - `name` - {string}: Name of the event.
+ * - `cancel` - {function=}: calling `cancel` function will cancel further event propagation
+ * (available only for events that were `$emit`-ed).
+ */
+ $on: function(name, listener) {
+ var namedListeners = this.$$listeners[name];
+ if (!namedListeners) {
+ this.$$listeners[name] = namedListeners = [];
+ }
+ namedListeners.push(listener);
+
+ return function() {
+ angularArray.remove(namedListeners, listener);
+ };
+ },
+
+
+ /**
+ * @ngdoc function
+ * @name angular.scope.$emit
+ * @function
+ *
+ * @description
+ * Dispatches an event `name` upwards through the scope hierarchy notifying the
+ * registered {@link angular.scope.$on} listeners.
+ *
+ * The event life cycle starts at the scope on which `$emit` was called. All
+ * {@link angular.scope.$on listeners} listening for `name` event on this scope get notified.
+ * Afterwards, the event traverses upwards toward the root scope and calls all registered
+ * listeners along the way. The event will stop propagating if one of the listeners cancels it.
+ *
+ * Any exception emmited from the {@link angular.scope.$on listeners} will be passed
+ * onto the {@link angular.service.$exceptionHandler $exceptionHandler} service.
+ *
+ * @param {string} name Event name to emit.
+ * @param {...*} args Optional set of arguments which will be passed onto the event listeners.
+ */
+ $emit: function(name, args) {
+ var empty = [],
+ namedListeners,
+ canceled = false,
+ scope = this,
+ event = {
+ name: name,
+ targetScope: scope,
+ cancel: function() {canceled = true;}
+ },
+ listenerArgs = concat([event], arguments, 1),
+ i, length;
+
+ do {
+ namedListeners = scope.$$listeners[name] || empty;
+ event.currentScope = scope;
+ for (i=0, length=namedListeners.length; i
*/
-angularServiceInject('$xhr', function($browser, $error, $log){
- var rootScope = this;
+angularServiceInject('$xhr', function($rootScope, $browser, $error, $log){
var xhrHeaderDefaults = {
common: {
"Accept": "application/json, text/plain, */*",
@@ -204,7 +203,7 @@ angularServiceInject('$xhr', function($browser, $error, $log){
response = fromJson(response, true);
}
}
- rootScope.$apply(function() {
+ $rootScope.$apply(function() {
if (200 <= code && code < 300) {
success(code, response);
} else if (isFunction(error)) {
@@ -226,4 +225,4 @@ angularServiceInject('$xhr', function($browser, $error, $log){
xhr.defaults = {headers: xhrHeaderDefaults};
return xhr;
-}, ['$browser', '$xhr.error', '$log']);
+}, ['$rootScope', '$browser', '$xhr.error', '$log']);
diff --git a/src/widget/select.js b/src/widget/select.js
index 9b9ed1727bc8..2e328b260d63 100644
--- a/src/widget/select.js
+++ b/src/widget/select.js
@@ -130,7 +130,8 @@ var NG_OPTIONS_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+
angularWidget('select', function(element){
this.directives(true);
this.descend(true);
- return element.attr('ng:model') && annotate('$formFactory', function($formFactory, selectElement){
+ return element.attr('ng:model') &&
+ annotate('$formFactory', '$compile', function($formFactory, $compile, selectElement){
var modelScope = this,
match,
form = $formFactory.forElement(selectElement),
@@ -245,7 +246,7 @@ angularWidget('select', function(element){
// developer declared null option, so user should be able to select it
nullOption = jqLite(option).remove();
// compile the element since there might be bindings in it
- compile(nullOption)(modelScope);
+ $compile(nullOption)(modelScope);
}
});
selectElement.html(''); // clear contents
diff --git a/test/AngularSpec.js b/test/AngularSpec.js
index 5b0a3466c9ee..50485a2272c6 100644
--- a/test/AngularSpec.js
+++ b/test/AngularSpec.js
@@ -455,22 +455,16 @@ describe('angular', function() {
'
');
- scope = angular.compile(template)();
- scope.$digest();
+ it('should link to existing node and create scope', inject(function($rootScope, $compile) {
+ var template = angular.element('
{{greeting = "hello world"}}
');
+ $compile(template)($rootScope);
+ $rootScope.$digest();
expect(template.text()).toEqual('hello world');
- expect(scope.greeting).toEqual('hello world');
- });
+ expect($rootScope.greeting).toEqual('hello world');
+ }));
- it('should link to existing node and given scope', function() {
- scope = angular.scope();
- template = angular.element('
{{greeting = "hello world"}}
');
- angular.compile(template)(scope);
- scope.$digest();
+ it('should link to existing node and given scope', inject(function($rootScope, $compile) {
+ var template = angular.element('
{{greeting = "hello world"}}
');
+ $compile(template)($rootScope);
+ $rootScope.$digest();
expect(template.text()).toEqual('hello world');
- expect(scope).toEqual(scope);
- });
+ }));
- it('should link to new node and given scope', function() {
- scope = angular.scope();
- template = jqLite('
{{greeting = "hello world"}}
');
+ it('should link to new node and given scope', inject(function($rootScope, $compile) {
+ var template = jqLite('
{{greeting = "hello world"}}
');
- var templateFn = angular.compile(template);
+ var templateFn = $compile(template);
var templateClone = template.clone();
- templateFn(scope, function(clone){
+ var element = templateFn($rootScope, function(clone){
templateClone = clone;
});
- scope.$digest();
+ $rootScope.$digest();
expect(template.text()).toEqual('');
- expect(scope.$element.text()).toEqual('hello world');
- expect(scope.$element).toEqual(templateClone);
- expect(scope.greeting).toEqual('hello world');
- });
-
- it('should link to cloned node and create scope', function() {
- scope = angular.scope();
- template = jqLite('
{{greeting = "hello world"}}
');
- angular.compile(template)(scope, noop).$digest();
+ expect(element.text()).toEqual('hello world');
+ expect(element).toEqual(templateClone);
+ expect($rootScope.greeting).toEqual('hello world');
+ }));
+
+ it('should link to cloned node and create scope', inject(function($rootScope, $compile) {
+ var template = jqLite('
');
- scope.$digest();
- expect(fromJson(scope.$element.text())).toEqual({key:'value'});
- });
+ it('should render object as JSON ignore $$', inject(function($rootScope, $compile) {
+ var element = $compile('
{{ {key:"value", $$key:"hide"} }}
')($rootScope);
+ $rootScope.$digest();
+ expect(fromJson(element.text())).toEqual({key:'value'});
+ }));
});
describe('ng:bind-attr', function() {
- it('should bind attributes', function() {
- var scope = compile('');
- scope.$digest();
+ it('should bind attributes', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.attr('src')).toEqual('http://localhost/mysrc');
expect(element.attr('alt')).toEqual('myalt');
- });
+ }));
- it('should not pretty print JSON in attributes', function() {
- var scope = compile('');
- scope.$digest();
+ it('should not pretty print JSON in attributes', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.attr('alt')).toEqual('{"a":1}');
- });
+ }));
});
- it('should remove special attributes on false', function() {
- var scope = compile('');
- var input = scope.$element[0];
+ it('should remove special attributes on false', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ var input = element[0];
expect(input.disabled).toEqual(false);
expect(input.readOnly).toEqual(false);
expect(input.checked).toEqual(false);
- scope.disabled = true;
- scope.readonly = true;
- scope.checked = true;
- scope.$digest();
+ $rootScope.disabled = true;
+ $rootScope.readonly = true;
+ $rootScope.checked = true;
+ $rootScope.$digest();
expect(input.disabled).toEqual(true);
expect(input.readOnly).toEqual(true);
expect(input.checked).toEqual(true);
- });
+ }));
describe('ng:click', function() {
- it('should get called on a click', function() {
- var scope = compile('');
- scope.$digest();
- expect(scope.clicked).toBeFalsy();
+ it('should get called on a click', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
+ expect($rootScope.clicked).toBeFalsy();
browserTrigger(element, 'click');
- expect(scope.clicked).toEqual(true);
- });
+ expect($rootScope.clicked).toEqual(true);
+ }));
- it('should stop event propagation', function() {
- var scope = compile('
');
- scope.$digest();
- expect(scope.outer).not.toBeDefined();
- expect(scope.inner).not.toBeDefined();
+ it('should stop event propagation', inject(function($rootScope, $compile) {
+ var element = $compile('
')($rootScope);
+ $rootScope.$digest();
+ expect($rootScope.outer).not.toBeDefined();
+ expect($rootScope.inner).not.toBeDefined();
var innerDiv = element.children()[0];
browserTrigger(innerDiv, 'click');
- expect(scope.outer).not.toBeDefined();
- expect(scope.inner).toEqual(true);
- });
+ expect($rootScope.outer).not.toBeDefined();
+ expect($rootScope.inner).toEqual(true);
+ }));
});
describe('ng:submit', function() {
- it('should get called on form submit', function() {
- var scope = compile('');
- scope.$digest();
- expect(scope.submitted).not.toBeDefined();
+ it('should get called on form submit', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
+ expect($rootScope.submitted).not.toBeDefined();
browserTrigger(element.children()[0]);
- expect(scope.submitted).toEqual(true);
- });
+ expect($rootScope.submitted).toEqual(true);
+ }));
});
describe('ng:class', function() {
- it('should add new and remove old classes dynamically', function() {
- var scope = compile('');
- scope.dynClass = 'A';
- scope.$digest();
+ it('should add new and remove old classes dynamically', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynClass = 'A';
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBe(true);
expect(element.hasClass('A')).toBe(true);
- scope.dynClass = 'B';
- scope.$digest();
+ $rootScope.dynClass = 'B';
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBe(true);
expect(element.hasClass('A')).toBe(false);
expect(element.hasClass('B')).toBe(true);
- delete scope.dynClass;
- scope.$digest();
+ delete $rootScope.dynClass;
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBe(true);
expect(element.hasClass('A')).toBe(false);
expect(element.hasClass('B')).toBe(false);
- });
+ }));
- it('should support adding multiple classes via an array', function() {
- var scope = compile('');
- scope.$digest();
+ it('should support adding multiple classes via an array', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBeTruthy();
expect(element.hasClass('A')).toBeTruthy();
expect(element.hasClass('B')).toBeTruthy();
- });
+ }));
- it('should support adding multiple classes via a space delimited string', function() {
- var scope = compile('');
- scope.$digest();
+ it('should support adding multiple classes via a space delimited string', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBeTruthy();
expect(element.hasClass('A')).toBeTruthy();
expect(element.hasClass('B')).toBeTruthy();
- });
+ }));
- it('should preserve class added post compilation with pre-existing classes', function() {
- var scope = compile('');
- scope.dynClass = 'A';
- scope.$digest();
+ it('should preserve class added post compilation with pre-existing classes', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynClass = 'A';
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBe(true);
// add extra class, change model and eval
element.addClass('newClass');
- scope.dynClass = 'B';
- scope.$digest();
+ $rootScope.dynClass = 'B';
+ $rootScope.$digest();
expect(element.hasClass('existing')).toBe(true);
expect(element.hasClass('B')).toBe(true);
expect(element.hasClass('newClass')).toBe(true);
- });
+ }));
- it('should preserve class added post compilation without pre-existing classes"', function() {
- var scope = compile('');
- scope.dynClass = 'A';
- scope.$digest();
+ it('should preserve class added post compilation without pre-existing classes"', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynClass = 'A';
+ $rootScope.$digest();
expect(element.hasClass('A')).toBe(true);
// add extra class, change model and eval
element.addClass('newClass');
- scope.dynClass = 'B';
- scope.$digest();
+ $rootScope.dynClass = 'B';
+ $rootScope.$digest();
expect(element.hasClass('B')).toBe(true);
expect(element.hasClass('newClass')).toBe(true);
- });
+ }));
- it('should preserve other classes with similar name"', function() {
- var scope = compile('');
- scope.dynCls = 'panel';
- scope.$digest();
- scope.dynCls = 'foo';
- scope.$digest();
+ it('should preserve other classes with similar name"', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynCls = 'panel';
+ $rootScope.$digest();
+ $rootScope.dynCls = 'foo';
+ $rootScope.$digest();
expect(element[0].className).toBe('ui-panel ui-selected ng-directive foo');
- });
+ }));
- it('should not add duplicate classes', function() {
- var scope = compile('');
- scope.dynCls = 'panel';
- scope.$digest();
+ it('should not add duplicate classes', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynCls = 'panel';
+ $rootScope.$digest();
expect(element[0].className).toBe('panel bar ng-directive');
- });
+ }));
- it('should remove classes even if it was specified via class attribute', function() {
- var scope = compile('');
- scope.dynCls = 'panel';
- scope.$digest();
- scope.dynCls = 'window';
- scope.$digest();
+ it('should remove classes even if it was specified via class attribute', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynCls = 'panel';
+ $rootScope.$digest();
+ $rootScope.dynCls = 'window';
+ $rootScope.$digest();
expect(element[0].className).toBe('bar ng-directive window');
- });
+ }));
- it('should remove classes even if they were added by another code', function() {
- var scope = compile('');
- scope.dynCls = 'foo';
- scope.$digest();
+ it('should remove classes even if they were added by another code', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynCls = 'foo';
+ $rootScope.$digest();
element.addClass('foo');
- scope.dynCls = '';
- scope.$digest();
+ $rootScope.dynCls = '';
+ $rootScope.$digest();
expect(element[0].className).toBe('ng-directive');
- });
+ }));
- it('should convert undefined and null values to an empty string', function() {
- var scope = compile('');
- scope.dynCls = [undefined, null];
- scope.$digest();
+ it('should convert undefined and null values to an empty string', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.dynCls = [undefined, null];
+ $rootScope.$digest();
expect(element[0].className).toBe('ng-directive');
- });
+ }));
});
- it('should ng:class odd/even', function() {
- var scope = compile('
');
- scope.$digest();
+ it('should ng:class odd/even', inject(function($rootScope, $compile) {
+ var element = $compile('
')($rootScope);
+ $rootScope.$digest();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[2]);
expect(e1.hasClass('existing')).toBeTruthy();
expect(e1.hasClass('odd')).toBeTruthy();
expect(e2.hasClass('existing')).toBeTruthy();
expect(e2.hasClass('even')).toBeTruthy();
- });
+ }));
- it('should allow both ng:class and ng:class-odd/even on the same element', function() {
- var scope = compile('
' +
- '' +
- '
');
- scope.$apply();
+ it('should allow both ng:class and ng:class-odd/even on the same element', inject(function($rootScope, $compile) {
+ var element = $compile('
' +
+ '' +
+ '
')($rootScope);
+ $rootScope.$apply();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[2]);
@@ -334,15 +321,15 @@ describe("directive", function() {
expect(e2.hasClass('plainClass')).toBeTruthy();
expect(e2.hasClass('even')).toBeTruthy();
expect(e2.hasClass('odd')).toBeFalsy();
- });
+ }));
- it('should allow both ng:class and ng:class-odd/even with multiple classes', function() {
- var scope = compile('
' +
- '' +
- '
');
- scope.$apply();
+ it('should allow both ng:class and ng:class-odd/even with multiple classes', inject(function($rootScope, $compile) {
+ var element = $compile('
' +
+ '' +
+ '
')($rootScope);
+ $rootScope.$apply();
var e1 = jqLite(element[0].childNodes[1]);
var e2 = jqLite(element[0].childNodes[2]);
@@ -359,76 +346,73 @@ describe("directive", function() {
expect(e2.hasClass('F')).toBeTruthy();
expect(e2.hasClass('C')).toBeFalsy();
expect(e2.hasClass('D')).toBeFalsy();
- });
+ }));
describe('ng:style', function() {
- it('should set', function() {
- var scope = compile('');
- scope.$digest();
+ it('should set', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.css('height')).toEqual('40px');
- });
+ }));
- it('should silently ignore undefined style', function() {
- var scope = compile('');
- scope.$digest();
+ it('should silently ignore undefined style', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.hasClass('ng-exception')).toBeFalsy();
- });
+ }));
- it('should preserve and remove previous style', function() {
- var scope = compile('');
- scope.$digest();
+ it('should preserve and remove previous style', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(getStyle(element)).toEqual({height: '10px'});
- scope.myStyle = {height: '20px', width: '10px'};
- scope.$digest();
+ $rootScope.myStyle = {height: '20px', width: '10px'};
+ $rootScope.$digest();
expect(getStyle(element)).toEqual({height: '20px', width: '10px'});
- scope.myStyle = {};
- scope.$digest();
+ $rootScope.myStyle = {};
+ $rootScope.$digest();
expect(getStyle(element)).toEqual({height: '10px'});
- });
+ }));
});
- it('should silently ignore undefined ng:style', function() {
- var scope = compile('');
- scope.$digest();
+ it('should silently ignore undefined ng:style', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ $rootScope.$digest();
expect(element.hasClass('ng-exception')).toBeFalsy();
- });
+ }));
describe('ng:show', function() {
- it('should show and hide an element', function() {
- var element = jqLite(''),
- scope = compile(element);
-
- scope.$digest();
+ it('should show and hide an element', inject(function($rootScope, $compile) {
+ var element = jqLite('');
+ var element = $compile(element)($rootScope);
+ $rootScope.$digest();
expect(isCssVisible(element)).toEqual(false);
- scope.exp = true;
- scope.$digest();
+ $rootScope.exp = true;
+ $rootScope.$digest();
expect(isCssVisible(element)).toEqual(true);
- });
-
+ }));
- it('should make hidden element visible', function() {
- var element = jqLite(''),
- scope = compile(element);
+ it('should make hidden element visible', inject(function($rootScope, $compile) {
+ var element = jqLite('');
+ var element = $compile(element)($rootScope);
expect(isCssVisible(element)).toBe(false);
- scope.exp = true;
- scope.$digest();
+ $rootScope.exp = true;
+ $rootScope.$digest();
expect(isCssVisible(element)).toBe(true);
- });
+ }));
});
describe('ng:hide', function() {
- it('should hide an element', function() {
- var element = jqLite(''),
- scope = compile(element);
-
+ it('should hide an element', inject(function($rootScope, $compile) {
+ var element = jqLite('');
+ var element = $compile(element)($rootScope);
expect(isCssVisible(element)).toBe(true);
- scope.exp = true;
- scope.$digest();
+ $rootScope.exp = true;
+ $rootScope.$digest();
expect(isCssVisible(element)).toBe(false);
- });
+ }));
});
describe('ng:controller', function() {
@@ -453,13 +437,13 @@ describe("directive", function() {
window.temp = undefined;
});
- it('should bind', function() {
- var scope = compile('');
- expect(scope.greeter.greeting).toEqual('hello');
- expect(scope.greeter.greet('misko')).toEqual('hello misko!');
- });
+ it('should bind', inject(function($rootScope, $compile) {
+ var element = $compile('')($rootScope);
+ expect($rootScope.greeter.greeting).toEqual('hello');
+ expect($rootScope.greeter.greet('misko')).toEqual('hello misko!');
+ }));
- it('should support nested controllers', function() {
+ it('should support nested controllers', inject(function($rootScope, $compile) {
temp.ChildGreeter = function() {
this.greeting = 'hey';
this.$root.childGreeter = this;
@@ -469,48 +453,48 @@ describe("directive", function() {
return this.greeting + ' dude' + this.suffix;
}
};
- var scope = compile('
');
expect(log).toEqual('init');
- });
+ }));
- it('should use the replaced element after calling widget', function() {
+ it('should use the replaced element after calling widget', inject(function($rootScope, $compile) {
widgets['H1'] = function(element) {
// HTML elements which are augmented by acting as widgets, should not be marked as so
expect(element.hasClass('ng-widget')).toEqual(false);
@@ -180,17 +176,17 @@ describe('compiler', function() {
this.directives(true);
return noop;
};
- markup.push(function(text, textNode, parent){
+ textMmarkup.push(function(text, textNode, parent){
if (text == '{{1+2}}')
parent.text('3');
});
- scope = compile('
');
- scope.groups = [['a', 'b'], ['c','d']];
- scope.$digest();
+ it('should repeat over nested arrays', inject(function($rootScope, $compile) {
+ var element = $compile(
+ '
' +
+ '
' +
+ '
{{group}}|
X' +
+ '
' +
+ '
')($rootScope);
+ $rootScope.groups = [['a', 'b'], ['c','d']];
+ $rootScope.$digest();
expect(element.text()).toEqual('a|b|Xc|d|X');
- });
+ }));
describe('stability', function() {
- var a, b, c, d, scope, lis;
+ var a, b, c, d, lis, element;
- beforeEach(function() {
- scope = compile(
+ beforeEach(inject(function($rootScope, $compile) {
+ element = $compile(
'
' +
- '
' +
- '
' +
- '
');
+ '' +
+ '
')($rootScope);
a = {};
b = {};
c = {};
d = {};
- scope.items = [a, b, c];
- scope.$digest();
+ $rootScope.items = [a, b, c];
+ $rootScope.$digest();
lis = element.find('li');
- });
+ }));
- it('should preserve the order of elements', function() {
- scope.items = [a, c, d];
- scope.$digest();
+ it('should preserve the order of elements', inject(function($rootScope, $compile) {
+ $rootScope.items = [a, c, d];
+ $rootScope.$digest();
var newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[2]);
expect(newElements[2]).not.toEqual(lis[1]);
- });
+ }));
- it('should support duplicates', function() {
- scope.items = [a, a, b, c];
- scope.$digest();
+ it('should support duplicates', inject(function($rootScope, $compile) {
+ $rootScope.items = [a, a, b, c];
+ $rootScope.$digest();
var newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).not.toEqual(lis[0]);
@@ -351,176 +346,168 @@ describe("widget", function() {
expect(newElements[3]).toEqual(lis[2]);
lis = newElements;
- scope.$digest();
+ $rootScope.$digest();
newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[1]);
expect(newElements[2]).toEqual(lis[2]);
expect(newElements[3]).toEqual(lis[3]);
- scope.$digest();
+ $rootScope.$digest();
newElements = element.find('li');
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[1]);
expect(newElements[2]).toEqual(lis[2]);
expect(newElements[3]).toEqual(lis[3]);
- });
+ }));
- it('should remove last item when one duplicate instance is removed', function() {
- scope.items = [a, a, a];
- scope.$digest();
+ it('should remove last item when one duplicate instance is removed', inject(function($rootScope, $compile) {
+ $rootScope.items = [a, a, a];
+ $rootScope.$digest();
lis = element.find('li');
- scope.items = [a, a];
- scope.$digest();
+ $rootScope.items = [a, a];
+ $rootScope.$digest();
var newElements = element.find('li');
expect(newElements.length).toEqual(2);
expect(newElements[0]).toEqual(lis[0]);
expect(newElements[1]).toEqual(lis[1]);
- });
+ }));
- it('should reverse items when the collection is reversed', function() {
- scope.items = [a, b, c];
- scope.$digest();
+ it('should reverse items when the collection is reversed', inject(function($rootScope, $compile) {
+ $rootScope.items = [a, b, c];
+ $rootScope.$digest();
lis = element.find('li');
- scope.items = [c, b, a];
- scope.$digest();
+ $rootScope.items = [c, b, a];
+ $rootScope.$digest();
var newElements = element.find('li');
expect(newElements.length).toEqual(3);
expect(newElements[0]).toEqual(lis[2]);
expect(newElements[1]).toEqual(lis[1]);
expect(newElements[2]).toEqual(lis[0]);
- });
+ }));
});
- });
+ }));
describe('@ng:non-bindable', function() {
- it('should prevent compilation of the owning element and its children', function() {
- var scope = compile('
');
- scope.name = 'misko';
- scope.$digest();
+ it('should prevent compilation of the owning element and its children', inject(function($rootScope, $compile) {
+ var element = $compile('
')($rootScope);
+ $rootScope.name = 'misko';
+ $rootScope.$digest();
expect(element.text()).toEqual('');
- });
+ }));
});
describe('ng:view', function() {
- var rootScope, $route, $location, $browser;
-
- beforeEach(function() {
- rootScope = angular.compile('')();
- $route = rootScope.$service('$route');
- $location = rootScope.$service('$location');
- $browser = rootScope.$service('$browser');
- });
-
- afterEach(function() {
- dealoc(rootScope);
- });
+ var element;
+ beforeEach(inject(function($rootScope, $compile) {
+ element = $compile('')($rootScope);
+ }));
- it('should do nothing when no routes are defined', function() {
+ it('should do nothing when no routes are defined', inject(function($rootScope, $compile, $location) {
$location.path('/unknown');
- rootScope.$digest();
- expect(rootScope.$element.text()).toEqual('');
- });
+ $rootScope.$digest();
+ expect(element.text()).toEqual('');
+ }));
- it('should load content via xhr when route changes', function() {
+ it('should load content via xhr when route changes', inject(function($rootScope, $compile, $browser, $location, $route) {
$route.when('/foo', {controller: angular.noop, template: 'myUrl1'});
$route.when('/bar', {controller: angular.noop, template: 'myUrl2'});
- expect(rootScope.$element.text()).toEqual('');
+ expect(element.text()).toEqual('');
$location.path('/foo');
$browser.xhr.expectGET('myUrl1').respond('
{{1+3}}
');
- rootScope.$digest();
- rootScope.$digest();
+ $rootScope.$digest();
+ $rootScope.$digest();
$browser.xhr.flush();
- expect(rootScope.$element.text()).toEqual('4');
+ expect(element.text()).toEqual('4');
$location.path('/bar');
$browser.xhr.expectGET('myUrl2').respond('angular is da best');
- rootScope.$digest();
- rootScope.$digest();
+ $rootScope.$digest();
+ $rootScope.$digest();
$browser.xhr.flush();
- expect(rootScope.$element.text()).toEqual('angular is da best');
- });
+ expect(element.text()).toEqual('angular is da best');
+ }));
- it('should remove all content when location changes to an unknown route', function() {
+ it('should remove all content when location changes to an unknown route',
+ inject(function($rootScope, $compile, $location, $browser, $route) {
$route.when('/foo', {controller: angular.noop, template: 'myUrl1'});
$location.path('/foo');
$browser.xhr.expectGET('myUrl1').respond('