Compare commits

...

293 Commits

Author SHA1 Message Date
Michał Gołębiowski b586bfdfab fix(testabilityPatch): fix invocations of angular.mock.dump 2014-06-30 16:58:15 -07:00
Igor Minar 1d69015e3d test($parse): skip Function.prototype.bind test on IE8 2014-06-30 11:22:41 -07:00
Igor Minar f13c33bf10 fix($parse): don't check Function.prototype.bind when it doesn't exist
e.g. IE8 doesn't have it
2014-06-30 11:15:39 -07:00
Igor Minar ba62e975f1 fix($parse): make the window check in ensureSafeObject IE8 friendly 2014-06-30 11:06:18 -07:00
rodyhaddad b89d941cdf style(parseSpec): make jshint happy 2014-06-30 10:50:02 -07:00
rodyhaddad 07fa87a8a8 fix($parse): prevent invocation of Function's bind, call and apply
BREAKING CHANGE:
You can no longer invoke .bind, .call or .apply on a function in angular expressions.
This is to disallow changing the behaviour of existing functions
in an unforseen fashion.
2014-06-30 10:43:29 -07:00
rodyhaddad 0af70eb99e refactor($parse): move around previous security changes made to $parse 2014-06-30 10:40:24 -07:00
Jann Horn cb713e6045 fix($parse): forbid __proto__ properties in angular expressions
__proto__ can be used to mess with global prototypes and it's
deprecated. Therefore, blacklisting it seems like a good idea.

BREAKING CHANGE:
The (deprecated) __proto__ propery does not work inside angular expressions
anymore.
2014-06-30 09:32:38 -07:00
Jann Horn 89ca859734 fix($parse): forbid __{define,lookup}{Getter,Setter}__ properties
It was possible to use `{}.__defineGetter__.call(null, 'alert', (0).valueOf.bind(0))` to set
`window.alert` to a false-ish value, thereby breaking the `isWindow` check, which might lead
to arbitrary code execution in browsers that let you obtain the window object using Array methods.
Prevent that by blacklisting the nasty __{define,lookup}{Getter,Setter}__ properties.

BREAKING CHANGE:
This prevents the use of __{define,lookup}{Getter,Setter}__ inside angular
expressions. If you really need them for some reason, please wrap/bind them to make them
less dangerous, then make them available through the scope object.
2014-06-30 09:29:53 -07:00
Jann Horn bc6fb7cc94 fix($parse): forbid referencing Object in angular expressions
It was possible to run arbitrary JS from inside angular expressions using the
`Object.getOwnPropertyDescriptor` method like this since commit 4ab16aaa:
    ''.sub.call.call(
      ({})["constructor"].getOwnPropertyDescriptor(''.sub.__proto__, "constructor").value,
      null,
      "alert(1)"
    )()
Fix that by blocking access to `Object` because `Object` isn't accessible
without tricks anyway and it provides some other nasty functions.

BREAKING CHANGE:
This prevents the use of `Object` inside angular expressions.
If you need Object.keys, make it accessible in the scope.
2014-06-30 09:26:29 -07:00
Kristian Hellang 0c80df21b6 fix($http): should not read statusText on IE<10 when request is aborted
Commit 1d2414c introduced a regression by retrieving the statusText
of an aborted xhr request. This breaks IE9, which throws a c00c023f
error when accessing properties of an aborted xhr request. The fix
is similar to the one in commit 6f1050d.
2014-06-30 08:09:59 -07:00
vaibhav kohli 3141dbf179 style(Angular.js): remove extra whitespace 2014-06-29 09:42:34 -07:00
Archer aad502bad8 docs(guide/$location): fix a typo
Change "window.location.path" to "window.location.pathname".

Closes #8012
2014-06-28 20:05:32 -07:00
Elnur Abdurrakhimov dca8972367 docs(Getting Started): fix typo
Closes #8015
2014-06-28 19:42:02 -07:00
rodyhaddad 284de57435 test($interval): add tests making sure $interval uses the methods from $window 2014-06-28 17:35:14 -07:00
Praveen f780ccfa1c fix($interval): when canceling, use clearInterval from $window instead of global scope.
In $interval.cancel, use clearInterval from the $window service instead of from global scope.
The variable clearInterval declared above isn't visible here.
2014-06-28 17:35:06 -07:00
Efthymis Sarbanis 55f99e0710 chore: use triple equals comparison with typeof operator.
It is common practice for typeof operator to be used with '==='.

Closes #8009
2014-06-27 16:53:32 -07:00
Domenic Denicola 1a99ca9c08 docs($q): remove unnecessary $scope.apply wrapping
As of Angular 1.2, this kind of thing is no longer necessary (thank goodness!)
2014-06-26 12:37:04 -07:00
Laurent Curau 37500fca83 docs(guide/unit-testing): correct spelling 2014-06-26 12:37:04 -07:00
rodyhaddad 4fe4fc5abf chore(ngMock): replace misplaced comma with semicolon 2014-06-26 12:32:51 -07:00
rodyhaddad 74e1cc683b fix(jqLite): change expando property to a more unique name
This was causing issue when element === window
A better strategy can be thought of later on.
2014-06-26 12:32:30 -07:00
Igor Minar a4faa5cde7 perf(jqLite): don't use reflection to access expandoId
Since we allow only one copy of Angular to be loaded at a time it doesn't
make much sense randomly generate the expando property name and then be
forced to use slow reflective calles to retrieve the IDs.
2014-06-26 12:32:30 -07:00
Eddie Hedges 32cb40b86d docs(guide/introduction): use durandal as an example of a framework
To me knockout is a library that does data binding well.
Durandal is a framework that uses knockout as it's data binding component.
2014-06-26 12:19:56 -07:00
m-tretyak d1cd677433 docs(srcset): fix mistake in example 2014-06-26 12:08:55 -07:00
thorn0 43c735a816 docs($location): hashPrefix, html5Mode are methods
Closes #7915
2014-06-25 14:48:19 -07:00
Caitlin Potter ab2e83c8c8 fix(input): improve html5 validation support
This CL improves mocking support for HTML5 validation, fixes the behaviour which invokes validators.

Previously, an input would only be revalidated if either its value changed, or if it was the empty
string but did not suffer from bad input --- now, it will be revalidated if either the value has
changed, or the value is the empty string, there is a ValidityState for the element, and that
ValidityState is being tested by one of the validators in the pipeline.

Closes #7937
Closes #7957
2014-06-24 08:35:47 -04:00
Igor Minar e5f454c8af fix(numberFilter): correctly round fractions despite floating-point arithmetics issues in JS
Closes #7870
Closes #7878
2014-06-24 00:37:50 -07:00
Igor Minar 67c11b9a39 fix($injector): check if a fn is an array explicitly
This change makes the code easier to read and also fixes a compatibility issue
with opal.js which pollutes the global state by setting $inject property on
Array prototype

Closes #7904
Closes #2653
2014-06-23 17:20:09 -07:00
Zacky Ma 5a306b7ba3 docs(guide/compiler): change {{user}} to {{user.name}} in example
If user has an `actions` property, it should be an object,
which means if you {{user}}, it'll print out the object.
2014-06-23 13:36:04 -07:00
ephigabay 8ce61bf178 docs(ngModelController): update setValidity
Needs to be `$error[validationErrorKey]!=isValid` and not
`$error[validationErrorKey]=isValid`.

See https://github.com/angular/angular.js/blob/master/src/ng/directive/input.js#L1627

Closes #7934
2014-06-23 13:21:44 -07:00
Chaker Nakhli 9d452bc845 docs(ngSrc): srcset used instead of src for img attribute
In `ngSrc` documentation `srcset` is used instead of `src` as `img` element attribute in the example.

Closes #7951
2014-06-23 13:15:03 -07:00
Julie 192fecc790 chore(grunt): check files in src for ddescribe/iit
Previously, only files in test/ were checked. This does not capture
end to end tests, which are in comments in src/.
2014-06-23 11:28:21 -07:00
Julie 32be6369e4 chore(tests): remove a lingering iit in end to end tests 2014-06-23 11:22:37 -07:00
Shahar Talmi 2a45cea0ba fix(input): escape forward slash in email regexp
This messed up with syntax coloring and variable hovering in chrome developer tools and made debugging really difficult.

Closes #7938
2014-06-22 21:28:55 -04:00
Peter Bacon Darwin ea653e4cdd docs($provide): it is a service not an object
Closes #7917
2014-06-20 17:01:10 +01:00
Alex Muntada 03777445e8 docs(tutorial/step-4): fix e2e test
After a protractor update the test syntax had to be changed.

Closes #7919
2014-06-20 14:36:13 +01:00
Neil Giarratana 8b25ea129a docs(ngPluralize): spell Mary's name correctly
Update ngPluralize.js

Just a silly change to the name of one of the examples that appears to be a typo. Changing Marry to
Mary as the first would be a verb and the latter would be an extremely common name.

Closes #7884
2014-06-17 17:44:17 -04:00
Shahar Talmi d71f16e745 fix(injector): allow multiple loading of function modules
Change HashMap to give $$hashKey also for functions so it will be possible to load multiple module
function instances. In order to prevent problem in angular's test suite,  added an option to HashMap
to maintain its own id counter and added cleanup of $$hashKey from all module functions after each
test.

Before this CL, functions were added to the HashMap via toString(), which could potentially return
the same value for different actual instances of a function. This corrects this behaviour by
ensuring that functions are mapped with hashKeys, and ensuring that hashKeys are removed from
functions and objects at the end of tests.

In addition to these changes, the injector uses its own set of UIDs in order to prevent confusingly
breaking tests which expect scopes or ng-repeated items to have specific hash keys.

Closes #7255
2014-06-16 20:45:49 -04:00
Jason Bedard ed59370d80 fix($compile): bind ng-attr-* even if unbound attribute follows ng-attr-*
Previously, <element ng-attr-foo="{{binding}}" foo="bar"></element>'s "foo" attribute would always
equal "bar", because the bound version was overwritten. This CL corrects this behaviour and ensures
that the ordering of attributes does not have an effect on whether or not ng-attr-bound attributes
do their work.
2014-06-16 20:35:13 -04:00
Ahmad Moussawi d8e5acfe27 docs($sce): update the parseAs method name 2014-06-16 13:46:02 -07:00
James Harrison Fisher af59f4e69a docs(ngMock): fix typo providers -> provides
Should be a verb, ☆.。.:・゜☆MERCI BEAUCOUP☆.。.:・゜☆
2014-06-16 11:47:45 -04:00
Colin Casey 24aee81634 refact(select): use prop to modify the select property
jQuery suggests using `prop` rather than `attr` to modify the `select` property of an element.
You can see the full list of migration warnings for jQuery:
https://github.com/jquery/jquery-migrate/blob/master/warnings.md

Closes #4107
Closes #4122
2014-06-14 17:28:12 +01:00
rodyhaddad f81d56e66c docs(CHANGELOG.md): add changes for 1.2.18 2014-06-13 14:52:28 -07:00
Caitlin Potter f0904cf12e fix(ngResource): don't convert literal values into Resource objects when isArray is true
Previously non-object literals would be thrown out of Resource responses with isArray===true, or
otherwise converted into Objects (in the case of string literals). The reason for this is because
shallowClearAndCopy iterates over keys, and copies keys into the destination. Iterating over String
keys results in integer keys, with a single-character value.

Not converting non-objects to Resources means that you lose the ability to perform Resource operations
on them. However, they become usable as strings, numbers, or booleans, which is important.

In the future, it would be useful to make these useful as Resources while still retaining their primitive
value usefulness.

Closes #6314
Closes #7741
2014-06-13 13:55:33 -07:00
Igor Minar 81b7e5ab0e perf($compile): move ng-binding class stamping for interpolation into compile phase 2014-06-13 13:23:22 -07:00
Igor Minar 1b1890274e perf(ngBind): set the ng-binding class during compilation instead of linking 2014-06-13 13:23:22 -07:00
Igor Minar 6d418ef5e3 perf(jqLite): cache collection length for all methods that work on a single element
This affects jqLite#html, #text, #attr, #prop, #css and others.
2014-06-13 13:23:22 -07:00
Igor Minar 3fa1606c43 chore: name the event callback used by ngClick and friends
This maskes looking at stack traces easier.

Since we generate the callbacks for each event type at runtime and we can't
set function's name because it's read-only, we have to use a generic name.
2014-06-13 13:23:21 -07:00
XrXr 8661a9e3d4 docs(CONTRIBUTING.md): fix link to unit testing docs
The old link points to a page that doesn't exist

Closes #7830
2014-06-13 13:56:28 -04:00
Caitlin Potter cf63292742 test($compile): make IE8 happy
Closes #7828
2014-06-13 12:09:39 -04:00
Peter Bacon Darwin fd420c4061 fix($compile): ensure transclude works at root of templateUrl
If a "replace" directive has an async template, which contains a transclusion
directive at its root node, then outer transclusions were failing to be
passed to this directive.  An example would be uses of `ngIf` inside and
outside the template.

Collaborated with @caitp

Closes #7183
Closes #7772
2014-06-13 12:09:33 -04:00
Vojta Jina 1382d4e88e fix($compile): bound transclusion to correct scope
Nested isolated transclude directives.

This improves/fixes the fix in d414b78717.

See the changed ng-ifunit test: The template inside ng-if should be bound to the
isolate scope of `iso` directive (resp. its child scope). Not to a child of
the root scope. This shows the issue with ng-if. It’s however problem with
other directives too.

Instead of remembering the scope, we pass around the bound parent transclusion.

Conflicts:
	test/ng/directive/ngIfSpec.js
2014-06-13 12:09:33 -04:00
Peter Bacon Darwin b9ddef2a49 fix($compile): don't pass transcludes to non-transclude templateUrl directives 2014-06-13 12:09:32 -04:00
Peter Bacon Darwin eafba9e2e5 fix($compile): don't pass transclude to template of non-transclude directive
If a directive provides a template but is not explicitly requesting transclusion
then the compiler should not pass a transclusion function to the directives
within the template.
2014-06-13 12:09:32 -04:00
Peter Bacon Darwin 6f1d9f8ca6 refactor($compile): no need to use bind 2014-06-13 12:09:32 -04:00
Peter Bacon Darwin bb9310974b fix($compile): fix nested isolated transclude directives
Closes #1809
Closes #7499
2014-06-13 12:09:32 -04:00
Peter Bacon Darwin 30279d7b9b refactor($compile): change parameter name
The boundTransclusionFn that is passed in is really the one from the
parent node.  The change to parentBoundTranscludeFn clarifies this compared
to the childBoundTranscludeFn.
2014-06-13 12:09:32 -04:00
Peter Bacon Darwin 8df5f3259a fix($compile): pass transcludeFn down to nested transclude directives
If you have two directives that both expect to receive transcluded content
the outer directive works but the inner directive never receives a
transclusion function. This only failed if the first transclude directive
was not the first directive found in compilation.

Handles the regression identified in e994259739

Fixes #7240
Closes #7387
2014-06-13 12:09:32 -04:00
Tero Parviainen 14e797c1a1 fix($injector): report circularity in circular dependency error message
Change the error message for a circular dependency to display the full
circle back to the first service being instantiated, so that the problem
is obvious. The previous message stopped one dependency short of the full
circle.

Changes the content of the cdep error message, which may be considered
a breaking change.

Closes #7500
2014-06-12 17:32:28 -07:00
Brian Ford 82cd6b87f0 docs(TRIAGING.md): clarification for issues that are not reproducable 2014-06-12 14:39:04 -07:00
Andreas Krummsdorf 6d7cc572b5 style(loader.js): correct JSDoc tags of the params of the function module(name, requires, configFn)
This will improve the hints for IDE's which support the Google Closure Compiler (e.g. Webstorm)
2014-06-12 13:49:33 -07:00
rodyhaddad 2ebbe00eb5 revert: style(loader.js): correct JSDoc tags of the params of the function module(name, requires, configFn)
This reverts commit f4c08fee85.
2014-06-12 13:49:33 -07:00
Michal Kawalec 8b86d363aa perf($http): move xsrf cookie check to after cache check in $http
$http was previously checking cookies to find an xsrf-token prior to checking
the cache. This caused a performance penalty of about 2ms, which can be very
significant when loading hundreds of template instances on a page.

Fixes #7717
2014-06-12 11:28:00 -07:00
Aiden N 9b51067516 docs(guide/bootstrap): fix link to angular.js script in example
code.angularjs.org/angular.js is 404, updated it

Updated http://code.angularjs.org/angular.js in the example to http://code.angularjs.org/snapshot/angular.js
It works fine now.
2014-06-12 14:24:34 -04:00
fuqcool a3208bf66e docs(guide/services): fix link to services api 2014-06-12 00:13:10 -04:00
Dave Wells 4e1fb82628 docs(errors/$compile/nonassing): fix reversed attribute and scope property names 2014-06-11 14:55:32 -07:00
Alexander Karpan ad466412c6 docs(guide/services): fix link to wikipedia article containing parentheses
Fix in a mis-parsed link with ')' symbol in it
2014-06-10 16:11:16 -04:00
fvanderwielen 299a32740c docs(loader): improve explanation of modules 2014-06-10 11:48:52 -07:00
Rocky Assad eb799bcb71 chore(jshint): dedupe jshint option 2014-06-10 01:46:46 -07:00
Brian Ford 7314c1b69e docs(tutorial/step_09): fix formatting 2014-06-10 01:42:21 -07:00
Brian Ford fcfe2b3793 docs(tutorial/step_09): fix link to filter guide 2014-06-10 01:34:58 -07:00
Kevin Western 5e140a99c7 docs($animate): $animate.enabled's param is optional
The docs show that param 'element' (of type DOMElement) is required when it is optional.
2014-06-10 01:09:42 -07:00
Amar Patel 4da169d15d docs(api/index): add Oxford comma
Verified grammar at https://owl.english.purdue.edu/owl/resource/607/01/
2014-06-10 00:58:02 -07:00
Dylan Semler f37c6f9f73 docs(error/$compile/tplrt): note that html comments can cause this 2014-06-10 00:52:41 -07:00
Carl Sutherland eae658fd96 docs(directive/input): document ngTrim for textarea 2014-06-10 00:38:14 -07:00
Matt Johansen 286f269753 docs(tutorial/step_09): note about 'Filter' suffix
Reminder that 'Filter' is appended to filter names when injected.
Link to Filter guide where this is mentioned.
2014-06-09 22:01:57 -07:00
Stephen Nancekivell 73640a6b7c docs(error/$sce/unsafe): suggest including ngSanatize 2014-06-09 20:55:53 -07:00
Arjunkumar 2dc55ff5c2 docs(guide/index): add codeschool link 2014-06-09 15:50:54 -07:00
Uri Goldshtein 98a2563ec4 docs(guide): add UI-Map to Complementary Libraries 2014-06-09 14:31:49 -07:00
Uri Goldshtein 8c02122837 docs(guide): add ngTagsInput to UI Widgets 2014-06-09 14:02:34 -07:00
Joseph Orbegoso Pea 1e069532fc docs(ngController): improve wording 2014-06-09 13:42:05 -07:00
Nikita Vasilyev 6f6f7e82a4 docs(guide/databinding): add line breaks after images
This looks much better, thanks a bunch @NV
2014-06-08 22:13:53 -04:00
Peter Bacon Darwin d852122442 docs(error/$rootScope/inprog): improve understanding and diagnosis of the error
See #5549
2014-06-08 19:35:13 +01:00
Caitlin Potter 66cb161221 docs(CHANGELOG.md): remove mention of strict-DI
Strict-DI feature was originally merged into v1.2.x, but was reverted by https://github.com/angular/angular.js/commit/373078a94cf3d525b9ae11a2f2876acb6e26f6a3
2014-06-08 02:05:35 -04:00
Sebastian Müller 6c14fb1eb6 perf(isArray): use native Array.isArray
see benchmark: http://jsperf.com/isarray-performance
2014-06-06 20:07:08 -04:00
Peter Bacon Darwin e906aafb0a docs(tutorial): mention additional Debian install step
Thanks to GSC Leticia (gsc-leticia) for identifying this problem.

Closes #7665
2014-06-06 21:28:13 +01:00
Peter Bacon Darwin 6acc73f3e0 chore(CHANGELOG.md): add changes for 1.2.17 2014-06-06 20:13:16 +01:00
Samuel Rats a4367ab00d docs($http): remove duplicate 'to' in withCredentials description
Typo in $http config documentation

Closes #7731
2014-06-06 11:13:48 -04:00
Caitlin Potter 2e0464fba4 test($http): test that timed out $http request rejects promise
Closes #7688
Closes #7686
2014-06-05 20:13:00 -04:00
rodyhaddad 6ffd53ee3c test(jqLite): adapt missed test to new expando name 2014-06-05 14:49:12 -07:00
rodyhaddad 2395bf604d test(jqLite): adapt tests to new expando name 2014-06-05 14:30:44 -07:00
rodyhaddad 2e5fe846e3 chore(jqLite): remove special characters from the expando property
Having special characters in the expando property created a memory bloat.
See https://code.google.com/p/chromium/issues/detail?id=378607#c6 to reproduce

Closes #7701
2014-06-05 13:48:45 -07:00
Wes Alvaro b6388b3f1d docs(misc core): use @kind function instead of @function 2014-06-04 20:42:00 +01:00
Matias Niemelä 669e3aeaa8 docs(ngShow): calirfy info about the .ng-hide class and the display style 2014-06-04 15:17:14 -04:00
Matias Niemelä 55b2f0e862 fix($animate): remove the need to add display:block!important for ngShow/ngHide
Since ngShow/ngHide animations add and remove the .ng-hide class, having to remember
to write display:block on your own is a hassle and leads to problematic animation
code. This fix places a default on the animation for you instead.

Closes #3813
2014-06-04 15:17:14 -04:00
Richard Littauer ca566d8d81 docs(guide/directive): explain controllerAs option
There was nothing about controllerAs in the developer guide.

Helps #6710 and #6759
2014-06-03 17:57:38 -07:00
Juan Manuel Palacios b306babe29 docs(tutorial/step_12): clarify that jQuery should be loaded first
jQuery needs to be loaded before *all* AngularJS modules in the app,
because otherwise AngularJS will not detect the presence of jQuery and
animations  will not work as expected.
2014-06-03 17:48:18 -07:00
Jake Buob d9317cde4f docs(tutorial/step_07): fix typo 2014-06-03 16:41:27 -07:00
Dken 23c8af232f docs(tutorial/index): update requisite version of node
Not all 0.10.x support `^` in versions in `package.json`
2014-06-03 16:36:31 -07:00
Richard Littauer 2fcbd39d0b docs(misc/faq): note that jQuery 2.x currently doesn't work with angular 2014-06-03 16:28:58 -07:00
JMRodriguez24 3ffbf202ce docs(index/api): fix link o templates
Link points to templates. Modified the link label templates instead of types.
2014-06-03 16:26:01 -07:00
Tim Ruffles 09367d88c2 docs(guide/expression): fix docs re $window
The documentation on context is incorrect and misleading:

1. "Angular expressions must use $window explicitly to refer to the global
   `window` object": expressions cannot access `$window`
1. The example doesn't actually attempt to use $window in a expression. It's in a
   function called from an expression, which incorrectly implies to readers that:
  1. functions ARE expressions
  1. functions called by expressions can't access `window`

Here's [a plunkr](http://plnkr.co/edit/Gd4xAV?p=preview) to make both these issues clear.

This change fixes the errors and informs the reader about Angular's `$window` etc services,
and adds an explicit example of an expression not being able to access `window`.
2014-06-03 16:21:28 -07:00
Anuj More 369f69d67a docs(tutorial/index): fix typo 2014-06-03 16:00:00 -07:00
Oivvio Polite 9f43d02af8 docs(guide/providers): fix typo 2014-06-03 15:40:37 -07:00
Oivvio Polite 08e6b88fb2 docs(guide/providers): fix grammar 2014-06-03 14:38:31 -07:00
Isaac Shapira 9227a5db94 fix(ngAnimate): $animate methods should accept native dom elements 2014-06-02 13:58:15 -07:00
Caitlin Potter fc6ce59cd2 chore(ngLocale): update ngLocale scripts 2014-06-02 16:23:20 -04:00
Caitlin Potter 96a314766c fix(ngLocale): fix i18n code-generation to support get_vf_, decimals_, and get_wt_
The updated Closure I18N code relies on these methods to enhance the localization quality.

This fix prevents ngLocale files from referencing undefined values. In the short term, this
means adding references to versions of these methods in locales where they are necessary.
2014-06-02 16:23:20 -04:00
Caitlin Potter 6aa31e17ee chore(ngLocale): change update-closure script to pull from github, not code.google.com
The closure-library migrated a while ago, so the script would fail. This is change
should allow us to update these more frequently.
2014-06-02 16:23:17 -04:00
Phil Westwell d18d5f57c2 docs(*): fix its/it's grammar 2014-05-31 18:47:28 -04:00
zainengineer 2d9e96772f docs(orderBy): add example of directly calling $filter('orderBy')
It's not a bad example of sorting fields in a table, which is something people are frequently wanting
to do. So I say, LGTM!
                        ~caitp, 1988-2014
2014-05-31 18:26:44 -04:00
Caitlin Potter bc2a5aaf05 docs(tutorial): fix minor errors in step 11
I added these when amending the previous commit. My bad ._.
2014-05-31 17:38:38 -04:00
aschokking 4547c11dad docs(tutorial): add karma config update reference to step 11
When including the ng-resource module you appear to need to add a reference to the karma config file
as well or the unit tests will fail. This burned me for a while when going through the tutorial.
2014-05-31 17:33:51 -04:00
Jan c0b360b993 docs(tutorial): add instructions to make experiment render readably in step 5
Update step_05.ngdoc

Closes #7653
2014-05-31 13:20:52 -04:00
dumpweed a659049893 docs(tutorial/step_05): improve explanation of _-prefix 2014-05-30 17:30:58 -07:00
ImaginaryDevelopment a3b9b1d205 docs(ngKeyup): improve example
show implicit `$event` argument
2014-05-30 16:19:04 -07:00
Hallvard Nygård 3305f38db2 docs(CONTRIBUTING.md): fixing link to Google Closure I18N library
The old seems to link to the source code of I18N. Found the same folder on their new Github repo.

"Closure Library's source repository has moved to GitHub.", https://code.google.com/p/closure-library/

Closes #7638
2014-05-30 16:07:10 -04:00
Caitlin Potter 9be4e035d1 style(ngClassSpec): fix indentation to make jscs happy
v1.2.x doesn't run jshint on tests, but if it ever does in the future, we'll want this.
2014-05-30 15:15:22 -04:00
Shahar Talmi 85ce5d0db9 fix(ngClass): support multiple classes in key 2014-05-30 11:56:27 -07:00
rodyhaddad 5c99720934 fix(angular.copy): support circular references in the value being copied
Closes #7618
2014-05-30 11:53:47 -07:00
rodyhaddad 8d26238664 chore(shallowCopy): handle arrays and primitives, and switch to using it where possible
In many cases, we want a shallow copy instead of a full copy

Closes #7618
2014-05-30 11:50:55 -07:00
Paul Jolly b7cb454546 docs($rootScope): fix incorrect docs about how dirty checking is done 2014-05-30 10:33:12 -07:00
Firexion 199825ec26 docs(tutorial): make code samples in step 7 match code in angular-phonecat
Update step_07.ngdoc

Modifying the code snippets provided to match what was on git
2014-05-30 09:58:16 -04:00
Vojta Jina bcdd925c9d style(ngTouch): make lint happy 2014-05-29 14:12:54 -07:00
Carlo s A. Guillen 0bcace309e docs(ngClick): improve style 2014-05-28 18:37:57 -07:00
Pavel Pomerantsev 46c9c942df docs($rootScope): fix incorrect docs and make them clearer
During the first $digest loop after registering a $watch
the listener always run, so the example was incorrect

Closes #7598
2014-05-28 17:40:24 -07:00
Erin Altenhof-Long 2ad439dfc5 docs(ngClick): improve style 2014-05-28 17:33:01 -07:00
Brian Ford 3fbfe3f966 docs($http): use HTTPS URLs in JSONP example 2014-05-28 14:04:55 -07:00
venticello 8ff671753c docs(tutorial/step-12): fix typo
Closes #7596
2014-05-28 10:26:18 -07:00
Peter Bacon Darwin 24092127d1 docs(ngController): add formatting to controller as description and example
Adds to #7591
2014-05-27 22:57:05 +01:00
Richard Littauer f1c7240f04 docs(ngController): add more description of controller as syntax
Using `controller as` in the template is not described well
in the docs, as both `scope` injection and `this` are presented
equally without too much discussion of the advantages of using
either. I added a bit more discussion based on google's internal
style guidelines.

Closes #7591
Closes #5076 (until Angular 2.0 comes out and we refactor everything)
2014-05-27 22:57:05 +01:00
Peter Bacon Darwin fcee3bea1a docs(misc/downloading): fix HTML example formatting
Closes #7060
2014-05-27 22:56:41 +01:00
Matias Niemelä ad08638c0a fix($animate): retain inline styles for property-specific transitions
Transitions that are run through ngAnimate which contain a specific property
cause any inline styles to be erased after the animation is done. This has
something to do with how the browsers handle transitions that do not use
"all" as a transition property.

Closes #7503
2014-05-23 14:46:55 -07:00
Julie 093e76fa15 tests(docsAppE2E): fix race condition flake with switching to new frame
Closes #7569
2014-05-23 11:29:17 -07:00
Juan Manuel Palacios 28c0497524 docs(tutorial): match html from angular-phonecat in step 7 instructions
Place phoneId binding in a proper HTML node

The code where the phoneId binding in the phone-detail.html template is first explained in step 7
of the tutorial doesn't make it a child of a proper HTML node, which makes the end-to-end test
against the view (also introduced in step 7) fail.

The test acquires the binding right from the view (by.binding('phoneId')), and apparently this
operation fails when the binding is not a child of an HTML node, and therefore the entire test also
fails. As soon as the binding is placed inside a <span></span> tag pair, the binding is found and
the test passes. The code on github for step 7 has it right, the binding is within the span tags,
but in the documentation I'm patching here the span's are missing.

☆.。.:*・゜☆ MERCI ☆.。.:*・゜☆
2014-05-23 01:33:07 -04:00
rodyhaddad 88505d8029 refactor($parse): remove the support of JSON parsing mode
It's a feature that isn't exposed to the public, and is
no longer used internally.
2014-05-22 15:22:33 -07:00
Zorigt Bazarragchaa c241a57761 docs(tutorial/index): explain E2E acronym
It was confusing to read "end 2 end" as a numeric two. I kept wondering what two end(s).
Later in the tutorial, the text switched to "End to End" which made more sense than numeric two.
2014-05-22 13:21:16 -07:00
Jesse Palmer b0e985fb67 style: added whitespace
spaces between the ')' and '{' make code a tad easier to read.

Closes #7542
2014-05-22 08:50:59 -07:00
Kevin Aenmey a0dbd95bb9 docs($cacheFactory): fix typo 2014-05-21 16:30:07 -07:00
Tobias Bosch 9fd5450ee8 docs($compile): deprecate replace directives
BREAKING CHANGE:

The `replace` flag for defining directives that
replace the element that they are on will be removed in the next
major angular version.
This feature has difficult semantics (e.g. how attributes are merged)
and leads to more problems compared to what it solves.
Also, with WebComponents it is normal to have custom elements in the DOM.
2014-05-21 16:11:00 -07:00
Max Tobias Weber b5391fae8f docs(tutorial): update code snippet for step 7
replaced bootstrap 2 with bootstrap 3 classes

Closes #7433
2014-05-21 15:54:41 -07:00
Jeff Whelpley b635903ec4 fix($compile): do not merge attrs that are the same for replace directives
If a directives specifies `replace:true` and the template of the directive contains
a root element with an attribute which already exists at the place
where the directive is used with the same value, don't duplicate the value.

Closes #7463
2014-05-21 15:51:48 -07:00
Jesse Palmer c9ee20b64b docs($interpolate): removed function name to match other examples 2014-05-21 15:50:06 -07:00
Freek Wielstra 25ae98ca77 docs($http): clear up Interceptor docs and code
The documentation and code example of $http interceptors is unclear about whether config can be null
or not, and whether the result should always be a promise or not. This pr clears up the documentation
a bit and removes the literal 'or a promise' interpretation of the docs in the code example.

Closes #7431
Closes #7460
2014-05-21 15:36:14 -07:00
NateRedding adb5ee2e0a docs(ngHide): corrected logic in example
Closes #7436
2014-05-21 15:27:01 -07:00
Brice 78954ffcde docs(tutorial): improve instructions for causing test failure (step 2)
Increase readability for test fail line

Closes #6943
2014-05-21 17:32:31 -04:00
specialorange 3b30a4b64a docs(error/$injector/unpr): clarify which code is being referred to in the text
Closes #7020
2014-05-21 22:27:44 +01:00
Edward Brey 866057233c docs(filter): description formatting fix
as it was, the third parameter's description was displayed as
a code block, rather than a bulletted list.
2014-05-21 13:58:22 -07:00
Richard Littauer ec1f4a8c9b test(ngClass): add missing assertions 2014-05-21 13:58:11 -07:00
Tim van den Eijnden c94190139d docs(tutorial): fix controller unit tests to match phonecat / avoid ReferenceError
fix 'ReferenceError: PhoneDetailCtrl is not defined' & 'ReferenceError: PhoneListCtrl is not defined' in Karma unit tests

Closes #7421
2014-05-21 16:38:40 -04:00
Adam Bradley 4896a0b4d4 docs(css): remove Firefox select element drop-down icon
In firefox the version picker's dropdown icon from the default `select` element
is still showing. This CSS forces FF to hide the ugly default.

Closes #6878
2014-05-21 21:34:27 +01:00
kalvn 32bd990eda docs(tutorial): corrected version of jquery installed via bower (step 12)
Corrected the version of jquery in the text content.
2014-05-21 16:31:30 -04:00
James Vanneman 23723298f9 docs($filter): add runnable example
Closes #6871
2014-05-21 21:26:38 +01:00
ABitTooCalm edab80cddb docs(ngStyle): clarify using styles that are not valid property names
Closes #6838
2014-05-21 21:07:54 +01:00
Andrew Mortimer f4bb006e45 docs(guide/controller): fix typo 2014-05-21 12:32:57 -07:00
Kristof Mattei cf3f709889 docs(shallowCopy): fixed typo
Closes #7456
2014-05-21 11:43:02 -07:00
Tobias Bosch e9ecd56dca docs(ngModelController): use $sce and $sanitize in the contenteditable example.
Closes #7464
2014-05-21 11:39:35 -07:00
Tobias Bosch f107ef8bd8 docs(): mention implicit usage of 2014-05-21 11:39:25 -07:00
markau e7eab501db docs(guide/concepts): The service is finance2.js
Closes #7470
2014-05-21 10:58:29 -07:00
Tobias Bosch 97fc47f39e docs(guide/concepts): clarify definitions 2014-05-21 10:58:17 -07:00
Sergei Z 6d1c67727a feat(ngMock): add support of mocha tdd interface
Closes #7489
2014-05-21 10:56:21 -07:00
Erich 8ba78f08b9 docs(guide/concepts): clarify definitions
Closes #7483
2014-05-21 10:56:12 -07:00
Sam Dornan 02a3c31c23 docs(guide/services): fix link to minify Wikipedia page
Closes #7488
2014-05-21 10:55:55 -07:00
Yaron Uliel d4c3d5caaf docs(ngRepeat): fix formatting 2014-05-21 10:28:47 -07:00
Takashi Nakagawa 279f98c4e3 docs(ngModelController): fix indentation code block
Closes #6660
2014-05-21 17:09:13 +01:00
Caitlin Potter 5e548edf67 docs(guide/$location): clarify "$watchers" / "$observers" terminology with links
Add links to API docs for Scope#$watch and Attributes#$observe in $location in order to clarify
the meaning of that terminology.

Closes #7497
2014-05-21 12:00:16 -04:00
Firexion acaf9be685 docs(tutorial): add <span> to index.html to match phonecat step4
Add missing <span> to app/index.html to match actual code on github

☆.。.:*・゜☆ Merci beaucoup! ☆.。.:*・゜☆
2014-05-21 11:29:30 -04:00
GSC Leticia bdd75c97ad docs(tutorial): add instructions for running node in Debian-based distributions
Specifying command for Debian based distributions, like Ubuntu, Mint, etc.

☆.。.:*・゜☆ Fantastique! ☆.。.:*・゜☆
2014-05-21 11:22:47 -04:00
Peter Bacon Darwin a3f1cba8ec docs(guide/expression): mention the ternary operator
Closes #6498
2014-05-21 14:47:46 +01:00
Peter Bacon Darwin 4195b04072 docs($compile): remove reference to ngRepeat providing compile function
ngRepeat no longer has the compile function in its directive definition
object, since it retrieves its transclusion via the link function.

Closes 5638
2014-05-21 14:31:41 +01:00
Peter Bacon Darwin ccc8ec869b docs(tutorial/step-5): update image to match code
Closes #2753
2014-05-21 13:58:48 +01:00
Eli Dalbey 6d8abaedd8 docs(guide/concepts-module-service.png): Typo in image
The ng-app was called ng=app in diagram image.

Closes #6473
Closes #7528
2014-05-21 12:51:26 +01:00
Wes Alvaro fd49d6634c docs(ngRoute): $routeParams is Object.<string, string>
Closes #7386
2014-05-21 12:49:04 +01:00
Ralph Giles cecd5214df docs(*): fix its vs it's typos.
Only use the apostrophe if you can expand "it's" to "it is" and
still have a grammatical phrase.
2014-05-20 17:51:32 -04:00
Gaëtan PRIOUR c5e39c688b docs(tutorial): fixed path to controllersSpec.js file
☆.。.:*・゜☆ DOMO ARIGATO MR ROBOTO ☆.。.:*・゜☆
2014-05-20 12:19:33 -04:00
Igor Minar 240608447a revert: fix($compile): pass transcludeFn down to nested transclude directives
This reverts commit 113850602d.

This change introduced regressions for several scenarios.

reduction: http://jsfiddle.net/5242N/4/
2014-05-19 15:01:19 -07:00
Slava Fomin bcb53deda8 docs(NgAnimate): update the list of supported animations
Closes #7502
2014-05-19 14:24:26 -04:00
Igor Minar 9d4fa33e35 perf($interpolate): optimize value stringification
previously we stringified numbers via toJson which was expensive, I optimized
the code so that toJson is invoked only if really necessary

Closes #7501
2014-05-19 06:35:28 -07:00
Diego Plentz de1461da78 docs(guide/$location) remove duplicate 'Crawling your app' section
The "Crawling your app" section was duplicated.
I just removed the duplication and moved the section to the 'Caveats' section.

Closes #7244
2014-05-19 14:07:36 +01:00
Kebabpizza 2866daf7d6 docs(tutorial): fix link to bower in step 12
Muchas gracias! \o/
2014-05-18 20:48:40 -04:00
Brian Ford b3de37e418 test($compile): use trim helper 2014-05-16 18:19:16 -07:00
Shahar Talmi a4cc9e1944 fix(ngClass): handle index changes when an item is unshifted
Closes #7256
2014-05-16 14:10:38 -07:00
M Alix f8a1c56cad chore($compile): make webstorm's regexp parser happy
Minor change puts \- at end of character pattern

In CLASS_DIRECTIVE_REGEXP and COMMENT_DIRECTIVE_REGEXP, putting the \- character at
the end of the character patter speeds up many IDE parsers and alleviates some
errors in certain IDE's. (WebStorm 8)

Functionally absolutely equivalent. No test change needed.

Closes #7093
2014-05-16 13:12:39 -07:00
Vojta Jina 113850602d fix($compile): pass transcludeFn down to nested transclude directives
If you have two directives that both expect to receive transcluded content
the outer directive works but the inner directive never receives a
transclusion function. This only failed if the first transclude directive
was not the first directive found in compilation.

Fixes #7240
Closes #7387
2014-05-16 13:08:17 -07:00
Jonathan Gotti e76105a320 docs(guide/directives): improve links to isolate scope docs
Link "isolate scope" to "Directive Definition object" section

Closes #7484
2014-05-16 11:23:35 -07:00
Zachary Babtkis 14e9be202a docs(guide/css-styling): tiny grammar fix
Closes #5443
2014-05-16 14:52:31 +01:00
Peter Bacon Darwin dd5215eceb docs(search): fix search submit functionality
When the search input box was submitted (i.e. by pressing enter) the
app was supposed to take you to the first item but this was not happening.
It turns out the app was just reading the wrong property for the path to
the item.

Closes #3078
2014-05-16 14:33:37 +01:00
Peter Bacon Darwin 497ba08775 docs(search): set minimum length to 2
With the minimum search length set to 3, it was not possible to search for `$q`.
Changing this to 2 fixes that without really upsetting the search display, since we
only display the first 40 API and 14 non-API items anyway.

Closes #3078
2014-05-16 14:25:42 +01:00
nderoche eaaf4967f9 docs(ngController): clarify that this is $scope in example
Replace `this` with `$scope` in second example to highlight the fact that
we are working with the `$scope` instead of an instance of the controller
in this example.

Closes #6478
2014-05-16 13:16:43 +01:00
Narretz f440ac7492 chore(docs): fix layout between 769px and 991px
Left nav and main content were tucked slightly under the version picker / breadcrumb navbar

Closes #6474
Closes #7079
2014-05-16 13:09:53 +01:00
linclark d566c4bc61 docs($resource): clarify paramDefaults usage 2014-05-16 12:44:57 +01:00
Peter Bacon Darwin 766b962eac docs(guide/services): add reference to ngmin tool
Closes #5908
2014-05-16 12:34:51 +01:00
Lucas Galfaso 0388eed7e5 fix(numberFilter): fix rounding error edge case
Fix a number rounding error.

Closes #7453
Closes #7478
2014-05-15 21:19:43 +01:00
Christopher Rose 82448b86b5 docs(guide/di): fix typos
Closes #7480
2014-05-15 21:11:54 +01:00
Rich Snapp fafcd6285a fix(jqLite): use jQuery only if jQuery.fn.on present
Make Angular not bind to jQuery versions older than 1.7 since older
versions of jQuery do not support necessary on()/off() methods.
2014-05-12 15:15:21 +02:00
Shahar Talmi 5319621afd fix($compile): set $isolateScope correctly for sync template directives
All isolated scope directives that do not have `templateUrl` were marked
as `$isolateScopeNoTemplate` even if they did have a `template` attribute.

This caused `jqLite#scope()` to return the wrong value for child elements
within the directive's template.

Closes #6942
2014-05-09 14:46:59 +01:00
Caitlin Potter 32aa491588 fix(ngSwitch): properly support case labels with different numbers of transclude fns
Due to a regression introduced several releases ago, the ability for multiple transclude functions
to work correctly changed, as they would break if different case labels had different numbers of
transclude functions.

This CL corrects this by not assuming that previous elements and scope count have the same length.

Fixes 7372
Closes 7373
2014-05-08 17:09:38 -04:00
Thomas Tuts 31bdb60f0a docs(directive): fix misspelled HTML class for an alert
Muchas gracias
2014-05-07 08:00:19 -04:00
barcahead a8aae48bc0 docs($rootScope): use unshift to add items to the array at the beginning
Closes #7364
2014-05-06 21:29:58 -04:00
Mathew Foscarini 517917f9fa docs(minErr): small grammar fix for $compileMinErr ctreq
Closes #7365
2014-05-06 21:27:57 -04:00
Caitlin Potter 1748abe8ef chore(travis): run protractor tests with ff28
FirefoxDriver seems to have an issue with FF29 which is breaking a test case, and causing false negatives.

There is an issue opened on protractor regarding this at https://github.com/angular/protractor/issues/784

Closes #7369
2014-05-06 18:59:05 -04:00
Peter Bacon Darwin 5f5bf07bb8 docs(tutorial): move bower_components into app folder 2014-05-05 20:55:30 +01:00
Yutaka Yamaguchi 3d0b49c07f fix(ngSanitize): encode surrogate pair properly
The encodeEndities function encode non-alphanumeric characters to entities with charCodeAt.
charCodeAt does not return one value when their unicode codeponts is higher than 65,356.
It returns surrogate pair, and this is why the Emoji which has higher codepoints is garbled.
We need to handle them properly.

Closes #5088
Closes #6911
2014-05-02 17:49:33 -04:00
Peter Bacon Darwin b6aec5642e docs(Attributes): ensure code sample is not escaped
Closes #6649
2014-05-02 21:49:44 +01:00
Peter Bacon Darwin e44e5f447b chore(doc-gen): update to dgeni 0.3.0 2014-05-02 16:12:43 +01:00
Chris Rose ca273fd9da docs($injector): fix typos 2014-05-01 15:37:17 -07:00
mjfroehlich 5ff453d422 docs(guide/providers): minor edits 2014-05-01 14:41:02 -07:00
Janas Page d9c75bee93 docs($anchorScroll): minor copyedit.
Singular-tense verb. Definite article.

Closes #7319
2014-04-30 17:50:09 -04:00
Brian Ford e030e64196 chore(package.json): add qq
e0375a61d0 removed qq, but the changelog script still needs it
2014-04-30 14:03:37 -07:00
Peter Bacon Darwin 7ba19cc355 chore(travis-build): don't re-package when testing docs
Since .travis is already running `grunt package` in the `before_script`
block there is no need to run it again when testing the docs.
2014-04-30 11:35:02 +01:00
Brian Ford 21428e5cea chore(travis): use npm cache 2014-04-29 15:33:41 -07:00
Vojta Jina 9321a5f14c chore(travis): update Chrome on SL to 34
For some reason, SL gives us Chrome 28 when no version is specified.
2014-04-29 15:57:18 +02:00
Peter Bacon Darwin 72421b2acf docs(directives): remove line numbers from code blocks 2014-04-29 12:33:45 +01:00
Peter Bacon Darwin c3fe170b8b docs(examples): don't attempt to convert example content from JSON
There are some files in the examples that look like JSON and the default
$http transformResponse handler was trying to convert these from strings
to object. An example was the style.css file in the
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController docs.

This commit fixes this by simply removing this transform when loading
these files.
2014-04-28 22:28:42 +01:00
Uri Goldshtein ed18b8c9da docs(guide): add another analytics library 2014-04-28 12:05:04 -07:00
Edward Brey 0cb276f7ac docs(angular.Module): add link to module.config() docs
Add a link on where to find more info about how to use `module.config()`

Closes #6270
2014-04-28 15:00:18 +01:00
Ryan Hall cfccb8f64a docs(orderBy): clarify how sorting is processed
An API was passing me numbers as strings (ex. '8.25'), and I was noticing
weird sorting behavior with `orderBy` because it was trying to sort the
numbers alphabetically.

Closes #5436
2014-04-28 14:53:55 +01:00
Thomas Tuts 0069f87007 docs(guide/unit-testing): remove unwanted whitespaces
Closes #5395
2014-04-28 14:40:59 +01:00
marcin-wosinek 9599234bae docs(select): improve naming of c variable in example
It was felt that `c` did not make it clear what the variable held. This
has been changed to `color` to match the ng-repeat expression above.
In turn the model value has been changed to `myColor` to prevent a name
collision.

Closes #7210
2014-04-28 14:00:49 +01:00
Tom Yam d423117158 fix(grunt-utils): ensure special inline CSS works when angular is not a global
The build includes a little script to angular.js, which adds some CSS styles to
the page to support things like ngCloak. This script checks that angular is
not in CSP mode, but before this fix assumed that angular would be in the global
scope.

This commit, references `window.angular` instead of just `angular` because when
running angular in an environment where the top-level scope is not the window
(nodejs for example), we angular is actually a property of `window`.

Closes #7176
2014-04-28 13:36:53 +01:00
eydreeyawn 3c8a940686 docs(ngCookies): added example usage of cookieStore
Closes #7278
2014-04-28 08:19:10 -04:00
Caitlin Potter 9f8e30f550 docs(ngCookies): use GFM code-snippet rather than example tag
The example tag creates a big ugly white rectangle on the docs page, and this is not very helpful
and kind of looks bad. So GFM snippets are a better way to go.

This fix also removes the unnecessary example heading from the $cookieStore page, as there has not
been an example use of $cookieStore for 2 years now.

Closes #7279
2014-04-28 07:56:26 -04:00
Peter Bacon Darwin dcd94a23e1 docs(tutorial/step-3): fix experiments
Closes https://github.com/angular/angular-phonecat/issues/142
2014-04-27 10:13:22 +01:00
Peter Bacon Darwin 9b79a00294 docs(tutorial/step-12): add info about app.css changes
Closes https://github.com/angular/angular-phonecat/issues/145
2014-04-27 09:11:06 +01:00
Igor Minar 02058bfbe2 fix($location): don't clobber path during parsing of path
Closes #7199
2014-04-24 23:36:39 -07:00
Michael Payne 76647d3855 docs(guide/di): fix typo
Fixed a typo. "depenendencies" -> "dependencies"

Closes #7232
2014-04-24 13:28:57 -04:00
Søren Louv-Jansen 1f1cad8517 docs(ngMock): fix example for $http respond() helper with function as 1st param
The  `whenPOST` method should return a response object containing status, response body and headers.
If omitted the following error will be thrown:

`Uncaught TypeError: Cannot read property '2' of undefined`

The documentation doesn't make it very clear, so I think it will be appropriate to add it here.

Closes #6761
2014-04-24 09:45:59 -04:00
benjamingr e1f1d65d0c style(Angular.js): remove redundant _angular
Going through the commit history on GitHub, the `_angular` is for noConflict mode,
the case where you have an old reference to a variable called Angular you want to preserve.

Here is the commit that added _angular in : https://github.com/angular/angular.js/commit/
12ba6cec4f

This feature was later removed here:
https://github.com/angular/angular.js/commit/9faabd1ba0b1929785bcf166dc0ff60008e7c442

The variable 'made it through', it's redundant now.

Closes #7215
2014-04-24 11:42:29 +01:00
Peter Bacon Darwin a5df2d4e36 docs(tutorial): clarify bower usage 2014-04-24 10:59:17 +01:00
Peter Bacon Darwin b6514d9e1a docs(ngSwitch): fix formatting of custom usage field 2014-04-24 06:17:43 +01:00
Peter Bacon Darwin 1b1f94d8fe docs(examples): use form POST to create Plunkers
The previous solution for opening Plunkers from the docs relied on tight
coupling between the docs site and the plunkr site, in particular the
URL to the example code on the docs server was hard coded in the Plunker
site.

This change goes back to the old POST method of creating a Plunker, but
with a subtle difference: In the very old docs, the content was injected
directly into the example HTML at build time.  This was easy enough to
do as the example actually ran in the current page but also increased
the size of the doc page.

The new examples are run in completely separate iframes. This new version
of showing a Plunker loads the file content for the Plunker from the
server by accessing the example's manifest.json file using $http requests.

This also has the additional benefit that you can now generate plunkers
from examples that are running locally or, frankly, in any folder on any
server, such as personal builds on the Jenkins CI server.

Closes #7186
Closes #7198
2014-04-24 06:17:42 +01:00
Peter Bacon Darwin 1362a9b456 chore(npm-shrinkwrap): update dependencies
Dgeni-packages is updated to v0.8.3 to fix a bug in the docs (#7184)
Karma is updated to get us the fixed version of chokidar (v0.8.2)

Closes #7184
2014-04-24 06:17:42 +01:00
Peter Bacon Darwin 6a26b2c38c chore(clean-shrinkwrap): chokidar is now fixed at v0.8.2 2014-04-24 06:17:42 +01:00
James deBoer 9ab9bf6b41 perf(scope): 10x. Share the child scope class.
This change causes Scope.$destory to run 10x faster. I suspect
Scope.$new is significantly faster as well, but I didn't measure it.
2014-04-23 13:28:23 -07:00
Caitlin Potter e7e56fe9bf chore(package.json): remove dependencies which are no longer required
Based on https://github.com/angular/angular.js/issues/3244#issuecomment-41003086, I don't believe
we actually use either of these now that dgeni has replaced the old docs app. These should be
removed if Travis is green.

The i18n scripts still rely on q, so unfortunately it can't be gotten rid of just yet.
2014-04-22 16:33:53 -07:00
nnennajohn 85ea376da2 docs(tutorial/index): add directions for installing bower 2014-04-22 16:27:06 -07:00
Amin Ogarrio 050aae3ceb docs(tutorial/step-12): correct file path
Fixed wrong file path:
- angular/angular-animate.js -> angular-animate/angular-animate.js, which meets the code in app/index.html

Closes #7168
2014-04-21 16:21:04 -07:00
Matthew Davies 79d9fd9d57 docs(guide/controller): fixed spelling of spiciness
Closes #7185
2014-04-21 16:03:17 -07:00
ttam3d0 2dc2265e4f docs(tutorial/step_03): sync markup with angular-phonecat
Update from Bootstrap 2.3 to 3.1 changed "span" to "col" tags

Closes #7172
2014-04-21 15:46:57 -07:00
Thomas Junghans 9cd33df50c docs(tutorial): update step_11.ngdoc
Change toEqual to toEqualData in the test 'should create "phones" model with 2 phones fetched from xhr'
to make test more consistent.

Closes #7182
2014-04-21 15:46:46 -07:00
Simon Taranto 1db20ce90f docs(tutorial): update step_07.ngdoc
Closes #7169
2014-04-21 15:32:37 -07:00
Amin Ogarrio 76cb5ce7c5 docs(tutorial): fix typos in examples
The phonecatApp and phonecatAnimations modules have a dot where a semicolon should be at the end of the sentence

Closes #7167
2014-04-21 15:09:18 -07:00
Igor Minar 2a778d0038 chore(jshint): add jshint for the test/ folder 2014-04-18 16:33:15 -07:00
Igor Minar b04d3a8ec6 chore(Scope): name the $watch deregistration function as 2014-04-18 16:32:27 -07:00
jerryfan 7839330b40 docs(tutorial/step_12): fix grammar 2014-04-18 18:43:49 -04:00
Caitlin Potter 2d7cb14a16 fix(input): fix ReferenceError in event listener
Sigh, I ran the tests and they passed... Because I am not running them on IE.

Grumble grumble grumble.
2014-04-18 18:38:20 -04:00
Caitlin Potter f20b06d26d style(input): fix trailing whitespace
Oops. I blame vim for this little accident.
2014-04-18 18:16:03 -04:00
Caitlin Potter 109e5d1d39 fix(input): don't dirty model when input event triggered due to placeholder change
Certain versions of IE inexplicably trigger an input event in response to a placeholder
being set.

It is not possible to sniff for this behaviour nicely as the event is not triggered if
the element is not attached to the document, and the event triggers asynchronously so
it is not possible to accomplish this without deferring DOM compilation and slowing down
load times.

Closes #2614
Closes #5960
2014-04-18 17:48:53 -04:00
Janas Page 227822dac3 docs(form.FormController): fix grammar 2014-04-18 16:46:39 -04:00
Chris Wheatley 92f6b45e02 docs(ngMock): grammar fix
Small grammar fix for mock $httpBackend documentation.
2014-04-18 15:45:37 -04:00
Caitlin Potter 24f7999bc1 fix($location): fix and test html5Mode url-parsing algorithm for legacy browsers
This CL fixes problems and adds test cases for changes from #6421. Changes
include fixing the algorithm for preprocessing href attribute values, as
well as supporting xlink:href attributes. Credit for the original URL
parsing algorithm still goes to @richardcrichardc.

Good work, champ!
2014-04-17 23:48:31 -04:00
Richard Collins e0203660d3 fix($location): make legacy browsers behave like modern ones in html5Mode
Previously, LocationHashbangInHtml5Url, which is used when html5Mode is enabled
in browsers which do not support the history API (IE8/9), would behave very
inconsistently WRT relative URLs always being resolved relative to the app root
url.

This fix enables these legacy browsers to behave like history enabled browsers,
by processing href attributes in order to resolve urls correctly.

Closes #6162
Closes #6421
Closes #6899
Closes #6832
Closes #6834
2014-04-17 23:48:24 -04:00
expilo b6eb5fdb05 docs(tutorial): specify that ng-repeat goes in markup and not script
Just to make clear that it should not go to test spec.

Closes #7104
2014-04-17 12:44:25 -04:00
Thom Allen 5bf6e50b40 docs(tutorial): fix typo in module name
Fixed a typo in step 2

Closes #7138
2014-04-16 16:25:33 -04:00
Caitlin Potter a7ccb7531c fix($httpBackend): don't error when JSONP callback called with no parameter
This change brings Angular's JSONP behaviour closer in line with jQuery's. The feature has
already landed in the 1.3 branch as 6680b7b, however this alternative version is intended
to implement the feature in an IE8-compatible fashion.

Closes #7031
2014-04-15 17:18:24 -04:00
Caitlin Potter 6bea059109 fix($compile): reference correct directive name in ctreq error
Previously, ctreq would possibly reference the incorrect directive name,
due to relying on a directiveName living outside of the closure which
throws the exception, which can change before the call is ever made.

This change saves the current value of directiveName as a property of
the link function, which prevents this from occurring.

Closes #7062
Closes #7067
2014-04-15 17:17:22 -04:00
Caitlin Potter fcdac65aed fix(limitTo): do not convert Infinity to NaN
parseInt(Infinity, 10) will result in NaN, which becomes undesirable when the expected behaviour is
to return the entire input.

I believe this is possibly useful as a way to toggle input limiting based on certain factors.

Closes #6771
Closes #7118
2014-04-15 17:15:15 -04:00
Caitlin Potter 373078a94c revert: feat(injector): "strict-DI" mode which disables "automatic" function annotation
This reverts commit f5a04f59cf.
2014-04-14 20:17:22 -04:00
Caitlin Potter db07ad2d4c docs(ngTouch): define module depending on ngTouch in ngTouch examples
In addition to requiring that the file is loaded, it's also necessary to depend on the ngTouch
module when creating the injector.

Closes #7077
2014-04-14 05:39:08 -04:00
Wojciech Fornal 19d7a127c7 docs(api): fix broken link to "Services" guide
Nothing big. Simply a broken link to "Services".

> Nothing big.

It IS big, don't you ever forget it!

Closes #7101
2014-04-12 20:48:01 -04:00
Caitlin Potter f5a04f59cf feat(injector): "strict-DI" mode which disables "automatic" function annotation
This modifies the injector to prevent automatic annotation from occurring for a given injector.

This behaviour can be enabled when bootstrapping the application by using the attribute
"ng-strict-di" on the root element (the element containing "ng-app"), or alternatively by passing
an object with the property "strictDi" set to "true" in angular.bootstrap, when bootstrapping
manually.

JS example:

    angular.module("name", ["dependencies", "otherdeps"])
      .provider("$willBreak", function() {
        this.$get = function($rootScope) {
        };
      })
      .run(["$willBreak", function($willBreak) {
        // This block will never run because the noMagic flag was set to true,
        // and the $willBreak '$get' function does not have an explicit
        // annotation.
      }]);

    angular.bootstrap(document, ["name"], {
      strictDi: true
    });

HTML:

    <html ng-app="name" ng-strict-di>
      <!-- ... -->
    </html>

This will only affect functions with an arity greater than 0, and without an $inject property.

Closes #6719
Closes #6717
Closes #4504
Closes #6069
Closes #3611
2014-04-11 00:15:32 -04:00
Caitlin Potter ab92da43b0 docs(ngTouch): include angular-touch.js in example iframes
Mystical [deps="angular-touch.js"] is enough to get the correct file to be added to the <head> tag.

Closes #7058
Closes #7074
2014-04-10 14:41:12 -04:00
Valentin Waeselynck 6782c45ddc docs(guide/e2e-testing): remove repeated word 'manually'
Fixed repetition in Caveats paragraph

"manually" was repeated. Grammatical correctness is the prime directive, resistance is futile.

Closes #7073
2014-04-10 11:04:33 -04:00
Julie c55477fb2b docs(e2e-testing): deprecate ng-scenario and update E2E testing doc to discuss protractor 2014-04-10 11:04:25 -04:00
Robert Speicher 2e2d62ca12 docs(tutorial): npm needs "run" keyword for custom script names
Fix "npm run update-webdriver" command

Closes #7071
2014-04-10 08:25:12 -04:00
Steven Benjamin e3141fe5f4 docs(guide/directive): directives can be comments, too
While it's not a widely used feature, and likely shouldn't be recommended, it is
possible to use a directive via a comment node.

Closes #7061
2014-04-10 06:48:06 -04:00
Rosseyn 1ebed26678 docs(guide/ie): fix spelling mistake
Closes #7050
2014-04-08 18:46:53 -04:00
Igor Minar e987efd4c0 docs($location): improve $location.search() docs
Closes #7030
2014-04-07 16:11:25 -07:00
Tyler McGinnis ea72e5f881 docs(ngMock): fix typo
"register" should be "registered"

Closes #7034
2014-04-07 16:34:04 -04:00
Jason Travis c550c12738 docs(guide): fix dropped word in controller description
Closes #7026
2014-04-07 07:46:55 -04:00
Thomas Wicker 651caffe45 docs(tutorial): fix typo in tutorial index page
Spelling error, "server" should be "serve"

Closes #7028
2014-04-07 07:41:02 -04:00
Jonathan Sampson 45855b8ba2 docs(guide/providers): fix grammar in note regarding factory naming conventions
It seems as though this sentence wasn't written the way it was originally planned. I did my best to
approximate the intent of the original author.

Closes #7022
2014-04-06 22:06:34 -04:00
Peter Bacon Darwin feb54d68d2 docs(tutorial/step-8): fix external link 2014-04-06 18:58:28 +01:00
Yiling Lu 2d0f6037f7 docs(tutorial/step-8): link to Protractor rather than ngScenario
Angular test runner API has been deprecated, Protractor is advised:
http://docs.angularjs.org/guide/e2e-testing
Link is updated to direct reader to Protractor API.

Closes #7001
2014-04-06 17:34:57 +01:00
Peter Bacon Darwin ebd9a2a960 chore(docs/app): remove redundant code directive
Closes #7000
2014-04-06 17:31:25 +01:00
Peter Bacon Darwin fcfa6ebb6b docs(guide/di): clarify what can be injected into what
Also do some general housekeeping and tidying of the page.

Closes #6999
2014-04-06 17:25:42 +01:00
Joseph Orbegoso Pea cf8ed01c6e docs(tutorial/step-5): clarify inline annotations
Closes #6998
2014-04-06 15:22:48 +01:00
Peter Bacon Darwin c352b92c40 docs(tutorial/step-7): clarify the new files & modules
Closes #6996
2014-04-06 14:57:19 +01:00
Thiago Colares 369145467c docs(tutorial): added a link to Wiki page on DI in Step 7
This article is fantastic and really helped on understanding how DI works on Angular. It may be
useful to other beginners -- because, at first glance, this topic (DI on Angular) ended a little bit
hazy for me.

Closes #7010
2014-04-05 13:52:37 -04:00
martco c3ec6ea226 docs(guide/directive): grammatical fixup
The 'to' in 'end-to-end' is directional, not numeric

Closes #6895
2014-04-05 09:49:28 -04:00
Jason Carver 291684c29c docs(CONTRIBUTING.md): typo fix
"submitting and issue" should be "submitting an issue"

Closes #7002
2014-04-05 09:46:07 -04:00
Yiling Lu d7615351df docs(tutorial): fix links to bower in Step 11
Minor change: add missing bower reference link.

Closes #7005
2014-04-05 09:36:03 -04:00
Caitlin Potter a97a172ee9 fix($compile): fix regression which affected old jQuery releases
ddb8081 and 4ea57e7 removed the calls which trimmed leading and trailing whitespace from templates
in the HTML compiler. This broke old versions of jQuery (such as 1.9.1), which do not trim
whitespace in their constructors. Naturally, this would not appear in the jQuery tests, as we are
testing against a version which does trim whitespace in the constructor.

This fix re-adds calls to `trim()` when compiling templates in $compile, in order to avoid breaking
old versions of jQuery.
2014-04-05 09:04:23 -04:00
Yiling Lu d53a787f0d docs(tutorial): change regexp so that assertion works
Need to remove this single space for the regex to work here.

Apparently `getText()` is trimming the text content or something, because there is no good reason
why that space should not be there.

Closes #6985
2014-04-04 18:10:49 -04:00
Yiling Lu 9682bd0c4e docs(guide/tutorial): make added assertion one which will actually work.
Update the by.binding usage to make the test case work.

Closes #6987
2014-04-04 16:40:13 -04:00
Matias Niemelä 34d0740350 fix($animate): ensure class-based animations always perform a domOperation if skipped
Closes #6957
2014-04-04 11:47:21 -04:00
Choi YoonSung 4d9efa2f76 docs(tutorial/step-8): module must be loaded in test
Closes #6930
2014-04-04 14:13:18 +01:00
Peter Bacon Darwin f9a7b064a0 docs(tutorial): synchronize with angular-phonecat changes 2014-04-04 14:07:33 +01:00
Peter Bacon Darwin 26ca443c10 chore(docs/app/tutorials): improve helper directives 2014-04-04 14:06:04 +01:00
Peter Bacon Darwin 0f37e49039 docs(tutorial): clean up and elaborate introduction 2014-04-04 10:23:52 +01:00
Peter Bacon Darwin 3fdcde73ae docs(tutorial): synch step 0 with angular-seed 2014-04-04 08:32:25 +01:00
Joseph Orbegoso Pea d2dc77169b docs(guide/scope): small fixes 2014-04-03 16:10:59 -07:00
Martin Jezek d99b506885 docs(tutorial): fix linked files to bower_components folder
Closes #6960
2014-04-03 16:03:26 -07:00
196 changed files with 7959 additions and 2391 deletions
+4
View File
@@ -18,6 +18,10 @@ env:
- LOGS_DIR=/tmp/angular-build/logs
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
install:
- npm config set registry http://23.251.144.68
- npm install
before_script:
- mkdir -p $LOGS_DIR
- ./lib/sauce/sauce_connect_setup.sh
+145 -2
View File
@@ -1,3 +1,146 @@
<a name="1.2.18"></a>
# 1.2.18 ear-extendability (2014-06-13)
## Bug Fixes
- **$compile:**
- ensure transclude works at root of templateUrl
([fd420c40](https://github.com/angular/angular.js/commit/fd420c40613d02b3a3f7b14d00a98664518c28f0),
[#7183](https://github.com/angular/angular.js/issues/7183), [#7772](https://github.com/angular/angular.js/issues/7772))
- bound transclusion to correct scope
([1382d4e8](https://github.com/angular/angular.js/commit/1382d4e88ec486b7749e45e6ccc864b3ec388cfe))
- don't pass transcludes to non-transclude templateUrl directives
([b9ddef2a](https://github.com/angular/angular.js/commit/b9ddef2a495b44cb5fe678b8753de0b7a369244d))
- don't pass transclude to template of non-transclude directive
([eafba9e2](https://github.com/angular/angular.js/commit/eafba9e2e5ddc668c534e930d83031d2e8dc32b9))
- fix nested isolated transclude directives
([bb931097](https://github.com/angular/angular.js/commit/bb9310974b6765c2b87e74ee7b8485a6e9c24740),
[#1809](https://github.com/angular/angular.js/issues/1809), [#7499](https://github.com/angular/angular.js/issues/7499))
- pass transcludeFn down to nested transclude directives
([8df5f325](https://github.com/angular/angular.js/commit/8df5f3259aa776f28bf3d869fb1c03e10a897c84),
[#7240](https://github.com/angular/angular.js/issues/7240), [#7387](https://github.com/angular/angular.js/issues/7387))
- **$injector:** report circularity in circular dependency error message
([14e797c1](https://github.com/angular/angular.js/commit/14e797c1a10eabd15bf8e845b62213398bcc0f58),
[#7500](https://github.com/angular/angular.js/issues/7500))
- **ngResource:** don't convert literal values into Resource objects when isArray is true
([f0904cf1](https://github.com/angular/angular.js/commit/f0904cf12e4f01daa2d4fcbb20c762050125ca55),
[#6314](https://github.com/angular/angular.js/issues/6314), [#7741](https://github.com/angular/angular.js/issues/7741))
## Performance Improvements
- **$compile:** move ng-binding class stamping for interpolation into compile phase
([81b7e5ab](https://github.com/angular/angular.js/commit/81b7e5ab0ee3fea410b16b09144359ceb99f5191))
- **$http:** move xsrf cookie check to after cache check in $http
([8b86d363](https://github.com/angular/angular.js/commit/8b86d363aa252c3264201b54b57c3e34f9632d45),
[#7717](https://github.com/angular/angular.js/issues/7717))
- **isArray:** use native Array.isArray
([6c14fb1e](https://github.com/angular/angular.js/commit/6c14fb1eb61dc0a0552fbcb2ca3ace11c9a2f6a5))
- **jqLite:** cache collection length for all methods that work on a single element
([6d418ef5](https://github.com/angular/angular.js/commit/6d418ef5e3a775577996caf0709f79f447f77025))
- **ngBind:** set the ng-binding class during compilation instead of linking
([1b189027](https://github.com/angular/angular.js/commit/1b1890274e5a75553ddf9915bb23da48800275f9))
<a name="1.2.17"></a>
# 1.2.17 - quantum disentanglement (2014-06-06)
## Bug Fixes
- **$animate:**
- remove the need to add `display:block!important` for `ngShow`/`ngHide`
([55b2f0e8](https://github.com/angular/angular.js/commit/55b2f0e8620465559016b424967d90a86af597c0),
[#3813](https://github.com/angular/angular.js/issues/3813))
- retain inline styles for property-specific transitions
([ad08638c](https://github.com/angular/angular.js/commit/ad08638c0ae61a22ce43d0b40e1220065b867672),
[#7503](https://github.com/angular/angular.js/issues/7503))
- ensure class-based animations always perform a DOM operation if skipped
([34d07403](https://github.com/angular/angular.js/commit/34d0740350a50ff2c3a076eaad1e8122283448c3),
[#6957](https://github.com/angular/angular.js/issues/6957))
- **$compile:**
- do not merge attrs that are the same for replace directives
([b635903e](https://github.com/angular/angular.js/commit/b635903ec435ea355b0f3688c7372627d01e23e2),
[#7463](https://github.com/angular/angular.js/issues/7463))
- pass `transcludeFn` down to nested transclude directives
([11385060](https://github.com/angular/angular.js/commit/113850602de2f8bc396df4ffd54bb0f1be565b17),
[#7240](https://github.com/angular/angular.js/issues/7240), [#7387](https://github.com/angular/angular.js/issues/7387))
- set `$isolateScope` correctly for sync template directives
([5319621a](https://github.com/angular/angular.js/commit/5319621afd0edf60aef177a0e98dbb7c282cc418),
[#6942](https://github.com/angular/angular.js/issues/6942))
- reference correct directive name in `ctreq` error
([6bea0591](https://github.com/angular/angular.js/commit/6bea0591095c19f747c08ef24cc60b34d28b2824),
[#7062](https://github.com/angular/angular.js/issues/7062), [#7067](https://github.com/angular/angular.js/issues/7067))
- fix regression which affected old jQuery releases
([a97a172e](https://github.com/angular/angular.js/commit/a97a172ee9f9bcff4d4d84854ded0c72fa0f7e9a))
- **$httpBackend:** don't error when JSONP callback is called with no parameter
([a7ccb753](https://github.com/angular/angular.js/commit/a7ccb7531c92fb976c6058aef2bb18316075efb2),
[#7031](https://github.com/angular/angular.js/issues/7031))
- **$location:**
- don't clobber path during parsing of path
([02058bfb](https://github.com/angular/angular.js/commit/02058bfbe27296c5441fc247e5a451da83c74134),
[#7199](https://github.com/angular/angular.js/issues/7199))
- fix and test html5Mode url-parsing algorithm for legacy browsers
([24f7999b](https://github.com/angular/angular.js/commit/24f7999bc16e347208aa18c418da85489286674b))
- make legacy browsers behave like modern ones in html5Mode
([e0203660](https://github.com/angular/angular.js/commit/e0203660d3af56c5a94e0a9b69c10fd5dabcf577),
[#6162](https://github.com/angular/angular.js/issues/6162), [#6421](https://github.com/angular/angular.js/issues/6421), [#6899](https://github.com/angular/angular.js/issues/6899), [#6832](https://github.com/angular/angular.js/issues/6832), [#6834](https://github.com/angular/angular.js/issues/6834))
- **angular.copy:** support circular references in the value being copied
([5c997209](https://github.com/angular/angular.js/commit/5c99720934edc35dd462b1ad02c4d0205683d917),
[#7618](https://github.com/angular/angular.js/issues/7618))
- **grunt-utils:** ensure special inline CSS works when `angular` is not a global
([d4231171](https://github.com/angular/angular.js/commit/d4231171582eb41d37bbb908eed23f074ab12f3f),
[#7176](https://github.com/angular/angular.js/issues/7176))
- **input:**
- fix `ReferenceError` in event listener
([2d7cb14a](https://github.com/angular/angular.js/commit/2d7cb14a167560edc1356dcec6f9e100ed7ac691))
- don't dirty model when input event is triggered due to a placeholder change
([109e5d1d](https://github.com/angular/angular.js/commit/109e5d1d39015af8ade1dc2aff31a2355fbab0a6),
[#2614](https://github.com/angular/angular.js/issues/2614), [#5960](https://github.com/angular/angular.js/issues/5960))
- **jqLite:** use jQuery only if `jQuery.fn.on` is present
([fafcd628](https://github.com/angular/angular.js/commit/fafcd6285a6799c4e377ea33011ae3a01aac49a6))
- **limitTo:** do not convert `Infinity` to `NaN`
([fcdac65a](https://github.com/angular/angular.js/commit/fcdac65aedfdf48dd2e11d6e5850e03ec188f068),
[#6771](https://github.com/angular/angular.js/issues/6771), [#7118](https://github.com/angular/angular.js/issues/7118))
- **ngAnimate:** `$animate` methods should accept native DOM elements
([9227a5db](https://github.com/angular/angular.js/commit/9227a5db947a78e3dbe8b91d5dac5d67444c855c))
- **ngClass:**
- support multiple classes in key
([85ce5d0d](https://github.com/angular/angular.js/commit/85ce5d0db9fc4ee5636015fc042224785f9aa997))
- handle index changes when an item is unshifted
([a4cc9e19](https://github.com/angular/angular.js/commit/a4cc9e194468573bae5232f63044459d0de6638f),
[#7256](https://github.com/angular/angular.js/issues/7256))
- **ngLocale:** fix i18n code-generation to support `get_vf_`, `decimals_`, and `get_wt_`
([96a31476](https://github.com/angular/angular.js/commit/96a314766c41bbb18bcddeddd25c8e566ab76acd))
- **ngSanitize:** encode surrogate pair properly
([3d0b49c0](https://github.com/angular/angular.js/commit/3d0b49c07f10c0a723c91629c63705647b690d81),
[#5088](https://github.com/angular/angular.js/issues/5088), [#6911](https://github.com/angular/angular.js/issues/6911))
- **ngSwitch:** properly support case labels with different numbers of transclude fns
([32aa4915](https://github.com/angular/angular.js/commit/32aa491588fe4982d4056e89a5d0dd19cf835e72))
- **numberFilter:** fix rounding error edge case
([0388eed7](https://github.com/angular/angular.js/commit/0388eed7e52fdbb832a5b4ef466420a128a43800),
[#7453](https://github.com/angular/angular.js/issues/7453), [#7478](https://github.com/angular/angular.js/issues/7478))
## Features
- **ngMock:** add support of mocha tdd interface
([6d1c6772](https://github.com/angular/angular.js/commit/6d1c67727ab872c44addc783ef1406952142d89e),
[#7489](https://github.com/angular/angular.js/issues/7489))
## Performance Improvements
- **$interpolate:** optimize value stringification
([9d4fa33e](https://github.com/angular/angular.js/commit/9d4fa33e35d73ab28a8a187e20dfbe1f77055825),
[#7501](https://github.com/angular/angular.js/issues/7501))
- **scope:** 10x. Share the child scope class.
([9ab9bf6b](https://github.com/angular/angular.js/commit/9ab9bf6b415aa216cfbfda040286e5ec99f56ee0))
<a name="1.2.16"></a>
# 1.2.16 badger-enumeration (2014-04-03)
@@ -3708,7 +3851,7 @@ behavior and migrate your controllers one at a time: <https://gist.github.com/16
([commit](https://github.com/angular/angular.js/commit/78656fe0dfc99c341ce02d71e7006e9c05b1fe3f))
- fn signature change for change listener functions registered via `scope.$watch` - this means that
the scope object can be listed in the arguments list only if its needed and skipped otherwise.
the scope object can be listed in the arguments list only if it's needed and skipped otherwise.
([commit](https://github.com/angular/angular.js/commit/0196411dbe179afe24f4faa6d6503ff3f69472da))
- before: `scope.$watch('someModel', function(scope, newVal, oldVal) {})`
@@ -4642,7 +4785,7 @@ with the `$route` service
- docs app UI polishing with dual scrolling and other improvements
### Bug Fixes
- `select` widget now behaves correctly when it's `option` items are created via `ng:repeat`
- `select` widget now behaves correctly when its `option` items are created via `ng:repeat`
(issue #170)
- fix for async xhr cache issue #152 by adding `$browser.defer` and `$defer` service
+3 -3
View File
@@ -23,7 +23,7 @@ discussion list or [StackOverflow][stackoverflow]. We are also available on [IRC
## <a name="issue"></a> Found an Issue?
If you find a bug in the source code or a mistake in the documentation, you can help us by
submitting and issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
with a fix.
***Localization Issue:*** *Angular.js uses the [Google Closure I18N library], to generate its own I18N files. This means that
@@ -252,7 +252,7 @@ You can find out more detailed information about contributing in the
[Google Closure I18N library]: https://code.google.com/p/closure-library/source/browse/closure/goog/i18n/
[Google Closure I18N library]: https://github.com/google/closure-library/tree/master/closure/goog/i18n
[angular-dev]: https://groups.google.com/forum/?fromgroups#!forum/angular-dev
[coc]: https://github.com/angular/code-of-conduct/blob/master/CODE_OF_CONDUCT.md
[commit-message-format]: https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit#
@@ -269,6 +269,6 @@ You can find out more detailed information about contributing in the
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
[plunker]: http://plnkr.co/edit
[stackoverflow]: http://stackoverflow.com/questions/tagged/angularjs
[unit-testing]: http://docs.angularjs.org/guide/dev_guide.unit-testing
[unit-testing]: https://docs.angularjs.org/guide/unit-testing
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/CONTRIBUTING.md?pixel)](https://github.com/igrigorik/ga-beacon)
+4 -1
View File
@@ -220,8 +220,11 @@ module.exports = function(grunt) {
"ddescribe-iit": {
files: [
'src/**/*.js',
'test/**/*.js',
'!test/ngScenario/DescribeSpec.js'
'!test/ngScenario/DescribeSpec.js',
'!src/ng/directive/booleanAttrs.js', // legitimate xit here
'!src/ngScenario/**/*.js'
]
},
+1 -1
View File
@@ -34,7 +34,7 @@ This process based on the idea of minimizing user pain
* Check if there are comments that link to a dupe. If so verify that this is indeed a dupe, [close it][], and go to the last step.
1. Bugs:
* Label `Type: Bug`
* Reproducible? - Steps to reproduce the bug are clear. If they are not,
* Reproducible? - Steps to reproduce the bug are clear. If they are not, ask for a clarification. If there's no reply after a week, [close it][].
* Reproducible on master? - <http://code.angularjs.org/snapshot/>
1. Non bugs:
+6
View File
@@ -14,3 +14,9 @@ ng\:form {
transition:0s all!important;
-webkit-transition:0s all!important;
}
/* show the element during a show/hide animation when the
* animation is ongoing, but the .ng-hide class is active */
.ng-hide-add-active, .ng-hide-remove {
display: block!important;
}
+12 -4
View File
@@ -184,10 +184,6 @@ h1,h2,h3,h4,h5,h6 {
}
pre {
padding: 15px;
border: 1px solid #ddd;
border-radius: 5px;
display: block;
white-space: pre-wrap;
word-break: normal;
}
@@ -257,6 +253,9 @@ code.highlighted {
z-index: 99;
cursor: pointer;
font-size: 16px;
-moz-appearance: none;
text-indent: 0.01px;
text-overflow: '';
}
.picker:after {
@@ -576,6 +575,15 @@ ul.events > li {
margin-bottom:40px;
}
@media only screen and (min-width: 769px) and (max-width: 991px) {
.main-body-grid {
margin-top: 160px;
}
.main-body-grid > .grid-left {
top: 160px;
}
}
@media only screen and (max-width : 768px) {
.picker, .picker select {
width:auto;
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

+1 -5
View File
@@ -1,9 +1,5 @@
angular.module('directives', [])
.directive('code', function() {
return { restrict:'E', terminal: true };
})
/**
* backToTop Directive
* @param {Function} $anchorScroll
@@ -25,7 +21,7 @@ angular.module('directives', [])
restrict: 'E',
terminal: true,
compile: function(element) {
var linenums = element.hasClass('linenum') || element.parent()[0].nodeName === 'PRE';
var linenums = element.hasClass('linenum');// || element.parent()[0].nodeName === 'PRE';
var match = /lang-(\S)+/.exec(element.className);
var lang = match && match[1];
var html = element.html();
+10 -3
View File
@@ -1,9 +1,16 @@
angular.module('DocsController', [])
.controller('DocsController', function($scope, $rootScope, $location, $window, $cookies, NG_PAGES, NG_NAVIGATION, NG_VERSION) {
.controller('DocsController', [
'$scope', '$rootScope', '$location', '$window', '$cookies', 'openPlunkr',
'NG_PAGES', 'NG_NAVIGATION', 'NG_VERSION',
function($scope, $rootScope, $location, $window, $cookies, openPlunkr,
NG_PAGES, NG_NAVIGATION, NG_VERSION) {
$scope.openPlunkr = openPlunkr;
$scope.docsVersion = NG_VERSION.isSnapshot ? 'snapshot' : NG_VERSION.version;
$scope.fold = function(url) {
if(url) {
$scope.docs_fold = '/notes/' + url;
@@ -120,4 +127,4 @@ angular.module('DocsController', [])
});
}
});
});
}]);
+47 -241
View File
@@ -1,65 +1,6 @@
angular.module('examples', [])
.directive('sourceEdit', function(getEmbeddedTemplate) {
return {
template: '<div class="btn-group pull-right">' +
'<a class="btn dropdown-toggle btn-primary" data-toggle="dropdown" href>' +
' <i class="icon-pencil icon-white"></i> Edit<span class="caret"></span>' +
'</a>' +
'<ul class="dropdown-menu">' +
' <li><a ng-click="plunkr($event)" href="">In Plunkr</a></li>' +
' <li><a ng-click="fiddle($event)" href="">In JsFiddle</a></li>' +
'</ul>' +
'</div>',
scope: true,
controller: function($scope, $attrs, openJsFiddle, openPlunkr) {
var sources = {
module: $attrs.sourceEdit,
deps: read($attrs.sourceEditDeps),
html: read($attrs.sourceEditHtml),
css: read($attrs.sourceEditCss),
js: read($attrs.sourceEditJs),
json: read($attrs.sourceEditJson),
unit: read($attrs.sourceEditUnit),
scenario: read($attrs.sourceEditScenario)
};
$scope.fiddle = function(e) {
e.stopPropagation();
openJsFiddle(sources);
};
$scope.plunkr = function(e) {
e.stopPropagation();
openPlunkr(sources);
};
}
};
function read(text) {
var files = [];
angular.forEach(text ? text.split(' ') : [], function(refId) {
// refId is index.html-343, so we need to strip the unique ID when exporting the name
files.push({name: refId.replace(/-\d+$/, ''), content: getEmbeddedTemplate(refId)});
});
return files;
}
})
.factory('angularUrls', function($document) {
var urls = {};
angular.forEach($document.find('script'), function(script) {
var match = script.src.match(/^.*\/(angular[^\/]*\.js)$/);
if (match) {
urls[match[1].replace(/(\-\d.*)?(\.min)?\.js$/, '.js')] = match[0];
}
});
return urls;
})
.factory('formPostData', function($document) {
.factory('formPostData', ['$document', function($document) {
return function(url, fields) {
var form = angular.element('<form style="display: none;" method="post" action="' + url + '" target="_blank"></form>');
angular.forEach(fields, function(value, name) {
@@ -71,196 +12,61 @@ angular.module('examples', [])
form[0].submit();
form.remove();
};
})
}])
.factory('prepareDefaultAppModule', function() {
return function(content) {
var deps = [];
angular.forEach(content.deps, function(file) {
if(file.name == 'angular-animate.js') {
deps.push('ngAnimate');
}
});
.factory('openPlunkr', ['formPostData', '$http', '$q', function(formPostData, $http, $q) {
return function(exampleFolder) {
var moduleName = 'App';
return {
module : moduleName,
script : "angular.module('" + moduleName + "', [" +
(deps.length ? "'" + deps.join("','") + "'" : "") + "]);\n\n"
};
};
})
var exampleName = 'AngularJS Example';
.factory('prepareEditorAssetTags', function(angularUrls) {
return function(content, options) {
options = options || {};
var includeLocalFiles = options.includeLocalFiles;
var html = makeScriptTag(angularUrls['angular.js']);
// Load the manifest for the example
$http.get(exampleFolder + '/manifest.json')
.then(function(response) {
return response.data;
})
.then(function(manifest) {
var filePromises = [];
var allFiles = [].concat(content.js, content.css, content.html, content.json);
angular.forEach(content.deps, function(file) {
if (file.name !== 'angular.js') {
var isLocal = false;
for(var i=0;i<allFiles.length;i++) {
if(allFiles[i].name == file.name) {
isLocal = true;
break;
}
}
if(!(isLocal && !includeLocalFiles)) {
var assetUrl = angularUrls[file.name] || file.name;
html += makeScriptTag(assetUrl);
}
}
});
if(includeLocalFiles) {
angular.forEach(content.css, function(file, index) {
html += makeCssLinkTag(file.name);
});
}
return html;
function makeScriptTag(src) {
return '<script type="text/javascript" src="' + src + '"></script>\n';
}
function makeCssLinkTag(src) {
return '<link rel="stylesheet" type="text/css" href="' + src + '" />\n';
}
};
})
.factory('openPlunkr', function(templateMerge, formPostData, prepareEditorAssetTags, prepareDefaultAppModule) {
return function(content) {
var hasRouting = false;
angular.forEach(content.deps, function(file) {
hasRouting = hasRouting || file.name == 'angular-route.js';
});
var indexHtmlContent = '<!doctype html>\n' +
'<html ng-app="{{module}}">\n' +
' <head>\n' +
'{{scriptDeps}}';
if(hasRouting) {
indexHtmlContent += '<script type="text/javascript">\n' +
'//this is here to make plunkr work with AngularJS routing\n' +
'angular.element(document.getElementsByTagName(\'head\')).append(' +
'angular.element(\'<base href="\' + window.location.pathname + \'" />\')' +
');\n' +
'</script>\n';
}
indexHtmlContent += '</head>\n' +
' <body>\n\n' +
'{{indexContents}}\n\n' +
' </body>\n' +
'</html>\n';
indexProp = {
module: content.module,
scriptDeps: prepareEditorAssetTags(content, { includeLocalFiles : true }),
indexContents: content.html[0].content
};
var allFiles = [].concat(content.js, content.css, content.html, content.json);
if(!content.module) {
var moduleData = prepareDefaultAppModule(content);
indexProp.module = moduleData.module;
var found = false;
angular.forEach(content.js, function(file) {
if(file.name == 'script.js') {
file.content = moduleData.script + file.content;
found = true;
}
});
if(!found) {
indexProp.scriptDeps += '<script type="text/javascript" src="script.js"></script>\n';
allFiles.push({
name : 'script.js',
content : moduleData.script
// Build a pretty title for the Plunkr
var exampleNameParts = manifest.name.split('-');
exampleNameParts.unshift('AngularJS');
angular.forEach(exampleNameParts, function(part, index) {
exampleNameParts[index] = part.charAt(0).toUpperCase() + part.substr(1);
});
}
}
exampleName = exampleNameParts.join(' - ');
var postData = {};
angular.forEach(manifest.files, function(filename) {
filePromises.push($http.get(exampleFolder + '/' + filename, { transformResponse: [] })
.then(function(response) {
angular.forEach(allFiles, function(file, index) {
if (file.content && file.name != 'index.html') {
postData['files[' + file.name + ']'] = file.content;
}
});
// The manifests provide the production index file but Plunkr wants
// a straight index.html
if (filename === "index-production.html") {
filename = "index.html"
}
postData['files[index.html]'] = templateMerge(indexHtmlContent, indexProp);
postData['tags[]'] = "angularjs";
return {
name: filename,
content: response.data
};
}));
});
return $q.all(filePromises);
})
.then(function(files) {
var postData = {};
postData.private = true;
postData.description = 'AngularJS Example Plunkr';
angular.forEach(files, function(file) {
postData['files[' + file.name + ']'] = file.content;
});
formPostData('http://plnkr.co/edit/?p=preview', postData);
postData['tags[0]'] = "angularjs";
postData['tags[1]'] = "example";
postData.private = true;
postData.description = exampleName;
formPostData('http://plnkr.co/edit/?p=preview', postData);
});
};
})
.factory('openJsFiddle', function(templateMerge, formPostData, prepareEditorAssetTags, prepareDefaultAppModule) {
var HTML = '<div ng-app=\"{{module}}\">\n{{html:2}}</div>',
CSS = '</style> <!-- Ugly Hack to make remote files preload in jsFiddle --> \n' +
'{{head:0}}<style>{{css}}',
SCRIPT = '{{script}}',
SCRIPT_CACHE = '\n\n<!-- {{name}} -->\n<script type="text/ng-template" id="{{name}}">\n{{content:2}}</script>',
BASE_HREF_TAG = '<!-- Ugly Hack to make AngularJS routing work inside of jsFiddle -->\n' +
'<base href="/" />\n\n';
return function(content) {
var prop = {
module: content.module,
html: '',
css: '',
script: ''
};
if(!prop.module) {
var moduleData = prepareDefaultAppModule(content);
prop.script = moduleData.script;
prop.module = moduleData.module;
}
angular.forEach(content.html, function(file, index) {
if (index) {
prop.html += templateMerge(SCRIPT_CACHE, file);
} else {
prop.html += file.content;
}
});
prop.head = prepareEditorAssetTags(content, { includeLocalFiles : false });
angular.forEach(content.js, function(file, index) {
prop.script += file.content;
});
angular.forEach(content.css, function(file, index) {
prop.css += file.content;
});
var hasRouting = false;
angular.forEach(content.deps, function(file) {
hasRouting = hasRouting || file.name == 'angular-route.js';
});
var compiledHTML = templateMerge(HTML, prop);
if(hasRouting) {
compiledHTML = BASE_HREF_TAG + compiledHTML;
}
formPostData("http://jsfiddle.net/api/post/library/pure/", {
title: 'AngularJS Example',
html: compiledHTML,
js: templateMerge(SCRIPT, prop),
css: templateMerge(CSS, prop)
});
};
});
}]);
+2 -2
View File
@@ -8,7 +8,7 @@ angular.module('search', [])
}
$scope.search = function(q) {
var MIN_SEARCH_LENGTH = 3;
var MIN_SEARCH_LENGTH = 2;
if(q.length >= MIN_SEARCH_LENGTH) {
var results = docsSearch(q);
var totalAreas = 0;
@@ -35,7 +35,7 @@ angular.module('search', [])
}
}
if(result) {
$location.path(result.url);
$location.path(result.path);
$scope.hideResults();
}
};
+15 -23
View File
@@ -22,7 +22,7 @@ angular.module('tutorials', [])
element.addClass('tutorial-nav');
element.append(templateMerge(
'<a href="tutorial/{{prev}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-step-backward"></i> Previous</li></a>\n' +
'<a href="http://angular.github.com/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
'<a href="http://angular.github.io/angular-phonecat/step-{{seq}}/app"><li class="btn btn-primary"><i class="glyphicon glyphicon-play"></i> Live Demo</li></a>\n' +
'<a href="https://github.com/angular/angular-phonecat/compare/step-{{diffLo}}...step-{{diffHi}}"><li class="btn btn-primary"><i class="glyphicon glyphicon-search"></i> Code Diff</li></a>\n' +
'<a href="tutorial/{{next}}"><li class="btn btn-primary">Next <i class="glyphicon glyphicon-step-forward"></i></li></a>', props));
}
@@ -31,28 +31,20 @@ angular.module('tutorials', [])
.directive('docTutorialReset', function() {
function tab(name, command, id, step) {
return '' +
' <div class=\'tab-pane well\' title="' + name + '" value="' + id + '">\n' +
' <ol>\n' +
' <li><p>Reset the workspace to step ' + step + '.</p>' +
' <pre>' + command + '</pre></li>\n' +
' <li><p>Refresh your browser or check the app out on <a href="http://angular.github.com/angular-phonecat/step-' + step + '/app">Angular\'s server</a>.</p></li>\n' +
' </ol>\n' +
' </div>\n';
}
return {
compile: function(element, attrs) {
var step = attrs.docTutorialReset;
element.html(
'<div ng-hide="show">' +
'<p><a href="" ng-click="show=true;$event.stopPropagation()">Workspace Reset Instructions ➤</a></p>' +
'</div>\n' +
'<div class="tabbable" ng-show="show" ng-model="$cookies.platformPreference">\n' +
tab('Git on Mac/Linux', 'git checkout -f step-' + step, 'gitUnix', step) +
tab('Git on Windows', 'git checkout -f step-' + step, 'gitWin', step) +
'</div>\n');
}
scope: {
'step': '@docTutorialReset'
},
template:
'<p><a href="" ng-click="show=!show;$event.stopPropagation()">Workspace Reset Instructions ➤</a></p>\n' +
'<div class="alert alert-info" ng-show="show">\n' +
' <p>Reset the workspace to step {{step}}.</p>' +
' <p><pre>git checkout -f step-{{step}}</pre></p>\n' +
' <p>Refresh your browser or check out this step online: '+
'<a href="http://angular.github.io/angular-phonecat/step-{{step}}/app">Step {{step}} Live Demo</a>.</p>\n' +
'</div>\n' +
'<p>The most important changes are listed below. You can see the full diff on ' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}">GitHub</a>\n' +
'</p>'
};
});
+1
View File
@@ -3,6 +3,7 @@ describe("DocsController", function() {
angular.module('fake', [])
.value('$cookies', {})
.value('openPlunkr', function() {})
.value('NG_PAGES', {})
.value('NG_NAVIGATION', {})
.value('NG_VERSION', {});
+4
View File
@@ -25,6 +25,10 @@ module.exports = function(config) {
require('./tag-defs/tutorial-step')
]);
config.append('processing.defaultTagTransforms', [
require('dgeni-packages/jsdoc/tag-defs/transforms/trim-whitespace')
]);
config.append('processing.inlineTagDefinitions', [
require('./inline-tag-defs/type')
]);
+15 -8
View File
@@ -1,26 +1,33 @@
var writer = require('dgeni/lib/utils/doc-writer');
var fs = require('q-io/fs');
var log = require('winston');
var util = require("util");
var filter, outputPath, depth;
module.exports = {
name: 'debug-dump',
runBefore: ['write-files'],
description: 'This processor dumps docs that match a filter to a file',
init: function(config, injectables) {
process: function(docs, config) {
var filter, outputPath, depth;
filter = config.get('processing.debug-dump.filter');
outputPath = config.get('processing.debug-dump.outputPath');
depth = config.get('processing.debug-dump.depth', 2);
},
process: function(docs) {
if ( filter && outputPath ) {
log.info('Dumping docs:', filter, outputPath);
var filteredDocs = filter(docs);
var dumpedDocs = util.inspect(filteredDocs, depth);
return writer.writeFile(outputPath, dumpedDocs).then(function() {
return writeFile(outputPath, dumpedDocs).then(function() {
return docs;
});
}
}
};
};
function writeFile(file, content) {
return fs.makeTree(fs.directory(file)).then(function() {
return fs.write(file, content, 'wb');
});
}
+12 -10
View File
@@ -5,16 +5,18 @@ var path = require('canonical-path');
module.exports = {
name: 'error-docs',
description: 'Compute the various fields for docs in the Error area',
runAfter: ['tags-extracted'],
init: function(config, injectables) {
injectables.value('errorNamespaces', {});
var minerrInfoPath = config.get('processing.errors.minerrInfoPath');
if ( !minerrInfoPath ) {
throw new Error('Error in configuration: Please provide a path to the minerr info file (errors.json) ' +
'in the `config.processing.errors.minerrInfoPath` property');
}
injectables.value('minerrInfo', require(minerrInfoPath));
runAfter: ['tags-extracted', 'compute-path'],
runBefore: ['extra-docs-added'],
exports: {
errorNamespaces: ['factory', function() { return {}; }],
minerrInfo: ['factory', function(config) {
var minerrInfoPath = config.get('processing.errors.minerrInfoPath');
if ( !minerrInfoPath ) {
throw new Error('Error in configuration: Please provide a path to the minerr info file (errors.json) ' +
'in the `config.processing.errors.minerrInfoPath` property');
}
return require(minerrInfoPath);
}]
},
process: function(docs, partialNames, errorNamespaces, minerrInfo) {
+9 -7
View File
@@ -3,14 +3,16 @@ var versionInfo = require('../../../lib/versions/version-info');
module.exports = {
name: 'git-data',
runBefore: ['loading-files'],
runBefore: ['reading-files'],
description: 'This processor adds information from the local git repository to the extraData injectable',
init: function(config, injectables) {
injectables.value('gitData', {
version: versionInfo.currentVersion,
versions: versionInfo.previousVersions,
info: versionInfo.gitRepoInfo
});
exports: {
gitData: ['factory', function() {
return {
version: versionInfo.currentVersion,
versions: versionInfo.previousVersions,
info: versionInfo.gitRepoInfo
};
}]
},
process: function(extraData, gitData) {
extraData.git = gitData;
+3 -5
View File
@@ -1,20 +1,18 @@
var _ = require('lodash');
var log = require('winston');
var path = require('canonical-path');
var deployment;
module.exports = {
name: 'index-page',
runAfter: ['adding-extra-docs'],
runBefore: ['extra-docs-added'],
description: 'This processor creates docs that will be rendered as the index page for the app',
init: function(config) {
deployment = config.deployment;
process: function(docs, config) {
var deployment = config.deployment;
if ( !deployment || !deployment.environments ) {
throw new Error('No deployment environments found in the config.');
}
},
process: function(docs) {
// Collect up all the areas in the docs
var areas = {};
+9 -12
View File
@@ -3,20 +3,20 @@ var log = require('winston');
var fs = require('fs');
var path = require('canonical-path');
// Keywords to ignore
var wordsToIgnore = [];
var propertiesToIgnore;
var areasToSearch;
// Keywords start with "ng:" or one of $, _ or a letter
var KEYWORD_REGEX = /^((ng:|[\$_a-z])[\w\-_]+)/;
module.exports = {
name: 'keywords',
runAfter: ['docs-processed'],
runBefore: ['adding-extra-docs'],
description: 'This processor extracts all the keywords from the document',
init: function(config) {
process: function(docs, config) {
// Keywords to ignore
var wordsToIgnore = [];
var propertiesToIgnore;
var areasToSearch;
// Keywords start with "ng:" or one of $, _ or a letter
var KEYWORD_REGEX = /^((ng:|[\$_a-z])[\w\-_]+)/;
// Load up the keywords to ignore, if specified in the config
if ( config.processing.search && config.processing.search.ignoreWordsFile ) {
@@ -34,9 +34,6 @@ module.exports = {
propertiesToIgnore = _.indexBy(config.get('processing.search.propertiesToIgnore', []));
log.debug('Properties to ignore', propertiesToIgnore);
},
process: function(docs) {
var ignoreWordsMap = _.indexBy(wordsToIgnore);
// If the title contains a name starting with ng, e.g. "ngController", then add the module name
+5 -8
View File
@@ -129,18 +129,15 @@ var navGroupMappers = {
}
};
var outputFolder;
module.exports = {
name: 'pages-data',
description: 'This plugin will create a new doc that will be rendered as an angularjs module ' +
'which will contain meta information about the pages and navigation',
runAfter: ['adding-extra-docs', 'component-groups-generate'],
runAfter: ['adding-extra-docs', 'component-groups-generate', 'compute-path'],
runBefore: ['extra-docs-added'],
init: function(config) {
outputFolder = config.rendering.outputFolder;
},
process: function(docs) {
process: function(docs, config) {
var outputFolder = config.rendering.outputFolder;
_(docs)
.filter(function(doc) { return doc.area === 'api'; })
@@ -191,7 +188,7 @@ module.exports = {
area.navGroups = navGroupMapper(pages, area);
});
// Extract a list of basic page information for mapping paths to paritals and for client side searching
// Extract a list of basic page information for mapping paths to partials and for client side searching
var pages = _(docs)
.map(function(doc) {
var page = _.pick(doc, [
+26 -29
View File
@@ -1,45 +1,20 @@
var _ = require('lodash');
var log = require('winston');
var path = require('canonical-path');
var trimIndentation = require('dgeni/lib/utils/trim-indentation');
var code = require('dgeni/lib/utils/code');
var protractorFolder;
function createProtractorDoc(example, file, env) {
var protractorDoc = {
docType: 'e2e-test',
id: 'protractorTest' + '-' + example.id,
template: 'protractorTests.template.js',
outputPath: path.join(protractorFolder, example.id, env + '_test.js'),
innerTest: file.fileContents,
pathPrefix: '.', // Hold for if we test with full jQuery
exampleId: example.id,
description: example.doc.id
};
if (env === 'jquery') {
protractorDoc.examplePath = example.outputFolder + '/index-jquery.html'
} else {
protractorDoc.examplePath = example.outputFolder + '/index.html'
}
return protractorDoc;
}
module.exports = {
name: 'protractor-generate',
description: 'Generate a protractor test file from the e2e tests in the examples',
runAfter: ['adding-extra-docs'],
runBefore: ['extra-docs-added'],
init: function(config, injectables) {
protractorFolder = config.get('rendering.protractor.outputFolder', 'ptore2e');
},
process: function(docs, examples) {
process: function(docs, examples, config) {
var protractorFolder = config.get('rendering.protractor.outputFolder', 'ptore2e');
_.forEach(examples, function(example) {
_.forEach(example.files, function(file) {
// Check if it's a Protractor test.
if (!(file.type == 'protractor')) {
if (file.type !== 'protractor') {
return;
}
@@ -48,5 +23,27 @@ module.exports = {
docs.push(createProtractorDoc(example, file, 'jqlite'));
});
});
function createProtractorDoc(example, file, env) {
var protractorDoc = {
docType: 'e2e-test',
id: 'protractorTest' + '-' + example.id,
template: 'protractorTests.template.js',
outputPath: path.join(protractorFolder, example.id, env + '_test.js'),
innerTest: file.fileContents,
pathPrefix: '.', // Hold for if we test with full jQuery
exampleId: example.id,
description: example.doc.id
};
if (env === 'jquery') {
protractorDoc.examplePath = example.outputFolder + '/index-jquery.html';
} else {
protractorDoc.examplePath = example.outputFolder + '/index.html';
}
return protractorDoc;
}
}
};
+2 -2
View File
@@ -1,9 +1,9 @@
module.exports = {
name: 'step',
transformFn: function(doc, tag) {
transforms: function(doc, tag, value) {
if ( doc.docType !== 'tutorial' ) {
throw new Error('Invalid tag, step. You should only use this tag on tutorial docs');
}
return parseInt(tag.description,10);
return parseInt(value,10);
}
};
@@ -2,7 +2,7 @@
is HTML and wrap each line in a <p> - thus breaking the HTML #}
<div>
<a ng-href="http://plnkr.co/edit/ngdoc:{$ doc.example.id $}@{{docsVersion}}?p=preview" class="btn pull-right" target="_blank">
<a ng-click="openPlunkr('{$ doc.example.outputFolder $}')" class="btn pull-right">
<i class="glyphicon glyphicon-edit">&nbsp;</i>
Edit in Plunker</a>
<div class="runnable-example"
@@ -15,7 +15,7 @@
{$ attrName $}="{$ attrValue $}"{% endfor %}>
{% code -%}
{$ file.fileContents $}
{%- endcode %}
{%- endcode %}
</div>
{% endfor %}
+3 -3
View File
@@ -6,7 +6,7 @@
Welcome to the AngularJS API docs page. These pages contain the AngularJS reference materials for version <strong ng-bind="version"></strong>.
The documentation is organized into **{@link guide/module modules}** which contain various components of an AngularJS application.
These components are {@link guide/directive directives}, {@link guide/dev_guide.services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates types}, global APIs and testing mocks.
These components are {@link guide/directive directives}, {@link guide/services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates templates}, global APIs, and testing mocks.
<div class="alert alert-info">
**Angular Namespaces `$` and `$$`**
@@ -212,7 +212,7 @@ Use ngTouch when developing for mobile browsers/devices.
{@link ngTouch#service Services / Factories}
</td>
<td>
The {@link ngTouch.$swipe $swipe} service is used to register and manage mobile DOM events.
The {@link ngTouch.$swipe $swipe} service is used to register and manage mobile DOM events.
</td>
</tr>
<tr>
@@ -252,7 +252,7 @@ Use ngSanitize to securely parse and manipulate HTML data in your application.
## {@link ngMock ngMock}
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
Use ngMock to inject and mock modules, factories, services and providers within your unit tests
<div class="alert alert-info">Include the **angular-mocks.js** file into your test runner for this to work.</div>
+1 -1
View File
@@ -8,7 +8,7 @@ but the required directive controller is not present on the current DOM element
To resolve this error ensure that there is no typo in the required controller name and that the required directive controller is present on the current element.
If the required controller is expected to be on a ancestor element, make ensure that you prefix the controller name in the `require` definition with `^`.
If the required controller is expected to be on a ancestor element, make sure that you prefix the controller name in the `require` definition with `^`.
If the required controller is optionally requested, use `?` or `^?` to specify that.
+1 -1
View File
@@ -16,7 +16,7 @@ myModule.directive('myDirective', function factory() {
return {
...
scope: {
'bind': '=localValue'
localValue: '=bind'
}
...
}
+14
View File
@@ -37,3 +37,17 @@ elements. For example:
```
<b>Hello</b> World!
```
Watch out for html comments at the beginning or end of templates, as these can cause this error as
well. Consider the following template:
```
<div class='container'>
<div class='wrapper>
...
</div> <!-- wrapper -->
</div> <!-- container -->
```
The `<!-- container -->` comment is interpreted as a second root element and causes the template to
be invalid.
+4 -3
View File
@@ -14,8 +14,9 @@ angular.module('myApp', [])
}]);
```
This code will fail with `$injector:unpr` if `myService` is not defined. Making
sure each dependency is defined will fix the problem.
The above code will fail with `$injector:unpr` if `myService` is not defined.
Making sure each dependency is defined will fix the problem, as noted below.
```
angular.module('myApp', [])
@@ -23,4 +24,4 @@ angular.module('myApp', [])
.controller('MyController', ['myService', function (myService) {
// Do something with myService
}]);
```
```
+17
View File
@@ -0,0 +1,17 @@
@ngdoc error
@name $parse:isecff
@fullName Referencing 'call', 'apply' and 'bind' Disallowed
@description
Occurs when an expression attempts to invoke Function's 'call', 'apply' or 'bind'.
Angular bans the invocation of 'call', 'apply' and 'bind' from within expressions
since access is a known way to modify the behaviour of existing functions.
To resolve this error, avoid using these methods in expressions.
Example expression that would result in this error:
```
<div>{{user.sendInfo.call({}, true)}}</div>
```
+18 -9
View File
@@ -1,18 +1,27 @@
@ngdoc error
@name $parse:isecfld
@fullName Referencing 'constructor' Field in Expression
@fullName Referencing Disallowed Field in Expression
@description
Occurs when an expression attempts to access an objects constructor field.
Occurs when an expression attempts to access one of the following fields:
AngularJS bans constructor access from within expressions since constructor
access is a known way to execute arbitrary Javascript code.
* __proto__
* __defineGetter__
* __defineSetter__
* __lookupGetter__
* __lookupSetter__
To resolve this error, avoid constructor access. As a last resort, alias
the constructor and access it through the alias instead.
AngularJS bans access to these fields from within expressions since
access is a known way to mess with native objects or
to execute arbitrary Javascript code.
Example expression that would result in this error:
To resolve this error, avoid using these fields in expressions. As a last resort,
alias their value and access them through the alias instead.
Example expressions that would result in this error:
```
<div>{{user.constructor.name}}</div>
```
<div>{{user.__proto__.hasOwnProperty = $emit}}</div>
<div>{{user.__defineGetter__('name', noop)}}</div>
```
+280 -42
View File
@@ -3,72 +3,310 @@
@fullName Action Already In Progress
@description
At any point in time there can be only one `$digest` or $apply operation in progress.
The stack trace of this error allows you to trace the origin of the currently executing $apply or $digest call.
At any point in time there can be only one `$digest` or `$apply` operation in progress. This is to
prevent very hard to detect bugs from entering your application. The stack trace of this error
allows you to trace the origin of the currently executing `$apply` or `$digest` call, which caused
the error.
`$digest` or `$apply` are processing operational states of the Scope - data-structure in Angular that provides context for models and enables model mutation observation.
## Background
Trying to reenter a `$digest` or `$apply` while one of them is already in progress is typically a sign of programming error that needs to be fixed.
Angular uses a dirty-checking digest mechanism to monitor and update values of the scope during
the processing of your application. The digest works by checking all the values that are being
watched against their previous value and running any watch handlers that have been defined for those
values that have changed.
This digest mechanism is triggered by calling `$digest` on a scope object. Normally you do not need
to trigger a digest manually, because every external action that can trigger changes in your
application, such as mouse events, timeouts or server responses, wrap the Angular application code
in a block of code that will run `$digest` when the code completes.
You wrap Angular code in a block that will be followed by a `$digest` by calling `$apply` on a scope
object. So, in pseudo-code, the process looks like this:
```
element.on('mouseup', function() {
scope.$apply(function() {
$scope.doStuff();
});
});
```
where `$apply()` looks something like:
```
$apply = function(fn) {
try {
fn();
} finally() {
$digest();
}
}
```
## Digest Phases
Angular keeps track of what phase of processing we are in, the relevant ones being `$apply` and
`$digest`. Trying to reenter a `$digest` or `$apply` while one of them is already in progress is
typically a sign of programming error that needs to be fixed. So Angular will throw this error when
that occurs.
In most situations it should be well defined whether a piece of code will be run inside an `$apply`,
in which case you should not be calling `$apply` or `$digest`, or it will be run outside, in which
case you should wrap any code that will be interacting with Angular scope or services, in a call to
`$apply`.
As an example, all Controller code should expect to be run within Angular, so it should have no need
to call `$apply` or `$digest`. Conversely, code that is being trigger directly as a call back to
some external event, from the DOM or 3rd party library, should expect that it is never called from
within Angular, and so any Angular application code that it calls should first be wrapped in a call
to $apply.
## Common Causes
Apart from simply incorrect calls to `$apply` or `$digest` there are some cases when you may get
this error through no fault of your own.
### Inconsistent API (Sync/Async)
This error is often seen when interacting with an API that is sometimes sync and sometimes async.
For example:
For example, imagine a 3rd party library that has a method which will retrieve data for us. Since it
may be making an asynchronous call to a server, it accepts a callback function, which will be called
when the data arrives.
```
function MyController() {
function MyController($scope, thirdPartyComponent) {
thirdPartyComponent.getData(function(someData) {
scope.$apply(function() {
scope.someData = someData;
$scope.$apply(function() {
$scope.someData = someData;
});
});
}
```
The controller constructor is always instantiated from within an $apply cycle, so if the third-party component called our callback synchronously, we'd be trying to enter the $apply again.
We expect that our callback will be called asynchronously, and so from outside Angular. Therefore, we
correctly wrap our application code that interacts with Angular in a call to `$apply`.
To resolve this type of issue, either fix the api to be always synchronous or asynchronous or wrap the call to the api with setTimeout call to make it always asynchronous.
The problem comes if `getData()` decides to call the callback handler synchronously; perhaps it has
the data already cached in memory and so it immediately calls the callback to return the data,
synchronously.
Since, the `MyController` constructor is always instantiated from within an `$apply` call, our
handler is trying to enter a new `$apply` block from within one.
Other situation that leads to this error is when you are trying to reuse a function to by using it as a callback for code that is called by various apis inside and outside of $apply.
This is not an ideal design choice on the part of the 3rd party library.
For example:
To resolve this type of issue, either fix the api to be always synchronous or asynchronous or force
your callback handler to always run asynchronously by using the `$timeout` service.
```
myApp.directive('myDirective', function() {
function MyController($scope, thirdPartyComponent) {
thirdPartyComponent.getData(function(someData) {
$timeout(function() {
$scope.someData = someData;
}, 0);
});
}
```
Here we have used `$timeout` to schedule the changes to the scope in a future call stack.
By providing a timeout period of 0ms, this will occur as soon as possible and `$timeout` will ensure
that the code will be called in a single `$apply` block.
### Triggering Events Programmatically
The other situation that often leads to this error is when you trigger code (such as a DOM event)
programmatically (from within Angular), which is normally called by an external trigger.
For example, consider a directive that will set focus on an input control when a value in the scope
is true:
```
myApp.directive('setFocusIf', function() {
return {
link: function($scope, $element) {
function doSomeWork() {
$scope.$apply(function() {
// do work here, and update the model
};
}
$element.on('click', doSomeWork);
doSomeWork(); // << this will throw an exception because templates are compiled within $apply
}
}
});
```
The fix for the example above looks like this:
```
myApp.directive('myDirective', function() {
return {
link: function($scope, $element) {
function doSomeWork() {
// do work here, and update the model
}
$element.on('click', function() {
$scope.$apply(doSomeWork); // <<< the $apply call was moved to the callsite that doesn't execute in $apply call already
link: function($scope, $element, $attr) {
$scope.$watch($attr.setFocusIf, function(value) {
if ( value ) { $element[0].focus(); }
});
}
};
});
```
doSomeWork();
If we applied this directive to an input which also used the `ngFocus` directive to trigger some
work when the element receives focus we will have a problem:
```
<input set-focus-if="hasFocus" ng-focus="msg='has focus'">
<button ng-click="hasFocus = true">Focus</button>
```
In this setup, there are two ways to trigger ngFocus. First from a user interaction:
* Click on the input control
* The input control gets focus
* The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to
`$apply()`
Second programmatically:
* Click the button
* The `ngClick` directive sets the value of `$scope.hasFocus` to true inside a call to `$apply`
* The `$digest` runs, which triggers the watch inside the `setFocusIf` directive
* The watch's handle runs, which gives the focus to the input
* The `ngFocus` directive is triggered, setting `$scope.msg='has focus'` from within a new call to
`$apply()`
In this second scenario, we are already inside a `$digest` when the ngFocus directive makes another
call to `$apply()`, causing this error to be thrown.
It is possible to workaround this problem by moving the call to set the focus outside of the digest,
by using `$timeOut(fn, 0, false)`, where the `false` value tells Angular not to wrap this `fn` in a
`$apply` block:
```
myApp.directive('setFocusIf', function($timeout) {
return {
link: function($scope, $element, $attr) {
$scope.$watch($attr.setFocusIf, function(value) {
if ( value ) {
$timeout(function() {
// We must reevaluate the value in case it was changed by a subsequent
// watch handler in the digest.
if ( $scope.$eval($attr.setFocusIf) ) {
$element[0].focus();
}
}, 0, false);
}
});
}
}
});
```
To learn more about Angular processing model please check out the {@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc.
## Diagnosing This Error
When you get this error it can be rather daunting to diagnose the cause of the issue. The best
course of action is to investigate the stack trace from the error. You need to look for places
where `$apply` or `$digest` have been called and find the context in which this occurred.
There should be two calls:
* The first call is the good `$apply`/`$digest` and would normally be triggered by some event near
the top of the call stack.
* The second call is the bad `$apply`/`$digest` and this is the one to investigate.
Once you have identified this call you work your way up the stack to see what the problem is.
* If the second call was made in your application code then you should look at why this code has been
called from within a `$apply`/`$digest`. It may be a simple oversight or maybe it fits with the
sync/async scenario described earlier.
* If the second call was made inside an Angular directive then it is likely that it matches the second
programmatic event trigger scenario described earlier. In this case you may need to look further up
the tree to what triggered the event in the first place.
### Example Problem
Let's look at how to investigate this error using the `setFocusIf` example from above. This example
defines a new `setFocusIf` directive that sets the focus on the element where it is defined when the
value of its attribute becomes true.
<example name="error-$rootScope-inprog" module="app">
<file name="index.html">
<button ng-click="focusInput = true">Focus</button>
<input ng-focus="count = count + 1" set-focus-if="focusInput" />
</file>
<file name="app.js">
angular.module('app', []).directive('setFocusIf', function() {
return function link($scope, $element, $attr) {
$scope.$watch($attr.setFocusIf, function(value) {
if ( value ) { $element[0].focus(); }
});
};
});
</file>
</example>
When you click on the button to cause the focus to occur we get our `$rootScope:inprog` error. The
stacktrace looks like this:
```
Error: [$rootScope:inprog]
at Error (native)
at angular.min.js:6:467
at n (angular.min.js:105:60)
at g.$get.g.$apply (angular.min.js:113:195)
at HTMLInputElement.<anonymous> (angular.min.js:198:401)
at angular.min.js:32:32
at Array.forEach (native)
at q (angular.min.js:7:295)
at HTMLInputElement.c (angular.min.js:32:14)
at Object.fn (app.js:12:38) angular.js:10111
(anonymous function) angular.js:10111
$get angular.js:7412
$get.g.$apply angular.js:12738 <--- $apply
(anonymous function) angular.js:19833 <--- called here
(anonymous function) angular.js:2890
q angular.js:320
c angular.js:2889
(anonymous function) app.js:12
$get.g.$digest angular.js:12469
$get.g.$apply angular.js:12742 <--- $apply
(anonymous function) angular.js:19833 <--- called here
(anonymous function) angular.js:2890
q angular.js:320
```
We can see (even though the Angular code is minified) that there were two calls to `$apply`, first
on line `19833`, then on line `12738` of `angular.js`.
It is this second call that caused the error. If we look at the angular.js code, we can see that
this call is made by an Angular directive.
```
var ngEventDirectives = {};
forEach(
'click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste'.split(' '),
function(name) {
var directiveName = directiveNormalize('ng-' + name);
ngEventDirectives[directiveName] = ['$parse', function($parse) {
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function(scope, element, attr) {
element.on(lowercase(name), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
};
}];
}
);
```
It is not possible to tell which from the stack trace, but we happen to know in this case that it is
the `ngFocus` directive.
Now look up the stack to see that our application code is only entered once in `app.js` at line `12`.
This is where our problem is:
```
10: link: function($scope, $element, $attr) {
11: $scope.$watch($attr.setFocusIf, function(value) {
12: if ( value ) { $element[0].focus(); } <---- This is the source of the problem
13: });
14: }
```
We can now see that the second `$apply` was caused by us programmatically triggering a DOM event
(i.e. focus) to occur. We must fix this by moving the code outside of the $apply block using
`$timeout` as described above.
## Further Reading
To learn more about Angular processing model please check out the
{@link guide/concepts concepts doc} as well as the {@link ng.$rootScope.Scope api} doc.
+1
View File
@@ -13,3 +13,4 @@ Angular template from a URL requires that the URL is one considered safe for loa
This helps prevent XSS and other security issues. Read more at {@link
api/ng.$sce Strict Contextual Escaping (SCE)}
You may want to include the ngSanitize module to use the automatic sanitizing.
+22 -34
View File
@@ -49,7 +49,7 @@ changes to $location are reflected into the browser address bar.
<tr>
<td class="head">integration with angular application life-cycle</td>
<td>none</td>
<td>knows about all internal life-cycle phases, integrates with $watch, ...</td>
<td>knows about all internal life-cycle phases, integrates with {@link ng.$rootScope.Scope#$watch $watch}, ...</td>
</tr>
<tr>
@@ -60,7 +60,7 @@ changes to $location are reflected into the browser address bar.
<tr>
<td class="head">aware of docroot/context from which the application is loaded</td>
<td>no - window.location.path returns "/docroot/actual/path"</td>
<td>no - window.location.pathname returns "/docroot/actual/path"</td>
<td>yes - $location.path() returns "/actual/path"</td>
</tr>
@@ -237,20 +237,6 @@ it('should show example', inject(
));
```
### Crawling your app
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
your document:
```html
<meta name="fragment" content="!" />
```
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
see [Making AJAX Applications
Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
## HTML5 mode
In HTML5 mode, the `$location` service getters and setters interact with the browser URL address
@@ -344,20 +330,6 @@ are not prefixed with `.` and will not be intercepted by the `otherwise` rule in
Using this mode requires URL rewriting on server side, basically you have to rewrite all your links
to entry point of your application (e.g. index.html)
### Crawling your app
If you want your AJAX application to be indexed by web crawlers, you will need to add the following
meta tag to the HEAD section of your document:
```html
<meta name="fragment" content="!" />
```
This statement causes a crawler to request links with an empty `_escaped_fragment_` parameter so that
your server can recognize the crawler and serve it HTML snapshots. For more information about this
technique, see [Making AJAX
Applications Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
### Relative links
Be sure to check all relative links, images, scripts etc. You must either specify the url base in
@@ -510,10 +482,12 @@ use a lower level API, {@link ng.$window $window.location.href}.
## Using $location outside of the scope life-cycle
`$location` knows about Angular's {@link ng.$rootScope.Scope scope} life-cycle. When a URL changes in
the browser it updates the `$location` and calls `$apply` so that all $watchers / $observers are
notified.
the browser it updates the `$location` and calls `$apply` so that all
{@link ng.$rootScope.Scope#$watch $watchers} /
{@link ng.$compile.directive.Attributes#$observe $observers} are notified.
When you change the `$location` inside the `$digest` phase everything is ok; `$location` will
propagate this change into browser and will notify all the $watchers / $observers.
propagate this change into browser and will notify all the {@link ng.$rootScope.Scope#$watch $watchers} /
{@link ng.$compile.directive.Attributes#$observe $observers}.
When you want to change the `$location` from outside Angular (for example, through a DOM Event or
during testing) - you must call `$apply` to propagate the changes.
@@ -525,6 +499,20 @@ forward slash if it is missing.
Note that the `!` prefix in the hashbang mode is not part of `$location.path()`; it is actually
hashPrefix.
## Crawling your app
To allow indexing of your AJAX application, you have to add special meta tag in the head section of
your document:
```html
<meta name="fragment" content="!" />
```
This will cause crawler bot to request links with `_escaped_fragment_` param so that your server
can recognize the crawler and serve a HTML snapshots. For more information about this technique,
see [Making AJAX Applications
Crawlable](http://code.google.com/web/ajaxcrawling/docs/specification.html).
# Testing with the $location service
@@ -632,7 +620,7 @@ then uses the information it obtains to compose hashbang URLs (such as
The Angular's compiler currently does not support two-way binding for methods (see [issue](https://github.com/angular/angular.js/issues/404)). If you should require two-way binding
to the $location object (using {@link input[text] ngModel} directive on an input
field), you will need to specify an extra model property (e.g. `locationPath`) with two watchers
field), you will need to specify an extra model property (e.g. `locationPath`) with two {@link ng.$rootScope.Scope#$watch $watchers}
which push $location updates in both directions. For example:
<example>
<file name="index.html">
+12 -9
View File
@@ -251,15 +251,18 @@ Although the CSS is a little different then what we saw before, the idea is the
A handful of common AngularJS directives support and trigger animation hooks whenever any major event occurs during its life cycle.
The table below explains in detail which animation events are triggered
| Directive | Supported Animations |
|-------------------------------------------------------------------------------------|------------------------------------------|
| {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave, and move |
| {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
| {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
| {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
| {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
| {@link ng.directive:ngClass#usage_animations ngClass or &#123;&#123;class&#125;&#125;} | add and remove |
| {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
-| Directive | Supported Animations |
-|-----------------------------------------------------------------|--------------------------------------------------------------------------|
-| {@link ng.directive:ngRepeat#usage_animations ngRepeat} | enter, leave and move |
-| {@link ngRoute.directive:ngView#usage_animations ngView} | enter and leave |
-| {@link ng.directive:ngInclude#usage_animations ngInclude} | enter and leave |
-| {@link ng.directive:ngSwitch#usage_animations ngSwitch} | enter and leave |
-| {@link ng.directive:ngIf#usage_animations ngIf} | enter and leave |
-| {@link ng.directive:ngClass#usage_animations ngClass} | add and remove |
-| {@link ng.directive:ngShow#usage_animations ngShow & ngHide} | add and remove (the ng-hide class value) |
-| {@link ng.directive:form#usage_animations form} | add and remove (dirty, pristine, valid, invalid & all other validations) |
-| {@link ng.directive:ngModel#usage_animations ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
For a full breakdown of the steps involved during each animation event, refer to the {@link ngAnimate.$animate API docs}.
+1 -1
View File
@@ -91,7 +91,7 @@ Here is an example of manually initializing Angular:
<html>
<body>
Hello {{'World'}}!
<script src="http://code.angularjs.org/angular.js"></script>
<script src="http://code.angularjs.org/snapshot/angular.js"></script>
<script>
angular.module('myApp', [])
+2 -2
View File
@@ -226,7 +226,7 @@ moved to the compile function for performance reasons.
To understand, let's look at a real-world example with `ngRepeat`:
```html
Hello {{user}}, you have these actions:
Hello {{user.name}}, you have these actions:
<ul>
<li ng-repeat="action in user.actions">
{{action.description}}
@@ -236,7 +236,7 @@ Hello {{user}}, you have these actions:
When the above example is compiled, the compiler visits every node and looks for directives.
`{{user}}` matches the {@link ng.$interpolate interpolation directive}
`{{user.name}}` matches the {@link ng.$interpolate interpolation directive}
and `ng-repeat` matches the {@link ng.directive:ngRepeat `ngRepeat` directive}.
But {@link ng.directive:ngRepeat ngRepeat} has a dilemma.
+4 -4
View File
@@ -11,7 +11,7 @@ For a more in-depth explanation, see the {@link tutorial/ tutorial}.
|------------------|------------------------------------------|
|{@link concepts#template Template} | HTML with additional markup |
|{@link concepts#directive Directives} | extend HTML with custom attributes and elements |
|{@link concepts#model Model} | the data that is shown to the user and with which the user interacts |
|{@link concepts#model Model} | the data shown to the user in the view and with which the user interacts |
|{@link concepts#scope Scope} | context where the model is stored so that controllers, directives and expressions can access it |
|{@link concepts#expression Expressions} | access variables and functions from the scope |
|{@link concepts#compiler Compiler} | parses the template and instantiates directives and expressions |
@@ -19,9 +19,9 @@ For a more in-depth explanation, see the {@link tutorial/ tutorial}.
|{@link concepts#view View} | what the user sees (the DOM) |
|{@link concepts#databinding Data Binding} | sync data between the model and the view |
|{@link concepts#controller Controller} | the business logic behind views |
|{@link concepts#di Dependency Injection} | Creates and wires objects / functions |
|{@link concepts#di Dependency Injection} | Creates and wires objects and functions |
|{@link concepts#injector Injector} | dependency injection container |
|{@link concepts#module Module} | configures the Injector |
|{@link concepts#module Module} | a container for the different parts of an app including controllers, services, filters, directives which configures the Injector |
|{@link concepts#service Service} | reusable business logic independent of views |
@@ -254,7 +254,7 @@ Let's refactor our example and move the currency conversion into a service in an
What changed?
We moved the `convertCurrency` function and the definition of the existing currencies
into the new file `finance.js`. But how does the controller
into the new file `finance2.js`. But how does the controller
get a hold of the now separated function?
This is where <a name="di">"{@link di Dependency Injection}"</a> comes into play.
+3 -3
View File
@@ -243,7 +243,7 @@ more information about scope inheritance.
}]);
myApp.controller('GrandChildController', ['$scope', function($scope) {
$scope.timeOfDay = 'evening';
$scope.name = 'Gingerbreak Baby';
$scope.name = 'Gingerbread Baby';
}]);
</file>
</example>
@@ -273,8 +273,8 @@ involves injecting the {@link ng.$rootScope $rootScope} and {@link ng.$controlle
myApp.controller('MyController', function($scope) {
$scope.spices = [{"name":"pasilla", "spiciness":"mild"},
{"name":"jalapeno", "spiceiness":"hot hot hot!"},
{"name":"habanero", "spiceness":"LAVA HOT!!"}];
{"name":"jalapeno", "spiciness":"hot hot hot!"},
{"name":"habanero", "spiciness":"LAVA HOT!!"}];
$scope.spice = "habanero";
});
```
+1 -1
View File
@@ -8,7 +8,7 @@ Angular sets these CSS classes. It is up to your application to provide useful s
# CSS classes used by angular
* `ng-scope`
- **Usage:** angular applies this class to any element that where a new {@link ng.$rootScope.Scope scope}
- **Usage:** angular applies this class to any element for which a new {@link api/ng.$rootScope.Scope scope}
is defined. (see {@link guide/scope scope} guide for more information about scopes)
* `ng-binding`
+2 -2
View File
@@ -9,7 +9,7 @@ When the model changes, the view reflects the change, and vice versa.
## Data Binding in Classical Template Systems
<img class="right" src="img/One_Way_Data_Binding.png"/>
<img class="right" src="img/One_Way_Data_Binding.png"/><br />
Most templating systems bind data in only one direction: they merge template and model components
together into a view. After the merge occurs, changes to the model
or related sections of the view are NOT automatically reflected in the view. Worse, any changes
@@ -18,7 +18,7 @@ to write code that constantly syncs the view with the model and the model with t
## Data Binding in Angular Templates
<img class="right" src="img/Two_Way_Data_Binding.png"/>
<img class="right" src="img/Two_Way_Data_Binding.png"/><br />
Angular templates work differently. First the template (which is the uncompiled HTML along with
any additional markup or directives) is compiled on the browser. The compilation step produces a
live view. Any changes to the view are immediately reflected in the model, and any changes in
+135 -83
View File
@@ -4,30 +4,29 @@
# Dependency Injection
Dependency Injection (DI) is a software design pattern that deals with how code gets hold of its
dependencies.
Dependency Injection (DI) is a software design pattern that deals with how components get hold of
their dependencies.
The Angular injector subsystem is in charge of service instantiation, resolution
of dependencies, and provision of dependencies to components as requested.
The Angular injector subsystem is in charge of creating components, resolving their dependencies,
and providing them to other components as requested.
For in-depth discussion about DI, see
[Dependency Injection](http://en.wikipedia.org/wiki/Dependency_injection) at Wikipedia,
[Inversion of Control](http://martinfowler.com/articles/injection.html) by Martin Fowler,
or read about DI in your favorite software design pattern book.
## DI in a nutshell
## DI in a Nutshell
There are only three ways an object or a function can get a hold of its dependencies:
1. The dependency can be created, typically using the `new` operator.
2. The dependency can be looked up by referring to a global variable.
3. The dependency can be passed in to where it is needed.
There are only three ways a component (object or function) can get a hold of its dependencies:
1. The component can create the dependency, typically using the `new` operator.
2. The component can look up the dependency, by referring to a global variable.
3. The component can have the dependency passed to it where it is needed.
The first two options of creating or looking up dependencies are not optimal because they hard
code the dependency. This makes it difficult, if not impossible, to modify the dependencies.
This is especially problematic in tests, where it is often desirable to provide mock dependencies
for test isolation.
code the dependency to the component. This makes it difficult, if not impossible, to modify the
dependencies. This is especially problematic in tests, where it is often desirable to provide mock
dependencies for test isolation.
The third option is the most viable, since it removes the responsibility of locating the
dependency from the component. The dependency is simply handed to the component.
@@ -42,8 +41,8 @@ SomeClass.prototype.doSomething = function(name) {
}
```
In the above example `SomeClass` is not concerned with locating the `greeter` dependency, it
is simply handed the `greeter` at runtime.
In the above example `SomeClass` is not concerned with creating or locating the `greeter`
dependency, it is simply handed the `greeter` when it is instantiated.
This is desirable, but it puts the responsibility of getting hold of the dependency on the
code that constructs `SomeClass`.
@@ -51,74 +50,90 @@ code that constructs `SomeClass`.
<img class="pull-right" style="padding-left: 3em; padding-bottom: 1em;" src="img/guide/concepts-module-injector.png">
To manage the responsibility of dependency creation, each Angular application has an {@link
angular.injector injector}. The injector is a service locator that is responsible for
angular.injector injector}. The injector is a
[service locator](http://en.wikipedia.org/wiki/Service_locator_pattern) that is responsible for
construction and lookup of dependencies.
Here is an example of using the injector service:
```js
// Provide the wiring information in a module
angular.module('myModule', []).
var myModule = angular.module('myModule', []);
```
// Teach the injector how to build a 'greeter'
// Notice that greeter itself is dependent on '$window'
factory('greeter', function($window) {
// This is a factory function, and is responsible for
// creating the 'greet' service.
return {
greet: function(text) {
$window.alert(text);
}
};
});
Teach the injector how to build a `greeter` service. Notice that `greeter` is dependent on the
`$window` service. The `greeter` service is an object that contains a `greet` method.
// New injector is created from the module.
// (This is usually done automatically by angular bootstrap)
```js
myModule.factory('greeter', function($window) {
return {
greet: function(text) {
$window.alert(text);
}
};
});
```
Create a new injector that can provide components defined in our `myModule` module and request our
`greeter` service from the injector. (This is usually done automatically by angular bootstrap).
```js
var injector = angular.injector(['myModule', 'ng']);
// Request any dependency from the injector
var greeter = injector.get('greeter');
```
Asking for dependencies solves the issue of hard coding, but it also means that the injector needs
to be passed throughout the application. Passing the injector breaks the [Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we turn the
dependency lookup responsibility to the injector by declaring the dependencies as in this example:
to be passed throughout the application. Passing the injector breaks the
[Law of Demeter](http://en.wikipedia.org/wiki/Law_of_Demeter). To remedy this, we use a declarative
notation in our HTML templates, to hand the responsibility of creating components over to the
injector, as in this example:
```html
<!-- Given this HTML -->
<div ng-controller="MyController">
<button ng-click="sayHello()">Hello</button>
</div>
```
```js
// And this controller definition
function MyController($scope, greeter) {
$scope.sayHello = function() {
greeter.greet('Hello World');
};
}
```
// The 'ng-controller' directive does this behind the scenes
When Angular compiles the HTML, it processes the `ng-controller` directive, which in turn
asks the injector to create an instance of the controller and its dependencies.
```js
injector.instantiate(MyController);
```
Notice that by having the `ng-controller` instantiate the class, it can satisfy all of the
dependencies of `MyController` without the controller ever knowing about the injector. This is
the best outcome. The application code simply asks for the dependencies it needs, without having to
deal with the injector. This setup does not break the Law of Demeter.
This is all done behinds the scenes. Notice that by having the `ng-controller` ask the injector to
instantiate the class, it can satisfy all of the dependencies of `MyController` without the
controller ever knowing about the injector.
This is the best outcome. The application code simply declares the dependencies it needs, without
having to deal with the injector. This setup does not break the Law of Demeter.
## Dependency Annotation
How does the injector know what service needs to be injected?
**How does the injector know what components need to be injected?**
The application developer needs to provide annotation information that the injector uses in order
to resolve the dependencies. Throughout Angular, certain API functions are invoked using the
injector, as per the API documentation. The injector needs to know what services to inject into
the function. Below are three equivalent ways of annotating your code with service name
information. These can be used interchangeably as you see fit and are equivalent.
the function. There are three equivalent ways of annotating your code with service name
information:
### Inferring Dependencies
- Implicitly from the function parameter names
- Using the `$inject` property annotation
- Using the inline array annotation
These can be used interchangeably as you see fit and are equivalent.
### Implicit Dependencies
The simplest way to get hold of the dependencies, is to assume that the function parameter names
are the names of the dependencies.
@@ -134,11 +149,12 @@ function declaration and extracting the parameter names. In the above example `$
`greeter` are two services which need to be injected into the function.
While straightforward, this method will not work with JavaScript minifiers/obfuscators as they
rename the method parameter names. This makes this way of annotating only useful for [pretotyping](http://www.pretotyping.org/), and demo applications.
rename the method parameter names. This makes this way of annotating only useful for
[pretotyping](http://www.pretotyping.org/), and demo applications.
### `$inject` Annotation
### `$inject` Property Annotation
To allow the minifers to rename the function parameters and still be able to inject right services
To allow the minifiers to rename the function parameters and still be able to inject right services,
the function needs to be annotated with the `$inject` property. The `$inject` property is an array
of service names to inject.
@@ -149,18 +165,18 @@ var MyController = function(renamed$scope, renamedGreeter) {
MyController['$inject'] = ['$scope', 'greeter'];
```
In this scenario the ordering of the values in the '$inject' array must match the ordering of the arguments to inject.
Using above code snippet as an example, '$scope' will be injected into 'renamed$scope' and 'greeter' into 'renamedGreeter'.
Care must be taken that the `$inject` annotation is kept in sync with the actual arguments in the
function declaration.
In this scenario the ordering of the values in the `$inject` array must match the ordering of the
arguments to inject. Using above code snippet as an example, `$scope` will be injected into
`renamed$scope` and `greeter` into `renamedGreeter`. Care must be taken that the `$inject`
annotation is kept in sync with the actual arguments in the function declaration.
This method of annotation is useful for controller declarations since it assigns the annotation
information with the function.
### Inline Annotation
### Inline Array Annotation
Sometimes using the `$inject` annotation style is not convenient such as when annotating
directives.
directives or services defined inline by a factory function.
For example:
@@ -190,18 +206,69 @@ someModule.factory('greeter', ['$window', function(renamed$window) {
}]);
```
Here, instead of simply providing the factory function, we pass an array, whose elements consist of
a list of strings (the names of the dependencies) followed by the function itself.
Keep in mind that all of the annotation styles are equivalent and can be used anywhere in Angular
where injection is supported.
## Where can I use DI?
## Where Can I Use DI?
DI is pervasive throughout Angular. You can use it in controllers, services, directives, filters,
animations, and `run` and `config` blocks.
DI is pervasive throughout Angular. You can use it when defining components or when providing `run`
and `config` blocks for a module.
### DI in controllers
- Components such as services, directives, filters and animations are defined by an injectable factory
method or constructor function. These components can be injected with "service" components as
dependencies.
Controllers are classes which are responsible for application behavior. The recommended way of
declaring controllers is using the array notation:
- The `run` and `config` methods accept a function, which can also be injected with "service"
components as dependencies.
- Controllers are defined by a constructor function, which can be injected with any of the "service"
components as dependencies, but they can also be provided with special dependencies. See "DI in
Controllers" below.
### Factory Methods
Factory methods are responsible for creating most objects in Angular. Examples are directives,
services, and filters. The factory methods are registered with the module, and the recommended way
of declaring factories is:
```js
angular.module('myModule', [])
.factory('serviceId', ['depService', function(depService) {
...
}])
.directive('directiveName', ['depService', function(depService) {
...
}])
.filter('filterName', ['depService', function(depService) {
...
}]);
```
### Module Methods
We can specify functions to run at configuration and run time for a module by calling the `run` and
`config` methods. These functions are injectable with dependencies just like the factory functions
above.
```js
angular.module('myModule', [])
.config(['depProvider', function(depProvider){
...
}])
.run(['depService', function(depService) {
...
}]);
```
### Controllers
Controllers are "classes" or "constructor functions" that are responsible for providing the
application behavior that supports the declarative markup in the template. The recommended way of
declaring Controllers is using the array notation:
```js
someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
@@ -215,28 +282,13 @@ someModule.controller('MyController', ['$scope', 'dep1', 'dep2', function($scope
This avoids the creation of global functions for controllers and also protects against minification.
Controllers are special in that, unlike services, there can be many instances of them in the
application. For example, there would be one instance for every `ng-controller` directive in the template.
### Factory methods
Moreover, additional dependencies are made available to Controllers:
Factory methods are responsible for creating most objects in Angular. Examples are directives,
services, and filters. The factory methods are registered with the module, and the recommended way
of declaring factories is:
```js
angular.module('myModule', []).
config(['depProvider', function(depProvider){
...
}]).
factory('serviceId', ['depService', function(depService) {
...
}]).
directive('directiveName', ['depService', function(depService) {
...
}]).
filter('filterName', ['depService', function(depService) {
...
}]).
run(['depService', function(depService) {
...
}]);
```
* {@link scope `$scope`}: Controllers are always associated with a point in the DOM and so are provided with
access to the {@link scope scope} at that point. Other components, such as services only have access to the
singleton {@link $rootScope} service.
* {@link $route} resolves: If a controller is instantiated as part of a route, then any values that
are resolved as part of the route are made available for injection into the controller.
+10 -5
View File
@@ -18,7 +18,7 @@ how to implement them.
## What are Directives?
At a high level, directives are markers on a DOM element (such as an attribute, element
name, or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
name, comment or CSS class) that tell AngularJS's **HTML compiler** ({@link ng.$compile `$compile`}) to
attach a specified behavior to that DOM element or even transform the DOM element and its children.
Angular comes with a set of these directives built-in, like `ngBind`, `ngModel`, and `ngView`.
@@ -282,7 +282,7 @@ using `templateUrl` instead:
Great! But what if we wanted to have our directive match the tag name `<my-customer>` instead?
If we simply put a `<my-customer>` element into the HTML, it doesn't work.
<div class="alert alert-waring">
<div class="alert alert-warning">
**Note:** When you create a directive, it is restricted to attribute only by default. In order to
create directives that are triggered by element or class name, you need to use the `restrict` option.
</div>
@@ -510,8 +510,8 @@ that you explicitly pass in.
<div class="alert alert-warning">
**Note:** Normally, a scope prototypically inherits from its parent. An isolated scope does not.
See the {@link guide/directive#isolating-the-scope-of-a-directive
"Isolating the Scope of a Directive"} section for more information about isolate scopes.
See the {@link api/ng/service/$compile#directive-definition-object
"Directive Definition Object - scope"} section for more information about isolate scopes.
</div>
<div class="alert alert-success">
@@ -537,7 +537,7 @@ where:
In our `link` function, we want to update the displayed time once a second, or whenever a user
changes the time formatting string that our directive binds to. We will use the `$interval` service
to call a handler on a regular basis. This is easier than using `$timeout` but also works better with
end 2 end testing, where we want to ensure that all $timeouts have completed before completing the test.
end-to-end testing, where we want to ensure that all $timeouts have completed before completing the test.
We also want to remove the `$interval` if the directive is deleted so we don't introduce a memory leak.
<example module="docsTimeDirective">
@@ -901,6 +901,11 @@ So where does this `myTabs` controller come from? Directives can specify control
the unsurprisingly named `controller` option. As you can see, the `myTabs` directive uses this
option. Just like `ngController`, this option attaches a controller to the template of the directive.
If it is necessary to reference the controller or any functions bound to the controller's scope in
the template, you can use the option `controllerAs` to specify the name of the controller as an alias.
The directive needs to define a scope for this configuration to be used. This is particularly useful
in the case when the directive is used as a component.
Looking back at `myPane`'s definition, notice the last argument in its `link` function: `tabsCtrl`.
When a directive requires a controller, it receives that controller as the fourth argument of its
`link` function. Taking advantage of this, `myPane` can call the `addPane` function of `myTabs`.
+53 -289
View File
@@ -3,319 +3,83 @@
@name E2E Testing
@description
# E2E Testing
<div class="alert alert-danger">
**Note:** Angular Scenario Runner is depricated. If you're starting a new Angular project,
consider using [Protractor](https://github.com/angular/protractor).
**Note:** In the past, end to end testing could be done with a deprecated tool called
[Angular Scenario Runner](http://code.angularjs.org/1.2.16/docs/guide/e2e-testing). That tool
is now in maintenance mode.
</div>
# E2E Testing with the Angular Scenario Runner
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
verify the correctness of new features, catch bugs and notice regressions.
verify the correctness of new features, catch bugs and notice regressions. End to end tests
are the first line of defense for catching bugs, but sometimes issues come up with integration
between components which can't be captured in a unit test. End to end tests are made to find
these problems.
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
that will help you verify the health of your Angular application.
We have built [Protractor](https://github.com/angular/protractor), an end
to end test runner which simulates user interactions that will help you verify the health of your
Angular application.
## Overview
## Using Protractor
You write scenario tests in JavaScript. These tests describe how your application should behave
given a certain interaction in a specific state.
Protractor is a [Node.js](http://nodejs.org) program, and runs end to end tests that are also
written in JavaScript and run with node. Protractor uses [WebDriver](https://code.google.com/p/selenium/wiki/GettingStarted)
to control browsers and simulate user actions.
A scenario is comprised of one or more `it` blocks that describe the requirements of your
application. `it` blocks are made of **commands** and **expectations**. Commands tell the Runner
to do something with the application such as navigate to a page or click on a button. Expectations
tell the Runner to assert something about the application's state, such as the value of a field or
the current URL.
For more information on Protractor, view [getting started](https://github.com/angular/protractor/blob/master/docs/getting-started.md)
or the [api docs](https://github.com/angular/protractor/blob/master/docs/api.md).
Protractor uses [Jasmine](http://jasmine.github.io/1.3/introduction.html) for its test syntax.
As in unit testing, a test file is comprised of one or
more `it` blocks that describe the requirements of your application. `it` blocks are made of
**commands** and **expectations**. Commands tell Protractor to do something with the application
such as navigate to a page or click on a button. Expectations tell Protractor to assert something
about the application's state, such as the value of a field or the current URL.
If any expectation within an `it` block fails, the runner marks the `it` as "failed" and continues
on to the next block.
Scenarios may also have `beforeEach` and `afterEach` blocks, which will be run before or after
Test files may also have `beforeEach` and `afterEach` blocks, which will be run before or after
each `it` block regardless of whether the block passes or fails.
<img src="img/guide/scenario_runner.png">
In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
In addition to the above elements, tests may also contain helper functions to avoid duplicating
code in the `it` blocks.
Here is an example of a simple scenario:
Here is an example of a simple test:
```js
describe('Buzz Client', function() {
it('should filter results', function() {
input('user').enter('jacksparrow');
element(':button').click();
expect(repeater('ul li').count()).toEqual(10);
input('filterText').enter('Bees');
expect(repeater('ul li').count()).toEqual(1);
});
describe('TODO list', function() {
it('should filter results', function() {
// Find the element with ng-model="user" and type "jacksparrow" into it
element(by.model('user')).sendKeys('jacksparrow');
// Find the first (and only) button on the page and click it
element(by.css(':button')).click();
// Verify that there are 10 tasks
expect(element.all(by.repeater('task in tasks')).count()).toEqual(10);
// Enter 'groceries' into the element with ng-model="filterText"
element(by.model('filterText')).sendKeys('groceries');
// Verify that now there is only one item in the task list
expect(element.all(by.repeater('task in tasks')).count()).toEqual(1);
});
});
```
Note that
[`input('user')`](https://github.com/angular/angular.js/blob/master/docs/content/guide/dev_guide.e2e-testing.ngdoc#L119)
finds the `<input>` element with `ng-model="user"` not `name="user"`.
This scenario describes the requirements of a Buzz Client, specifically, that it should be able to
filter the stream of the user. It starts by entering a value in the input field with ng-model="user", clicking
the only button on the page, and then it verifies that there are 10 items listed. It then enters
'Bees' in the input field with ng-model='filterText' and verifies that the list is reduced to a single item.
The API section below lists the available commands and expectations for the Runner.
## API
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/dsl.js
### `pause()`
Pauses the execution of the tests until you call `resume()` in the console (or click the resume
link in the Runner UI).
### `sleep(seconds)`
Pauses the execution of the tests for the specified number of `seconds`.
### `browser().navigateTo(url)`
Loads the `url` into the test frame.
### `browser().navigateTo(url, fn)`
Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test
output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
write the test).
### `browser().reload()`
Refreshes the currently loaded page in the test frame.
### `browser().window().href()`
Returns the window.location.href of the currently loaded page in the test frame.
### `browser().window().path()`
Returns the window.location.pathname of the currently loaded page in the test frame.
### `browser().window().search()`
Returns the window.location.search of the currently loaded page in the test frame.
### `browser().window().hash()`
Returns the window.location.hash (without `#`) of the currently loaded page in the test frame.
### `browser().location().url()`
Returns the {@link ng.$location $location.url()} of the currently loaded page in
the test frame.
### `browser().location().path()`
Returns the {@link ng.$location $location.path()} of the currently loaded page in
the test frame.
### `browser().location().search()`
Returns the {@link ng.$location $location.search()} of the currently loaded page
in the test frame.
### `browser().location().hash()`
Returns the {@link ng.$location $location.hash()} of the currently loaded page in
the test frame.
### `expect(future).{matcher}`
Asserts the value of the given `future` satisfies the `matcher`. All API statements return a
`future` object, which get a `value` assigned after they are executed. Matchers are defined using
`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example:
`expect(browser().location().href()).toEqual('http://www.google.com')`. Available matchers
are presented further down this document.
### `expect(future).not().{matcher}`
Asserts the value of the given `future` satisfies the negation of the `matcher`.
### `using(selector, label)`
Scopes the next DSL element selection.
### `binding(name)`
Returns the value of the first binding matching the given `name`.
### `input(name).enter(value)`
Enters the given `value` in the text field with the corresponding ng-model `name`.
### `input(name).check()`
Checks/unchecks the checkbox with the corresponding ng-model `name`.
### `input(name).select(value)`
Selects the given `value` in the radio button with the corresponding ng-model `name`.
### `input(name).val()`
Returns the current value of an input field with the corresponding ng-model `name`.
### `repeater(selector, label).count()`
Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
used for test output.
### `repeater(selector, label).row(index)`
Returns an array with the bindings in the row at the given `index` in the repeater matching the
given jQuery `selector`. The `label` is used for test output.
### `repeater(selector, label).column(binding)`
Returns an array with the values in the column with the given `binding` in the repeater matching
the given jQuery `selector`. The `label` is used for test output.
### `select(name).option(value)`
Picks the option with the given `value` on the select with the given ng-model `name`.
### `select(name).options(value1, value2...)`
Picks the options with the given `values` on the multi select with the given ng-model `name`.
### `element(selector, label).count()`
Returns the number of elements that match the given jQuery `selector`. The `label` is used for test
output.
### `element(selector, label).click()`
Clicks on the element matching the given jQuery `selector`. The `label` is used for test output.
### `element(selector, label).query(fn)`
Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that
match the given jQuery `selector` and `done` is a function that is called at the end of the `fn`
function. The `label` is used for test output.
### `element(selector, label).{method}()`
Returns the result of calling `method` on the element matching the given jQuery `selector`, where
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
`scrollTop`, `offset`. The `label` is used for test output.
### `element(selector, label).{method}(value)`
Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
`scrollTop`, `offset`. The `label` is used for test output.
### `element(selector, label).{method}(key)`
Returns the result of calling `method` passing in `key` on the element matching the given jQuery
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
`label` is used for test output.
### `element(selector, label).{method}(key, value)`
Executes the `method` passing in `key` and `value` on the element matching the given jQuery
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
`label` is used for test output.
## Matchers
Matchers are used in combination with the `expect(...)` function as described above and can
be negated with `not()`. For instance: `expect(element('h1').text()).not().toEqual('Error')`.
Source: https://github.com/angular/angular.js/blob/master/src/ngScenario/matchers.js
```js
// value and Object comparison following the rules of angular.equals().
expect(value).toEqual(value)
// a simpler value comparison using ===
expect(value).toBe(value)
// checks that the value is defined by checking its type.
expect(value).toBeDefined()
// the following two matchers are using JavaScript's standard truthiness rules
expect(value).toBeTruthy()
expect(value).toBeFalsy()
// verify that the value matches the given regular expression. The regular
// expression may be passed in form of a string or a regular expression
// object.
expect(value).toMatch(expectedRegExp)
// a check for null using ===
expect(value).toBeNull()
// Array.indexOf(...) is used internally to check whether the element is
// contained within the array.
expect(value).toContain(expected)
// number comparison using < and >
expect(value).toBeLessThan(expected)
expect(value).toBeGreaterThan(expected)
```
This test describes the requirements of a ToDo list, specifically, that it should be able to
filter the list of items.
## Example
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples.
### Conditional actions with element(...).query(fn)
E2E testing with angular scenario is highly asynchronous and hides a lot of complexity by
queueing actions and expectations that can handle futures. From time to time, you might need
conditional assertions or element selection. Even though you should generally try to avoid this
(as it is can be sign for unstable tests), you can add conditional behavior with
`element(...).query(fn)`. The following code listing shows how this function can be used to delete
added entries (where an entry is some domain object) using the application's web interface.
Imagine the application to be structured into two views:
1. *Overview view* which lists all the added entries in a table and
2. a *detail view* which shows the entries' details and contains a delete button. When clicking the
delete button, the user is redirected back to the *overview page*.
```js
beforeEach(function () {
var deleteEntry = function () {
browser().navigateTo('/entries');
// we need to select the <tbody> element as it might be the case that there
// are no entries (and therefore no rows). When the selector does not
// result in a match, the test would be marked as a failure.
element('table tbody').query(function (tbody, done) {
// ngScenario gives us a jQuery lite wrapped element. We call the
// `children()` function to retrieve the table body's rows
var children = tbody.children();
if (children.length > 0) {
// if there is at least one entry in the table, click on the link to
// the entry's detail view
element('table tbody a').click();
// and, after a route change, click the delete button
element('.btn-danger').click();
}
// if there is more than one entry shown in the table, queue another
// delete action.
if (children.length > 1) {
deleteEntry();
}
// remember to call `done()` so that ngScenario can continue
// test execution.
done();
});
};
// start deleting entries
deleteEntry();
});
```
In order to understand what is happening, we should emphasize that ngScenario calls are not
immediately executed, but queued (in ngScenario terms, we would be talking about adding
future actions). If we had only one entry in our table, then the following future actions
would be queued:
```js
// delete entry 1
browser().navigateTo('/entries');
element('table tbody').query(function (tbody, done) { ... });
element('table tbody a');
element('.btn-danger').click();
```
For two entries, ngScenario would have to work on the following queue:
```js
// delete entry 1
browser().navigateTo('/entries');
element('table tbody').query(function (tbody, done) { ... });
element('table tbody a');
element('.btn-danger').click();
// delete entry 2
// indented to represent "recursion depth"
browser().navigateTo('/entries');
element('table tbody').query(function (tbody, done) { ... });
element('table tbody a');
element('.btn-danger').click();
```
See the [angular-seed](https://github.com/angular/angular-seed) project for more examples, or look
at the embedded examples in the Angular documentation (For example, [$http](http://docs.angularjs.org/api/ng/service/$http)
has an end to end test in the example under the `protractor.js` tag).
## Caveats
`ngScenario` does not work with apps that manually bootstrap using `angular.bootstrap`. You must use the `ng-app` directive.
Protractor does not work out-of-the-box with apps that bootstrap manually using
`angular.bootstrap`. You must use the `ng-app` directive.
+10 -7
View File
@@ -95,16 +95,18 @@ You can try evaluating different expressions here:
Angular does not use JavaScript's `eval()` to evaluate expressions. Instead Angular's
{@link ng.$parse $parse} service processes these expressions.
Unlike JavaScript, where names default to global `window` properties, Angular expressions must use
{@link ng.$window `$window`} explicitly to refer to the global `window` object. For example, if you
want to call `alert()` in an expression you must use `$window.alert()`. This restriction is
intentional. It prevents accidental access to the global state a common source of subtle bugs.
Angular expressions do not have access to global variables like `window`, `document` or `location`.
This restriction is intentional. It prevents accidental access to the global state a common source of subtle bugs.
Instead use services like `$window` and `$location` in functions called from expressions. Such services
provide mockable access to globals.
<example>
<file name="index.html">
<div class="example2" ng-controller="Cntl1">
Name: <input ng-model="name" type="text"/>
<button ng-click="greet()">Greet</button>
<button ng-click="window.alert('Should not see me')">Won't greet</button>
</div>
</file>
@@ -152,9 +154,10 @@ Similarly, invoking a function `a.b.c()` on `undefined` or `null` simply returns
## No Control Flow Statements
You cannot write a control flow statement in an expression. The reason behind this is core to the
Angular philosophy that application logic should be in controllers, not the views. If you need a
conditional, loop, or to throw from a view expression, delegate to a JavaScript method instead.
Apart from the ternary operator (`a ? b : c`), you cannot write a control flow statement in an
expression. The reason behind this is core to the Angular philosophy that application logic should
be in controllers, not the views. If you need a real conditional, loop, or to throw from a view
expression, delegate to a JavaScript method instead.
## `$event`
+1 -1
View File
@@ -5,7 +5,7 @@
A filter formats the value of an expression for display to the user. They can be used in view templates,
controllers or services and it is easy to define your own filter.
The underlying API is the {@link ng.$filterProvider filterProvider}.
The underlying API is the {@link ng.$filterProvider `filterProvider`}.
## Using filters in view templates
+1 -1
View File
@@ -21,7 +21,7 @@ integration server runs all the tests against IE9, IE10, and IE11. See
[ci.angularjs.org](http://ci.angularjs.org).
We do not run tests on IE8 and below. A subset of the AngularJS functionality may work on these
browesers, but it is up to you to test and decide whether it works for your particular app.
browsers, but it is up to you to test and decide whether it works for your particular app.
## Short Version
+4 -2
View File
@@ -73,15 +73,16 @@ This is a short list of libraries with specific support and documentation for wo
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/)
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
* **SQL and NoSQL backends:** [BreezeJS](http://www.breezejs.com/), [AngularFire](http://angularfire.com/)
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/)
* **UI Widgets: **[KendoUI](http://kendo-labs.github.io/angular-kendo/#/), [UI Bootstrap](http://angular-ui.github.io/bootstrap/), [Wijmo](http://wijmo.com/tag/angularjs-2/), [ngTagsInput](https://github.com/mbenford/ngTagsInput)
* **Advanced Routing:** [UI-Router](https://github.com/angular-ui/ui-router)
* **Maps:** [UI-Map (Google Maps)](https://github.com/angular-ui/ui-map)
## Deployment
### General
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ngmin automation tool](http://www.thinkster.io/pick/XlWneEZCqY/angularjs-ngmin)
* **Tracking:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
* **Analytics and Logging:** [Angularyitcs (Google Analytics)](http://ngmodules.org/modules/angularytics), [Angulartics (Analytics)](https://github.com/luisfarzati/angulartics), [Logging Client-Side Errors](http://www.bennadel.com/blog/2542-Logging-Client-Side-Errors-With-AngularJS-And-Stacktrace-js.htm)
* **SEO:** [By hand](http://www.yearofmoo.com/2012/11/angularjs-and-seo.html), [prerender.io](http://prerender.io/), [Brombone](http://www.brombone.com/), [SEO.js](http://getseojs.com/), [SEO4Ajax](http://www.seo4ajax.com/)
### Server-Specific
@@ -112,6 +113,7 @@ This is a short list of libraries with specific support and documentation for wo
* **Free online:**
[thinkster.io](http://thinkster.io),
[CodeAcademy](http://www.codecademy.com/courses/javascript-advanced-en-2hJ3J/0/1)
[CodeSchool](https://www.codeschool.com/courses/shaping-up-with-angular-js)
* **Paid online:**
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
+1 -1
View File
@@ -22,7 +22,7 @@ The impedance mismatch between dynamic applications and static documents is ofte
in charge and it calls into the library when it sees fit. E.g., `jQuery`.
* **frameworks** - a particular implementation of a web application, where your code fills in
the details. The framework is in charge and it calls into your code when it needs something
app specific. E.g., `knockout`, `ember`, etc.
app specific. E.g., `durandal`, `ember`, etc.
Angular takes another approach. It attempts to minimize the impedance mismatch between document
+9 -9
View File
@@ -83,7 +83,7 @@ On to more complex examples!
## Factory Recipe
The Value recipe is very simple to write, but lacks some important features we often need when
creating services. Let's now look at the Value recipe's more powerful sibling, the Factory.The
creating services. Let's now look at the Value recipe's more powerful sibling, the Factory. The
Factory recipe adds the following abilities:
* ability to use other services (have dependencies)
@@ -97,7 +97,7 @@ created by this recipe.
Note: All services in Angular are singletons. That means that the injector uses each recipe at most
once to create the object. The injector then caches the reference for all future needs.
Since Factory is more powerful version of Value recipe, you can construct the same service with it.
Since Factory is more a powerful version of the Value recipe, you can construct the same service with it.
Using our previous `clientId` Value recipe example, we can rewrite it as a Factory recipe like
this:
@@ -111,8 +111,8 @@ But given that the token is just a string literal, sticking with the Value recip
appropriate as it makes the code easier to follow.
Let's say, however, that we would also like to create a service that computes a token used for
authentication against a remote API. This token will be called 'apiToken' and will be computed
based on the `clientId` value and a secret stored in browser's local storage:
authentication against a remote API. This token will be called `apiToken` and will be computed
based on the `clientId` value and a secret stored in the browser's local storage:
```javascript
myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) {
@@ -129,11 +129,11 @@ myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) {
```
In the code above, we see how the `apiToken` service is defined via the Factory recipe that depends
on `clientId` service. The factory service then uses NSA-proof encryption to produce an authentication
on the `clientId` service. The factory service then uses NSA-proof encryption to produce an authentication
token.
Note: It is a best practice to name the factory functions as "<serviceId>Factory"
(e.g. apiTokenFactory). While this names are not required, they help when navigating the code base
Note: It is best practice to name the factory functions as `<serviceId>Factory`
(e.g. apiTokenFactory). While this naming convention is not required, it helps when navigating the code base
or looking at stack traces in the debugger.
Just like with Value recipe, Factory recipe can create a service of any type, whether it be a
@@ -143,7 +143,7 @@ primitive, object literal, function, or even an instance of a custom type.
## Service Recipe
JavaScript developers often use custom types to write object-oriented code. Let's explore how we
could launch a unicorn into space via our `unicornLauncher` service that is an instance of
could launch a unicorn into space via our `unicornLauncher` service which is an instance of a
custom type:
```javascript
@@ -187,7 +187,7 @@ myApp.service('unicornLauncher', ["apiToken", UnicornLauncher]);
Much simpler!
Note: Yes, we have called one of our service recipes 'Service'. We regret this and know that we'll
be somehow punished for our mis-deed. It's like we named one of our offspring 'Children'. Boy,
be somehow punished for our mis-deed. It's like we named one of our offspring 'Child'. Boy,
that would mess with the teachers.
+6 -6
View File
@@ -268,7 +268,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
4. **Mutation observation**
At the end `$apply`, Angular performs a {@link ng.$rootScope.Scope#$digest
At the end of `$apply`, Angular performs a {@link ng.$rootScope.Scope#$digest
$digest} cycle on the root scope, which then propagates throughout all child scopes. During
the `$digest` cycle, all `$watch`ed expressions or functions are checked for model mutation
and if a mutation is detected, the `$watch` listener is called.
@@ -350,15 +350,15 @@ The diagram and the example below describe how Angular interacts with the browse
Angular modifies the normal JavaScript flow by providing its own event processing loop. This
splits the JavaScript into classical and Angular execution context. Only operations which are
applied in Angular execution context will benefit from Angular data-binding, exception handling,
property watching, etc... You can also use $apply() to enter Angular execution context from JavaScript. Keep in
applied in the Angular execution context will benefit from Angular data-binding, exception handling,
property watching, etc... You can also use $apply() to enter the Angular execution context from JavaScript. Keep in
mind that in most places (controllers, services) $apply has already been called for you by the
directive which is handling the event. An explicit call to $apply is needed only when
implementing custom event callbacks, or when working with third-party library callbacks.
1. Enter Angular execution context by calling {@link guide/scope scope}`.`{@link
ng.$rootScope.Scope#$apply $apply}`(stimulusFn)`. Where `stimulusFn` is
the work you wish to do in Angular execution context.
1. Enter the Angular execution context by calling {@link guide/scope scope}`.`{@link
ng.$rootScope.Scope#$apply $apply}`(stimulusFn)`, where `stimulusFn` is
the work you wish to do in the Angular execution context.
2. Angular executes the `stimulusFn()`, which typically modifies application state.
3. Angular enters the {@link ng.$rootScope.Scope#$digest $digest} loop. The
loop is made up of two smaller loops which process {@link
+9 -3
View File
@@ -130,8 +130,14 @@ injection of `$window`, `$scope`, and our `notify` service:
</example>
<div class="alert alert-danger">
**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming) your code,
your variable names will get renamed unless you use one of the annotation techniques above.
**Careful:** If you plan to [minify](http://en.wikipedia.org/wiki/Minification_(programming&#41;) your
code, your variable names will get renamed unless you use one of the annotation techniques above.
</div>
<div class="alert alert-info">
If you use a tool like [ngmin](https://github.com/btford/ngmin#ngmin) in your workflow you can
use implicit dependency notation within your codebase and let **ngmin** automatically convert such
injectable functions to the array notation prior to minifying.
</div>
@@ -293,5 +299,5 @@ it('should clear messages after alert', function() {
## Related API
* {@link ./ng Angular Service API}
* {@link ./api/ng/service Angular Service API}
* {@link angular.injector Injector API}
+4 -4
View File
@@ -49,7 +49,7 @@ Out of the four options in the list above, only the last one is testable. Let's
### Using the `new` operator
While there is nothing wrong with the `new` operator fundamentally, a problem arises when calling `new`
on a constructor. This permanently binds the call site to the type. For example, lets say that we try to
on a constructor. This permanently binds the call site to the type. For example, let's say that we try to
instantiate an `XHR` that will retrieve data from the server.
```js
@@ -65,7 +65,7 @@ function MyClass() {
A problem surfaces in tests when we would like to instantiate a `MockXHR` that would
allow us to return fake data and simulate network failures. By calling `new XHR()` we are
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
permanently bound to the actual XHR and there is no way to replace it. Yes, we could monkey
patch, but that is a bad idea for many reasons which are outside the scope of this document.
Here's an example of how the class above becomes hard to test when resorting to monkey patching:
@@ -133,7 +133,7 @@ function MyClass() {
However, where does the serviceRegistry come from? If it is:
* `new`-ed up, the test has no chance to reset the services for testing.
* a global look-up then the service returned is global as well (but resetting is easier, since
* a global look-up then the service returned is global as well (but resetting is easier, since
only one global variable exists to be reset).
The class above is hard to test since we have to change the global state:
@@ -296,7 +296,7 @@ Now we can add a directive to our app.
app.directive('aGreatEye', function () {
return {
restrict: 'E',
replace: true,
replace: true,
template: '<h1>lidless, wreathed in flame, {{1 + 1}} times</h1>'
};
});
+2 -2
View File
@@ -17,7 +17,7 @@ production.
To point your code to an angular script on the Google CDN server, use the following template. This
example points to the minified version 1.2.0:
<pre>
```
<!doctype html>
<html ng-app>
<head>
@@ -27,7 +27,7 @@ example points to the minified version 1.2.0:
<body>
</body>
</html>
</pre>
```
Note that only versions 1.0.1 and above are available on the CDN, if you need an earlier version
you can use the <http://code.angularjs.org/> URL which was the previous recommended location for
+1 -1
View File
@@ -82,7 +82,7 @@ application is being bootstrapped. If jQuery is not present in your script path,
to its own implementation of the subset of jQuery that we call {@link angular.element jQLite}.
Due to a change to use `on()`/`off()` rather than `bind()`/`unbind()`, Angular 1.2 only operates with
jQuery 1.7.1 or above.
jQuery 1.7.1 or above. However, Angular does not currently support jQuery 2.x or above.
### What is testability like in Angular?
+1 -1
View File
@@ -10,7 +10,7 @@ becoming an Angular expert.
1. Read the {@link guide/concepts conceptual overview}.<br/>Understand Angular's vocabulary and how all the Angular
components work together.
1. Do the {@link tutorial/ AngularJS Tutorial}.<br/>Walk end-to-end through building an application complete with tests
on top of a node.js web server. Covers every major AngularJS feature and show you how to set up your development
on top of a node.js web server. Covers every major AngularJS feature and shows you how to set up your development
environment.
1. Download or clone the [Seed App project template](https://github.com/angular/angular-seed).<br/>Gives you a
starter app with a directory layout, test harness, and scripts to begin building your application.
+153 -72
View File
@@ -13,23 +13,25 @@ details for any device.
<img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413" alt="demo
application running in the browser">
Work through the tutorial to see how Angular makes browsers smarter — without the use of native
extensions or plug-ins. As you work through the tutorial, you will:
Follow the tutorial to see how Angular makes browsers smarter — without the use of native
extensions or plug-ins:
* See examples of how to use client-side data binding and dependency injection to build dynamic
views of data that change immediately in response to user actions.
* See how Angular creates listeners on your data without the need for DOM manipulation.
* Learn a better, easier way to test your web apps.
* Learn how to use Angular services to make common web tasks, such as getting data into your app,
easier.
* See examples of how to use client-side data binding to build dynamic views of data that change
immediately in response to user actions.
* See how Angular keeps your views in sync with your data without the need for DOM manipulation.
* Learn a better, easier way to test your web apps, with Karma and Protractor.
* Learn how to use dependency injection and services to make common web tasks, such as getting data
into your app, easier.
When you finish the tutorial you will be able to:
* Create a dynamic application that works in all modern browsers.
* Define the differences between Angular and common JavaScript frameworks.
* Understand how data binding works in AngularJS.
* Create and run unit tests.
* Create and run end to end tests.
* Use data binding to wire up your data model to your views.
* Create and run unit tests, with Karma.
* Create and run end to end tests, with Protractor.
* Move application logic out of the template and into Controllers.
* Get data from a server using Angular services.
* Apply animations to your application, using ngAnimate.
* Identify resources for learning more about AngularJS.
The tutorial guides you through the entire process of building a simple application, including
@@ -40,31 +42,46 @@ You can go through the whole tutorial in a couple of hours or you may want to sp
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
{@link misc/started Getting Started} document.
# Get Started
The rest of this page explains how you can set up your machine to work with the code on your local
machine. If you just want to read the tutorial then you can just go straight to the first step:
[Step 0 - Bootstrapping](tutorial/step_00).
# Working with the code
You can follow along with this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. The tutorial relies on the use of the [Git][git] versioning system for source code
management.
You can follow along with this tutorial and hack on the code in the comfort of your own computer.
In this way you can get hands-on practice of really writing AngularJS code and also on using the
recommended testing tools.
You don't need to know anything about Git to follow the tutorial. Select one of the tabs below
and follow the instructions for setting up your computer.
The tutorial relies on the use of the [Git][git] versioning system for source code management.
You don't need to know anything about Git to follow the tutorial other than how to install and run
a few git commands.
## Install Git
### Install Git
You'll need Git, which you can get from [the Git site][git].
You can download and install Git from http://git-scm.com/download. Once installed you should have
access to the `git` command line tool. The main commands that you will need to use are:
- `git clone ...` : clone a remote repository onto your local machine
- `git checkout ...` : check out a particular branch or a tagged version of the code to hack on
### Download angular-phonecat
Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by running the following
command:
```
git clone https://github.com/angular/angular-phonecat.git
git clone --depth=14 https://github.com/angular/angular-phonecat.git
```
This command creates the `angular-phonecat` directory in your current directory.
<div class="alert alert-info">The `--depth=14` option just tells Git to pull down only the last 14 commits. This makes the
download much smaller and faster.
</div>
Change your current directory to `angular-phonecat`.
```
@@ -75,119 +92,183 @@ The tutorial instructions, from now on, assume you are running all commands from
`angular-phonecat` directory.
## Install Node.js
### Install Node.js
If you want to run the built-in web-server and the test tools then you will also need
Node.js v0.10, or later.
If you want to run the preconfigured local web-server and the test tools then you will also need
[Node.js v0.10.27+][node].
You can download Node.js from the [node.js website][node].
You can download a Node.js installer for your operating system from http://nodejs.org/download/.
You can check the version of Node.js that you have installed by running the following command:
Check the version of Node.js that you have installed by running the following command:
```
node --version
```
<div class="alert alert-info"><strong>Helpful note:<strong> If you need to run a different versions of node.js
In Debian based distributions, there is a name clash with another utility called `node`. The
suggested solution is to also install the `nodejs-legacy` apt package, which renames `node` to
`nodejs`.
```
apt-get install nodejs-legacy
nodejs --version
```
<div class="alert alert-info">If you need to run a different versions of node.js
in your local environment, consider installing
<a href="https://github.com/creationix/nvm" title="Node Version Manager Github Repo link">
Node Version Manager (nvm)
</a>.
</div>
Once you have Node.js installed you can install the tool dependencies by running:
Once you have Node.js installed on your machine you can download the tool dependencies by running:
```
npm install
```
This command will download the following tools, into the `node_modules` directive:
This command will download the following tools, into the `node_modules` directory:
- [Bower][bower] - client-side code package manager
- [http-server][http-server] - simple local static web server
- [Http-Server][http-server] - simple local static web server
- [Karma][karma] - unit test runner
- [protractor][protractor] - end 2 end test runner
- [Protractor][protractor] - end to end (E2E) test runner
Running `npm install` will also automatically run `bower install`, which will download the Angular
framework into the `bower_components` directory.
Running `npm install` will also automatically use bower to download the Angular framework into the
`app/bower_components` directory.
<div class="alert alert-info">
Note the angular-phonecat project is setup to install and run these utilities via npm scripts.
This means that you do not have to have any of these utilities installed globally on your system
to follow the tutorial. See **Installing Helper Tools** below for more information.
</div>
The project is preconfigured with a number of npm helper scripts to make it easy to run the common
tasks that you will need while developing.
tasks that you will need while developing:
## Running Development Web Server
- `npm start` : start a local development web-server
- `npm test` : start the Karma unit test runner
- `npm run protractor` : run the Protractor end to end (E2E) tests
- `npm run update-webdriver` : install the drivers needed by Protractor
The project is preconfigured to provide a simple static web server for hosting the application.
Start the web server by running:
### Install Helper Tools (optional)
The Bower, Http-Server, Karma and Protractor modules are also executables, which can be installed
globally and run directly from a terminal/command prompt. You don't need to do this to follow the
tutorial, but if you decide you do want to run them directly, you can install these modules globally
using, `sudo npm install -g ...`.
For instance to install the Bower command line executable you would do:
```
sudo npm install -g bower
```
*(Omit the sudo if running on Windows)*
Then you can run the bower tool directly, such as:
```
bower install
```
### Running Development Web Server
While Angular applications are purely client-side code, and it is possible to open them in a web
browser directly from the file system, it is better to serve them from a HTTP web server. In
particular, for security reasons, most modern browsers will not allow JavaScript to make server
requests if the page is loaded directly from the file system.
The angular-phonecat project is configured with a simple static web server for hosting the
application during development. Start the web server by running:
```
npm start
```
Now you can browse to the application at:
This will create a local webserver that is listening to port 8000 on your local machine.
You can now browse to the application at:
```
http://localhost:8000/app/index.html
```
## Running Unit Tests
### Running Unit Tests
The project is preconfigured to use [Karma][karma] to run the unit tests for the application. Start
Karma by running:
We use unit tests to ensure that the JavaScript code in our application is operating correctly.
Unit tests focus on testing small isolated parts of the application. The unit tests are kept in the
`test/unit` directory.
The angular-phonecat project is configured to use [Karma][karma] to run the unit tests for the
application. Start Karma by running:
```
npm test
```
This will start the Karma unit test runner, open up a Chrome browser and execute all the unit tests
in this browser. Karma will then sit and watch all the source and test JavaScript files.
Whenever one of these files changes Karma re-runs all the unit tests.
This will start the Karma unit test runner. Karma will read the configuration file at
`test/karma.conf.js`. This configuration file tells Karma to:
It is good to leave this running all the time as you will get immediate feedback from Karma as you
are working on the code.
- open up a Chrome browser and connect it to Karma
- execute all the unit tests in this browser
- report the results of these tests in the terminal/command line window
- watch all the project's JavaScript files and re-run the tests whenever any of these change
It is good to leave this running all the time, in the background, as it will give you immediate
feedback about whether your changes pass the unit tests while you are working on the code.
## Running End to End Tests
### Running End to End Tests
The project is preconfigured to use [Protractor][protractor] to run the end to end tests for the
application. Set up the binaries protractor needs to run by running:
We use End to End tests to ensure that the application as a whole operates as expected.
End to End tests are designed to test the whole client side application, in particular that the
views are displaying and behaving correctly. It does this by simulating real user interaction with
the real application running in the browser.
The End to End tests are kept in the `test/e2e` directory.
The angular-phonecat project is configured to use [Protractor][protractor] to run the End to End
tests for the application. Protractor relies upon a set of drivers to allow it to interact with
the browser. You can install these drivers by running:
```
npm run update-webdriver
```
(You will only need to do this once) Execute the Protractor test scripts against your application
by running:
*(You should only need to do this once.)*
Since Protractor works by interacting with a running application, we need to start our web server:
```
npm start
```
Then in a separate terminal/command line window, we can run the Protractor test scripts against the
application by running:
```
npm run protractor
```
This will start the Protractor end to end test runner, open up a Chrome browser and execute all the
end to end test scripts in this browser. Once the test scripts finish, the browser will close down
and Protractor will exit.
Protractor will read the configuration file at `test/protractor-conf.js`. This configuration tells
Protractor to:
- open up a Chrome browser and connect it to the application
- execute all the End to End tests in this browser
- report the results of these tests in the terminal/command line window
- close down the browser and exit
It is good to run the end to end tests whenever you make changes to the HTML views or want to check
that the application as a whole is executing correctly.
<div class="alert alert-info"><strong>Helpful note:</strong> Protractor requires that a web server is running
and serving up the application at the default URL: `http://localhost:8000/app/index.html`. You can
start the provided development server by running `npm start`.
</div>
that the application as a whole is executing correctly. It is very common to run End to End tests
before pushing a new commit of changes to a remote repository.
# Get Started
Now your environment is ready, it is time to get started developing the Angular PhoneCat
application.
<a href="tutorial/step_00" title="Next Step"><span class="btn btn-primary">Step 0 - Bootstrapping</span></a>
[git]: http://git-scm.com/download
[angular-phonecat]: https://github.com/angular/angular-phonecat
[git]: http://git-scm.com/
[node]: http://nodejs.org/
[angular-phonecat]: https://github.com/angular/angular-phonecat
[protractor]: https://github.com/angular/protractor
[bower]: http://bower.io/
[http-server]: https://github.com/nodeapps/http-server
[karma]: https://github.com/karma-runner/karma
[karma]: https://github.com/karma-runner/karma
+28 -68
View File
@@ -11,65 +11,27 @@ with the most important source code files, learn how to start the development se
angular-seed, and run the application in the browser.
<div class="tabbable" show="true" ng-model="$cookies.platformPreference">
<div class="tab-pane well" id="git-mac" title="Git on Mac/Linux" value="gitUnix">
<ol>
<li><p>In <code>angular-phonecat</code> directory, run this command:</p>
<pre>git checkout -f step-0</pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
In `angular-phonecat` directory, run this command:
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>npm start</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code> directory.</li>
<li>Navigate in your browser to <code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</div>
```
git checkout -f step-0
```
<div class="tab-pane well" id="git-win" title="Git on Windows" value="gitWin">
<ol>
<li><p>Open Git bash and run this command (in <code>angular-phonecat</code> directory):</p>
<pre>git checkout -f step-0</pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code> directory.</li>
<li>Navigate in your browser to <code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</div>
</div>
This resets your workspace to step 0 of the tutorial app.
You must repeat this for every future step in the tutorial and change the number to the number of
the step you are on. This will cause any changes you made within your working directory to be lost.
If you haven't already done so you need to install the dependencies by running:
```
npm install
```
To see the app running in a browser, open a *separate* terminal/command line tab or window, then
run `npm start` to start the web server. Now, open a browser window for the app and navigate to
<a href="http://localhost:8000/app/index.html" target="_blank">`http://localhost:8000/app/index.html`</a>
You can now see the page in your browser. It's not very exciting, but that's OK.
@@ -84,9 +46,9 @@ __`app/index.html`:__
<head>
<meta charset="utf-8">
<title>My HTML File</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="css/app.css">
<link rel="stylesheet" href="css/bootstrap.css">
<script src="lib/angular/angular.js"></script>
<script src="bower_components/angular/angular.js"></script>
</head>
<body>
@@ -114,7 +76,7 @@ __`app/index.html`:__
* AngularJS script tag:
<script src="lib/angular/angular.js">
<script src="bower_components/angular/angular.js">
This code downloads the `angular.js` script and registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
@@ -171,16 +133,18 @@ and one static binding, and our model is empty. That will soon change!
## What are all these files in my working directory?
Most of the files in your working directory come from the [angular-seed project](https://github.com/angular/angular-seed) which is typically used to bootstrap
new Angular projects. The seed project includes the latest Angular libraries, test libraries,
scripts and a simple example app, all pre-configured for developing a typical web app.
Most of the files in your working directory come from the [angular-seed project][angular-seed] which
is typically used to bootstrap new Angular projects. The seed project is pre-configured to install
the angular framework (via `bower` into the `app/bower_components/` folder) and tools for developing
a typical web app (via `npm`).
For the purposes of this tutorial, we modified the angular-seed with the following changes:
* Removed the example app
* Added phone images to `app/img/phones/`
* Added phone data files (JSON) to `app/phones/`
* Added [Bootstrap](http://getbootstrap.com) files to `app/css/` and `app/img/`
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
@@ -199,9 +163,5 @@ Now let's go to {@link step_01 step 1} and add some content to the web app.
<ul doc-tutorial-nav="0"></ul>
<div style="display: none">
Note: During the bootstrap the injector and the root scope will then be associated with the
element on which the `ngApp` directive was declared, so when debugging the app you can retrieve
them from browser console via `angular.element(rootElement).scope()` and
`angular.element(rootElement).injector()`.
</div>
[angular-seed]: https://github.com/angular/angular-seed
+2 -5
View File
@@ -12,15 +12,12 @@ dynamically display the same result with any set of data.
In this step you will add some basic information about two cell phones to an HTML page.
- The page now contains a list with information about two phones.
<div doc-tutorial-reset="1"></div>
The page now contains a list with information about two phones.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-0...step-1):
__`app/index.html`:__
**`app/index.html`:**
```html
<ul>
+69 -68
View File
@@ -9,20 +9,16 @@
Now it's time to make the web page dynamic — with AngularJS. We'll also add a test that verifies the
code for the controller we are going to add.
There are many ways to structure the code for an application. For Angular apps, we encourage the
use of [the Model-View-Controller (MVC)
design pattern](http://en.wikipedia.org/wiki/ModelViewController) to decouple the code and to separate concerns. With that in mind, let's use a
little Angular and JavaScript to add model, view, and controller components to our app.
There are many ways to structure the code for an application. For Angular apps, we encourage the use of
[the Model-View-Controller (MVC) design pattern](http://en.wikipedia.org/wiki/ModelViewController)
to decouple the code and to separate concerns. With that in mind, let's use a little Angular and
JavaScript to add model, view, and controller components to our app.
- The list of three phones is now generated dynamically from data
<div doc-tutorial-reset="2"></div>
The app now contains a list with three phones.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-1...step-2):
## View and Template
In Angular, the __view__ is a projection of the model through the HTML __template__. This means that
@@ -37,7 +33,7 @@ __`app/index.html`:__
<html ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="js/controllers.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
@@ -53,21 +49,21 @@ __`app/index.html`:__
</html>
```
We replaced the hard-coded phone list with the
{@link ng.directive:ngRepeat ngRepeat directive} and two
{@link guide/expression Angular expressions} enclosed in curly braces:
`{{phone.name}}` and `{{phone.snippet}}`:
We replaced the hard-coded phone list with the {@link ng.directive:ngRepeat ngRepeat directive}
and two {@link guide/expression Angular expressions}:
* The `ng-repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
* The `ng-repeat="phone in phones"` attribute in the `<li>` tag is an Angular repeater directive.
The repeater tells Angular to create a `<li>` element for each phone in the list using the `<li>`
tag as the template.
* The expressions wrapped in curly braces (`{{phone.name}}` and `{{phone.snippet}}`) will be replaced
by the value of the expressions.
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
__controller__ to the DOM at this point.
__controller__ to the DOM at this point:
* As we've learned in {@link step_00 step 0}, the curly braces around `phone.name` and `phone.snippet` denote
bindings. As opposed to evaluating constants, these expressions are referring to our application
model, which was set up in our `PhoneListCtrl` controller.
* The expressions in curly braces (`{{phone.name}}` and `{{phone.snippet}}` denote
bindings, which are referring to our application model, which is set up in our `PhoneListCtrl`
controller.
<img class="diagram" src="img/tutorial/tutorial_02.png">
@@ -128,23 +124,19 @@ To learn more about Angular scopes, see the {@link ng.$rootScope.Scope angular s
## Tests
The "Angular way" of separating controller from the view, makes it easy to test code as it is being
developed. If our controller is available on the global namespace then we can simply instantiate it
with a mock `scope` object. Take a look at the following unit test for our controller:
__`test/unit/controllersSpec.js`:__
developed. If our controller is available on the global namespace then we could simply instantiate it
with a mock `scope` object:
```js
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
expect(scope.phones.length).toBe(3);
});
expect(scope.phones.length).toBe(3);
});
});
```
@@ -154,67 +146,72 @@ Angular. Since testing is such a critical part of software development, we make
tests in Angular so that developers are encouraged to write them.
### Testing non-Global Controllers
In practice, you will not want to have your controller functions in the global namespace. Instead,
we have registered our controllers in the `phonecatApp` module. In this case Angular provides a
service, `$controller`, which will retrieve your controller by name. Here is the same test using
`$controller`:
In practice, you will not want to have your controller functions in the global namespace. Instead,
you can see that we have registered it via an anonymous constructor function on the `phonecatApp`
module.
In this case Angular provides a service, `$controller`, which will retrieve your controller by name.
Here is the same test using `$controller`:
__`test/unit/controllersSpec.js`:__
```js
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
beforeEach(module('phonecatApp'));
describe('PhoneListCtrl', function(){
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', {$scope:scope});
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', { $scope: scope });
expect(scope.phones.length).toBe(3);
}));
expect(scope.phones.length).toBe(3);
}));
});
});
```
Don't forget that we need to load up the `phonecatApp` module into the test so that the controller
is available to be injected.
* Before each test we tell Angular to load the `phonecatApp` module.
* We ask Angular to `inject` the `$controller` service into our test function
* We use `$controller` to create an instance of the `PhoneListCtrl`
* With this instance, we verify that the phones array property on the scope contains three records.
### Writing and Running Tests
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the [Jasmine home page](http://jasmine.github.io/) and at the [Jasmine docs](http://jasmine.github.io/).
this tutorial in Jasmine v1.3. You can learn about Jasmine on the [Jasmine home page][jasmine] and
at the [Jasmine docs][jasmine-docs].
The angular-seed project is pre-configured to run all unit tests using [Karma](http://karma-runner.github.io/). Ensure that the necessary karma plugins are installed.
You can do this by issuing `npm install` into your terminal.
The angular-seed project is pre-configured to run unit tests using [Karma][karma] but you will need
to ensure that Karma and its necessary plugins are installed. You can do this by running
`npm install`.
To run the tests, and then watch the files for changes: `npm test`.
To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`npm test` to start the Karma server (the config file necessary to start the server is
located at `./test/karma.conf.js`).
2. Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
* Karma will start a new instance of Chrome browser automatically. Just ignore it and let it run in
the background. Karma will use this browser for test execution.
* You should see the following or similar output in the terminal:
3. You should see the following or similar output in the terminal:
info: Karma server started at http://localhost:9876/
info (launcher): Starting browser "Chrome"
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
<pre>
info: Karma server started at http://localhost:9876/
info (launcher): Starting browser "Chrome"
info (Chrome 22.0): Connected on socket id tPUm9DXcLHtZTKbAEO-n
Chrome 22.0: Executed 1 of 1 SUCCESS (0.093 secs / 0.004 secs)
</pre>
Yay! The test passed! Or not...
4. To rerun the tests, just change any of the source or test .js files. Karma will notice the change
* To rerun the tests, just change any of the source or test .js files. Karma will notice the change
and will rerun the tests for you. Now isn't that sweet?
# Experiments
* Add another binding to `index.html`. For example:
<p>Total number of phones: {{phones.length}}</p>
```html
<p>Total number of phones: {{phones.length}}</p>
```
* Create a new model property in the controller and bind to it from the template. For example:
@@ -226,11 +223,11 @@ To run the test, do the following:
Refresh your browser and verify that it says "Hello, World!".
* Update the unit test for the controller in ./tests/unit/controllersSpec.js to reflect the previous change. For example by adding:
* Update the unit test for the controller in ./test/unit/controllersSpec.js to reflect the previous change. For example by adding:
expect(scope.name).toBe('World');
* Create a repeater that constructs a simple table:
* Create a repeater in `index.html` that constructs a simple table:
<table>
<tr><th>row number</th></tr>
@@ -244,7 +241,7 @@ To run the test, do the following:
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`.
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
# Summary
@@ -255,3 +252,7 @@ to the app.
<ul doc-tutorial-nav="2"></ul>
[jasmine]: http://jasmine.github.io/
[jasmine-docs]: http://jasmine.github.io/1.3/introduction.html
[karma]: http://karma-runner.github.io/
+50 -53
View File
@@ -11,15 +11,10 @@ simple; we will add full text search (yes, it will be simple!). We will also wri
test, because a good end-to-end test is a good friend. It stays with your app, keeps an eye on it,
and quickly detects regressions.
<div doc-tutorial-reset="3"></div>
The app now has a search box. Notice that the phone list on the page changes depending on what a
* The app now has a search box. Notice that the phone list on the page changes depending on what a
user types into the search box.
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
[GitHub](https://github.com/angular/angular-phonecat/compare/step-2...step-3):
<div doc-tutorial-reset="3"></div>
## Controller
@@ -33,14 +28,14 @@ __`app/index.html`:__
```html
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="query">
</div>
<div class="span10">
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
@@ -118,77 +113,79 @@ describe('PhoneCat App', function() {
});
```
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
### Running End to End Tests with Protractor
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of {@link guide/dev_guide.e2e-testing Angular's end-to-end
test runner}.
Jasmine, the end-to-end test uses APIs of [Protractor](https://github.com/angular/protractor). Read
about the Protractor APIs at https://github.com/angular/protractor/blob/master/docs/api.md.
Much like Karma is the test runner for unit tests, we use Protractor to run end-to-end tests.
Try it with `npm run protractor`. End-to-end tests are slow, so unlike with unit tests, Protractor
will exit after the test run and will not automatically rerun the test suite on every file change.
To rerun the test suite, execute `npm run protractor` again.
Note: You must ensure you've installed the protractor and updated webdriver prior to running the
`npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
your terminal.
<div class="alert alert-info">
Note: You must ensure you've installed the protractor and updated webdriver prior to running the
`npm run protractor`. You can do this by issuing `npm install` and `npm run update-webdriver` into
your terminal.
</div>
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
# Experiments
* Display the current value of the `query` model by adding a `{{query}}` binding into the
### Display Current Query
Display the current value of the `query` model by adding a `{{query}}` binding into the
`index.html` template, and see how it changes when you type in the input box.
* Let's see how we can get the current value of the `query` model to appear in the HTML page title.
### Display Query in Title
Let's see how we can get the current value of the `query` model to appear in the HTML page title.
You might think you could just add the `{{query}}` to the title tag element as follows:
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
<title>Google Phone Gallery: {{query}}</title>
```js
it('should display the current filter value in the title bar', function() {
expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
element(by.model('query')).sendKeys('nexus');
expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
});
```
Run protractor (`npm run protractor`) to see this test fail.
* You might think you could just add the `{{query}}` to the title tag element as follows:
<title>Google Phone Gallery: {{query}}</title>
However, when you reload the page, you won't see the expected result. This is because the "query"
model lives in the scope, defined by the `ng-controller="PhoneListCtrl"` directive, on the body element:
model lives in the scope, defined by the `ng-controller="PhoneListCtrl"` directive, on the body
element:
<body ng-controller="PhoneListCtrl">
If you want to bind to the query model from the `<title>` element, you must __move__ the
`ngController` declaration to the HTML element because it is the common parent of both the body
and title elements:
`ngController` declaration to the HTML element because it is the common parent of both the body
and title elements:
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
Be sure to __remove__ the `ng-controller` declaration from the body element.
While using double curlies works fine within the title element, you might have noticed that
* Re-run `npm run protractor` to see the test now pass.
* While using double curlies works fine within the title element, you might have noticed that
for a split second they are actually displayed to the user while the page is loading. A better
solution would be to use the {@link ng.directive:ngBind
ngBind} or {@link ng.directive:ngBindTemplate
ngBindTemplate} directives, which are invisible to the user while the page is loading:
solution would be to use the {@link ng.directive:ngBind ngBind} or
{@link ng.directive:ngBindTemplate ngBindTemplate} directives, which are invisible to the user
while the page is loading:
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
```js
it('should display the current filter value within an element with id "status"',
function() {
var statusElement = element(by.id('status'));
expect(statusElement.getText()).toMatch(/Current filter: \s*$/);
element(by.model('query')).sendKeys('nexus');
expect(statusElement.getText()).toMatch(/Current filter: nexus\s*$/);
//alternative version of the last assertion that tests just the value of the binding
expect(statusElement.element(by.binding('query'))).toBe('nexus');
});
```
Re-run `npm run protractor` to see the test fail. To make the test pass, edit the `index.html`
template to add a `div` or `p` element with `id` `"status"` and content with the `query` binding,
prefixed by "Current filter:". For instance:
<div id="status">Current filter: {{query}}</div>
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
# Summary
+6 -10
View File
@@ -10,17 +10,13 @@ In this step, you will add a feature to let your users control the order of the
list. The dynamic ordering is implemented by creating a new model property, wiring it together with
the repeater, and letting the data binding magic do the rest of the work.
* In addition to the search box, the app displays a drop down menu that allows users to control the
order in which the phones are listed.
<div doc-tutorial-reset="4"></div>
You should see that in addition to the search box, the app displays a drop down menu that allows
users to control the order in which the phones are listed.
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
[GitHub](https://github.com/angular/angular-phonecat/compare/step-3...step-4):
## Template
__`app/index.html`:__
@@ -36,7 +32,7 @@ __`app/index.html`:__
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
{{phone.name}}
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
@@ -143,7 +139,7 @@ shared by all tests in the parent `describe` block.
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.021 secs / 0.001 secs)</pre>
Let's turn our attention to the end-to-end test.
@@ -170,7 +166,7 @@ __`test/e2e/scenarios.js`:__
"MOTOROLA XOOM\u2122"
]);
element(by.model('orderProp')).findElement(by.css('option[value="name"]')).click();
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
expect(getNames()).toEqual([
"MOTOROLA XOOM\u2122",
+26 -27
View File
@@ -11,14 +11,11 @@ from our server using one of Angular's built-in {@link guide/services services}
ng.$http $http}. We will use Angular's {@link guide/di dependency
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
* There are now a list of 20 phones, loaded from the server.
<div doc-tutorial-reset="5"></div>
You should now see a list of 20 phones.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-4...step-5):
## Data
The `app/phones/phones.json` file in your project is a dataset that contains a larger list of phones
stored in the JSON format.
@@ -89,7 +86,7 @@ Note that the names of arguments are significant, because the injector uses thes
dependencies.
<img class="diagram" src="img/tutorial/xhr_service_final.png">
<img class="diagram" src="img/tutorial/tutorial_05.png">
### `$` Prefix Naming Convention
@@ -108,28 +105,30 @@ properties are considered private, and should not be accessed or modified.
### A Note on Minification
Since Angular infers the controller's dependencies from the names of arguments to the controller's
constructor function, if you were to [minify](http://goo.gl/SAnnsm) the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be
minified as well, and the dependency injector would not be able to identify services correctly.
constructor function, if you were to [minify](http://goo.gl/SAnnsm) the JavaScript code for
`PhoneListCtrl` controller, all of its function arguments would be minified as well, and the
dependency injector would not be able to identify services correctly.
There are two ways to overcome issues caused by minification:
We can overcome this problem by annotating the function with the names of the dependencies, provided
as strings, which will not get minified. There are two ways to provide these injection annotations:
* You can create a `$inject` property on the controller function which holds an array of strings.
* Create a `$inject` property on the controller function which holds an array of strings.
Each string in the array is the name of the service to inject for the corresponding parameter.
In the case of our example we would write:
In our example we would write:
```js
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
```
```js
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
```
* Use the inline bracket notation which wraps the function to be injected into an array of strings
(representing the dependency names) followed by the function to be injected:
* Use an inline annotation where, instead of just providing the function, you provide an array.
This array contains a list of the service names, followed by the function itself.
```js
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
```
```js
function PhoneListCtrl($scope, $http) {...}
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', PhoneListCtrl]);
```
Both of these methods work with any function that can be injected by Angular, so it's up to your
project's style guide to decide which one you use.
@@ -165,9 +164,9 @@ __`test/unit/controllersSpec.js`:__
Because we started using dependency injection and our controller has dependencies, constructing the
controller in our tests is a bit more complicated. We could use the `new` operator and provide the
constructor with some kind of fake `$http` implementation. However, the recommended (and easier) way
is to create a controller in the test environment in the same way that Angular does it in the
production code behind the scenes, as follows:
constructor with some kind of fake `$http` implementation. However, Angular provides a mock `$http`
service that we can use in unit tests. We configure "fake" responses to server requests by calling
methods on a service called $httpBackend:
```js
describe('PhoneCat controllers', function() {
@@ -180,7 +179,7 @@ describe('PhoneCat controllers', function() {
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service but then attach it to a variable
// with the same name as the service.
// with the same name as the service in order to avoid a name conflict.
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
@@ -251,13 +250,13 @@ Finally, we verify that the default value of `orderProp` is set correctly:
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)</pre>
# Experiments
* At the bottom of `index.html`, add a `{{phones | json}}` binding to see the list of phones
* At the bottom of `index.html`, add a `<pre>{{phones | json}}</pre>` binding to see the list of phones
displayed in json format.
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
+1 -6
View File
@@ -10,15 +10,10 @@ In this step, you will add thumbnail images for the phones in the phone list, an
now, will go nowhere. In subsequent steps you will use the links to display additional information
about the phones in the catalog.
* There are now links and images of the phones in the list.
<div doc-tutorial-reset="6"></div>
You should now see links and images of the phones in the list.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-5...step-6):
## Data
Note that the `phones.json` file contains unique ids and image urls for each of the phones. The
+179 -126
View File
@@ -7,18 +7,50 @@
In this step, you will learn how to create a layout template and how to build an app that has
multiple views by adding routing.
multiple views by adding routing, using an Angular module called 'ngRoute'.
* When you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
and the phone list appears in the browser.
* When you click on a phone link the url changes to one specific to that phone and the stub of a
phone detail page is displayed.
<div doc-tutorial-reset="7"></div>
## Dependencies
Note that when you now navigate to `app/index.html`, you are redirected to `app/index.html#/phones`
and the same phone list appears in the browser. When you click on a phone link the stub of a phone
detail page is displayed.
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
is distributed separately from the core Angular framework.
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-6...step-7).
```json
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.x",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.x"
}
}
```
The new dependency `"angular-route": "~1.2.x"` tells bower to install a version of the
angular-route component that is compatible with version 1.2.x. We must tell bower to download
and install this dependency.
If you have bower installed globally then you can run `bower install` but for this project we have
preconfigured npm to run bower install for us:
```
npm install
```
## Multiple Views, Routing and Layout Template
@@ -34,12 +66,11 @@ template into what we call a "layout template". This is a template that is commo
our application. Other "partial templates" are then included into this layout template depending on
the current "route" — the view that is currently displayed to the user.
Application routes in Angular are declared via the
{@link ngRoute.$routeProvider $routeProvider}, which is the provider of the
{@link ngRoute.$route $route service}. This service makes it easy to wire together
controllers, view templates, and the current
URL location in the browser. Using this feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us utilize the browser's
history (back and forward navigation) and bookmarks.
Application routes in Angular are declared via the {@link ngRoute.$routeProvider $routeProvider},
which is the provider of the {@link ngRoute.$route $route service}. This service makes it easy to
wire together controllers, view templates, and the current URL location in the browser. Using this
feature we can implement [deep linking](http://en.wikipedia.org/wiki/Deep_linking), which lets us
utilize the browser's history (back and forward navigation) and bookmarks.
### A Note About DI, Injector and Providers
@@ -66,109 +97,10 @@ service, the `$routeProvider` exposes APIs that allow you to define routes for y
Angular modules solve the problem of removing global state from the application and provide a way
of configuring the injector. As opposed to AMD or require.js modules, Angular modules don't try to
solve the problem of script load ordering or lazy script fetching. These goals are totally independent and
both module systems can live side by side and fulfil their goals.
both module systems can live side by side and fulfill their goals.
## The App Module
__`app/js/app.js`:__
```js
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
```
In order to configure our application with routes, we need to create a module for our application.
We call this module `phonecatApp`. Notice the second argument passed to `angular.module`:
`['ngRoute', 'phonecatControllers']`. This array lists the modules that `phonecatApp` depends on.
Above, we added `angular-route.js` to `index.html`. That's not all we need to do to be able to use
it, however. We also have to add `ngRoute` as a dependency of our app. To improve the organization
of the app, we're going to move the controllers into their own file (as shown below), and call the
module `phonecatControllers`. By listing these two modules as dependencies of `phonecatApp`, we
can use the directives and services they provide.
Thus using the `config` API we request the `$routeProvider` to be injected into our config function
and use the {@link ngRoute.$routeProvider#when `$routeProvider.when`} API to define our routes.
Our application routes are defined as follows:
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
view, Angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
`:phoneId` is a variable part of the URL. To construct the phone details view, Angular will use the
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
`$routeProvider.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when the browser
address doesn't match either of our routes.
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
URL. All variables defined with the `:` notation are extracted into the
{@link ngRoute.$routeParams `$routeParams`} object.
In order for our application to bootstrap with our newly created module we'll also need to specify
the module name as the value of the {@link ng.directive:ngApp ngApp}
directive:
__`app/index.html`:__
```html
<!doctype html>
<html lang="en" ng-app="phonecatApp">
...
```
## Controllers
__`app/js/controllers.js`:__
```js
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);
```
Again, note that we created a new module called `phonecatControllers`. For small AngularJS applications,
it's common to create just one module for all of your controllers if there are just a few. For larger apps,
you will probably want to create separate modules for each major feature of your app.
Because our example app is relatively small, we'll add all of our controllers to this module.
To deepen your understanding of DI on Angular, see
[Understanding Dependency Injection](https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection).
## Template
@@ -177,9 +109,8 @@ ngView} directive. The role of the `ngView` directive is to include the view tem
route into the layout template. This makes it a perfect fit for our `index.html` template.
<div class="alert alert-info">
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by loading
the `angular-route.js` file distributed with Angular. The easiest way to load the file is to add a `<script>`
tag to your `index.html` file as shown below.
**Note:** Starting with AngularJS version 1.2, `ngRoute` is in its own module and must be loaded by
loading the additional `angular-route.js` file, which we download via Bower above.
</div>
__`app/index.html`:__
@@ -189,8 +120,8 @@ __`app/index.html`:__
<html lang="en" ng-app="phonecatApp">
<head>
...
<script src="lib/angular/angular.js"></script>
<script src="lib/angular/angular-route.js"></script>
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers.js"></script>
</head>
@@ -202,6 +133,12 @@ __`app/index.html`:__
</html>
```
We have added two new `<script>` tags in our index file to load up extra JavaScript files into our
application:
- `angular-route.js` : defines the Angular `ngRoute` module, which provides us with routing.
- `app.js` : this file now holds the root module of our application.
Note that we removed most of the code in the `index.html` template and replaced it with a single
line containing a div with the `ng-view` attribute. The code that we removed was placed into the
`phone-list.html` template:
@@ -210,8 +147,8 @@ __`app/partials/phone-list.html`:__
```html
<div class="container-fluid">
<div class="row-fluid">
<div class="span2">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="query">
@@ -222,7 +159,7 @@ __`app/partials/phone-list.html`:__
</select>
</div>
<div class="span10">
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
@@ -248,10 +185,116 @@ We also added a placeholder template for the phone details view:
__`app/partials/phone-detail.html`:__
```html
TBD: detail view for {{phoneId}}
TBD: detail view for <span>{{phoneId}}</span>
```
Note how we are using `phoneId` model defined in the `PhoneDetailCtrl` controller.
Note how we are using the `phoneId` expression which will be defined in the `PhoneDetailCtrl` controller.
## The App Module
To improve the organization of the app, we are making use of Angular's `ngRoute` module and we've
moved the controllers into their own module `phonecatControllers` (as shown below).
We added `angular-route.js` to `index.html` and created a new `phonecatControllers` module in
`controllers.js`. That's not all we need to do to be able to use their code, however. We also have
to add the modules dependencies of our app. By listing these two modules as dependencies of
`phonecatApp`, we can use the directives and services they provide.
__`app/js/app.js`:__
```js
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
...
```
Notice the second argument passed to `angular.module`, `['ngRoute', 'phonecatControllers']`. This
array lists the modules that `phonecatApp` depends on.
```js
...
phonecatApp.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/phones', {
templateUrl: 'partials/phone-list.html',
controller: 'PhoneListCtrl'
}).
when('/phones/:phoneId', {
templateUrl: 'partials/phone-detail.html',
controller: 'PhoneDetailCtrl'
}).
otherwise({
redirectTo: '/phones'
});
}]);
```
Using the `phonecatApp.config()` method, we request the `$routeProvider` to be injected into our
config function and use the {@link ngRoute.$routeProvider#when `$routeProvider.when()`} method to
define our routes.
Our application routes are defined as follows:
* `when('/phones')`: The phone list view will be shown when the URL hash fragment is `/phones`. To
construct this view, Angular will use the `phone-list.html` template and the `PhoneListCtrl`
controller.
* `when('/phones/:phoneId')`: The phone details view will be shown when the URL hash fragment
matches '/phone/:phoneId', where `:phoneId` is a variable part of the URL. To construct the phone
details view, Angular will use the `phone-detail.html` template and the `PhoneDetailCtrl`
controller.
* `otherwise({redirectTo: '/phones'})`: triggers a redirection to `/phones` when the browser
address doesn't match either of our routes.
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
Note the use of the `:phoneId` parameter in the second route declaration. The `$route` service uses
the route declaration — `'/phones/:phoneId'` — as a template that is matched against the current
URL. All variables defined with the `:` notation are extracted into the
{@link ngRoute.$routeParams `$routeParams`} object.
## Controllers
__`app/js/controllers.js`:__
```js
var phonecatControllers = angular.module('phonecatControllers', []);
phonecatControllers.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams',
function($scope, $routeParams) {
$scope.phoneId = $routeParams.phoneId;
}]);
```
Again, note that we created a new module called `phonecatControllers`. For small AngularJS
applications, it's common to create just one module for all of your controllers if there are just a
few. As your application grows it is quite common to refactor your code into additional modules.
For larger apps, you will probably want to create separate modules for each major feature of
your app.
Because our example app is relatively small, we'll just add all of our controllers to the
`phonecatControllers` module.
## Test
@@ -261,10 +304,17 @@ to various URLs and verify that the correct view was rendered.
```js
...
it('should redirect index.html to index.html#/phones', function() {
browser().navigateTo('app/index.html');
expect(browser().location().url()).toBe('/phones');
it('should redirect index.html to index.html#/phones', function() {
browser.get('app/index.html');
browser.getLocationAbsUrl().then(function(url) {
expect(url.split('#')[1]).toBe('/phones');
});
});
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones');
});
...
describe('Phone detail view', function() {
@@ -307,3 +357,6 @@ step 8} to implement the phone details view.
<ul doc-tutorial-nav="7"></ul>
[bower]: http://bower.io
+13 -12
View File
@@ -9,18 +9,15 @@
In this step, you will implement the phone details view, which is displayed when a user clicks on a
phone in the phone list.
* When you click on a phone on the list, the phone details page with phone-specific information
is displayed.
To implement the phone details view we used {@link ng.$http $http} to fetch our data, and we
fleshed out the `phone-detail.html` view template.
<div doc-tutorial-reset="8"></div>
Now when you click on a phone on the list, the phone details page with phone-specific information
is displayed.
To implement the phone details view we will use {@link ng.$http $http} to fetch
our data, and we'll flesh out the `phone-detail.html` view template.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-7...step-8):
## Data
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
@@ -127,7 +124,11 @@ step 5.
__`test/unit/controllersSpec.js`:__
```js
...
beforeEach(module('phonecatApp'));
...
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl;
@@ -153,7 +154,7 @@ __`test/unit/controllersSpec.js`:__
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)
<pre>Chrome 22.0: Executed 3 of 3 SUCCESS (0.039 secs / 0.012 secs)</pre>
We also added a new end-to-end test that navigates to the Nexus S detail page and verifies that the
@@ -183,8 +184,8 @@ You can now rerun `npm run protractor` to see the tests run.
# Experiments
* Using the {@link guide/dev_guide.e2e-testing Angular's end-to-end test runner API}, write a test
that verifies that we display 4 thumbnail images on the Nexus S details page.
* Using the [Protractor API](https://github.com/angular/protractor/blob/master/docs/api.md),
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
# Summary
+11 -12
View File
@@ -5,20 +5,13 @@
<ul doc-tutorial-nav="9"></ul>
In this step you will learn how to create your own custom display filter.
<div doc-tutorial-reset="9"></div>
Navigate to one of the detail pages.
In the previous step, the details page displayed either "true" or "false" to indicate whether
* In the previous step, the details page displayed either "true" or "false" to indicate whether
certain phone features were present or not. We have used a custom filter to convert those text
strings into glyphs: ✓ for "true", and ✘ for "false". Let's see what the filter code looks like.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-8...step-9):
<div doc-tutorial-reset="9"></div>
## Custom Filter
@@ -116,9 +109,13 @@ for this test run.
Note that we call the helper function, `inject(function(checkmarkFilter) { ... })`, to get
access to the filter that we want to test. See {@link angular.mock.inject angular.mock.inject()}.
Notice that the suffix 'Filter' is appended to your filter name when injected.
See the {@link guide/filter#using-filters-in-controllers-services-and-directives Filter Guide}
section where this is outlined.
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)</pre>
# Experiments
@@ -131,9 +128,11 @@ following bindings to `index.html`:
* `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}`
* We can also create a model with an input element, and combine it with a filtered binding. Add
the following to index.html:
the following to index.html:
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
```html
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
```
# Summary
+2 -7
View File
@@ -8,16 +8,11 @@
In this step, you will add a clickable phone image swapper to the phone details page.
<div doc-tutorial-reset="10"></div>
The phone details view displays one large image of the current phone and several smaller thumbnail
* The phone details view displays one large image of the current phone and several smaller thumbnail
images. It would be great if we could replace the large image with any of the thumbnails just by
clicking on the desired thumbnail image. Let's have a look at how we can do this with Angular.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-9...step-10):
<div doc-tutorial-reset="10"></div>
## Controller
+78 -22
View File
@@ -6,37 +6,74 @@
<ul doc-tutorial-nav="11"></ul>
In this step, you will improve the way our app fetches data.
In this step, you will change the way our app fetches data.
* We defined a custom service that represents a [RESTful][restful] client. Using this client we
can make requests to the server for data in an easier way, without having to deal with the
lower-level {@link ng.$http $http} API, HTTP methods and URLs.
<div doc-tutorial-reset="11"></div>
## Dependencies
The next improvement we will make to our app is to define a custom service that represents a [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) client. Using this client we
can make XHR requests for data in an easier way, without having to deal with the lower-level {@link
ng.$http $http} API, HTTP methods and URLs.
The RESTful functionality is provided by Angular in the `ngResource` module, which is distributed
separately from the core Angular framework.
The most important changes are listed below. You can see the full diff on [GitHub](https://github.com/angular/angular-phonecat/compare/step-10...step-11):
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
```
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.x",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.x",
"angular-resource": "~1.2.x"
}
}
```
The new dependency `"angular-resource": "~1.2.x"` tells bower to install a version of the
angular-resource component that is compatible with version 1.2.x. We must ask bower to download
and install this dependency. We can do this by running:
```
npm install
```
<div class="alert alert-info">
If you have bower installed globally then you can run `bower install` but for this project we have
preconfigured `npm install` to run bower for us.
</div>
## Template
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
template. Additionally, we also need to load the `angular-resource.js` file, which contains the
{@link api/ngResource ngResource} module and in it the {@link api/ngResource.$resource $resource}
service, that we'll soon use:
Our custom resource service will be defined in `app/js/services.js` so we need to include this file
in our layout template. Additionally, we also need to load the `angular-resource.js` file, which
contains the {@link api/ngResource ngResource} module:
__`app/index.html`.__
```html
...
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="js/services.js"></script>
<script src="lib/angular/angular-resource.js"></script>
...
```
## Service
We create our own service to provide access to the phone data on the server:
__`app/js/services.js`.__
```js
@@ -52,13 +89,12 @@ phonecatServices.factory('Phone', ['$resource',
We used the module API to register a custom service using a factory function. We passed in the name
of the service - 'Phone' - and the factory function. The factory function is similar to a
controller's constructor in that both can declare dependencies via function arguments. The Phone
service declared a dependency on the `$resource` service.
controller's constructor in that both can declare dependencies to be injected via function
arguments. The Phone service declared a dependency on the `$resource` service.
The {@link ngResource.$resource `$resource`} service makes it easy to create a
[RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) client with just a few
lines of code. This client can then be used in our application, instead of the lower-level {@link
ng.$http $http} service.
[RESTful][restful] client with just a few lines of code. This client can then be used in our
application, instead of the lower-level {@link ng.$http $http} service.
__`app/js/app.js`.__
@@ -128,6 +164,22 @@ we require, so in these cases, we can add a callback to process the server respo
## Test
Because we're now using the {@link ngResource ngResource} module, it's necessary to also need to
update the Karma config file with angular-resource so the new tests will pass.
__`test/karma.conf.js`:__
```js
files : [
'app/bower_components/angular/angular.js',
'app/bower_components/angular-route/angular-route.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-mocks/angular-mocks.js',
'app/js/**/*.js',
'test/unit/**/*.js'
],
```
We have modified our unit tests to verify that our new service is issuing HTTP requests and
processing them as expected. The tests also check that our controllers are interacting with the
service correctly.
@@ -135,13 +187,13 @@ service correctly.
The {@link ngResource.$resource $resource} service augments the response object
with methods for updating and deleting the resource. If we were to use the standard `toEqual`
matcher, our tests would fail because the test values would not match the responses exactly. To
solve the problem, we use a newly-defined `toEqualData` [Jasmine matcher](https://github.com/pivotal/jasmine/wiki/Matchers). When the
`toEqualData` matcher compares two objects, it takes only object properties into account and
solve the problem, we use a newly-defined `toEqualData` [Jasmine matcher][jasmine-matchers]. When
the `toEqualData` matcher compares two objects, it takes only object properties into account and
ignores methods.
__`test/unit/controllersSpec.js`:__
```js
describe('PhoneCat controllers', function() {
@@ -166,12 +218,12 @@ describe('PhoneCat controllers', function() {
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller(PhoneListCtrl, {$scope: scope});
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toEqual([]);
expect(scope.phones).toEqualData([]);
$httpBackend.flush();
expect(scope.phones).toEqualData(
@@ -201,7 +253,7 @@ describe('PhoneCat controllers', function() {
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller(PhoneDetailCtrl, {$scope: scope});
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));
@@ -217,7 +269,7 @@ describe('PhoneCat controllers', function() {
You should now see the following output in the Karma tab:
Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.038 secs / 0.01 secs)</pre>
# Summary
@@ -227,3 +279,7 @@ learn how to improve this application with animations.
<ul doc-tutorial-nav="11"></ul>
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
[jasmine-matchers]: https://github.com/pivotal/jasmine/wiki/Matchers
[bower]: http://bower.io/
+131 -44
View File
@@ -9,22 +9,60 @@
In this final step, we will enhance our phonecat web application by attaching CSS and JavaScript
animations on top of the template code we created before.
* Used the `ngAnimate` to enable animations throughout the application.
* Common `ng` directives automatically trigger hooks for animations to tap into.
* When an animation is found then the animation will run in between the standard DOM operation that
is being issued on the element at the given time (e.g. inserting and removing nodes on
{@link api/ng.directive:ngRepeat `ngRepeat`} or adding and removing classes on
{@link api/ng.directive:ngClass `ngClass`}).
<div doc-tutorial-reset="12"></div>
## Dependencies
Now that everything is set in place for a fully functional web application, we can attach CSS and JavaScript
animations to common directives that are used to render our application. AngularJS comes bundled with an
additional JavaScript file called `angular-animate.js` which, when included into the website and set as
a dependency with the application module, will enable animations throughout the application.
The animation functionality is provided by Angular in the `ngAnimate` module, which is distributed
separately from the core Angular framework. In addition we will use `JQuery` in this project to do
extra JavaScript animations.
Common `ng` directives automatically trigger hooks for animations to tap into. When an animation is found
then the animation will run in between the standard DOM operation that is being issued on the element at
the given time (e.g. inserting and removing nodes on {@link api/ng.directive:ngRepeat `ngRepeat`} or adding
and removing classes on {@link api/ng.directive:ngClass `ngClass`}).
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
The most important changes are listed below. You can see the full diff on
[GitHub](https://github.com/angular/angular-phonecat/compare/step-11...step-12):
```
{
"name": "angular-seed",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-seed",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.2.x",
"angular-mocks": "~1.2.x",
"bootstrap": "~3.1.1",
"angular-route": "~1.2.x",
"angular-resource": "~1.2.x",
"jquery": "1.10.2",
"angular-animate": "~1.2.x"
}
}
```
* `"angular-animate": "~1.2.x"` tells bower to install a version of the
angular-animate component that is compatible with version 1.2.x.
* `"jquery": "1.10.2"` tells bower to install the 1.10.2 version of JQuery. Note that this is not an
Angular library, it is the standard JQuery library. We can use bower to install a wide range of 3rd
party libraries.
We must ask bower to download and install this dependency. We can do this by running:
```
npm install
```
<div class="alert alert-info">
If you have bower installed globally then you can run `bower install` but for this project we have
preconfigured `npm install` to run bower for us.
</div>
## How Animations work with `ngAnimate`
@@ -40,28 +78,35 @@ well as the `angular-animate.js` file. The animation module, known as {@link api
defined within `angular-animate.js` and contains the code necessary to make your application become animation
aware.
Here's what needs to changed in the index file:
Here's what needs to be changed in the index file:
__`app/index.html`.__
```html
...
<!-- for CSS Transitions and/or Keyframe Animations -->
<link rel="stylesheet" href="css/animations.css">
...
<!-- jQuery is used for JavaScript animations (include this before angular.js) -->
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="bower_components/jquery/jquery.js"></script>
...
<!-- required module to enable animation support in AngularJS -->
<script src="lib/angular/angular-animate.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<!-- for JavaScript Animations -->
<script src="js/animations.js"></script>
<!-- for CSS Transitions and/or Keyframe Animations -->
<link rel="stylesheet" href="css/animations.css">
...
```
<div class="alert alert-error">
**Important:** Be sure to use jQuery version `1.10.x`. AngularJS does not yet support jQuery `2.x`.
Be sure to load jQuery before all AngularJS scripts, otherwise AngularJS won't detect jQuery and
animations will not work as expected.
</div>
Animations can now be created within the CSS code (`animations.css`) as well as the JavaScript code (`animations.js`).
@@ -73,7 +118,7 @@ with `ngResource`.
__`app/js/animations.js`.__
```js
angular.module('phonecatAnimations', ['ngAnimate']).
angular.module('phonecatAnimations', ['ngAnimate']);
// ...
// this module will later be used to define animations
// ...
@@ -92,7 +137,7 @@ angular.module('phonecatApp', [
'phonecatControllers',
'phonecatFilters',
'phonecatServices',
]).
]);
// ...
```
@@ -280,18 +325,22 @@ __`app/css/animations.css`.__
/* don't forget about the vendor-prefixes! */
```
Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the ordinary thing
here is that we're using absolute positioning to position the next page (identified via `ng-enter`) on top of the
previous page (the one that has the `ng-leave` class) while performing a cross fade animation in between. So
as the previous page is just about to be removed, it fades out while the new page fades in right on top of it.
Once the leave animation is over then element is removed and once the enter animation is complete then the
`ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to be position itself
with its default CSS code (so no more absolute positioning once the animation is over). This works fluidly so
that pages flow naturally between route changes without anything jumping around.
Nothing crazy here! Just a simple fade in and fade out effect between pages. The only out of the
ordinary thing here is that we're using absolute positioning to position the next page (identified
via `ng-enter`) on top of the previous page (the one that has the `ng-leave` class) while performing
a cross fade animation in between. So as the previous page is just about to be removed, it fades out
while the new page fades in right on top of it.
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time a new page is
loaded the ng-view directive will create a copy of itself, download the template and append the contents. This
ensures that all views are contained within a single HTML element which allows for easy animation control.
Once the leave animation is over then element is removed and once the enter animation is complete
then the `ng-enter` and `ng-enter-active` CSS classes are removed from the element rendering it to
be position itself with its default CSS code (so no more absolute positioning once the animation is
over). This works fluidly so that pages flow naturally between route changes without anything
jumping around.
The CSS classes applied (the start and end classes) are much the same as with `ng-repeat`. Each time
a new page is loaded the ng-view directive will create a copy of itself, download the template and
append the contents. This ensures that all views are contained within a single HTML element which
allows for easy animation control.
For more on CSS animations, see the
[Web Platform documentation](http://docs.webplatform.org/wiki/css/properties/animations).
@@ -303,14 +352,14 @@ Let's add another animation to our application. Switching to our `phone-detail.h
we see that we have a nice thumbnail swapper. By clicking on the thumbnails listed on the page,
the profile phone image changes. But how can we change this around to add animations?
Let's think about it first. Basically, when you click on a thumbnail image, you're changing the state of the profile image to reflect the newly
selected thumbnail image.
Let's think about it first. Basically, when you click on a thumbnail image, you're changing the
state of the profile image to reflect the newly selected thumbnail image.
The best way to specify state changes within HTML is to use classes.
Much like before, how we used a CSS class to specify
an animation, this time the animation will occur whenever the CSS class itself changes.
Much like before, how we used a CSS class to specify an animation, this time the animation will
occur whenever the CSS class itself changes.
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added to the matching
profile image and the animation plays.
Whenever a new phone thumbnail is selected, the state changes and the `.active` CSS class is added
to the matching profile image and the animation plays.
Let's get started and tweak our HTML code on the `phone-detail.html` page first:
@@ -336,18 +385,54 @@ __`app/partials/phone-detail.html`.__
</ul>
```
Just like with the thumbnails, we're using a repeater to display **all** the profile images as a list, however we're
not animating any repeat-related animations. Instead, we're keeping our eye on the ng-class directive since whenever
the `active` class is true then it will be applied to the element and will render as visible. Otherwise, the profile image
is hidden. In our case, there is always one element that has the active class, and, therefore, there will always
be one phone profile image visible on screen at all times.
Just like with the thumbnails, we're using a repeater to display **all** the profile images as a
list, however we're not animating any repeat-related animations. Instead, we're keeping our eye on
the ng-class directive since whenever the `active` class is true then it will be applied to the
element and will render as visible. Otherwise, the profile image is hidden. In our case, there is
always one element that has the active class, and, therefore, there will always be one phone profile
image visible on screen at all times.
When the active class is added to the element, the `active-add` and the `active-add-active` classes
are added just before to signal AngularJS to fire off an animation. When removed, the
`active-remove` and the `active-remove-active` classes are applied to the element which in turn
trigger another animation.
To ensure that the phone images are displayed correctly when the page is first loaded we also tweak
the detail page CSS styles:
__`app/css/app.css`__
```css
.phone-images {
background-color: white;
width: 450px;
height: 450px;
overflow: hidden;
position: relative;
float: left;
}
...
img.phone {
float: left;
margin-right: 3em;
margin-bottom: 2em;
background-color: white;
padding: 2em;
height: 400px;
width: 400px;
display: none;
}
img.phone:first-child {
display: block;
}
```
When the active class is added to the element, the `active-add` and the `active-add-active` classes are added just before
to signal AngularJS to fire off an animation. When removed, the `active-remove` and the `active-remove-active` classes
are applied to the element which in turn trigger another animation.
You may be thinking that we're just going to create another CSS-enabled animation.
Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled animations with the `animation()` module method.
Although we could do that, let's take the opportunity to learn how to create JavaScript-enabled
animations with the `animation()` module method.
__`app/js/animations.js`.__
@@ -411,7 +496,7 @@ isn't required to do JavaScript animations with AngularJS, but we're going to us
your own JavaScript animation library is beyond the scope of this tutorial. For more on
`jQuery.animate`, see the [jQuery documentation](http://api.jquery.com/animate/).
The `addClass` and `removeClass` callback functions are called whenever an a class is added or removed
The `addClass` and `removeClass` callback functions are called whenever a class is added or removed
on the element that contains the class we registered, which is in this case `.phone`. When the `.active`
class is added to the element (via the `ng-class` directive) the `addClass` JavaScript callback will
be fired with `element` passed in as a parameter to that callback. The last parameter passed in is the
@@ -440,3 +525,5 @@ There you have it! We have created a web app in a relatively short amount of ti
the_end closing notes} we'll cover where to go from here.
<ul doc-tutorial-nav="12"></ul>
[bower]: http://bower.io/
+3 -3
View File
@@ -2,7 +2,7 @@ var gulp = require('gulp');
var concat = require('gulp-concat');
var jshint = require('gulp-jshint');
var bower = require('bower');
var docGenerator = require('dgeni');
var dgeni = require('dgeni');
var merge = require('event-stream').merge;
var path = require('canonical-path');
@@ -49,8 +49,8 @@ gulp.task('assets', ['bower'], function() {
gulp.task('doc-gen', function() {
return docGenerator('docs.config.js')
.generateDocs()
var generateDocs = dgeni.generator('docs.config.js');
return generateDocs()
.catch(function(error) {
process.exit(1);
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 56 KiB

+57 -9
View File
@@ -87,7 +87,11 @@ function pluralExtractor(content, localeInfo) {
continue;
}
var temp = goog.i18n.pluralRules.select.toString().
replace(/goog.i18n.pluralRules.Keyword/g, 'PLURAL_CATEGORY').replace(/\n/g, '');
replace(/goog\.i18n\.pluralRules\.Keyword/g, 'PLURAL_CATEGORY').
replace(/goog\.i18n\.pluralRules\.get_vf_/g, 'getVF').
replace(/goog\.i18n\.pluralRules\.get_wt_/g, 'getWT').
replace(/goog\.i18n\.pluralRules\.decimals_/g, 'getDecimals').
replace(/\n/g, '');
///@@ is a crazy place holder to be replaced before writing to file
localeInfo[localeIds[i]].pluralCat = "@@" + temp + "@@";
@@ -158,15 +162,39 @@ function outputLocale(localeInfo, localeID) {
}
localeObj.id = correctedLocaleId(localeID);
var prefix =
"'use strict';\n" +
'angular.module("ngLocale", [], ["$provide", function($provide) {\n' +
'var PLURAL_CATEGORY = {' +
'ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"' +
'};\n' +
'$provide.value("$locale", ';
var getDecimals = [
'function getDecimals(n) {',
' n = n + \'\';',
' var i = n.indexOf(\'.\');',
' return (i == -1) ? 0 : n.length - i - 1;',
'}', '', ''
].join('\n');
var suffix = ');\n}]);';
var getVF = [
'function getVF(n, opt_precision) {',
' var v = opt_precision;', '',
' if (undefined === v) {',
' v = Math.min(getDecimals(n), 3);',
' }', '',
' var base = Math.pow(10, v);',
' var f = ((n * base) | 0) % base;',
' return {v: v, f: f};',
'}', '', ''
].join('\n');
var getWT =
[
'function getWT(v, f) {',
' if (f === 0) {',
' return {w: 0, t: 0};',
' }', '',
' while ((f % 10) === 0) {',
' f /= 10;',
' v--;',
' }', '',
' return {w: v, t: f};',
'}', '', ''
].join('\n');
localeObj = {
DATETIME_FORMATS: localeObj.DATETIME_FORMATS,
@@ -176,6 +204,26 @@ function outputLocale(localeInfo, localeID) {
};
var content = serializeContent(localeInfo[localeID]);
if (content.indexOf('getVF(') < 0) {
getVF = '';
}
if (content.indexOf('getWT(') < 0) {
getWT = '';
}
if (!getVF && content.indexOf('getDecimals(') < 0) {
getDecimals = '';
}
var prefix =
"'use strict';\n" +
'angular.module("ngLocale", [], ["$provide", function($provide) {\n' +
'var PLURAL_CATEGORY = {' +
'ZERO: "zero", ONE: "one", TWO: "two", FEW: "few", MANY: "many", OTHER: "other"' +
'};\n' +
getDecimals + getVF + getWT +
'$provide.value("$locale", ';
var suffix = ');\n}]);';
return prefix + content + suffix;
}
+7 -5
View File
@@ -7,9 +7,11 @@ cd $BASE_DIR
set -x # Trace commands as they're executed.
I18N_BASE="https://raw.githubusercontent.com/google/closure-library/master/closure/goog/i18n"
# use the github repo as it is more up to date than the svn repo
curl https://closure-library.googlecode.com/git/closure/goog/i18n/currency.js > closure/currencySymbols.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/datetimesymbols.js > closure/datetimeSymbols.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/datetimesymbolsext.js > closure/datetimeSymbolsExt.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/numberformatsymbols.js > closure/numberSymbols.js
curl https://closure-library.googlecode.com/git/closure/goog/i18n/pluralrules.js > closure/pluralRules.js
curl "$I18N_BASE/currency.js" > closure/currencySymbols.js
curl "$I18N_BASE/datetimesymbols.js" > closure/datetimeSymbols.js
curl "$I18N_BASE/datetimesymbolsext.js" > closure/datetimeSymbolsExt.js
curl "$I18N_BASE/numberformatsymbols.js" > closure/numberSymbols.js
curl "$I18N_BASE/pluralrules.js" > closure/pluralRules.js
+2 -1
View File
@@ -32,7 +32,8 @@ module.exports = function(config, specificOptions) {
customLaunchers: {
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome'
browserName: 'chrome',
version: '34'
},
'SL_Firefox': {
base: 'SauceLabs',
+1 -1
View File
@@ -136,7 +136,7 @@ module.exports = {
.replace(/\\/g, '\\\\')
.replace(/'/g, "\\'")
.replace(/\r?\n/g, '\\n');
js = "!angular.$$csp() && angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
js = "!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type=\"text/css\">" + css + "</style>');";
state.js.push(js);
return state;
+112 -40
View File
@@ -466,7 +466,7 @@
"version": "0.0.2"
},
"dgeni": {
"version": "0.2.2",
"version": "0.3.0",
"dependencies": {
"rimraf": {
"version": "2.2.6"
@@ -498,39 +498,17 @@
},
"di": {
"version": "0.0.1"
},
"marked": {
"version": "0.2.10"
},
"glob": {
"version": "3.2.9",
"dependencies": {
"minimatch": {
"version": "0.2.14",
"dependencies": {
"lru-cache": {
"version": "2.5.0"
},
"sigmund": {
"version": "1.0.0"
}
}
},
"inherits": {
"version": "2.0.1"
}
}
}
}
},
"dgeni-packages": {
"version": "0.8.2",
"version": "0.9.1",
"dependencies": {
"lodash": {
"version": "2.4.1"
},
"graceful-fs": {
"version": "2.0.2"
"version": "2.0.3"
},
"glob": {
"version": "3.2.9",
@@ -552,7 +530,7 @@
}
},
"nunjucks": {
"version": "1.0.1",
"version": "1.0.5",
"dependencies": {
"optimist": {
"version": "0.6.1",
@@ -564,14 +542,83 @@
"version": "0.0.8"
}
}
},
"chokidar": {
"version": "0.8.2",
"dependencies": {
"fsevents": {
"version": "0.2.0",
"dependencies": {
"nan": {
"version": "0.8.0"
}
}
},
"recursive-readdir": {
"version": "0.0.2"
}
}
}
}
},
"catharsis": {
"version": "0.7.0"
"version": "0.7.1"
},
"esprima": {
"version": "1.0.4"
"version": "1.2.0"
},
"change-case": {
"version": "2.1.1",
"dependencies": {
"camel-case": {
"version": "0.1.0"
},
"constant-case": {
"version": "0.1.0"
},
"dot-case": {
"version": "0.1.0"
},
"is-lower-case": {
"version": "0.0.1"
},
"is-upper-case": {
"version": "0.0.1"
},
"lower-case": {
"version": "0.0.1"
},
"param-case": {
"version": "0.1.0"
},
"pascal-case": {
"version": "0.1.1"
},
"path-case": {
"version": "0.1.0"
},
"sentence-case": {
"version": "0.1.1"
},
"snake-case": {
"version": "0.1.1"
},
"swap-case": {
"version": "0.0.1"
},
"title-case": {
"version": "0.1.0"
},
"upper-case": {
"version": "0.0.1"
},
"upper-case-first": {
"version": "0.0.1"
}
}
},
"marked": {
"version": "0.2.10"
}
}
},
@@ -865,7 +912,7 @@
},
"grunt-jasmine-node": {
"version": "0.1.0",
"from": "git://github.com/vojtajina/grunt-jasmine-node.git#fix-grunt-exit-code",
"from": "grunt-jasmine-node@git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7",
"resolved": "git://github.com/vojtajina/grunt-jasmine-node.git#ced17cbe52c1412b2ada53160432a5b681f37cd7"
},
"grunt-jscs-checker": {
@@ -1969,7 +2016,7 @@
}
},
"karma": {
"version": "0.12.0",
"version": "0.12.9",
"dependencies": {
"di": {
"version": "0.0.1"
@@ -2025,7 +2072,20 @@
}
},
"chokidar": {
"version": "0.8.1"
"version": "0.8.2",
"dependencies": {
"fsevents": {
"version": "0.2.0",
"dependencies": {
"nan": {
"version": "0.8.0"
}
}
},
"recursive-readdir": {
"version": "0.0.2"
}
}
},
"glob": {
"version": "3.2.9",
@@ -2065,7 +2125,7 @@
"version": "0.3.2"
},
"mkdirp": {
"version": "0.3.5"
"version": "0.4.0"
},
"ncp": {
"version": "0.4.2"
@@ -2101,7 +2161,7 @@
"version": "1.2.11"
},
"log4js": {
"version": "0.6.12",
"version": "0.6.14",
"dependencies": {
"async": {
"version": "0.1.15"
@@ -2110,17 +2170,26 @@
"version": "1.1.4"
},
"readable-stream": {
"version": "1.0.26-2",
"version": "1.0.27-1",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.25-1"
},
"inherits": {
"version": "2.0.1"
}
}
}
}
},
"useragent": {
"version": "2.0.7",
"version": "2.0.8",
"dependencies": {
"lru-cache": {
"version": "2.2.4"
@@ -2128,7 +2197,7 @@
}
},
"graceful-fs": {
"version": "2.0.2"
"version": "2.0.3"
},
"connect": {
"version": "2.12.0",
@@ -2169,7 +2238,7 @@
"version": "0.0.3"
},
"debug": {
"version": "0.7.4"
"version": "0.8.0"
},
"methods": {
"version": "0.1.0"
@@ -2184,16 +2253,19 @@
"version": "2.2.0",
"dependencies": {
"readable-stream": {
"version": "1.1.11",
"version": "1.1.13-1",
"dependencies": {
"core-util-is": {
"version": "1.0.1"
},
"isarray": {
"version": "0.0.1"
},
"string_decoder": {
"version": "0.10.25-1"
},
"debuglog": {
"version": "0.0.2"
"inherits": {
"version": "2.0.1"
}
}
},
+4 -5
View File
@@ -23,8 +23,6 @@
"bower": "~1.2.2",
"jasmine-node": "~1.11.0",
"q": "~1.0.0",
"q-io": "~1.10.6",
"qq": "~0.3.5",
"shelljs": "~0.2.6",
"karma": "^0.12.0",
"karma-jasmine": "0.1.5",
@@ -49,12 +47,13 @@
"gulp-concat": "~2.1.7",
"canonical-path": "0.0.2",
"winston": "~0.7.2",
"dgeni": "^0.2.2",
"dgeni-packages": "^0.8.1",
"dgeni": "^0.3.0",
"dgeni-packages": "^0.9.1",
"gulp-jshint": "~1.4.2",
"jshint-stylish": "~0.1.5",
"node-html-encoder": "0.0.2",
"sorted-object": "^1.0.0"
"sorted-object": "^1.0.0",
"qq": "^0.3.5"
},
"licenses": [
{
+2 -1
View File
@@ -12,7 +12,8 @@ config.multiCapabilities = [{
'browserName': 'firefox',
'name': 'Angular E2E',
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'build': process.env.TRAVIS_BUILD_NUMBER
'build': process.env.TRAVIS_BUILD_NUMBER,
'version': '28'
}, {
browserName: 'safari',
'platform': 'OS X 10.9',
+1 -1
View File
@@ -23,7 +23,7 @@ function cleanModule(module, name) {
if (name === 'chokidar') {
if (module.version === '0.8.1') {
delete module.dependencies;
} else {
} else if ( module.version !== '0.8.2') {
throw new Error("Unfamiliar chokidar version (v" + module.version +
") , please check status of https://github.com/paulmillr/chokidar/pull/106");
}
+1 -1
View File
@@ -8,7 +8,7 @@ if [ $JOB = "unit" ]; then
grunt ci-checks
grunt test:promises-aplus
grunt test:unit --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
grunt test:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
grunt tests:docs --browsers SL_Chrome,SL_Safari,SL_Firefox,SL_IE_8,SL_IE_9,SL_IE_10,SL_IE_11 --reporters dots
elif [ $JOB = "e2e" ]; then
export TARGET_SPECS="build/docs/ptore2e/**/*jqlite_test.js"
if [ $TEST_TARGET = "jquery" ]; then
+1 -1
View File
@@ -38,7 +38,6 @@
"uppercase": false,
"manualLowercase": false,
"manualUppercase": false,
"nodeName_": false,
"isArrayLike": false,
"forEach": false,
"sortedKeys": false,
@@ -101,6 +100,7 @@
"assertNotHasOwnProperty": false,
"getter": false,
"getBlockElements": false,
"VALIDITY_STATE_PROPERTY": false,
/* AngularPublic.js */
"version": false,
+92 -55
View File
@@ -10,10 +10,10 @@
-push,
-toString,
-ngMinErr,
-_angular,
-angularModule,
-nodeName_,
-uid,
-VALIDITY_STATE_PROPERTY,
-lowercase,
-uppercase,
@@ -103,11 +103,15 @@
* <div doc-module-components="ng"></div>
*/
// The name of a form control's ValidityState property.
// This is used so that it's possible for internal tests to create mock ValidityStates.
var VALIDITY_STATE_PROPERTY = 'validity';
/**
* @ngdoc function
* @name angular.lowercase
* @module ng
* @function
* @kind function
*
* @description Converts the specified string to lowercase.
* @param {string} string String to be converted to lowercase.
@@ -120,7 +124,7 @@ var hasOwnProperty = Object.prototype.hasOwnProperty;
* @ngdoc function
* @name angular.uppercase
* @module ng
* @function
* @kind function
*
* @description Converts the specified string to uppercase.
* @param {string} string String to be converted to uppercase.
@@ -161,8 +165,6 @@ var /** holds major version number for IE or NaN for real browsers */
toString = Object.prototype.toString,
ngMinErr = minErr('ng'),
_angular = window.angular,
/** @name angular */
angular = window.angular || (window.angular = {}),
angularModule,
@@ -204,7 +206,7 @@ function isArrayLike(obj) {
* @ngdoc function
* @name angular.forEach
* @module ng
* @function
* @kind function
*
* @description
* Invokes the `iterator` function once for each item in `obj` collection, which can be either an
@@ -218,7 +220,7 @@ function isArrayLike(obj) {
```js
var values = {name: 'misko', gender: 'male'};
var log = [];
angular.forEach(values, function(value, key){
angular.forEach(values, function(value, key) {
this.push(key + ': ' + value);
}, log);
expect(log).toEqual(['name: misko', 'gender: male']);
@@ -232,7 +234,7 @@ function isArrayLike(obj) {
function forEach(obj, iterator, context) {
var key;
if (obj) {
if (isFunction(obj)){
if (isFunction(obj)) {
for (key in obj) {
// Need to check if hasOwnProperty exists,
// as on IE8 the result of querySelectorAll is an object without a hasOwnProperty function
@@ -333,7 +335,7 @@ function setHashKey(obj, h) {
* @ngdoc function
* @name angular.extend
* @module ng
* @function
* @kind function
*
* @description
* Extends the destination object `dst` by copying all of the properties from the `src` object(s)
@@ -345,9 +347,9 @@ function setHashKey(obj, h) {
*/
function extend(dst) {
var h = dst.$$hashKey;
forEach(arguments, function(obj){
forEach(arguments, function(obj) {
if (obj !== dst) {
forEach(obj, function(value, key){
forEach(obj, function(value, key) {
dst[key] = value;
});
}
@@ -370,7 +372,7 @@ function inherit(parent, extra) {
* @ngdoc function
* @name angular.noop
* @module ng
* @function
* @kind function
*
* @description
* A function that performs no operations. This function can be useful when writing code in the
@@ -390,7 +392,7 @@ noop.$inject = [];
* @ngdoc function
* @name angular.identity
* @module ng
* @function
* @kind function
*
* @description
* A function that returns its first argument. This function is useful when writing code in the
@@ -412,7 +414,7 @@ function valueFn(value) {return function() {return value;};}
* @ngdoc function
* @name angular.isUndefined
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is undefined.
@@ -427,7 +429,7 @@ function isUndefined(value){return typeof value === 'undefined';}
* @ngdoc function
* @name angular.isDefined
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is defined.
@@ -442,7 +444,7 @@ function isDefined(value){return typeof value !== 'undefined';}
* @ngdoc function
* @name angular.isObject
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is an `Object`. Unlike `typeof` in JavaScript, `null`s are not
@@ -458,7 +460,7 @@ function isObject(value){return value != null && typeof value === 'object';}
* @ngdoc function
* @name angular.isString
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is a `String`.
@@ -473,7 +475,7 @@ function isString(value){return typeof value === 'string';}
* @ngdoc function
* @name angular.isNumber
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is a `Number`.
@@ -488,7 +490,7 @@ function isNumber(value){return typeof value === 'number';}
* @ngdoc function
* @name angular.isDate
* @module ng
* @function
* @kind function
*
* @description
* Determines if a value is a date.
@@ -496,7 +498,7 @@ function isNumber(value){return typeof value === 'number';}
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Date`.
*/
function isDate(value){
function isDate(value) {
return toString.call(value) === '[object Date]';
}
@@ -505,7 +507,7 @@ function isDate(value){
* @ngdoc function
* @name angular.isArray
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is an `Array`.
@@ -513,16 +515,20 @@ function isDate(value){
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is an `Array`.
*/
function isArray(value) {
return toString.call(value) === '[object Array]';
}
var isArray = (function() {
if (!isFunction(Array.isArray)) {
return function(value) {
return toString.call(value) === '[object Array]';
};
}
return Array.isArray;
})();
/**
* @ngdoc function
* @name angular.isFunction
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is a `Function`.
@@ -596,7 +602,7 @@ var trim = (function() {
* @ngdoc function
* @name angular.isElement
* @module ng
* @function
* @kind function
*
* @description
* Determines if a reference is a DOM element (or wrapped jQuery element).
@@ -614,7 +620,7 @@ function isElement(node) {
* @param str 'key1,key2,...'
* @returns {object} in the form of {key1:true, key2:true, ...}
*/
function makeMap(str){
function makeMap(str) {
var obj = {}, items = str.split(","), i;
for ( i = 0; i < items.length; i++ )
obj[ items[i] ] = true;
@@ -661,7 +667,7 @@ function size(obj, ownPropsOnly) {
if (isArray(obj) || isString(obj)) {
return obj.length;
} else if (isObject(obj)){
} else if (isObject(obj)) {
for (key in obj)
if (!ownPropsOnly || obj.hasOwnProperty(key))
count++;
@@ -707,7 +713,7 @@ function isLeafNode (node) {
* @ngdoc function
* @name angular.copy
* @module ng
* @function
* @kind function
*
* @description
* Creates a deep copy of `source`, which should be an object or an array.
@@ -760,7 +766,7 @@ function isLeafNode (node) {
</file>
</example>
*/
function copy(source, destination){
function copy(source, destination, stackSource, stackDest) {
if (isWindow(source) || isScope(source)) {
throw ngMinErr('cpws',
"Can't copy! Making copies of Window or Scope instances is not supported.");
@@ -770,52 +776,82 @@ function copy(source, destination){
destination = source;
if (source) {
if (isArray(source)) {
destination = copy(source, []);
destination = copy(source, [], stackSource, stackDest);
} else if (isDate(source)) {
destination = new Date(source.getTime());
} else if (isRegExp(source)) {
destination = new RegExp(source.source);
} else if (isObject(source)) {
destination = copy(source, {});
destination = copy(source, {}, stackSource, stackDest);
}
}
} else {
if (source === destination) throw ngMinErr('cpi',
"Can't copy! Source and destination are identical.");
stackSource = stackSource || [];
stackDest = stackDest || [];
if (isObject(source)) {
var index = indexOf(stackSource, source);
if (index !== -1) return stackDest[index];
stackSource.push(source);
stackDest.push(destination);
}
var result;
if (isArray(source)) {
destination.length = 0;
for ( var i = 0; i < source.length; i++) {
destination.push(copy(source[i]));
result = copy(source[i], null, stackSource, stackDest);
if (isObject(source[i])) {
stackSource.push(source[i]);
stackDest.push(result);
}
destination.push(result);
}
} else {
var h = destination.$$hashKey;
forEach(destination, function(value, key){
forEach(destination, function(value, key) {
delete destination[key];
});
for ( var key in source) {
destination[key] = copy(source[key]);
result = copy(source[key], null, stackSource, stackDest);
if (isObject(source[key])) {
stackSource.push(source[key]);
stackDest.push(result);
}
destination[key] = result;
}
setHashKey(destination,h);
}
}
return destination;
}
/**
* Create a shallow copy of an object
* Creates a shallow copy of an object, an array or a primitive
*/
function shallowCopy(src, dst) {
dst = dst || {};
if (isArray(src)) {
dst = dst || [];
for(var key in src) {
// shallowCopy is only ever called by $compile nodeLinkFn, which has control over src
// so we don't need to worry about using our custom hasOwnProperty here
if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
for ( var i = 0; i < src.length; i++) {
dst[i] = src[i];
}
} else if (isObject(src)) {
dst = dst || {};
for (var key in src) {
if (hasOwnProperty.call(src, key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) {
dst[key] = src[key];
}
}
}
return dst;
return dst || src;
}
@@ -823,7 +859,7 @@ function shallowCopy(src, dst) {
* @ngdoc function
* @name angular.equals
* @module ng
* @function
* @kind function
*
* @description
* Determines if two objects or two values are equivalent. Supports value types, regular
@@ -835,7 +871,7 @@ function shallowCopy(src, dst) {
* * Both objects or values are of the same type and all of their properties are equal by
* comparing them with `angular.equals`.
* * Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal)
* * Both values represent the same regular expression (In JavasScript,
* * Both values represent the same regular expression (In JavaScript,
* /abc/ == /abc/ => false. But we consider two regular expressions as equal when their textual
* representation matches).
*
@@ -910,7 +946,7 @@ function sliceArgs(args, startIndex) {
* @ngdoc function
* @name angular.bind
* @module ng
* @function
* @kind function
*
* @description
* Returns a function which calls function `fn` bound to `self` (`self` becomes the `this` for
@@ -966,7 +1002,7 @@ function toJsonReplacer(key, value) {
* @ngdoc function
* @name angular.toJson
* @module ng
* @function
* @kind function
*
* @description
* Serializes input into a JSON-formatted string. Properties with leading $ characters will be
@@ -986,7 +1022,7 @@ function toJson(obj, pretty) {
* @ngdoc function
* @name angular.fromJson
* @module ng
* @function
* @kind function
*
* @description
* Deserializes a JSON string.
@@ -1063,7 +1099,7 @@ function tryDecodeURIComponent(value) {
*/
function parseKeyValue(/**string*/keyValue) {
var obj = {}, key_value, key;
forEach((keyValue || "").split('&'), function(keyValue){
forEach((keyValue || "").split('&'), function(keyValue) {
if ( keyValue ) {
key_value = keyValue.split('=');
key = tryDecodeURIComponent(key_value[0]);
@@ -1243,7 +1279,7 @@ function angularInit(element, bootstrap) {
*
* Angular will detect if it has been loaded into the browser more than once and only allow the
* first loaded script to be bootstrapped and will report a warning to the browser console for
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
* each of the subsequent scripts. This prevents strange results in applications, where otherwise
* multiple instances of Angular try to work on the DOM.
*
* <example name="multi-bootstrap" module="multi-bootstrap">
@@ -1325,7 +1361,7 @@ function bootstrap(element, modules) {
}
var SNAKE_CASE_REGEXP = /[A-Z]/g;
function snake_case(name, separator){
function snake_case(name, separator) {
separator = separator || '_';
return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
return (pos ? separator : '') + letter.toLowerCase();
@@ -1335,8 +1371,9 @@ function snake_case(name, separator){
function bindJQuery() {
// bind to jQuery if present;
jQuery = window.jQuery;
// reset to jQuery or default to us.
if (jQuery) {
// Use jQuery if it exists with proper functionality, otherwise default to us.
// Angular 1.2+ requires jQuery 1.7.1+ for on()/off() support.
if (jQuery && jQuery.fn.on) {
jqLite = jQuery;
extend(jQuery.fn, {
scope: JQLitePrototype.scope,
@@ -1372,7 +1409,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) {
}
assertArg(isFunction(arg), name, 'not a function, got ' +
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg));
(arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
return arg;
}
+13 -7
View File
@@ -13,16 +13,16 @@
* @returns {string} hash string such that the same input will have the same hash string.
* The resulting string key is in 'type:hashKey' format.
*/
function hashKey(obj) {
function hashKey(obj, nextUidFn) {
var objType = typeof obj,
key;
if (objType == 'object' && obj !== null) {
if (objType == 'function' || (objType == 'object' && obj !== null)) {
if (typeof (key = obj.$$hashKey) == 'function') {
// must invoke on object to keep the right this
key = obj.$$hashKey();
} else if (key === undefined) {
key = obj.$$hashKey = nextUid();
key = obj.$$hashKey = (nextUidFn || nextUid)();
}
} else {
key = obj;
@@ -34,7 +34,13 @@ function hashKey(obj) {
/**
* HashMap which can use objects as keys
*/
function HashMap(array){
function HashMap(array, isolatedUid) {
if (isolatedUid) {
var uid = 0;
this.nextUid = function() {
return ++uid;
};
}
forEach(array, this.put, this);
}
HashMap.prototype = {
@@ -44,7 +50,7 @@ HashMap.prototype = {
* @param value value to store can be any type
*/
put: function(key, value) {
this[hashKey(key)] = value;
this[hashKey(key, this.nextUid)] = value;
},
/**
@@ -52,7 +58,7 @@ HashMap.prototype = {
* @returns {Object} the value for the key
*/
get: function(key) {
return this[hashKey(key)];
return this[hashKey(key, this.nextUid)];
},
/**
@@ -60,7 +66,7 @@ HashMap.prototype = {
* @param key
*/
remove: function(key) {
var value = this[key = hashKey(key)];
var value = this[key = hashKey(key, this.nextUid)];
delete this[key];
return value;
}
+13 -13
View File
@@ -4,7 +4,7 @@
* @ngdoc function
* @module ng
* @name angular.injector
* @function
* @kind function
*
* @description
* Creates an injector function that can be used for retrieving services as well as for
@@ -31,7 +31,7 @@
*
* Sometimes you want to get access to the injector of a currently running Angular app
* from outside Angular. Perhaps, you want to inject and compile some markup after the
* application has been bootstrapped. You can do this using extra `injector()` added
* application has been bootstrapped. You can do this using the extra `injector()` added
* to JQuery/jqLite elements. See {@link angular.element}.
*
* *This is fairly rare but could be the case if a third party library is injecting the
@@ -72,7 +72,7 @@ function annotate(fn) {
argDecl,
last;
if (typeof fn == 'function') {
if (typeof fn === 'function') {
if (!($inject = fn.$inject)) {
$inject = [];
if (fn.length) {
@@ -101,7 +101,7 @@ function annotate(fn) {
/**
* @ngdoc service
* @name $injector
* @function
* @kind function
*
* @description
*
@@ -144,7 +144,7 @@ function annotate(fn) {
* minification, and obfuscation tools since these tools change the argument names.
*
* ## `$inject` Annotation
* By adding a `$inject` property onto a function the injection parameters can be specified.
* By adding an `$inject` property onto a function the injection parameters can be specified.
*
* ## Inline
* As an array of injection names, where the last item in the array is the function to call.
@@ -181,7 +181,7 @@ function annotate(fn) {
* @name $injector#has
*
* @description
* Allows the user to query if the particular service exist.
* Allows the user to query if the particular service exists.
*
* @param {string} Name of the service to query.
* @returns {boolean} returns true if injector has given service.
@@ -191,8 +191,8 @@ function annotate(fn) {
* @ngdoc method
* @name $injector#instantiate
* @description
* Create a new instance of JS type. The method takes a constructor function invokes the new
* operator and supplies all of the arguments to the constructor function as specified by the
* Create a new instance of JS type. The method takes a constructor function, invokes the new
* operator, and supplies all of the arguments to the constructor function as specified by the
* constructor annotation.
*
* @param {Function} Type Annotated constructor function.
@@ -285,7 +285,7 @@ function annotate(fn) {
/**
* @ngdoc object
* @ngdoc service
* @name $provide
*
* @description
@@ -591,7 +591,7 @@ function createInjector(modulesToLoad) {
var INSTANTIATING = {},
providerSuffix = 'Provider',
path = [],
loadedModules = new HashMap(),
loadedModules = new HashMap([], true),
providerCache = {
$provide: {
provider: supportObject(provider),
@@ -724,7 +724,8 @@ function createInjector(modulesToLoad) {
function getService(serviceName) {
if (cache.hasOwnProperty(serviceName)) {
if (cache[serviceName] === INSTANTIATING) {
throw $injectorMinErr('cdep', 'Circular dependency found: {0}', path.join(' <- '));
throw $injectorMinErr('cdep', 'Circular dependency found: {0}',
serviceName + ' <- ' + path.join(' <- '));
}
return cache[serviceName];
} else {
@@ -761,8 +762,7 @@ function createInjector(modulesToLoad) {
: getService(key)
);
}
if (!fn.$inject) {
// this means that we must be an array.
if (isArray(fn)) {
fn = fn[length];
}
+12 -9
View File
@@ -16,7 +16,7 @@
* @ngdoc function
* @name angular.element
* @module ng
* @function
* @kind function
*
* @description
* Wraps a raw DOM element or HTML string as a [jQuery](http://jquery.com) element.
@@ -98,8 +98,9 @@
* @returns {Object} jQuery object.
*/
JQLite.expando = 'ng339';
var jqCache = JQLite.cache = {},
jqName = JQLite.expando = 'ng-' + new Date().getTime(),
jqId = 1,
addEventListenerFn = (window.document.addEventListener
? function(element, type, fn) {element.addEventListener(type, fn, false);}
@@ -309,7 +310,7 @@ function jqLiteOff(element, type, fn, unsupported) {
}
function jqLiteRemoveData(element, name) {
var expandoId = element[jqName],
var expandoId = element.ng339,
expandoStore = jqCache[expandoId];
if (expandoStore) {
@@ -323,17 +324,17 @@ function jqLiteRemoveData(element, name) {
jqLiteOff(element);
}
delete jqCache[expandoId];
element[jqName] = undefined; // ie does not allow deletion of attributes on elements.
element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
}
}
function jqLiteExpandoStore(element, key, value) {
var expandoId = element[jqName],
var expandoId = element.ng339,
expandoStore = jqCache[expandoId || -1];
if (isDefined(value)) {
if (!expandoStore) {
element[jqName] = expandoId = jqNextId();
element.ng339 = expandoId = jqNextId();
expandoStore = jqCache[expandoId] = {};
}
expandoStore[key] = value;
@@ -652,6 +653,7 @@ forEach({
*/
JQLite.prototype[name] = function(arg1, arg2) {
var i, key;
var nodeCount = this.length;
// jqLiteHasClass has only two arguments, but is a getter-only fn, so we need to special-case it
// in a way that survives minification.
@@ -661,7 +663,7 @@ forEach({
if (isObject(arg1)) {
// we are a write, but the object properties are the key/values
for (i = 0; i < this.length; i++) {
for (i = 0; i < nodeCount; i++) {
if (fn === jqLiteData) {
// data() takes the whole object in jQuery
fn(this[i], arg1);
@@ -675,9 +677,10 @@ forEach({
return this;
} else {
// we are a read, so read the first child.
// TODO: do we still need this?
var value = fn.$dv;
// Only if we have $dv do we iterate over all, otherwise it is just the first element.
var jj = (value === undefined) ? Math.min(this.length, 1) : this.length;
var jj = (value === undefined) ? Math.min(nodeCount, 1) : nodeCount;
for (var j = 0; j < jj; j++) {
var nodeValue = fn(this[j], arg1, arg2);
value = value ? value + nodeValue : nodeValue;
@@ -686,7 +689,7 @@ forEach({
}
} else {
// we are a write, so apply to all children
for (i = 0; i < this.length; i++) {
for (i = 0; i < nodeCount; i++) {
fn(this[i], arg1, arg2);
}
// return self for chaining
+6 -4
View File
@@ -44,7 +44,7 @@ function setupModuleLoader(window) {
*
* # Module
*
* A module is a collection of services, directives, filters, and configuration information.
* A module is a collection of services, directives, controllers, filters, and configuration information.
* `angular.module` is used to configure the {@link auto.$injector $injector}.
*
* ```js
@@ -72,9 +72,9 @@ function setupModuleLoader(window) {
* {@link angular.bootstrap} to simplify this process for you.
*
* @param {!string} name The name of the module to create or retrieve.
<<<<<* @param {!Array.<string>=} requires If specified then new module is being created. If
>>>>>* unspecified then the module is being retrieved for further configuration.
* @param {Function} configFn Optional configuration function for the module. Same as
* @param {!Array.<string>=} requires If specified then new module is being created. If
* unspecified then the module is being retrieved for further configuration.
* @param {Function=} configFn Optional configuration function for the module. Same as
* {@link angular.Module#config Module#config()}.
* @returns {module} new module with the {@link angular.Module} api.
*/
@@ -266,6 +266,8 @@ function setupModuleLoader(window) {
* configuration.
* @description
* Use this method to register work which needs to be performed on module loading.
* For more about how to configure services, see
* {@link providers#providers_provider-recipe Provider Recipe}.
*/
config: config,
+1 -1
View File
@@ -9,7 +9,7 @@
* @requires $rootScope
*
* @description
* When called, it checks current value of `$location.hash()` and scroll to related element,
* When called, it checks current value of `$location.hash()` and scrolls to the related element,
* according to rules specified in
* [Html5 spec](http://dev.w3.org/html5/spec/Overview.html#the-indicated-part-of-the-document).
*
+7 -7
View File
@@ -110,7 +110,7 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @ngdoc method
* @name $animate#enter
* @function
* @kind function
* @description Inserts the element into the DOM either after the `after` element or within
* the `parent` element. Once complete, the done() callback will be fired (if provided).
* @param {DOMElement} element the element which will be inserted into the DOM
@@ -137,7 +137,7 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @ngdoc method
* @name $animate#leave
* @function
* @kind function
* @description Removes the element from the DOM. Once complete, the done() callback will be
* fired (if provided).
* @param {DOMElement} element the element which will be removed from the DOM
@@ -153,7 +153,7 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @ngdoc method
* @name $animate#move
* @function
* @kind function
* @description Moves the position of the provided element within the DOM to be placed
* either after the `after` element or inside of the `parent` element. Once complete, the
* done() callback will be fired (if provided).
@@ -177,7 +177,7 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @ngdoc method
* @name $animate#addClass
* @function
* @kind function
* @description Adds the provided className CSS class value to the provided element. Once
* complete, the done() callback will be fired (if provided).
* @param {DOMElement} element the element which will have the className value
@@ -200,7 +200,7 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @ngdoc method
* @name $animate#removeClass
* @function
* @kind function
* @description Removes the provided className CSS class value from the provided element.
* Once complete, the done() callback will be fired (if provided).
* @param {DOMElement} element the element which will have the className value
@@ -223,10 +223,10 @@ var $AnimateProvider = ['$provide', function($provide) {
*
* @ngdoc method
* @name $animate#setClass
* @function
* @kind function
* @description Adds and/or removes the given CSS classes to and from the element.
* Once complete, the done() callback will be fired (if provided).
* @param {DOMElement} element the element which will it's CSS classes changed
* @param {DOMElement} element the element which will have its CSS classes changed
* removed from it
* @param {string} add the CSS classes which will be added to the element
* @param {string} remove the CSS class which will be removed from the element
+7 -7
View File
@@ -141,7 +141,7 @@ function $CacheFactoryProvider() {
/**
* @ngdoc method
* @name $cacheFactory.Cache#put
* @function
* @kind function
*
* @description
* Inserts a named entry into the {@link $cacheFactory.Cache Cache} object to be
@@ -177,7 +177,7 @@ function $CacheFactoryProvider() {
/**
* @ngdoc method
* @name $cacheFactory.Cache#get
* @function
* @kind function
*
* @description
* Retrieves named data stored in the {@link $cacheFactory.Cache Cache} object.
@@ -201,7 +201,7 @@ function $CacheFactoryProvider() {
/**
* @ngdoc method
* @name $cacheFactory.Cache#remove
* @function
* @kind function
*
* @description
* Removes an entry from the {@link $cacheFactory.Cache Cache} object.
@@ -229,7 +229,7 @@ function $CacheFactoryProvider() {
/**
* @ngdoc method
* @name $cacheFactory.Cache#removeAll
* @function
* @kind function
*
* @description
* Clears the cache object of any entries.
@@ -245,7 +245,7 @@ function $CacheFactoryProvider() {
/**
* @ngdoc method
* @name $cacheFactory.Cache#destroy
* @function
* @kind function
*
* @description
* Destroys the {@link $cacheFactory.Cache Cache} object entirely,
@@ -262,7 +262,7 @@ function $CacheFactoryProvider() {
/**
* @ngdoc method
* @name $cacheFactory.Cache#info
* @function
* @kind function
*
* @description
* Retrieve information regarding a particular {@link $cacheFactory.Cache Cache}.
@@ -317,7 +317,7 @@ function $CacheFactoryProvider() {
* @name $cacheFactory#info
*
* @description
* Get information about all the of the caches that have been created
* Get information about all the caches that have been created
*
* @returns {Object} - key-value map of `cacheId` to the result of calling `cache#info`
*/
+105 -71
View File
@@ -21,7 +21,7 @@
/**
* @ngdoc service
* @name $compile
* @function
* @kind function
*
* @description
* Compiles an HTML string or DOM into a template and produces a template function, which
@@ -59,7 +59,6 @@
* template: '<div></div>', // or // function(tElement, tAttrs) { ... },
* // or
* // templateUrl: 'directive.html', // or // function(tElement, tAttrs) { ... },
* replace: false,
* transclude: false,
* restrict: 'A',
* scope: false,
@@ -235,7 +234,7 @@
* api/ng.$sce#getTrustedResourceUrl $sce.getTrustedResourceUrl}.
*
*
* #### `replace`
* #### `replace` ([*DEPRECATED*!], will be removed in next major release)
* specify where the template should be inserted. Defaults to `false`.
*
* * `true` - the template will replace the current element.
@@ -262,11 +261,7 @@
* ```
*
* The compile function deals with transforming the template DOM. Since most directives do not do
* template transformation, it is not used often. Examples that require compile functions are
* directives that transform template DOM, such as {@link
* api/ng.directive:ngRepeat ngRepeat}, or load the contents
* asynchronously, such as {@link ngRoute.directive:ngView ngView}. The
* compile function takes the following arguments.
* template transformation, it is not used often. The compile function takes the following arguments:
*
* * `tElement` - template element - The element where the directive has been declared. It is
* safe to do template transformation on the element and child elements only.
@@ -504,7 +499,7 @@ var $compileMinErr = minErr('$compile');
/**
* @ngdoc provider
* @name $compileProvider
* @function
* @kind function
*
* @description
*/
@@ -512,8 +507,8 @@ $CompileProvider.$inject = ['$provide', '$$sanitizeUriProvider'];
function $CompileProvider($provide, $$sanitizeUriProvider) {
var hasDirectives = {},
Suffix = 'Directive',
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w\-_]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w\-_]+)(?:\:([^;]+))?;?)/;
COMMENT_DIRECTIVE_REGEXP = /^\s*directive\:\s*([\d\w_\-]+)\s+(.*)$/,
CLASS_DIRECTIVE_REGEXP = /(([\d\w_\-]+)(?:\:([^;]+))?;?)/;
// Ref: http://developers.whatwg.org/webappapis.html#event-handler-idl-attributes
// The assumption is that future DOM event attribute names will begin with
@@ -523,7 +518,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compileProvider#directive
* @function
* @kind function
*
* @description
* Register a new directive with the compiler.
@@ -576,7 +571,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compileProvider#aHrefSanitizationWhitelist
* @function
* @kind function
*
* @description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
@@ -606,7 +601,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compileProvider#imgSrcSanitizationWhitelist
* @function
* @kind function
*
* @description
* Retrieves or overrides the default regular expression that is used for whitelisting of safe
@@ -650,7 +645,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compile.directive.Attributes#$addClass
* @function
* @kind function
*
* @description
* Adds the CSS class value specified by the classVal parameter to the element. If animations
@@ -667,7 +662,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compile.directive.Attributes#$removeClass
* @function
* @kind function
*
* @description
* Removes the CSS class value specified by the classVal parameter from the element. If
@@ -684,7 +679,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compile.directive.Attributes#$updateClass
* @function
* @kind function
*
* @description
* Adds and removes the appropriate CSS class values to the element based on the difference
@@ -772,7 +767,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
/**
* @ngdoc method
* @name $compile.directive.Attributes#$observe
* @function
* @kind function
*
* @description
* Observes an interpolated attribute.
@@ -835,7 +830,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
compileNodes($compileNodes, transcludeFn, $compileNodes,
maxPriority, ignoreDirective, previousCompileContext);
safeAddClass($compileNodes, 'ng-scope');
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers){
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn){
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.
@@ -857,7 +852,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (cloneConnectFn) cloneConnectFn($linkNode, scope);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode);
if (compositeLinkFn) compositeLinkFn(scope, $linkNode, $linkNode, parentBoundTranscludeFn);
return $linkNode;
};
}
@@ -912,7 +907,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
!childNodes.length)
? null
: compileNodes(childNodes,
nodeLinkFn ? nodeLinkFn.transclude : transcludeFn);
nodeLinkFn ? (
(nodeLinkFn.transcludeOnThisElement || !nodeLinkFn.templateOnThisElement)
&& nodeLinkFn.transclude) : transcludeFn);
linkFns.push(nodeLinkFn, childLinkFn);
linkFnFound = linkFnFound || nodeLinkFn || childLinkFn;
@@ -923,8 +920,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// return a linking function if we have found anything, null otherwise
return linkFnFound ? compositeLinkFn : null;
function compositeLinkFn(scope, nodeList, $rootElement, boundTranscludeFn) {
var nodeLinkFn, childLinkFn, node, $node, childScope, childTranscludeFn, i, ii, n;
function compositeLinkFn(scope, nodeList, $rootElement, parentBoundTranscludeFn) {
var nodeLinkFn, childLinkFn, node, $node, childScope, i, ii, n, childBoundTranscludeFn;
// copy nodeList so that linking doesn't break due to live list updates.
var nodeListLength = nodeList.length,
@@ -946,23 +943,32 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
} else {
childScope = scope;
}
childTranscludeFn = nodeLinkFn.transclude;
if (childTranscludeFn || (!boundTranscludeFn && transcludeFn)) {
nodeLinkFn(childLinkFn, childScope, node, $rootElement,
createBoundTranscludeFn(scope, childTranscludeFn || transcludeFn)
);
if ( nodeLinkFn.transcludeOnThisElement ) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, nodeLinkFn.transclude, parentBoundTranscludeFn);
} else if (!nodeLinkFn.templateOnThisElement && parentBoundTranscludeFn) {
childBoundTranscludeFn = parentBoundTranscludeFn;
} else if (!parentBoundTranscludeFn && transcludeFn) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, transcludeFn);
} else {
nodeLinkFn(childLinkFn, childScope, node, $rootElement, boundTranscludeFn);
childBoundTranscludeFn = null;
}
nodeLinkFn(childLinkFn, childScope, node, $rootElement, childBoundTranscludeFn);
} else if (childLinkFn) {
childLinkFn(scope, node.childNodes, undefined, boundTranscludeFn);
childLinkFn(scope, node.childNodes, undefined, parentBoundTranscludeFn);
}
}
}
}
function createBoundTranscludeFn(scope, transcludeFn) {
return function boundTranscludeFn(transcludedScope, cloneFn, controllers) {
function createBoundTranscludeFn(scope, transcludeFn, previousBoundTranscludeFn) {
var boundTranscludeFn = function(transcludedScope, cloneFn, controllers) {
var scopeCreated = false;
if (!transcludedScope) {
@@ -971,12 +977,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
scopeCreated = true;
}
var clone = transcludeFn(transcludedScope, cloneFn, controllers);
var clone = transcludeFn(transcludedScope, cloneFn, controllers, previousBoundTranscludeFn);
if (scopeCreated) {
clone.on('$destroy', bind(transcludedScope, transcludedScope.$destroy));
clone.on('$destroy', function() { transcludedScope.$destroy(); });
}
return clone;
};
return boundTranscludeFn;
}
/**
@@ -1002,7 +1010,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
// iterate over the attributes
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
var attrStartName = false;
var attrEndName = false;
@@ -1010,9 +1018,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
attr = nAttrs[j];
if (!msie || msie >= 8 || attr.specified) {
name = attr.name;
value = trim(attr.value);
// support ngAttr attribute binding
ngAttrName = directiveNormalize(name);
if (NG_ATTR_BINDING.test(ngAttrName)) {
if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
name = snake_case(ngAttrName.substr(6), '-');
}
@@ -1025,9 +1035,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim(attr.value);
if (getBooleanAttrName(node, nName)) {
attrs[nName] = true; // presence means true
if (isNgAttr || !attrs.hasOwnProperty(nName)) {
attrs[nName] = value;
if (getBooleanAttrName(node, nName)) {
attrs[nName] = true; // presence means true
}
}
addAttrInterpolateDirective(node, directives, value, nName);
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
@@ -1154,6 +1166,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
templateDirective = previousCompileContext.templateDirective,
nonTlbTranscludeDirective = previousCompileContext.nonTlbTranscludeDirective,
hasTranscludeDirective = false,
hasTemplate = false,
hasElementTranscludeDirective = previousCompileContext.hasElementTranscludeDirective,
$compileNode = templateAttrs.$$element = jqLite(compileNode),
directive,
@@ -1244,6 +1257,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (directive.template) {
hasTemplate = true;
assertNoDuplicate('template', templateDirective, directive, $compileNode);
templateDirective = directive;
@@ -1258,7 +1272,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(directiveValue)) {
$template = [];
} else {
$template = jqLite(directiveValue);
$template = jqLite(trim(directiveValue));
}
compileNode = $template[0];
@@ -1293,6 +1307,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
if (directive.templateUrl) {
hasTemplate = true;
assertNoDuplicate('template', templateDirective, directive, $compileNode);
templateDirective = directive;
@@ -1301,7 +1316,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
nodeLinkFn = compileTemplateUrl(directives.splice(i, directives.length - i), $compileNode,
templateAttrs, jqCollection, childTranscludeFn, preLinkFns, postLinkFns, {
templateAttrs, jqCollection, hasTranscludeDirective && childTranscludeFn, preLinkFns, postLinkFns, {
controllerDirectives: controllerDirectives,
newIsolateScopeDirective: newIsolateScopeDirective,
templateDirective: templateDirective,
@@ -1329,7 +1344,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
nodeLinkFn.scope = newScopeDirective && newScopeDirective.scope === true;
nodeLinkFn.transclude = hasTranscludeDirective && childTranscludeFn;
nodeLinkFn.transcludeOnThisElement = hasTranscludeDirective;
nodeLinkFn.templateOnThisElement = hasTemplate;
nodeLinkFn.transclude = childTranscludeFn;
previousCompileContext.hasElementTranscludeDirective = hasElementTranscludeDirective;
// might be normal or delayed nodeLinkFn depending on if templateUrl is present
@@ -1341,6 +1359,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (pre) {
if (attrStart) pre = groupElementsLinkFnWrapper(pre, attrStart, attrEnd);
pre.require = directive.require;
pre.directiveName = directiveName;
if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
pre = cloneAndAnnotateFn(pre, {isolateScope: true});
}
@@ -1349,6 +1368,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (post) {
if (attrStart) post = groupElementsLinkFnWrapper(post, attrStart, attrEnd);
post.require = directive.require;
post.directiveName = directiveName;
if (newIsolateScopeDirective === directive || directive.$$isolateScope) {
post = cloneAndAnnotateFn(post, {isolateScope: true});
}
@@ -1357,7 +1377,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function getControllers(require, $element, elementControllers) {
function getControllers(directiveName, require, $element, elementControllers) {
var value, retrievalMethod = 'data', optional = false;
if (isString(require)) {
while((value = require.charAt(0)) == '^' || value == '?') {
@@ -1383,7 +1403,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
} else if (isArray(require)) {
value = [];
forEach(require, function(require) {
value.push(getControllers(require, $element, elementControllers));
value.push(getControllers(directiveName, require, $element, elementControllers));
});
}
return value;
@@ -1406,7 +1426,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
isolateScope = scope.$new(true);
if (templateDirective && (templateDirective === newIsolateScopeDirective.$$originalDirective)) {
if (templateDirective && (templateDirective === newIsolateScopeDirective ||
templateDirective === newIsolateScopeDirective.$$originalDirective)) {
$linkNode.data('$isolateScope', isolateScope) ;
} else {
$linkNode.data('$isolateScopeNoTemplate', isolateScope);
@@ -1526,7 +1547,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
try {
linkFn = preLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
@@ -1546,7 +1567,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
try {
linkFn = postLinkFns[i];
linkFn(linkFn.isolateScope ? isolateScope : scope, $element, attrs,
linkFn.require && getControllers(linkFn.require, $element, elementControllers), transcludeFn);
linkFn.require && getControllers(linkFn.directiveName, linkFn.require, $element, elementControllers), transcludeFn);
} catch (e) {
$exceptionHandler(e, startingTag($element));
}
@@ -1632,7 +1653,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// reapply the old attributes to the new element
forEach(dst, function(value, key) {
if (key.charAt(0) != '$') {
if (src[key]) {
if (src[key] && src[key] !== value) {
value += (key === 'style' ? ';' : ' ') + src[key];
}
dst.$set(key, value, true, srcAttr[key]);
@@ -1685,7 +1706,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(content)) {
$template = [];
} else {
$template = jqLite(content);
$template = jqLite(trim(content));
}
compileNode = $template[0];
@@ -1721,7 +1742,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
});
afterTemplateChildLinkFn = compileNodes($compileNode[0].childNodes, childTranscludeFn);
while(linkQueue.length) {
var scope = linkQueue.shift(),
beforeTemplateLinkNode = linkQueue.shift(),
@@ -1743,8 +1763,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// Copy in CSS classes from original node
safeAddClass(jqLite(linkNode), oldClasses);
}
if (afterTemplateNodeLinkFn.transclude) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude);
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
} else {
childBoundTranscludeFn = boundTranscludeFn;
}
@@ -1758,13 +1778,17 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
});
return function delayedNodeLinkFn(ignoreChildLinkFn, scope, node, rootElement, boundTranscludeFn) {
var childBoundTranscludeFn = boundTranscludeFn;
if (linkQueue) {
linkQueue.push(scope);
linkQueue.push(node);
linkQueue.push(rootElement);
linkQueue.push(boundTranscludeFn);
linkQueue.push(childBoundTranscludeFn);
} else {
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, boundTranscludeFn);
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
}
afterTemplateNodeLinkFn(afterTemplateChildLinkFn, scope, node, rootElement, childBoundTranscludeFn);
}
};
}
@@ -1789,23 +1813,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
}
function addTextInterpolateDirective(directives, text) {
var interpolateFn = $interpolate(text, true);
if (interpolateFn) {
directives.push({
priority: 0,
compile: valueFn(function textInterpolateLinkFn(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
bindings.push(interpolateFn);
safeAddClass(parent.data('$binding', bindings), 'ng-binding');
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
})
});
function addTextInterpolateDirective(directives, text) {
var interpolateFn = $interpolate(text, true);
if (interpolateFn) {
directives.push({
priority: 0,
compile: function textInterpolateCompileFn(templateNode) {
// when transcluding a template that has bindings in the root
// then we don't have a parent and should do this in the linkFn
var parent = templateNode.parent(), hasCompileParent = parent.length;
if (hasCompileParent) safeAddClass(templateNode.parent(), 'ng-binding');
return function textInterpolateLinkFn(scope, node) {
var parent = node.parent(),
bindings = parent.data('$binding') || [];
bindings.push(interpolateFn);
parent.data('$binding', bindings);
if (!hasCompileParent) safeAddClass(parent, 'ng-binding');
scope.$watch(interpolateFn, function interpolateFnWatchAction(value) {
node[0].nodeValue = value;
});
};
}
});
}
}
}
function getTrustedContext(node, attrNormalizedName) {
@@ -1966,7 +1998,9 @@ function directiveNormalize(name) {
* element attributes. The values reflect current binding state `{{ }}`. The normalization is
* needed since all of these are treated as equivalent in Angular:
*
* ```
* <span ng:bind="a" ng-bind="a" data-ng-bind="a" x-ng-bind="a">
* ```
*/
/**
@@ -1980,7 +2014,7 @@ function directiveNormalize(name) {
/**
* @ngdoc method
* @name $compile.directive.Attributes#$set
* @function
* @kind function
*
* @description
* Set DOM element attribute value.
+1 -1
View File
@@ -74,7 +74,7 @@ function $ControllerProvider() {
instance = $injector.instantiate(expression, locals);
if (identifier) {
if (!(locals && typeof locals.$scope == 'object')) {
if (!(locals && typeof locals.$scope === 'object')) {
throw minErr('$controller')('noscp',
"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
constructor || expression.name, identifier);
+1 -1
View File
@@ -38,7 +38,7 @@ var nullFormCtrl = {
* - `url`
*
* @description
* `FormController` keeps track of all its controls and nested forms as well as state of them,
* `FormController` keeps track of all its controls and nested forms as well as the state of them,
* such as being valid/invalid or dirty/pristine.
*
* Each {@link ng.directive:form form} directive creates an instance
+61 -27
View File
@@ -9,7 +9,7 @@
*/
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
var inputType = {
@@ -435,15 +435,29 @@ function validate(ctrl, validatorName, validity, value){
return validity ? value : undefined;
}
function testFlags(validity, flags) {
var i, flag;
if (flags) {
for (i=0; i<flags.length; ++i) {
flag = flags[i];
if (validity[flag]) {
return true;
}
}
}
return false;
}
function addNativeHtml5Validators(ctrl, validatorName, element) {
var validity = element.prop('validity');
// Pass validity so that behaviour can be mocked easier.
function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) {
if (isObject(validity)) {
ctrl.$$hasNativeValidators = true;
var validator = function(value) {
// Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
// perform the required validation)
if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError ||
validity.typeMismatch) && !validity.valueMissing) {
if (!ctrl.$error[validatorName] &&
!testFlags(validity, ignoreFlags) &&
testFlags(validity, badFlags)) {
ctrl.$setValidity(validatorName, false);
return;
}
@@ -454,7 +468,10 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
}
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
var validity = element.prop('validity');
var validity = element.prop(VALIDITY_STATE_PROPERTY);
var placeholder = element[0].placeholder, noevent = {};
ctrl.$$validityState = validity;
// In composition mode, users are still inputing intermediate text buffer,
// hold the listener until composition is done.
// More about composition events: https://developer.mozilla.org/en-US/docs/Web/API/CompositionEvent
@@ -471,10 +488,19 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
});
}
var listener = function() {
var listener = function(ev) {
if (composing) return;
var value = element.val();
// IE (11 and under) seem to emit an 'input' event if the placeholder value changes.
// We don't want to dirty the value when this happens, so we abort here. Unfortunately,
// IE also sends input events for other non-input-related things, (such as focusing on a
// form control), so this change is not entirely enough to solve this.
if (msie && (ev || noevent).type === 'input' && element[0].placeholder !== placeholder) {
placeholder = element[0].placeholder;
return;
}
// By default we will trim the value
// If the attribute ng-trim exists we will avoid trimming
// e.g. <input ng-model="foo" ng-trim="false">
@@ -482,11 +508,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
value = trim(value);
}
if (ctrl.$viewValue !== value ||
// If the value is still empty/falsy, and there is no `required` error, run validators
// again. This enables HTML5 constraint validation errors to affect Angular validation
// even when the first character entered causes an error.
(validity && value === '' && !validity.valueMissing)) {
// If a control is suffering from bad input, browsers discard its value, so it may be
// necessary to revalidate even if the control's value is the same empty value twice in
// a row.
var revalidate = validity && ctrl.$$hasNativeValidators;
if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
if (scope.$$phase) {
ctrl.$setViewValue(value);
} else {
@@ -592,6 +618,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
}
var numberBadFlags = ['badInput'];
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
textInputType(scope, element, attr, ctrl, $sniffer, $browser);
@@ -606,7 +634,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
}
});
addNativeHtml5Validators(ctrl, 'number', element);
addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState);
ctrl.$formatters.push(function(value) {
return ctrl.$isEmpty(value) ? '' : '' + value;
@@ -738,6 +766,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
* patterns defined as scope expressions.
* @param {string=} ngChange Angular expression to be executed when input changes due to user
* interaction with the input element.
* @param {boolean=} [ngTrim=true] If set to false Angular will not automatically trim the input.
*/
@@ -887,14 +916,14 @@ var VALID_CLASS = 'ng-valid',
* @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
the model value changes. Each function is called, in turn, passing the value through to the
next. Used to format / convert values for display in the control and validation.
* ```js
* function formatter(value) {
* if (value) {
* return value.toUpperCase();
* }
* }
* ngModel.$formatters.push(formatter);
* ```
* ```js
* function formatter(value) {
* if (value) {
* return value.toUpperCase();
* }
* }
* ngModel.$formatters.push(formatter);
* ```
*
* @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
* view value has changed. It is called with no arguments, and its return value is ignored.
@@ -923,7 +952,12 @@ var VALID_CLASS = 'ng-valid',
* Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element
* contents be edited in place by the user. This will not work on older browsers.
*
* <example name="NgModelController" module="customControl">
* We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
* module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
* However, as we are using `$sce` the model can still decide to to provide unsafe content if it marks
* that content using the `$sce` service.
*
* <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
<file name="style.css">
[contenteditable] {
border: 1px solid black;
@@ -937,8 +971,8 @@ var VALID_CLASS = 'ng-valid',
</file>
<file name="script.js">
angular.module('customControl', []).
directive('contenteditable', function() {
angular.module('customControl', ['ngSanitize']).
directive('contenteditable', ['$sce', function($sce) {
return {
restrict: 'A', // only activate on element attribute
require: '?ngModel', // get a hold of NgModelController
@@ -947,7 +981,7 @@ var VALID_CLASS = 'ng-valid',
// Specify how UI should be updated
ngModel.$render = function() {
element.html(ngModel.$viewValue || '');
element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
};
// Listen for change events to enable binding
@@ -968,7 +1002,7 @@ var VALID_CLASS = 'ng-valid',
}
}
};
});
}]);
</file>
<file name="index.html">
<form name="myForm">
@@ -1082,7 +1116,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* This method should be called by validators - i.e. the parser or formatter functions.
*
* @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
* to `$error[validationErrorKey]=isValid` so that it is available for data-binding.
* to `$error[validationErrorKey]=!isValid` so that it is available for data-binding.
* The `validationErrorKey` should be in camelCase and will get converted into dash-case
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
* class and can be bound to as `{{someForm.someControl.$error.myError}}` .
+13 -8
View File
@@ -50,14 +50,19 @@
</file>
</example>
*/
var ngBindDirective = ngDirective(function(scope, element, attr) {
element.addClass('ng-binding').data('$binding', attr.ngBind);
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
// We are purposefully using == here rather than === because we want to
// catch when value is "null or undefined"
// jshint -W041
element.text(value == undefined ? '' : value);
});
var ngBindDirective = ngDirective({
compile: function(templateElement) {
templateElement.addClass('ng-binding');
return function (scope, element, attr) {
element.data('$binding', attr.ngBind);
scope.$watch(attr.ngBind, function ngBindWatchAction(value) {
// We are purposefully using == here rather than === because we want to
// catch when value is "null or undefined"
// jshint -W041
element.text(value == undefined ? '' : value);
});
};
}
});
+3 -3
View File
@@ -19,7 +19,7 @@ function classDirective(name, selector) {
scope.$watch('$index', function($index, old$index) {
// jshint bitwise: false
var mod = $index & 1;
if (mod !== old$index & 1) {
if (mod !== (old$index & 1)) {
var classes = arrayClasses(scope.$eval(attr[name]));
mod === selector ?
addClasses(classes) :
@@ -78,7 +78,7 @@ function classDirective(name, selector) {
updateClasses(oldClasses, newClasses);
}
}
oldVal = copy(newVal);
oldVal = shallowCopy(newVal);
}
}
};
@@ -106,7 +106,7 @@ function classDirective(name, selector) {
var classes = [], i = 0;
forEach(classVal, function(v, k) {
if (v) {
classes.push(k);
classes = classes.concat(k.split(' '));
}
});
return classes;
+181 -160
View File
@@ -10,7 +10,7 @@
*
* MVC components in angular:
*
* * Model — The Model is scope properties; scopes are attached to the DOM where scope properties
* * Model — Models are the properties of a scope; scopes are attached to the DOM where scope properties
* are accessed through bindings.
* * View — The template (HTML with data bindings) that is rendered into the View.
* * Controller — The `ngController` directive specifies a Controller class; the class contains business
@@ -31,165 +31,186 @@
* @example
* Here is a simple form for editing user contact information. Adding, removing, clearing, and
* greeting are methods declared on the controller (see source tab). These methods can
* easily be called from the angular markup. Notice that the scope becomes the `this` for the
* controller's instance. This allows for easy access to the view data from the controller. Also
* notice that any changes to the data are automatically reflected in the View without the need
* for a manual update. The example is shown in two different declaration styles you may use
* according to preference.
<example>
<file name="index.html">
<script>
function SettingsController1() {
this.name = "John Smith";
this.contacts = [
{type: 'phone', value: '408 555 1212'},
{type: 'email', value: 'john.smith@example.org'} ];
};
SettingsController1.prototype.greet = function() {
alert(this.name);
};
SettingsController1.prototype.addContact = function() {
this.contacts.push({type: 'email', value: 'yourname@example.org'});
};
SettingsController1.prototype.removeContact = function(contactToRemove) {
var index = this.contacts.indexOf(contactToRemove);
this.contacts.splice(index, 1);
};
SettingsController1.prototype.clearContact = function(contact) {
contact.type = 'phone';
contact.value = '';
};
</script>
<div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
Name: <input type="text" ng-model="settings.name"/>
[ <a href="" ng-click="settings.greet()">greet</a> ]<br/>
Contact:
<ul>
<li ng-repeat="contact in settings.contacts">
<select ng-model="contact.type">
<option>phone</option>
<option>email</option>
</select>
<input type="text" ng-model="contact.value"/>
[ <a href="" ng-click="settings.clearContact(contact)">clear</a>
| <a href="" ng-click="settings.removeContact(contact)">X</a> ]
</li>
<li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li>
</ul>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should check controller as', function() {
var container = element(by.id('ctrl-as-exmpl'));
expect(container.findElement(by.model('settings.name'))
.getAttribute('value')).toBe('John Smith');
var firstRepeat =
container.findElement(by.repeater('contact in settings.contacts').row(0));
var secondRepeat =
container.findElement(by.repeater('contact in settings.contacts').row(1));
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('408 555 1212');
expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('john.smith@example.org');
firstRepeat.findElement(by.linkText('clear')).click();
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('');
container.findElement(by.linkText('add')).click();
expect(container.findElement(by.repeater('contact in settings.contacts').row(2))
.findElement(by.model('contact.value'))
.getAttribute('value'))
.toBe('yourname@example.org');
});
</file>
</example>
<example>
<file name="index.html">
<script>
function SettingsController2($scope) {
$scope.name = "John Smith";
$scope.contacts = [
{type:'phone', value:'408 555 1212'},
{type:'email', value:'john.smith@example.org'} ];
$scope.greet = function() {
alert(this.name);
};
$scope.addContact = function() {
this.contacts.push({type:'email', value:'yourname@example.org'});
};
$scope.removeContact = function(contactToRemove) {
var index = this.contacts.indexOf(contactToRemove);
this.contacts.splice(index, 1);
};
$scope.clearContact = function(contact) {
contact.type = 'phone';
contact.value = '';
};
}
</script>
<div id="ctrl-exmpl" ng-controller="SettingsController2">
Name: <input type="text" ng-model="name"/>
[ <a href="" ng-click="greet()">greet</a> ]<br/>
Contact:
<ul>
<li ng-repeat="contact in contacts">
<select ng-model="contact.type">
<option>phone</option>
<option>email</option>
</select>
<input type="text" ng-model="contact.value"/>
[ <a href="" ng-click="clearContact(contact)">clear</a>
| <a href="" ng-click="removeContact(contact)">X</a> ]
</li>
<li>[ <a href="" ng-click="addContact()">add</a> ]</li>
</ul>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should check controller', function() {
var container = element(by.id('ctrl-exmpl'));
expect(container.findElement(by.model('name'))
.getAttribute('value')).toBe('John Smith');
var firstRepeat =
container.findElement(by.repeater('contact in contacts').row(0));
var secondRepeat =
container.findElement(by.repeater('contact in contacts').row(1));
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('408 555 1212');
expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('john.smith@example.org');
firstRepeat.findElement(by.linkText('clear')).click();
expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
.toBe('');
container.findElement(by.linkText('add')).click();
expect(container.findElement(by.repeater('contact in contacts').row(2))
.findElement(by.model('contact.value'))
.getAttribute('value'))
.toBe('yourname@example.org');
});
</file>
</example>
* easily be called from the angular markup. Any changes to the data are automatically reflected
* in the View without the need for a manual update.
*
* Two different declaration styles are included below:
*
* * one binds methods and properties directly onto the controller using `this`:
* `ng-controller="SettingsController1 as settings"`
* * one injects `$scope` into the controller:
* `ng-controller="SettingsController2"`
*
* The second option is more common in the Angular community, and is generally used in boilerplates
* and in this guide. However, there are advantages to binding properties directly to the controller
* and avoiding scope.
*
* * Using `controller as` makes it obvious which controller you are accessing in the template when
* multiple controllers apply to an element.
* * If you are writing your controllers as classes you have easier access to the properties and
* methods, which will appear on the scope, from inside the controller code.
* * Since there is always a `.` in the bindings, you don't have to worry about prototypal
* inheritance masking primitives.
*
* This example demonstrates the `controller as` syntax.
*
* <example name="ngControllerAs">
* <file name="index.html">
* <div id="ctrl-as-exmpl" ng-controller="SettingsController1 as settings">
* Name: <input type="text" ng-model="settings.name"/>
* [ <a href="" ng-click="settings.greet()">greet</a> ]<br/>
* Contact:
* <ul>
* <li ng-repeat="contact in settings.contacts">
* <select ng-model="contact.type">
* <option>phone</option>
* <option>email</option>
* </select>
* <input type="text" ng-model="contact.value"/>
* [ <a href="" ng-click="settings.clearContact(contact)">clear</a>
* | <a href="" ng-click="settings.removeContact(contact)">X</a> ]
* </li>
* <li>[ <a href="" ng-click="settings.addContact()">add</a> ]</li>
* </ul>
* </div>
* </file>
* <file name="app.js">
* function SettingsController1() {
* this.name = "John Smith";
* this.contacts = [
* {type: 'phone', value: '408 555 1212'},
* {type: 'email', value: 'john.smith@example.org'} ];
* }
*
* SettingsController1.prototype.greet = function() {
* alert(this.name);
* };
*
* SettingsController1.prototype.addContact = function() {
* this.contacts.push({type: 'email', value: 'yourname@example.org'});
* };
*
* SettingsController1.prototype.removeContact = function(contactToRemove) {
* var index = this.contacts.indexOf(contactToRemove);
* this.contacts.splice(index, 1);
* };
*
* SettingsController1.prototype.clearContact = function(contact) {
* contact.type = 'phone';
* contact.value = '';
* };
* </file>
* <file name="protractor.js" type="protractor">
* it('should check controller as', function() {
* var container = element(by.id('ctrl-as-exmpl'));
* expect(container.findElement(by.model('settings.name'))
* .getAttribute('value')).toBe('John Smith');
*
* var firstRepeat =
* container.findElement(by.repeater('contact in settings.contacts').row(0));
* var secondRepeat =
* container.findElement(by.repeater('contact in settings.contacts').row(1));
*
* expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
* .toBe('408 555 1212');
*
* expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
* .toBe('john.smith@example.org');
*
* firstRepeat.findElement(by.linkText('clear')).click();
*
* expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
* .toBe('');
*
* container.findElement(by.linkText('add')).click();
*
* expect(container.findElement(by.repeater('contact in settings.contacts').row(2))
* .findElement(by.model('contact.value'))
* .getAttribute('value'))
* .toBe('yourname@example.org');
* });
* </file>
* </example>
*
* This example demonstrates the "attach to `$scope`" style of controller.
*
* <example name="ngController">
* <file name="index.html">
* <div id="ctrl-exmpl" ng-controller="SettingsController2">
* Name: <input type="text" ng-model="name"/>
* [ <a href="" ng-click="greet()">greet</a> ]<br/>
* Contact:
* <ul>
* <li ng-repeat="contact in contacts">
* <select ng-model="contact.type">
* <option>phone</option>
* <option>email</option>
* </select>
* <input type="text" ng-model="contact.value"/>
* [ <a href="" ng-click="clearContact(contact)">clear</a>
* | <a href="" ng-click="removeContact(contact)">X</a> ]
* </li>
* <li>[ <a href="" ng-click="addContact()">add</a> ]</li>
* </ul>
* </div>
* </file>
* <file name="app.js">
* function SettingsController2($scope) {
* $scope.name = "John Smith";
* $scope.contacts = [
* {type:'phone', value:'408 555 1212'},
* {type:'email', value:'john.smith@example.org'} ];
*
* $scope.greet = function() {
* alert($scope.name);
* };
*
* $scope.addContact = function() {
* $scope.contacts.push({type:'email', value:'yourname@example.org'});
* };
*
* $scope.removeContact = function(contactToRemove) {
* var index = $scope.contacts.indexOf(contactToRemove);
* $scope.contacts.splice(index, 1);
* };
*
* $scope.clearContact = function(contact) {
* contact.type = 'phone';
* contact.value = '';
* };
* }
* </file>
* <file name="protractor.js" type="protractor">
* it('should check controller', function() {
* var container = element(by.id('ctrl-exmpl'));
*
* expect(container.findElement(by.model('name'))
* .getAttribute('value')).toBe('John Smith');
*
* var firstRepeat =
* container.findElement(by.repeater('contact in contacts').row(0));
* var secondRepeat =
* container.findElement(by.repeater('contact in contacts').row(1));
*
* expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
* .toBe('408 555 1212');
* expect(secondRepeat.findElement(by.model('contact.value')).getAttribute('value'))
* .toBe('john.smith@example.org');
*
* firstRepeat.findElement(by.linkText('clear')).click();
*
* expect(firstRepeat.findElement(by.model('contact.value')).getAttribute('value'))
* .toBe('');
*
* container.findElement(by.linkText('add')).click();
*
* expect(container.findElement(by.repeater('contact in contacts').row(2))
* .findElement(by.model('contact.value'))
* .getAttribute('value'))
* .toBe('yourname@example.org');
* });
* </file>
*</example>
*/
var ngControllerDirective = [function() {
+8 -3
View File
@@ -45,7 +45,7 @@ forEach(
return {
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function(scope, element, attr) {
return function ngEventHandler(scope, element) {
element.on(lowercase(name), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
@@ -262,8 +262,13 @@ forEach(
* @example
<example>
<file name="index.html">
<input ng-keyup="count = count + 1" ng-init="count=0">
key up count: {{count}}
<p>Typing in the input box below updates the key count</p>
<input ng-keyup="count = count + 1" ng-init="count=0"> key up count: {{count}}
<p>Typing in the input box below updates the keycode</p>
<input ng-keyup="event=$event">
<p>event keyCode: {{ event.keyCode }}</p>
<p>event altKey: {{ event.altKey }}</p>
</file>
</example>
*/

Some files were not shown because too many files have changed in this diff Show More