Compare commits

...

294 Commits

Author SHA1 Message Date
Peter Bacon Darwin 22b669407c docs(CHANGELOG.md): add release notes for v1.5.8 2016-07-22 16:01:46 +01:00
Georgios Kalpakas 99a063f9cb docs(CHANGELOG.md): add release notes for v1.2.30 2016-07-22 15:45:08 +01:00
Georgios Kalpakas 27db9f018e test($anchorScroll): add e2e tests
Fixes #9535
Closes #9583

Closes #14932
2016-07-20 11:45:09 +03:00
Brad 2ed2399d49 docs($httpParamSerializer): fix typo (remove superfluous quote)
Closes #14934
2016-07-20 10:48:32 +03:00
Georgios Kalpakas 9d2de344f5 refactor($http): clean up code
Closes #14921
2016-07-19 23:25:11 +03:00
Georgios Kalpakas 9c0659ca27 refactor($http): move functions together and order alphabetically
Closes #14921
2016-07-19 23:10:39 +03:00
Georgios Kalpakas 3b9963c54a refactor(test/e2e): make file/directory names consistent 2016-07-19 21:46:34 +03:00
Georgios Kalpakas 26db937377 test($resource): add some tests wrt handling response errors
The test currently fail on master, but should pass on 1.5.x.
A subsequent commit will fix the regressions on master.

(This commit should be backportable to 1.5.x.)

Related to #14837.
2016-07-18 14:55:55 +01:00
Georgios Kalpakas 1d0e582b3f chore(protractor): use directConnect locally to speed up tests
Closes #14919
2016-07-18 10:03:41 +01:00
Georgios Kalpakas a6b58019e1 refactor(test/e2e): remove explicit call to waitForAngular()
Protractor automatically calls `waitForAngular()`, before every WebDriver action.

Closes #14920
2016-07-18 10:03:40 +01:00
Georgios Kalpakas 38fd896b74 refactor(test/e2e): restore consistency across tests (just because) 2016-07-18 10:03:40 +01:00
Georgios Kalpakas f96d553822 docs(guide): fix links to tutorial steps 2016-07-15 20:07:10 +03:00
John (J5) Palmieri f4149b70bc doc(guide/Animations): point to $animate.pin() to enable animation for elements outside an Angular app
Add a section inside of the ngAnimate documentation to point to $animate.pin().
It was not clear in the documentation why animations were being disabled inside of
modals and popup menus. This documentation explains in what situations elements may
be dynamically placed outside of the application DOM and points to the API documentation
on how to enable animations for these types of elements.

Related #14907
Closes #14918
2016-07-15 18:07:37 +02:00
Martin Staffa 8a8c94c3d5 docs(ngSelected): note that it doesn't interact with ngModel
Related #14916
Related #14876
2016-07-15 17:49:22 +02:00
Michał Gołębiowski d9de526059 test(jQuery): Run tests with jQuery 3
(cherry-picked from 859b1e300c)

Closes #14874
2016-07-13 10:13:58 +02:00
Georgios Kalpakas 0318761f7a refactor($animate): avoid unnecessary lookup
Closes #14902
2016-07-12 22:32:38 +03:00
Ronan Connolly 74f7b57823 docs(guide/unit-testing): add missing heading
Closes #14901
2016-07-12 18:31:30 +03:00
Steve Berube 995ccfc9f6 docs(guide/animations): use the arguments of enabled() in correct order
Closes #14900
2016-07-12 17:52:05 +03:00
Georgios Kalpakas 72c244564a chore(Jenkins): fix e2e tests on Jenkins
Follow-up to c72e13f and a82a8a5.

Closes #14889
2016-07-11 13:37:43 +03:00
Georgios Kalpakas 4bfe7e8663 docs(guide/module): remove newline in the middle of sentence 2016-07-10 14:13:33 +03:00
Oliver Salzburg 63fe144397 docs($http): fix default $http cache name
The name of the cache is the string "$http", not the symbol itself.

Closes #14885
2016-07-09 10:23:30 +03:00
Martin Staffa 0f551e6109 chore(protractor): use jasmine2 as framework 2016-07-08 11:30:10 +02:00
Martin Staffa 541f4ba21f chore(package.json): update jasmine-reporters to 2.2.0 2016-07-08 11:30:08 +02:00
Georgios Kalpakas 6e7d04ddb2 docs(guide/component): improve tests
Fixes #14739
2016-07-06 13:40:47 +03:00
Jack 06fb391687 docs(guide/directive): remove confusing best practice
Prior to this point, the docs do not mention returning a postLink function, and all the examples use
the definition object form. So, this message is confusing to new readers who may misinterpret
"returning a function" as returning the factory function mentioned in the previous section.

Since this page is only a "gentle introduction" to directives, and using the definition object is a
best practice, it is best to just remove the message altogether.

Closes #14871
2016-07-06 12:07:15 +03:00
Georgios Kalpakas 203dd4d1e3 docs(tutorial): fix typo in images (phoneList --> phoneDetail)
Fixes #14858

Closes #14868
2016-07-05 21:52:37 +03:00
Georgios Kalpakas c991f93817 docs(guide/interpolation): add known issue (do not change the content of interpolated strings)
Closes #12813

Closes #14825
2016-07-05 00:11:26 +03:00
Peter Bacon Darwin 52ddc13bee chore(package.json): update dgeni-packages dependency to 0.14.0 2016-07-04 21:31:47 +01:00
Igor Zhukov 1645924d49 fix(copy): fix handling of typed subarrays
Previously, it would return a copy of the whole original typed array, not its slice.
Now, the `byteOffset` and `length` are also preserved.

Fixes #14842

Closes #14845
2016-07-04 23:24:16 +03:00
Georgios Kalpakas 6bee655679 docs(copy): mention ignoring non-enumerable properties
This also improves the example a bit:
- better code formatting
- initialization of `form` to an empty object
- avoid using `email`, which doesn't get coppied when invalid (and might confuse users)

Fixes #14853
2016-07-04 10:16:08 +03:00
David Rodenas Pico acd455181d perf($compile): wrap try/catch of collect comment directives into a function to avoid V8 deopt
Closes #14848
2016-07-04 08:56:52 +03:00
jfisher446 a89b6e27a9 docs(tutorial/step_04): fix syntactic error (add missing 'a')
Closes #14860
2016-07-04 08:43:16 +03:00
mmuppa 46d559dc8d docs(misc/Develop): using https:// works more widely than git@
Closes #14838
2016-07-01 11:18:33 +01:00
Peter Bacon Darwin 26212fb204 docs(guide): tweaks to the index and external resources guides
Closes #14843
2016-07-01 11:18:33 +01:00
Martin Staffa 244cfc7cd5 docs(guide): create new page for external resources
The official and external resources  have been split into two different guide sections. Official stays at the index, external gets its own page.

The external resources have also been reorganized and updated, but
I haven't checked if all material is still relevant.
2016-07-01 11:18:33 +01:00
Jeff Andrews c6074dc34c fix(ngMocks): allow ErrorAddingDeclarationLocationStack to be recognized as an Error
Change `ErrorAddingDeclarationLocationStack`'s prototype so test frameworks (such as Jasmine 2.x)
are able to recognize it as `Error`.

Fixes #13821

Closes #14344
2016-06-29 13:44:45 +03:00
atenhar 4930ef2bfe docs(tutorial/step_03): fix grammar and typos
Closes #14834
2016-06-28 11:08:41 +03:00
Georgios Kalpakas 1778d347cd fix($jsonpCallbacks): do not overwrite callbacks added by other apps
Closes #14824
2016-06-27 15:18:40 +03:00
Georgios Kalpakas 4724d56c93 fix($injector): fix class detection RegExp
Mentioned in https://github.com/angular/angular.js/pull/14531#discussion_r61410683.

Closes #14533
2016-06-27 13:35:50 +03:00
Georgios Kalpakas 70fcffc375 test($resource): make test for function as param more explicit
Related to aa8d783.

Closes #14820
2016-06-27 11:54:29 +03:00
Georgios Kalpakas 181e44019e fix($animate): do not get affected by custom, enumerable properties on Object.prototype
Fixes #14804

Closes #14830
2016-06-27 11:50:31 +03:00
Liran Tal 5ce859bcdd docs(guide/filter): add link to list of built-in filters
Closes #14828
2016-06-26 20:28:40 +03:00
Deplay fb994e4d90 refactor(jqLite): remove unused code in jqLiteBuildFragment()
Closes #14822
2016-06-25 22:09:43 +03:00
GARCIA Guillaume 55f26fc246 docs(*): correct bower version separator (@ --> #)
Closes #14819
2016-06-25 21:56:58 +03:00
Martin Staffa 1161002857 docs(ngReadonly): note input type restrictions
Closes #14816
2016-06-23 12:02:01 +02:00
Martin Staffa 7ccfe92bed fix(ngAnimate): allow removal of class that is scheduled to be added with requestAnimationFrame
Also affects the reverse case, adding a class that is scheduled to be removed with rAF.

The following case can happen when ngClass updates an element's classes in very quick order in the following way:

- First animation adds class "a"
- A digest passes, but "a" is not yet added to the element
- Second animation adds class "b"
- No digest passes, and "a" is still not added to the element,
  because requestAnimationFrame hasn't been flushed yet
- Third animation removes class "a"
- the third animation gets merged into the second animation

Before this change:

- Because the element doesn't have class "a" yet, ngAnimate
resolves that it cannot remove class "a". However,
the first animation is still running, and finally adds "a"

After this change:

- ngAnimate reacts to the temporary class "add-a", which indicates
that "a" is about to be added and decides that "a" can be removed
after all.

This is a very rare case where setting the element's class
is not fast enough, and subsequent animations operate on incorrect assumptions.

"In the wild", this is caused by rapidly updating ngClass,
which uses inidvidual addClass and removeClass calls when both operations happen in a single digest.

Fixes #14582
PR  (#14760)
2016-06-23 12:01:57 +02:00
Chirayu Krishnappa a126fcfee3 feat($resource): pass the resource to a dynamic param functions
Closes #4899
2016-06-22 12:22:16 +01:00
Peter Bacon Darwin f22af2b874 docs($http): fix a dangling link 2016-06-22 11:19:22 +01:00
Peter Bacon Darwin 2ca4170903 docs($compile): reorganize the life-cycle hooks docs
Closes #14811
2016-06-22 11:19:21 +01:00
Peter Bacon Darwin fd57df1235 docs($compile): add additional runnable examples for the $doCheck hook
Closes #14811
2016-06-22 11:19:21 +01:00
Peter Bacon Darwin 3010ed4ee5 fix($compile): ensure $doCheck hooks can be defined in the controller constructor
Closes #14811
2016-06-22 11:19:21 +01:00
Zach Bjornson de59ca7107 feat($compile): backport $doCheck
Backuport ngDoCheck from Angular 2.

Closes #14656
2016-06-22 11:19:21 +01:00
Peter Bacon Darwin fa3214cfb8 refact($http): use the $jsonpCallbacks service
Use the built-in service to handling callbacks to `$http.jsonp` requests.

Closes #3073
Closes #14795
2016-06-21 19:10:13 +01:00
Peter Bacon Darwin a8cacfe938 feat($jsonpCallbacks): new service to abstract how JSONP callbacks are handled
You can now override this service if you have specific requirements about
the behaviour and formatting of the JSON_CALLBACK that is sent to the server
for `$http.jsonp` requests.

Closes #14795
2016-06-21 19:08:28 +01:00
Lucas Galfasó 1a387ba5dd fix($timeout): make $flush handle new $timeouts added in $timeout callbacks
If a $timeout handler calls $timeout itself, this new $timeout should be
added to the mock deferred queue with a "now" time based on when the original
handler was triggered.

Previously it was being added with a now time of when the `$timeout.flush`
method was originally called.

Closes #5420
Closes #14686
2016-06-20 10:01:19 -07:00
Georgios Kalpakas 98e4a220fe fix(modules): allow modules to be loaded in any order when using angular-loader
Some modules used to assume that the angular helpers would always be available when their script was
executed. This could be a problem when using `angular-loader` and the module file happened to get
loaded before the core `angular.js` file.
This commit fixes the issue by delaying the access to angular helpers, until they are guaranteed to
be available.

Affected modules:
- `ngAnimate`
- `ngMessageFormat`
- `ngMessages`
- `ngRoute`
- `ngSanitize`

Fixes #9140

Closes #14794
2016-06-18 00:20:56 +03:00
Georgios Kalpakas 9001ae2822 docs(misc/downloading): mention ngMessageFormat 2016-06-18 00:20:54 +03:00
Peter Bacon Darwin c405f88bbc fix(ngTransclude): ensure that fallback content is compiled and linked correctly
Closes #14787
2016-06-17 10:59:51 +01:00
Daniel Herman 159a68ec7b fix(ngTransclude): only compile fallback content if necessary
If the instance of the directive does provide transcluded content, then the fallback
content should not be compiled and linked as it will never be used.

If the instance of the directive does not provide transcluded content, then the
transcluded scope that was created for this non-existent content is never used,
so it should be destroy in order to clean up unwanted memory use and digests.

Fixes #14768
Fixes #14765
Closes #14775
2016-06-17 10:52:57 +01:00
Yihang Ho b9a56d588f feat($q): implement $q.race
Implement $q.race. $q.race takes in an array or hash of promises and
returns a promise that resolves or rejects as soon as one of those promises
resolves or rejects, with the value or reason from that promise.

Closes #12929
Closes #14757
2016-06-17 10:21:31 +01:00
Abdulkader f797f83cd6 feat($swipe): add pointer support
Add pointer events to $swipe to support IE11 on touch devices

Closes #14061
Closes #14791
2016-06-17 09:52:43 +01:00
Georgios Kalpakas 6df970eb3c docs($resource): clarify how a @-prefix param is used 2016-06-17 00:45:34 +03:00
Georgios Kalpakas f88b4be92f docs(angular.mock.dump): remove incorrect statement
Fixes #14790
2016-06-17 00:26:43 +03:00
Roman Rodych a7bcc01581 refactor(input[number]): remove redandant second argument to parseFloat()
Closes #14793
2016-06-16 22:19:08 +03:00
Martin Staffa 40ed8094d1 chore(changelog.js): don't add empty breaking change section 2016-06-16 14:38:53 +02:00
Martin Staffa 1a722f7b19 docs($compile): add link to transcludeFn description
Closes #14764
2016-06-16 14:16:59 +02:00
Martin Staffa 4991207c7c chore(version-info): make online build compatible with Windows
Since we cannot write to dev/null on Windows, don't write to a file at all, but simply use stdout and extract the output from --write-out from the process response. Credit to @petebacondarwin for the idea.

The commit also avoids a premature error when no cdnVersion could be found online, and improves the log wrt the origin of the versions.

PR (#14780)
2016-06-16 14:16:59 +02:00
Georgios Kalpakas 33514ec384 feat(filterFilter): allow overwriting the special $ property name
Previously, the special property name that would match against any
property was hard-coded to `$`.
With this commit, the user can specify an arbitrary property name,
by passing a 4th argument to `filterFilter()`. E.g.:

```js
var items = [{foo: 'bar'}, {baz: 'qux'}];
var expr  =  {'%': 'bar'};

console.log(filterFilter(items, expr, null, '%'));   // [{foo: 'bar'}]
```

Fixes #13313
PR (#13356)
2016-06-16 14:15:55 +02:00
Georgios Kalpakas 28bd20ed30 docs($interpolate): fix heading 2016-06-16 14:15:55 +02:00
Martin Staffa c3bfd7f59d fix(ngOptions): don't duplicate groups with falsy values
Previously, ngOptions would fail to remove optgroups with falsy values when the options were changed / removed.

Related #14781 
PR (#14784)
2016-06-16 14:15:54 +02:00
Georgios Kalpakas 553cccdb85 docs(CHANGELOG.md): add release notes for v1.5.7 2016-06-16 14:15:06 +02:00
Martin Staffa c7dd2c24f4 docs(guide/filters): clarify when filters are executed
Closes #14590
PR  (#14758)
2016-06-16 14:13:01 +02:00
Peter Bacon Darwin fbf5aebda2 chore(version-info): add offline-build support
Previously it was slow and problematic to run builds if you were not connected
to the internet because the build scripts make requests to github and the
Google CDN to find out version information.

You can now disable these requests by setting the NG1_BUILD_NO_REMOTE_VERSION_REQUESTS
environment variable to a non-empty value

Closes #14769
2016-06-15 10:32:31 +01:00
Peter Bacon Darwin ab35c16ccf chore(app.scenario): test docs app against production libraries
We have had issues where the docs application has gone down because
our build included invalid URLs to the production angular libraries.

This change runs a subset of the docs e2e tests directly against these
production libraries to ensure that they are accessible. If not then these
tests will fail and the CI build will abort, preventing the docs app from
being updated.

Closes #14769
2016-06-15 10:32:18 +01:00
Peter Bacon Darwin 37b6a71678 chore(Gruntfile): kill the build if the CDN version was not found
Closes #14769
2016-06-15 10:32:07 +01:00
Martin Staffa e79ebff223 docs(changelog.md): add 1.5.0-beta.0 entry
This was a preparation release for the 1.5.x branch,
which only contains one fix over the previous 1.4.6
release.
2016-06-15 11:22:03 +02:00
Wesley Cho 36b38e1355 docs($q): add note about optional arguments for promise.then()
- Add note about optional arguments in the promise.then api

Closes #8562
PR (#14754)
2016-06-15 11:21:58 +02:00
Wesley Cho 3168d95793 docs(guide): add notes about using ngAttr for IE
- Add note about using the value attribute for the progress tag with IE in the IE compat / interpolation guide
- Add note about placeholder attribute in textarea to the ie compat guide

Closes #7218
PR (#14753)
2016-06-15 11:21:39 +02:00
Geoffrey Bauduin 538c63f057 docs($exceptionHandler): add missing end bracket in example
PR  (#14756)
2016-06-15 11:21:21 +02:00
Wesley Cho a9a1461351 docs(ngOpen): add note about browser compatibility
- Add note about browser compatibility and recommend ng-show/ng-hide

Closes ##7337
PR (#14752)
2016-06-15 11:21:11 +02:00
Peter Bacon Darwin 29190f9318 revert: chore(version-info): disable remote requests when offline
This reverts commit aa38ded59d, which
caused the docs to build incorrectly causing the docs website to crash.
2016-06-14 20:44:12 +01:00
Peter Bacon Darwin ca7d2b3b3f docs(ngMessageFormat): fix e2e test in example 2016-06-14 20:41:23 +01:00
Peter Bacon Darwin 81676f8323 style(ngMessageFormat): fix jscs whitespace error 2016-06-14 20:41:23 +01:00
Martin Staffa 6f9d07d9c7 chore(docsApp): don't display module components if none exist
Some modules (ngMessageFormat, ngParseExt) don't have their own api surface
because they integrate with core services ($interpolate and $parse respectively).

Closes #14770
2016-06-14 20:41:23 +01:00
Martin Staffa 5201b8146a docs(ngMessageFormat): move docs for $$messageFormat to module namespace.
This is a private service that should not be documented.
The docs have been moved to the ngMessageFormat module namespace.

Closes #14770
2016-06-14 20:41:23 +01:00
Martin Staffa 6aa83d04b7 docs(ngMessageFormat): add more examples
Closes #14770
2016-06-14 20:41:23 +01:00
Igor Minar cfbe1a701a revert: fix($timeout): make $flush handle new $timeouts added in $timeout callbacks
This reverts commit 1e5d36d4b0.

Breaks tests at google, needs more investigation.
2016-06-14 08:08:25 -07:00
Pete Bacon Darwin aa38ded59d chore(version-info): disable remote requests when offline
When the internet is not accessible build scripts are unnecessarily slow
because of failed attempts to download git tag and CDN version information
from remote servers.

This commit does a synchronous check of internet connectivity before attempting
any remote requests.

Closes #14759
2016-06-13 15:58:21 +01:00
Wesley Cho b03957f004 feat(limitTo): add support for array-like objects
Fixes #14657

Closes #14694
2016-06-10 21:14:04 +03:00
Martin Staffa 56f33af890 fix(ngMessages): create new scope for ngMessage, clean it up correctly
Previously, ngMessage elements used the same scope as ngMessages. When ngMessage
has interpolation in the textContent, then removing the message would not remove
the watcher from the scope - it would only be removed when the whole ngMessages
element was removed.

This commit changes the ngMessage transclude function to create a new child scope
instead, which can be destroyed safely when the message element is removed and
the message is detached

Fixes #14307
PR (#14308)
2016-06-10 14:49:08 +02:00
CarlosMiguelDesarrollo 41bea59eb7 chore(ngAnimate): delete an unused conditional statement
Delete a conditional statement that appears to have only been for debugging.
It is marked with a TODO tag to be deleted.

Closes #14282
2016-06-10 12:27:24 +01:00
Georgios Kalpakas 6d0dcca6f1 fix($routeProvider): do not deep-copy route definition objects
Deep-copying route definition objects can break specific custom implementations of `$sce` (used to
trust a `templateUrl` as RESOURCE_URL). The purpose of copying route definition objects was to guard
against the user's modifying the route definition object after route registration, while still
capturing inherited properties.
As suggested by @IgorMinar in https://github.com/angular/angular.js/pull/14699#discussion_r66480539,
we can achieve both _and_ support custom `$sce` implementations, by shallow-copying instead.

This is an alternative implementation for #14699, which avoids the breaking change.

Fixes #14478
Closes #14699

Closes #14750
2016-06-10 14:09:40 +03:00
mirabilos f423dc6b51 fix(input[email]): improve email address validation
**Limit size of local-part and total path size in eMail addresses**

RFC 5321 §4.5.3.1.1 ⇒ local-part can have up to 64 octets
RFC 5321 §4.5.3.1.3 ⇒ path “including the punctuation and
element separators” can have up to 256 octets
RFC 5321 §4.1.2 specifies path as ‘<’ + mailbox¹ + ‘>’ in
the best case, leaving us 254 octets

The limitation of the total path size to 254 octets leaves
at most 252 octets (one local-part, one ‘@’) for the domain,
which means we don’t need to explicitly check the domain
size any more (removing the assertion after the ‘@’).

① RFC 821/5321 “mailbox” is the same as RFC 822 “addr-spec”

**Optimise eMail address regex for speed**

There is no need to make it case-insensitive; the local-part
already catches the entire range, and the host part is easily
done. Furthermore, this makes the regex locale-independent,
avoiding issues with e.g. turkish case conversions.

cf. http://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/mailfrom.php?rev=HEAD

**Limit eMail address total host part length**

RFC 1035 §2.3.4 imposes a maximum length for any DNS object;
RFC 5321 §2.3.5 references this (and requires FQDNs, but there
have been cases where a TLD had an MX RR and thus eMail addresses
like “localpart@tld” are valid).

Credits: Natureshadow <d.george@tarent.de>

**Limit eMail address individual host part length**

A “label” (each of the things between the dots (‘.’) after the ‘@’ in
the eMail address) MUST NOT be longer than 63 characters.

cf. http://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/mailfrom.php?rev=HEAD
and RFC 1035 §2.3.4

**Fix eMail address local-part validation**

A period (‘.’) may not begin or end a local-part

cf. http://www.mirbsd.org/cvs.cgi/contrib/hosted/tg/mailfrom.php?rev=HEAD
and RFC 822 / 5321

Closes #14719
2016-06-10 10:46:21 +01:00
Georgios Kalpakas 9fca87ca5b docs(misc/downloading): update content - include missing modules
Closes #14746
2016-06-10 10:41:25 +01:00
Martin Staffa ebbaa4ac5e fix($compile): don't add merged attributes twice to $attrs
In replace directives, attribute values from the template are added twice
to the replaced element when both the replaced element and the template
element have the attribute. This does not affect the DOM, as it normalizes
duplicate values. The values are however duplicated in the $attrs object
that is available to directive link functions.

Fixes #8159
Closes #14737
2016-06-09 16:27:13 +01:00
J. Roberto Vidal 617137231e docs($resource): document Resource#toJSON
Document the `toJSON` method of `Resource` instances as part of its public API.

See #14637
Closes #14725
2016-06-09 16:00:59 +01:00
Stepan Suvorov 71b966ace7 docs(misc/downloading): update Angular version in example
Closes #14741
2016-06-09 11:17:37 +03:00
Stepan Suvorov 488d5b4288 updated version of angular for script src 2016-06-09 11:17:36 +03:00
Georgios Kalpakas 01b8772cca feat(ngMessagesInclude): don't break on empty (or whitespace-only) templates
Fixes #12941

Closes #14726
2016-06-07 23:22:15 +03:00
Georgios Kalpakas 05ac0ba926 style(ngMessages): fix indentation 2016-06-07 23:20:28 +03:00
mirabilos 399e872228 docs(guide/Security): warn about $http.jsonp()
Warn users about security issues with JSONP that are *not* mitigated by AngularJS

Closes #14727
2016-06-07 18:34:48 +01:00
Stephen Fluin 3115408be7 docs(guide/Component Router): improving comms 2016-06-07 16:33:20 +01:00
Martin Staffa aea39e9fdc docs(changelog, guide/migration): mention rare BC for ngInclude
See https://github.com/angular/angular.js/issues/13555#issuecomment-165118890
for detailed explanation.

Closes #13555
2016-06-07 10:43:40 +02:00
Georgios Kalpakas 51f6752cec style(ngMocks): remove unnecessary jsHint comment 2016-06-07 11:19:11 +03:00
Georgios Kalpakas db58801a55 fix(ngMock#$controller): properly assign bindings to all types of controllers (e.g. class-based)
The decorated version of `$controller` is able to assign bindings to a controller instance prior to
instantiation, emulating the behavior of `$compile` with directive controllers.

There are cases, that the actual controller instance is different than the pre-populated one (e.g.
when the controller constructor function returns a value or when the controller is an ES2015 Class).
While `$compile` accounts for such situation, by re-asigning the bindings after the controller has
been instantiated, `ngMock`'s `$controller` didn't.

This commit fixes it, by re-applying the bindings if the actual controller instance is different
than the original one (similar to how `$compile` would do it).

Fixes #14437

Closes #14439
2016-06-07 11:18:34 +03:00
Georgios Kalpakas 2e33d170be feat(orderBy): add support for custom comparators
Add an optional, 4th argument (`comparator`) for specifying a custom comparator function, used to
compare the values returned by the predicates. Omitting the argument, falls back to the default,
built-in comparator. The 3rd argument (`reverse`) can still be used for controlling the sorting
order (i.e. ascending/descending).

Additionally, the documentation has been expanded to cover the algorithm used by the built-in
comparator and a few more unit and e2e tests (unrelated to the change) have been added.

Helps with #12572 (maybe this is as close as we want to get).

Fixes #13238
Fixes #14455

Closes #5123
Closes #8112
Closes #10368

Closes #14468
2016-06-07 00:32:19 +03:00
Peter Bacon Darwin cc6dcb4bc2 fix($parse): allow arguments to contain filter chains
Thanks to @esarbanis for the original PR that got out-dated due to the
big $parse overhaul.

Closes #4175
Closes #4168
Closes #14720
2016-06-06 15:20:50 +01:00
Guillaume Salles 294d6793fd fix($location) : initialize $$absUrl to empty string
Initialize `$$absUrl` to an empty string, in order to avoid exception, when base href
and current location have different domains.

Fixes #11091
Fixes #13565

Closes #14488
2016-06-06 15:52:28 +03:00
Peter Bacon Darwin cefcc46e1b docs(tutorial/step-9): fix typo 2016-06-06 13:38:17 +01:00
Martin Staffa c10dba144e docs(ngComponentRouter): add deprecation notice and better install notes
Yes, that contradicts itself.

Closes #14655
Closes #14717
2016-06-06 13:30:31 +01:00
Peter Bacon Darwin f05ffd9bf1 test(browserTrigger): fix typo in eventData property 2016-06-06 13:28:17 +01:00
Peter Bacon Darwin e208d81293 test(browserTrigger): allow event bubbling
In some browsers, events don't bubble when they are dispatched on node inside
a detached tree. When this is the case, the bubbling is made manually.
This may be convenient when unit testing directives, for example.

Closes #5178
2016-06-06 11:57:23 +01:00
Peter Bacon Darwin b51fca1a2b test(browserTrigger): support key events
Support for key events like keypress, keyup or keydown.

Closes #8688
2016-06-06 11:57:21 +01:00
Andrew Schmadel 6685043ad4 fix(ngMockE2E): allow $httpBackend.passThrough() to work when ngMock is loaded
Allow $httpBackend.passThrough() to work normally when ngMock is loaded
concurrently with ngMockE2E, as is typically the case when writing tests with
angular.mock.module()

Fixes #1434
Closes #13124
2016-06-06 11:30:50 +01:00
Martin Staffa f176a2dbb7 docs(angular.element): note restrictions of addClass and removeClass
Closes #12851
Closes #12793
2016-06-03 21:50:00 +02:00
Georgios Kalpakas 3a366bd42b docs($exceptionHandler): include better example
The previous example overwrote the `$exceptionHandler` in order to re-throw the error. This is not
recommended, since it would leave the application in an inconsistent state and prevent proper
clean-up.
The new example simulates logging the error both to the backend and the console.

Fixes #14704
2016-06-03 21:34:48 +03:00
Georgios Kalpakas 1c5db02638 refactor($resource): explicitly set $resourceProvider.defaults.cancellable to false
Previously, it was just `undefined` and had the same behavior (since we only check for falsy-ness).
Just making it explicit, that this property is also available on `defaults` and is "not true" by
default.

Closes #14711
2016-06-03 20:15:45 +03:00
Georgios Kalpakas ffd5308d86 docs($resourceProvider): improve wording and formatting
Closes #11910
2016-06-03 20:15:02 +03:00
Yiling Lu aed9de9d37 docs($resourceProvider): provide info and example for configuring $resourceProvider 2016-06-03 20:14:37 +03:00
Wesley Cho aab6825e85 docs($animate): detail options object
- Document options object used in various $animate methods

Fixes #12645
PR: #14700
2016-06-03 13:28:44 +02:00
Martin Staffa c4fad6ad61 fix(ngSanitize): call attribute setter in linky for all links
Fixes #14707 
PR: #14710
2016-06-03 13:22:24 +02:00
ZachLamb 2a7c37cf01 docs(tutorial/step_04): fix type (JacaScript --> JavaScript)
Closes #14661
2016-06-01 11:08:42 +03:00
Peter Bacon Darwin 194c3adfed docs(CHANGELOG): fix typo 2016-05-27 22:34:51 +01:00
Martin Staffa 371957d9d5 docs(changelog): update with changes for 1.5.6 2016-05-27 17:59:43 +02:00
Lucas Galfasó 1e5d36d4b0 fix($timeout): make $flush handle new $timeouts added in $timeout callbacks
If a $timeout handler calls $timeout itself, this new $timeout should be
added to the mock deferred queue with a "now" time based on when the original
handler was triggered.

Previously it was being added with a now time of when the `$timeout.flush`
method was originally called.

Closes #5420
Closes #14686
2016-05-27 16:19:54 +01:00
Peter Bacon Darwin 3749c85829 fix($compile): cope with $onChanges hooks throwing
Previously, if one of these hooks threw an error, then the compilation
was terminated unceremoniously. In particular any other `$onChanges`
hooks that had been scheduled were not executed, nor was the queue cleaned
up properly.

Closes #14444
Closes #14463
2016-05-27 16:14:51 +01:00
Martin Staffa 886b59d317 chore(angularFiles): add missing files
ngMessageFormat test files were missing from angularTest
ngMessages and ngAnimate were missing from karmaModules.

This change highlighted a number of problems in the tests and code,
which are now fixed.

Closes #14314
Closes #14669
2016-05-27 16:13:31 +01:00
Victor Oliveira da Silva 2048b6df24 docs(ngRepeat): fix typo
Closes #14681
2016-05-26 22:22:57 +03:00
Georgii Dolzhykov 5ec0a50d39 chore(package.json): remove unused grunt-jasmine-node
Closes #14363
2016-05-26 01:43:24 +03:00
Peter Bacon Darwin 60689afe1c docs(tutorial/step-9): fix typo in inline link 2016-05-25 17:00:13 +01:00
Hadrien Tulipe 881fddaaba docs(ngAnimateMock): document how to require mocked service in tests
Closes #14668
Closes #14670
2016-05-25 16:57:37 +01:00
Robin Böhm fda338e8e2 refactor(ngMock): use exactly equal in browserTrigger
Closes #6763
2016-05-25 15:32:48 +02:00
David Goldberg 05c8d88126 style(src/Angular.js): make comment formatting consistent
Corrected formatting errors on lines 258, 695, 696, 1096, 1219, 1451, and 1536 by adding periods at the end of single line comments that were missing them in order to be consistent with the formatting found in the file.

Capitalized the first letter in the first word of the comments on lines 695, 696, 1096, to be consistent with the formatting of the rest of the file.

Added a space after the // in the comment on line 1536 to be consistent with the formatting of the file.
2016-05-25 15:17:20 +02:00
Martin Staffa aa57f57ccb docs(angular.element): note restriction in toggleClass 2016-05-25 15:12:08 +02:00
thorn0 51b78f61f9 perf($rootScope): make queues more efficient
By using a pointer to the current start of the queue and only clearing
up the queue storage later, we save lots of time that was spent manipulating
arrays with `slice`

Closes #14545
2016-05-25 13:22:12 +01:00
Ajay 739100e8a8 docs($provide): document decorator method
Closes #14562
2016-05-25 13:16:55 +01:00
Peter Briers 20e0c7f966 docs(ngAnimate): remove unnecessary transition prefixes
We can remove the vendor-prefixing from our docs, since all major browsers
support these properties, without prefixes.

Closes #14586
2016-05-25 12:38:46 +01:00
Jacob Reid f34e3413c7 docs(tutorial): fix typos
Closes #14662
Closes #14663
Closes #14664
2016-05-25 12:18:15 +01:00
Peter Bacon Darwin 4d28598b3d refactor(*): faster check for a string starting with a substring
Thanks to @spamdaemon for the original PR to make this improvement.

In naive tests on Chrome I got the following results:

```
             Matches   Misses
indexOf      33ms      1494ms
lastIndexOf  11ms      11ms
```

Closes #3711
2016-05-24 19:10:29 +01:00
Peter Bacon Darwin 16d6e89f3b refactor($route): consolidate route locals processing into a single handler 2016-05-24 19:02:09 +01:00
Peter Bacon Darwin 6e220ab612 refactor($route): move resolving locals into its own function 2016-05-24 19:02:09 +01:00
Peter Bacon Darwin ce14e4a6b7 refactor($route): move getting the template into its own function 2016-05-24 19:02:09 +01:00
Georgios Kalpakas b5dbc9834a docs(tutorial): update to use v1.5.x and best practices
This is a major re-structuring of the tutorial app's codebase, aiming at applying established best
practices (in terms of file naming/layout and code organization) and utilizing several new features
and enhancements (most notably components) introduced in recent versions of Angular (especially
v1.5).

Apart from the overall changes, two new chapters were introduced: one on components and one on code
organization.

--
In the process, several other things were (incidentally) taken care of, including:

* Dependencies were upgraded to latest versions.
* Animations were polished.
* Outdated links were updated.
* The app's base URL was changed to `/` (instead of `/app/`).

BTW, this has been tested with the following versions of Node (on Windows 10) and everything worked
fine:

* 0.11.16
* 4.2.6
* 4.4.2
* 5.10.0
* 6.2.0

--
This was inspired by (and loosely based on) #13834.
Again, mad props to @teropa for leading the way :)

--
**Note:**
The old version of the tutorial, that is compatible with Angular version 1.4 or older, has been
saved on the `pre-v1.5.0-snapshot` branch of
[angular-phonecat](https://github.com/angular/angular-phonecat). The `v1.4.x` version of the
tutorial should be pointed to that branch instead of `master`.

--
Related to angular/angular-phonecat#326.
Related to angular/angular-seed#341.

Closes #14416
2016-05-24 15:44:19 +03:00
Peter Bacon Darwin 57f10251db docs(tutorial): add svg and visio versions of the graffle images 2016-05-24 15:43:25 +03:00
Peter Bacon Darwin 48cd05a1ad docs(guide): add svg and visio versions of the graffle images 2016-05-24 15:42:33 +03:00
Peter Bacon Darwin 6a71ba17f0 docs(images): add svg and visio versions of the graffle image 2016-05-24 15:41:09 +03:00
marija cf1b175508 perf(ngClass): improve even-odd checking
This now uses the same technique as ngRepeat.
2016-05-23 23:31:52 +02:00
marija 5bba68c264 refactor($sniffer): avoid unnecessary call to substring 2016-05-23 23:31:51 +02:00
Alireza Mirian e5a4345aac docs(guide/Conceptual Overview): clarify service instantiation explanation
Change a sentence which describes how currencyConverter service is instantiated
2016-05-23 23:31:16 +02:00
Martin Staffa 49ef92041f docs(input[text]): remove deprecated tt tag
Closes #14547
2016-05-23 23:30:33 +02:00
Peter Bacon Darwin 081ae8bfca chore(docs): add horizontal rules between known issues 2016-05-23 22:19:23 +01:00
Peter Bacon Darwin 4f8aa6f5bb chore(docs): fix api.template.html to display git links 2016-05-23 22:19:19 +01:00
Xavier Haniquaut d7e888b8c6 docs(error/$compile): add reqslot error description
Explains what could generate a `reqslot` error.

Closes #14618
2016-05-23 14:21:00 +01:00
Peter Bacon Darwin 491b4af92e chore(docs): add horizontal rules between known issues 2016-05-23 14:03:00 +01:00
Peter Bacon Darwin 5930b8ee27 docs($interpolate): add known issue about custom interpolation symbols
Closes #14610
Closes #6493
2016-05-23 13:52:57 +01:00
Jukka 69d9072d5b fix(ngMessagesInclude): do not compile template if scope is destroyed
Messages imported with `ngMessagesInclude` are loaded asynchronously and
they can arrive after the ngMessages element has already been removed from DOM.

Previously we tried to compile these messages and that caused a `$compile:ctreq`
error. Now they are silently ignored if `ngMessagesInclude`'s scope has already
been destroyed.

Closes #12695
Closes #14640
2016-05-23 13:00:56 +01:00
Wesley Cho 9b5de0ac97 docs(contributing): fix the styleguide link
Update the link to Google's JavaScript style guide to the updated version

Closes #14652
2016-05-23 12:49:09 +01:00
Georgios Kalpakas 21b76aed06 fix($animate): don't break on anchored animations without duration
If the `from` element of an animation does not actually have an animation
then there will be no animation runner on that element. This is possible
if the element has the `ng-animate-ref` attribute but does not have any
CSS animations or transitions defined.

In this case, it is not necessary to try to update the host of the
non-existent runner.

Fixes #14641
Closes #14645
2016-05-22 19:02:45 +01:00
Bijay Deo 363fb16c10 fix(ngMock): match HTTP request regardless of the order of query params
Closes #12762
2016-05-21 10:44:37 +01:00
Peter Bacon Darwin e00f9f62e3 docs(toJson): documented the Safari RangeError known issue
Closes #14221
Closes #13415
2016-05-21 10:15:45 +01:00
Georgios Kalpakas 507c666d16 docs(ngMock/$exceptionHandler): fix formatting
Too much indentation, made text get formatted as code block.
2016-05-20 21:23:51 +03:00
Adrian Moos 743b33ab9d fix($brower): set the url even if the browser transforms it
While $location expects that $browser stores the URL unchanged, "some browsers" transform the URL
when setting or defer the acutal update. To work around this, $browser.url() kept the unchanged
URL in pendingLocation.

However, it failed to update pendingLocation in all code paths, causing
$browser.url() to sometimes incorrectly report previous URLs, which horribly confused $location.

This fix ensures that pendingLocation is always updated if set, causing url() to report the
current url.

Fixes #14427
Closes #14499
2016-05-20 15:44:53 +01:00
Peter Bacon Darwin 9f8fa79f25 docs(misc/contribute): specify node version 2016-05-18 16:46:44 +01:00
Peter Bacon Darwin fe3c1d44a5 docs(ngMock): final installation description fix 2016-05-18 08:42:05 +01:00
Georgios Kalpakas 5ae72dc7af fix($compile): properly bind context to linking functions for directives with templateUrl
Also, fix some styling issues.
2016-05-17 22:21:22 +03:00
Paulo Cesar 47bc98a1ea fix($compile): always use the DDO as this in pre-/post-linking functions
Closes #9306
2016-05-17 22:20:26 +03:00
Peter Bacon Darwin 10920a2857 docs(ngMock): fix installation instructions 2016-05-17 18:25:16 +01:00
Thomas Kowalski 90a9270dfa docs(guide/component-router): remove unused code
Closes #14613
2016-05-17 12:06:52 +03:00
Igor Minar d4b05dd13a revert: perf(ngAnimate): listen for document visibility changes
This reverts commit b377d6b043.

This commit introduces dependency on $document during the injector
bootstrap. This is usually fine, except for cases when $document is
mocked out in existing test suites. Since we don't provide an out
of box mock, people have created their owns that don't sufficiently
mock all apis. In these environments this change results in errors
like $document.on is not a function, etc.
2016-05-16 16:59:08 -07:00
Peter Bacon Darwin 59802bc790 docs(ngMock): fix installation formatting
The `@installation` section should be formatted with markdown, not
nunjucks template directives.

Closes #14604
2016-05-16 22:46:51 +01:00
Sander Boom b0a35a794a docs(guide/$location): fix typo
Closes #14607
2016-05-14 01:53:25 +03:00
Mark Pieszak a9545e3963 docs(readme): remove "(beta)" from Angular 2 link
Closes #14599
2016-05-13 23:17:18 +03:00
Georgios Kalpakas 269c15c74b docs(ngComponentRouter): specify correct version of npm package
The latest npm package does not contain the angular 1 version of the router.

Related to 824ce30.
2016-05-13 19:25:01 +03:00
Maksim Ryzhikov 9f48025733 fix(ngAnimate): properly handle empty jqLite collections
Previously `stripCommentsFromElement()` would return an empty Array (instead of a jqLite collection)
which would cause an exception to be thrown: "element.parent not a function".
This commit fixes it, by ensuring that the returned value is always a jqLite collection.

Closes #14558

Closes #14559
2016-05-13 18:17:43 +03:00
Peter Bacon Darwin 1d0ccd0d73 chore(package.json): update to use dgeni-packages@0.13.0 2016-05-12 11:05:27 +01:00
Peter Bacon Darwin 03e5164528 chore(npm/check-node-modules): cope with no npm-shrinkwrap file 2016-05-12 11:05:25 +01:00
Pete Bacon Darwin 3047964712 fix($compile): don't run unnecessary update to one-way bindings
The watch handler was triggering on its first invocation, even though
its change had already been dealt with when setting up the binding.

Closes #14546
Closes #14580
2016-05-10 22:31:29 +01:00
Peter Bacon Darwin 225afb9eb4 docs(guide/component-router): specify correct version of npm package
The latest npm package does not contain the angular 1 version of the router.

Closes #14564
Closes #14560
2016-05-07 21:40:13 +01:00
Martin Staffa b377d6b043 perf(ngAnimate): listen for document visibility changes
Accessing the document for the hidden state is costly for
platforms like Electron. Instead, listen for visibilitychange
and store the state.

Closes #14568
2016-05-06 15:53:24 +02:00
dmandelin ac650cb2e6 docs($sanitizeProvider): fix param comment for enableSvg
Change the text of the doc comment for the parameter of enableSvg to match the type of the comment and the behavior of the code (#14543).
2016-05-06 15:45:50 +02:00
FGasper 926088ba77 docs($location): clarify return value for path method
docs for return of path() inaccurately describe the function’s return when a value is passed in.

Closes #14544
2016-05-06 15:45:50 +02:00
Stephen Barker f94cf634d8 docs(guide/decorators): add decorator guide
+ explain decorators and how they are implemented in angular
+ explain how different types of services can be selected
+ explain `$delegate` objects and how they differ between services
+ warn of the risks/caveats of `$delegate` modification
+ note the exposure of `decorator` through the module api
+ show an example of decorating a core service
+ show an example of decorating a core directive
+ show an example of decorating a core filter

Closes #12163
Closes #14372
2016-05-06 15:45:49 +02:00
Michael Warner 85b3786f55 docs(ngRequired): add 'restrict' info
The Angular Docs do not show the restrictions

Closes #14541
2016-05-06 15:45:12 +02:00
Ivo Mirra 1116a96df6 fix($compile): removing unnecessary white space in element-transclusion comments
Fixes #14549

Closes #14550
2016-05-05 18:56:19 -06:00
Martin Probst 1c1c9b27cb fix(ng-bind-html): watch the unwrapped value using $sce.valueOf() (instead of toString())
Custom `$sce` implementations might not provide a `toString()` method on the wrapped object, or it
might be compiled away in non-debug mode. Watching the unwrapped value (retrieved using
`$sce.valueOf()`) fixes the problem.

The performance of this should be equivalent - `toString()` on objects usually touches all fields,
plus we will also avoid the (potentially big) string allocation.

Fixes #14526
Closes #14527
2016-04-28 20:25:13 +03:00
Mark Pieszak c5e7a5bc9b docs(README.md): add link to Angular 2 repo
Might be helpful for those completely new to Angular in general, that intended to view the upcoming
2.0 version.

Closes #14532
2016-04-28 19:46:57 +03:00
Georgios Kalpakas 7550b7bda8 fix($injector): add workaround for class stringification in Chrome v50/51
Partially fixes #14240.

Closes #14531
2016-04-28 14:31:50 +03:00
Petr Mikota 43b97b211b docs(guide/component-router): add missing backticks
Closes #14530
2016-04-28 11:40:39 +03:00
Weijing Jay Lin 76405897fc docs(identity): add example
Closes #14528
2016-04-28 11:07:00 +03:00
Georgios Kalpakas bb730704f2 fix($injector): add workaround for fat-arrow stringification in Chrome v50/51
Closes #14487

Closes #14495
2016-04-28 10:55:51 +03:00
Georgios Kalpakas 8965d57aa0 feat($compile): support omitting required controller name if same as the local name
Basically, making `require: {someDir: '?^someDir'}` equivalent to `require: {someDir: '?^'}`.

Closes #14513
2016-04-28 10:44:21 +03:00
Fabricio Ronchi 951a2743d5 docs(ngAria): remove redundant table column
Closes #14518
2016-04-28 10:38:35 +03:00
Michael de Wit a2a907e439 docs(guide/component): several unit-test fixes and remove obsolete testcase
Fixes #14426

Closes #14443
2016-04-26 20:24:56 +03:00
Perry Hooker 308f22ba9a fix($compile): properly handle setting srcset to undefined
Previously, calling `Attributes#$set('srcset', value)` on an `<img>` element would throw if `value`
were undefined, as it assumed `value` is always a string.
This commit fixes the issue, by skipping the unnecessary string manipulation when `value` is not
defined.

Closes #14470

Closes #14493
2016-04-26 09:17:51 +03:00
andrea 32e2643d90 docs(guide/index): fixed formatting with subtitles
The "Books" and "Videos" subtitles had no space between text and the '#' so it didn't render as a
subtitle.

Closes #14514
2016-04-26 08:33:36 +03:00
Tim van Dalen 3edaf0922f docs($anchorScroll): fix link to HTML5 spec
Closes #14364
2016-04-26 00:22:58 +03:00
Raphael Jamet 1095596962 fix($templateRequest): trust empty templates in $templateCache as well
Implicitly trust empty templates added to `$templateCache` as is the case for all other templates.

Fixes #14479

Closes #14496
2016-04-25 20:22:26 +03:00
Georgios Kalpakas ee0dac4d1d docs($interpolate): add known issue about end-symbol in expression
This has been discussed in #8642.

Closes #14494
2016-04-22 15:42:21 +03:00
Georgios Kalpakas 6f1fae8a91 fix(filters): always call splice() with 2 arguments or more
When calling `.splice()` without a 2nd argument (`deleteCount`), most browsers will splice to the
end of the array. As it turns out, this is the behavior specified in [ES2015][es6]. In [ES5][es5],
the spec seems to imply that nothing should happen.

To avoid inconsistent behavior among browsers implementing different versions of the EcmaScript
standart or when ES5 shims are included (e.g. https://github.com/es-shims/es5-shim/), this commit
ensures that all calls to `.splice()` provide at least two arguments.

[es5]: http://www.ecma-international.org/ecma-262/5.1/#sec-15.4.4.12
[es6]: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.splice

Fixes #14467

Closes #14489
2016-04-22 12:41:28 +03:00
Abhishek Garg f96485085a docs($http): add better explanation for error status codes
Add explanation for error status codes in `$http` and mention negative codes normalization.
This alleviates confusion about which status codes will trigger the error callback and what values
to expect in such callbacks.

Fixes #11945

Closes #14459
2016-04-22 12:24:11 +03:00
andreyjkee 147650b3be fix(ngMocks): pass eventHandlers to $httpBackend if passThrough is active
Closes #14471
2016-04-21 23:10:28 +02:00
Martin Staffa 10c426ffa3 docs(guide/Expressions): mention absence of bitwise operators
Closes #14485
2016-04-21 16:29:04 +02:00
Abhishek Garg 785647eb9d docs(changelog): make labels and list style consistent
Remove 'Breaking Changes' label where there are no breaking changes.

Only for '1.4.0-rc.2', breaking changes have different styling (bold without bulleted list). Change to be consistent with others

(#14482)
2016-04-21 16:29:04 +02:00
Amy a2a6279157 docs($sce): add missing article
...teeny letter missing.  Just a grammar change.
(#14480)
2016-04-21 16:29:03 +02:00
jody tate b50d2a4a1f docs(ngAria): fix markdown heading syntax
(#14477)
2016-04-21 16:29:03 +02:00
Martin Staffa f539fb1f7c docs(ngMock): add custom installation instructions
ngMock should be used in a test runner context, not directly in the application

Closes #14248
2016-04-21 16:01:26 +02:00
Martin Staffa 2dcb5acb7b docs: reorganize module overview page template 2016-04-21 16:01:23 +02:00
Michał Gołębiowski 905a8fd4a4 chore(jQuery): bump jQuery version from 2.2.1 to 2.2.3 2016-04-20 13:36:48 +02:00
Michał Gołębiowski df9b57a481 test(jqLite): make add/removeEventListener tests work with jQuery 2.1
The useCapture parameter defaults to false even in oldest of our supported
browsers; this is no longer needed so jQuery has removed it in 2.2.

In Angular 1.6 we apply the same refactoring, here in 1.5 we're just making
tests work with jQuery 2.2.
2016-04-20 13:30:46 +02:00
Michał Gołębiowski fee2077d0b chore(jqLite): stop patching the change jQuery special handler
Code patching the change event in jQuery doesn't exist in jQuery 2 so this patch
hasn't been needed since Angular 1.3
2016-04-20 12:29:40 +02:00
Michał Gołębiowski 691aafc981 tests(jQuery): test on both oldest & latest supported jQuery version 2016-04-20 12:26:36 +02:00
Ken Powers da7af937d6 docs(guide/Components): change snake to kebab case
The example given is not in snake_case, it is in kebab-case.

Closes #14464
2016-04-19 20:34:54 +02:00
Lucas Mirelmann 1c9b9e24ed feat($parse): Add support for ES6 object initializers
- Add support for computed property names
- Add support for short notation in properties definitions

Eg. The expressions `{x}` and `{[x + 'foo']: 1}` are now valid.

Closes: #14407
2016-04-18 22:09:17 +02:00
Martin Staffa 46a5b2c298 docs(ngMockE2E): add $httpBackend mock example 2016-04-18 14:09:24 +02:00
Peter Bacon Darwin 28e4707dc4 chore(CHANGELOG): fix version number for 1.5.5 2016-04-18 12:31:11 +01:00
Peter Bacon Darwin 420b5211da chore(CHANGELOG): add 1.5.5 release notes 2016-04-18 10:52:06 +01:00
Jeremy 04bccee099 docs(guide/Component Router): fix a typo in npm install instructions
Related #14452
2016-04-17 23:50:13 +02:00
Peter Bacon Darwin a404839795 docs(CHANGELOG): remove changes from 1.5.4
1.5.4 was only partially released.
These changes will be included in the 1.5.5 release
2016-04-16 09:29:37 +01:00
Georgios Kalpakas 25d4e5cca4 fix($http): pass event object to eventHandlers/uploadEventHandlers
Fixes #14436
2016-04-15 14:09:39 +01:00
Peter Bacon Darwin c67112563f revert: refactor($compile): move setting of controller data to single location
Reverted from commit 21d148aedc since it caused
the Angular Material tabs directive to fail.
2016-04-15 14:09:05 +01:00
Jannick Fahlbusch 2b2ec26a75 chore(docsApp): open plnkr.co with HTTPS
Plnkr should be opened via HTTPS instead of HTTP to supress warnings about an insecure connection

Closes #14445
2016-04-15 14:55:13 +02:00
cloverharvest 41f90e5fb0 docs($http): fix a typo (his --> this)
Closes #14430
2016-04-15 01:25:09 +03:00
Adam Demuri a85c591d36 docs(component): document 'require' in angular.component
Closes #14429
2016-04-15 01:12:43 +03:00
Peter Bacon Darwin f276ad0d51 docs(CHANGELOG): add release notes for 1.5.4 2016-04-14 09:13:48 +01:00
Pete Bacon Darwin 33f817b99c feat($componentController): provide isolated scope if none is passed (#14425)
Closes #14425
2016-04-13 18:34:39 +01:00
Martin Staffa 6a4124d0fb perf(ngOptions): use documentFragment to populate select
This changes the way option elements are generated when the ngOption collection changes.
Previously, we would re-use option elements when possible (updating their text and
label). Now, we first remove all currently displayed options and the create new options for the
collection. The new options are first appended to a documentFragment, which is in the end appended
to the selectElement.

Using documentFragment improves render performance in IE with large option collections
(> 100 elements) considerably.

Creating new options from scratch fixes issues in IE where the select would become unresponsive
to user input.

Fixes #13607
Fixes #13239
Fixes #12076
2016-04-13 18:42:10 +02:00
Georgios Kalpakas 26d1b34321 docs(ngView): add known issue about asynchronously loaded ngView
Closes #14424
2016-04-13 18:45:40 +03:00
Georgios Kalpakas 7fba6b603f revert: "fix(ngRoute): allow ngView to be included in an asynchronously loaded template"
This reverts commit 5e37b2a7fd.
Eagerly loading `$route`, could break tests, because it might request the root or default route
template (something `$httpBackend` would know nothing about).

It will be re-applied for `v1.6.x`, with a breaking change notice and possibly a way to disable
the feature is tests.

Fixes #14337
2016-04-13 18:45:06 +03:00
Jason Bedard c115b37c33 perf($compile): use createMap() for directive bindings to allow fast forEach
Closes #12529
2016-04-13 12:05:30 +03:00
Georgii Dolzhykov d20ba95e28 docs(Module): fix parameter names for .decorator()
Closes #14413
2016-04-12 14:52:18 +03:00
aortyl 7945e5010a docs(guide/scope): add comma for readability
Closes #14411
2016-04-11 22:22:36 +03:00
Jason Bedard 21d148aedc refactor($compile): move setting of controller data to single location
Closes #13421
2016-04-11 18:54:41 +01:00
Georgios Kalpakas 4c8aeefb62 fix($compile): do not use noop() as controller for multiple components
Currently, custom annotations are copied from the CDO onto the controller constructor.
Using `noop()` when no controller has been specified, pollutes it with custom annotations and
makes one component's annotations available to all other components that have `noop()` as their
controller.

Fixes #14391
Closes #14402
2016-04-11 18:43:17 +01:00
a510 d384834fde fix($injector): ensure functions with overridden toString() are annotated properly
Closes #14361
2016-04-11 13:03:56 +03:00
David Rodenas Pico f975d8d448 fix(ngClass): fix watching of an array expression containing an object
Closes #14405
2016-04-11 12:40:36 +03:00
Georgios Kalpakas 2602daf993 refactor($compile): remove unnecessary call to isDefined()
(As discussed in https://github.com/angular/angular.js/pull/14406/files#r59131398.)
2016-04-11 12:33:07 +03:00
Gene McCulley 9f681c459a docs(numberFilter): fix the description of the returned value
Closes #14408
2016-04-11 12:28:08 +03:00
Peter Bacon Darwin d9448dcb9f fix($compile): still trigger $onChanges even if the inner value already matches the new value
Closes #14406
2016-04-10 20:31:01 +01:00
Jurko Gospodnetić e9c718a465 fix(ngMock): fix collecting stack trace in inject() on IE10+, PhantomJS
Add support for collecting current stack trace information in browsers
(e.g. IE10+, PhantomJS) that do not automatically store the current stack trace
information in a newly created `Error` object's `stack` property, but
only add it there once the `Error` gets thrown.

The original implementation works fine in Firefox & Chrome, but fails on IE10+
and PhantomJS where it, for example, breaks Karma's error reporting in cases
when an exception is thrown in a test like the following:

```
it('the holy crusade', inject(function() {
  var x = {};
  x.holyGrail();
}));
```

In this case, the ngMock `inject()` implementation would incorrectly add the
word `undefined` at the end of the collected error stack trace information,
thus causing the main error description to be reported back to Karma as
`undefined`.

The added test makes sure this functionality:

- works as expected in browsers supporting JavaScript stack trace
  collection, e.g. Chrome, Firefox, IE10+, Opera & PhantomJS
- does not add any bogus stack track information in browsers that do
  not support JavaScript stack trace collection, e.g. IE9

Fixes #13591
Closes #13592

Closes #13593
2016-04-09 20:51:22 +03:00
Georgios Kalpakas 4dc44f7205 test(helpers): fix error message generation for toHaveBeenCalledOnce[With] matchers
Jasmine 2.4 (maybe earlier) does not support returning an array containing both the "normal" and the
negative error messages. It will always concatenate them.

Closes #14275
2016-04-09 17:06:47 +03:00
Jan Niehusmann 499e1b2adf fix($compile): handle boolean attributes in @ bindings
Commit db5e0ff handles initial values of boolean attributes. The same
change needs to be applied inside the attrs.$observe() call.

Closes #14070
2016-04-09 16:53:41 +03:00
Peter Bacon Darwin 28c6c4dae2 docs(jqlite): add known issue
Closes #14251
2016-04-08 21:27:21 +01:00
Peter Bacon Darwin 791148a328 chore(package.json): update dgeni-packages to 0.12.0
This gives us `@knownissues` tags
2016-04-08 21:27:05 +01:00
Peter Bacon Darwin 01b1845088 feat($http): support handling additional XHR events
Closes #14367
Closes #11547
Closes #1934
2016-04-08 19:16:56 +01:00
Chris Chua 56c861c9e1 feat($http): support handling additional XHR events
Closes #11547
Closes #1934
2016-04-08 19:16:49 +01:00
Martin Staffa 451e0f6175 docs(README): fix typo 2016-04-08 19:00:20 +02:00
Martin Staffa aee7f1c72f docs(guide/accessibility): make jshint happy 2016-04-08 18:55:22 +02:00
Pablo Iván G. Soto 5fe28a0422 docs(README): add https links, improve style 2016-04-08 18:37:04 +02:00
Martin Staffa f56586d9a9 docs(guide/Accessibility): fix markdown errors, tweak layout 2016-04-08 17:32:13 +02:00
mohamed amr ec0baadcb6 feat(ngAria): add support for aria-readonly based on ngReadonly
Closes #14140
Closes #14077
2016-04-08 17:32:12 +02:00
Martin Staffa 9147d5c04a docs(ngComponentRouter): add note about shims needed for IE 2016-04-08 17:32:12 +02:00
Josh cb801378c9 docs(ngComponentRouter): fix typo
Simple typo fix from `betweent he` to `between the`

Closes #14396
2016-04-08 15:47:01 +02:00
Martin Staffa 79604f4628 fix(ngAnimate): remove event listeners only after all listeners have been called
The fix for removing the event callbacks on destroy introduced in
ce7f400011 removed the events too early, so that the event callbacks
for the "close" phase in "leave" animations were not called.

This commit fixes the behavior so that the event callbacks are only removed during on('$destroy')
when no animation is currently active on the element. When an animation is active, the event callbacks
will be removed after all callbacks have run, and if the element has no parent (has been removed from
the DOM).

Closes #14321
2016-04-08 15:47:00 +02:00
Martin Staffa bf6cb8ab0d feat(ngAnimate): let $animate.off() remove all listeners for an element 2016-04-08 15:46:59 +02:00
Andrew de74b3fd85 docs(guide/Components): fix small single letter typo
line 136: 'not' should be 'note'

Closes #14390
2016-04-08 01:32:00 +02:00
Martin Staffa c7a92d2a9a fix(ngAnimate): fire callbacks when document is hidden
Since commit a3a7afd3aa, animations are not run
when the document is hidden (only their structural or class change effects are executed).
However, some libraries rely on the $animate.on() callbacks to be called even when no actual animation
runs.
This commit restores the behavior for the ngAnimate.$animate functions.
Note that callbacks still won't be called if animations are disabled, because this would be be a potential
breaking change, as some applications might rely on this implementation.

Fixes #14120
2016-04-08 01:31:59 +02:00
Martin Staffa da284fc354 test(ngAnimate): test calling callbacks for various constellations 2016-04-08 01:31:58 +02:00
Martin Staffa 90da3059ce fix(ngAnimate): fire callbacks in the correct order for certain skipped animations 2016-04-08 01:31:58 +02:00
SHAHRUKH-KHAN 651fd05eca docs(angular.equals): add example
This Pull requests improves the doc by adding a example to `angular.equals` function.

Closes #14232
2016-04-07 15:17:54 +02:00
glenr4 85d7e09bef docs(ngAnimate): fix toggle button in example
The toggle button code on line 153 only sets bool to true, rather than toggling it.
The proposed change fixes this.

Closes #14387
2016-04-07 15:17:54 +02:00
andykuszyk 243a57648b docs(misc/Getting Started): fix markdown for headings
Closes #14353
2016-04-07 15:17:54 +02:00
Maciej Kołodziejczak 158dec330c docs(ngComponentRouter): fix a typo
Closes #14357
2016-04-07 15:17:53 +02:00
Andrew a1e63c8d4a docs(guide/Components): clarify output events with extra example and note
Add additional line from example which demonstrates using the snake cased attribute binding in
parent component template.
Add note clarifying camelCase to snake-case requirement to use the Output binding callback feature.

Closes #14365
2016-04-07 15:17:53 +02:00
Artur 8cc9978d15 docs(guide/Components): fix typo, improve style
Closes #14384
2016-04-07 15:17:52 +02:00
Robin Janssens 78a404da0e docs($httpBackend): update response data types
Updated docs to reflect that response data can either be an array, object _or_ a string
Technically, response data can be anything that can be handled by angular.copy,
but since string and JSON data is most commonly mocked, the main types are sufficient.

Closes #14346
2016-04-07 15:17:52 +02:00
Michał Gołębiowski f1bb8369b5 refactor(jshint): don't assume browser-only globals
(cherry-picked from ddad26402b)

Fixes #13442
Closes #14345
2016-04-06 20:43:06 +02:00
Peter Bacon Darwin 76b6941b4c docs(componentRouter): add custom installation instructions 2016-04-05 21:10:19 +01:00
Peter Bacon Darwin e72990dc37 fix($compile): don't throw if controller is named 2016-04-04 21:26:13 +01:00
Peter Bacon Darwin f338e96ccc feat($compile): put custom annotations on DDO
Closes #14369
Closes #14279
Closes #14284
2016-04-04 20:07:31 +01:00
Peter Bacon Darwin 0ad2b70862 fix($compile): ensure that $onChanges hook is called correctly
Due to the way that we instantiate controllers, the `$onChanges` hook
was not always available at the time we were trying to trigger the initial
call to this hook. For instance, if the hook was actually defined inside
the constructor function.

This commit fixes that but also fixes the fact that the initial call was
being made in the postDigest anyway, which was incorrect because the
it should have been made before the `$onInit` call.

Closes #14355
Closes #14359
2016-04-01 17:41:41 +01:00
Georgios Kalpakas 5261a374a9 test($browser): fix typo in property name (histroy --> history)
(The semantics of the test aren't affected, because we just needed a falsy value.)
2016-03-31 13:30:25 +03:00
Martin Staffa 832eba5fc9 fix(ngOptions): set select value when model matches disabled option
When ngModel is set to a value that matches a disabled option, ngOptions will now select the option
in the select element, which will set select.val() to the option hash value and visually
show the option value / label as selected in the select box. Previously, disabled
options forced the unknown value.
The previous behavior is inconsistent with both default HTML behavior and select with
ngModel but without ngOptions. Both allow disabled values to be selected programmatically.

A common use case for this behavior is an option that was previously valid, but has
been disabled, and cannot be selected again.

This commit removes a duplicate test, and all other tests that previously checked that disabled
options are not set have been adjusted to the ensure the opposite.

Fixes #12756
2016-03-30 11:42:07 +02:00
cscport 2b569fc0b2 docs(angular.bootstrap): fix capitalization in error message
Closes #14325
2016-03-27 15:43:37 +03:00
Lucas Mirelmann ee6aeb08bf docs(ngParseExt): Fix package name 2016-03-27 00:00:14 +01:00
Peter Bacon Darwin 8d43d8b8e7 feat($compile): add isFirstChange() method to onChanges object
Closes #14318
Closes #14323
2016-03-26 20:06:13 +00:00
Lucas Mirelmann d08f5c6986 feat(ngParseExt): New ngParseExt module
New ngParseExt module

Including this module into an application will extend $parse to allow identifiers
following ES6 identifiers
2016-03-26 20:41:11 +01:00
Lucas Mirelmann 3e7fa19197 feat($parse): Add the ability to define the identifier characters
Add the ability to define the identifier starts and identifier continue characters
2016-03-26 20:41:01 +01:00
Lucas Mirelmann 5d379db72b chore(bower): Add parse-ext repository 2016-03-26 20:40:49 +01:00
Peter Bacon Darwin 514639b585 docs(CHANGELOG): add 1.5.3 release notes 2016-03-25 20:01:45 +00:00
Peter Bacon Darwin 9cd9956dcb feat($compile): add more lifecycle hooks to directive controllers
This change adds in the following new lifecycle hooks, which map in some
way to those in Angular 2:

 * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys
   are the names of the bound properties that have changed, and the values are an object of the form
   `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as
   cloning the bound value to prevent accidental mutation of the outer value.
 * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
   external resources, watches and event handlers.
 * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link
   function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
   Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
   they are waiting for their template to load asynchronously and their own compilation and linking has been
   suspended until that occurs.

Closes #14127
Closes #14030
Closes #14020
Closes #13991
Closes #14302
2016-03-25 12:56:08 +00:00
Martin Staffa c7813e9ebf fix(ngAnimate): run structural animations with cancelled out class changes
When multiple animations on the same element are queued before a $digest passes,
the animator tries to create as few actual animations as possible by joining / canceling
redundant animations. Class-based animations for example are cancelled when the classes that
are added and removed are the same, and the result is no class-change. This however must only
happen if there's no structural animation currently queued.

Fixes #14249
2016-03-24 00:13:16 +01:00
Martin Staffa ef91b04cdd fix(ngMessages): don't crash when nested messages are removed
Under specific circumstances, ngMessages would go into an infinite loop and crash the
browser / page:
- At least two ngMessage elements are wrapped inside another element (e.g. ngTransclude)
- The first message is currently visible
- The first message is removed (e.g. when the whole ngMessages element is removed by an ngIf)

When a message is removed, it looks for a previous message - in this specific case it would misidentify
the second message for a previous message, which would then cause the first message to be marked as the
second message's next message, resulting in an infinite loop, and crash.

This fix ensures that when searching for previous messages, ngMessage walks the DOM in a way so
that messages that come after the current message are never identified as previous messages.

This commit also detaches and destroys all child ngMessage elements when the ngMessages element is
destroyed, which should improve performance slightly.

Fixes #14183
Closes #14242
2016-03-24 00:13:16 +01:00
Alex Chuev 1acd97e18f docs(guide/component): add missing closing bracket
Closes #14299
2016-03-23 23:58:35 +02:00
Daniel Herman 513199ee9f fix($compile): workaround a GC bug in Chrome < 50
In the version of V8 used in Chrome < 50, the parent of template nodes for
`transclude: "element"` directives would be improperly garbage collected
despite still having been referenced via `parentNode`.

This bug surfaced due to the introduction of lazy transclusion (652b83e),
and appears under certain circumstances when using directive start and end elements.

It should be removed some time after Chrome 50 has been released.

Fixes #14041
Closes #14286
2016-03-23 22:06:17 +01:00
pmadruga 33f3c40e93 docs(error/$compile.baddir): mention "components" in directive name error
Closes #14212
2016-03-23 22:18:54 +02:00
Steve Mao 696cb95d5e docs($q): mention ES2015 (as a "synonym" for ES6) and remove "harmony"
Closes #14294
2016-03-22 12:09:02 +02:00
Georgios Kalpakas 457fd21a1a fix($sniffer): fix history sniffing in Chrome Packaged Apps
Although `window.history` is present in the context of Chrome Packaged Apps, it is not allowed to
access `window.history.pushState` or `window.history.state`, resulting in errors when trying to
"sniff" history support.
This commit fixes it by detecting a Chrome Packaged App (through the presence of
`window.chrome.app.runtime`). Note that `window.chrome.app` is present in the context of "normal"
webpages as well, but it doesn't have the `runtime` property, which is only available to packaged
apps (e.g. see https://developer.chrome.com/apps/api_index).

(It also also contains some style changes for making the structure and layout of `$sniffer` tests
 more consistent.)

Fixes #11932

Closes #13945
2016-03-22 11:57:18 +02:00
Wassim Chegham 3c6dfbf67d docs(guide/component-router): fix typos
Closes #14278
2016-03-22 02:18:44 +02:00
Owen Craig 3277b885c4 fix(formatNumber): handle small numbers correctly when gSize !== lgSize
By using `>=` when comparing the number length to `lgSize`, we'll provide the correct value, when
formatting numbers with different `lgSize` than `gSize`.

Fixes #14289

Closes #14290
2016-03-22 00:11:26 +02:00
Georgios Kalpakas 48a256d04b test(TzDate): fix test in Australia
Probably due to implementation differences in browsers for pre-DST period (see
https://github.com/angular/angular.js/issues/5017 and especially
https://github.com/angular/angular.js/issues/5017#issuecomment-90775226 for context), some
`TzDate` tests had different behavior on different Timezones/Regions (e.g. failed in Australia,
which started to observe DST in 1971).
Since the used year (`1970`) didn't have any particular significance, this commit fixes the issue
by using a year that is more consistently handled by browsers (`2000`).

Fixes #14272

Closes #14285
2016-03-21 20:45:35 +02:00
surya prakash singh 0579430799 docs(input[time]): fix a typo in the example
Closes #14220
2016-03-21 01:26:17 +02:00
Rongduan Zhu 39ac68dac1 docs(guide/component-router): changed path to match diagram
Closes #14277
2016-03-21 00:00:12 +02:00
Georgios Kalpakas 87fb44a5d3 docs(CHANGELOG.md): rearrange v1.5.1 to be right below v1.5.2
Moved the `v1.5.1` section above the `v1.4.10` one, so that it is right below the `v1.5.2` section
for easier reference. Also removed an empty "Breaking Changes" sub-section.

Closes #14283
2016-03-20 22:54:53 +02:00
Georgios Kalpakas 5c76b406f7 chore(ci-checks): fix the ddescribe-iit task for Jasmine 2
Closes #14276
2016-03-20 22:26:06 +02:00
339 changed files with 134181 additions and 17005 deletions
+8 -3
View File
@@ -1,19 +1,24 @@
{
"bitwise": true,
"esversion": 6,
"immed": true,
"newcap": true,
"noarg": true,
"noempty": true,
"nonew": true,
"trailing": true,
"maxlen": 200,
"boss": true,
"eqnull": true,
"expr": true,
"globalstrict": true,
"laxbreak": true,
"loopfunc": true,
"strict": "global",
"sub": true,
"undef": true,
"indent": 2
"indent": 2,
"globals": {
"ArrayBuffer": false,
"Uint8Array": false
}
}
+1
View File
@@ -19,6 +19,7 @@ env:
- JOB=unit BROWSER_PROVIDER=saucelabs
- JOB=docs-e2e BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jqlite BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery-2.1 BROWSER_PROVIDER=saucelabs
- JOB=e2e TEST_TARGET=jquery BROWSER_PROVIDER=saucelabs
global:
- CXX=g++-4.8 # node 4 likes the G++ v4.8 compiler
+520 -95
View File
@@ -1,7 +1,402 @@
<a name="1.5.8"></a>
# 1.5.8 arbitrary-fallbacks (2016-07-22)
## Bug Fixes
- **$animate:** do not get affected by custom, enumerable properties on `Object.prototype`
([181e4401](https://github.com/angular/angular.js/commit/181e44019e850e5253378e29415cddf8d768bbef),
[#14804](https://github.com/angular/angular.js/issues/14804), [#14830](https://github.com/angular/angular.js/issues/14830))
- **$compile:** ensure `$doCheck` hooks can be defined in the controller constructor
([3010ed4e](https://github.com/angular/angular.js/commit/3010ed4ee5c2c18b9848b5664639971b9fdc8919),
[#14811](https://github.com/angular/angular.js/issues/14811))
- **$injector:** fix class detection RegExp
([4724d56c](https://github.com/angular/angular.js/commit/4724d56c939d02675433e7fe554608dff97ecf81),
[#14533](https://github.com/angular/angular.js/issues/14533))
- **$jsonpCallbacks:** do not overwrite callbacks added by other apps
([1778d347](https://github.com/angular/angular.js/commit/1778d347cd3c91335e3840eaa49f1e387db8602f),
[#14824](https://github.com/angular/angular.js/issues/14824))
- **$timeout:** make $flush handle new $timeouts added in $timeout callbacks
([1a387ba5](https://github.com/angular/angular.js/commit/1a387ba5dd1a8a831486fce23f6795bd1eef3d8b),
[#5420](https://github.com/angular/angular.js/issues/5420), [#14686](https://github.com/angular/angular.js/issues/14686))
- **copy:** fix handling of typed subarrays
([1645924d](https://github.com/angular/angular.js/commit/1645924d49a7905ce55cced4c4276654970bb226),
[#14842](https://github.com/angular/angular.js/issues/14842), [#14845](https://github.com/angular/angular.js/issues/14845))
- **modules:** allow modules to be loaded in any order when using `angular-loader`
([98e4a220](https://github.com/angular/angular.js/commit/98e4a220fe8301cec35498ae592adc0266f12437),
[#9140](https://github.com/angular/angular.js/issues/9140), [#14794](https://github.com/angular/angular.js/issues/14794))
- **ngAnimate:** allow removal of class that is scheduled to be added with requestAnimationFrame
([7ccfe92b](https://github.com/angular/angular.js/commit/7ccfe92bed7361832f1b8d25b1a8411eb24d3fb5),
[#14582](https://github.com/angular/angular.js/issues/14582))
- **ngMocks:** allow `ErrorAddingDeclarationLocationStack` to be recognized as an `Error`
([c6074dc3](https://github.com/angular/angular.js/commit/c6074dc34c31a07269bf7f628b971ef6dc805f17),
[#13821](https://github.com/angular/angular.js/issues/13821), [#14344](https://github.com/angular/angular.js/issues/14344))
- **ngOptions:** don't duplicate groups with falsy values
([c3bfd7f5](https://github.com/angular/angular.js/commit/c3bfd7f59d0ecbf4ba3253fb407e683c7bb0766c))
- **ngTransclude:**
- ensure that fallback content is compiled and linked correctly
([c405f88b](https://github.com/angular/angular.js/commit/c405f88bbc743f41591f6f3cfc022eea3c6c34ad),
[#14787](https://github.com/angular/angular.js/issues/14787))
- only compile fallback content if necessary
([159a68ec](https://github.com/angular/angular.js/commit/159a68ec7ba77e9128b0d0516b813ed3d223b6e3),
[#14768](https://github.com/angular/angular.js/issues/14768), [#14765](https://github.com/angular/angular.js/issues/14765), [#14775](https://github.com/angular/angular.js/issues/14775))
## Features
- **$compile:** backport $doCheck
([de59ca71](https://github.com/angular/angular.js/commit/de59ca71072eac95ee68de308f92bc5f921dd07b),
[#14656](https://github.com/angular/angular.js/issues/14656))
- **$jsonpCallbacks:** new service to abstract how JSONP callbacks are handled
([a8cacfe9](https://github.com/angular/angular.js/commit/a8cacfe938287c54ce7099125cb735ad53f4c7c2),
[#14795](https://github.com/angular/angular.js/issues/14795))
- **$q:** implement $q.race
([b9a56d58](https://github.com/angular/angular.js/commit/b9a56d588f8b597b1dff30d8e184b7c37d94cdcf),
[#12929](https://github.com/angular/angular.js/issues/12929), [#14757](https://github.com/angular/angular.js/issues/14757))
- **$resource:** pass the resource to a dynamic param functions
([a126fcfe](https://github.com/angular/angular.js/commit/a126fcfee3bd8b02869bd2542c73e1eb21afe927),
[#4899](https://github.com/angular/angular.js/issues/4899))
- **$swipe:** add pointer support
([f797f83c](https://github.com/angular/angular.js/commit/f797f83cd66f1fd11b3c9399e7894217ffa06c38),
[#14061](https://github.com/angular/angular.js/issues/14061), [#14791](https://github.com/angular/angular.js/issues/14791))
- **filterFilter:** allow overwriting the special `$` property name
([33514ec3](https://github.com/angular/angular.js/commit/33514ec384d676d84b2a445bc15bae38c8c3ac8d),
[#13313](https://github.com/angular/angular.js/issues/13313))
## Performance Improvements
- **$compile:** wrap try/catch of collect comment directives into a function to avoid V8 deopt
([acd45518](https://github.com/angular/angular.js/commit/acd455181de1cfa6b34d75f8d71a6c0b6995a777),
[#14848](https://github.com/angular/angular.js/issues/14848))
<a name="1.2.30"></a>
# 1.2.30 patronal-resurrection (2016-07-21)
_**Note:** This release contains some security fixes that required breaking changes. Since the
legacy 1.2.x branch is the only version branch that supports IE8, it was necessary to introduce a
couple of low-impact breaking changes in a patch release - something we generally avoid - in order
to make the fixes available to people that still need IE8 support._
## Bug Fixes
- **$compile:**
- secure `link[href]` as a `RESOURCE_URL`s in `$sce`
([f35f334b](https://github.com/angular/angular.js/commit/f35f334bd3197585bdf034f4b6d9ffa3122dac62),
[#14687](https://github.com/angular/angular.js/issues/14687))
- properly sanitize `xlink:href` attribute interoplation
([f2fa1ed8](https://github.com/angular/angular.js/commit/f2fa1ed83d18d4e79a36f8c0db1c2524d762e513),
[2687c261](https://github.com/angular/angular.js/commit/2687c26140585d9e3716f9f559390f5d8d598fdf))
- **ngSanitize:** blacklist the attribute `usemap` as it can be used as a security exploit
([ac0d5286](https://github.com/angular/angular.js/commit/ac0d5286b8931633d774080d6396fb4825d8be33),
[#14903](https://github.com/angular/angular.js/issues/14903))
- **ngAnimate:** do not use event.timeStamp anymore for time tracking
([8d83b563](https://github.com/angular/angular.js/commit/8d83b5633471c847d58f337426fe069797dd49d9),
[#13494](https://github.com/angular/angular.js/issues/13494), [#13495](https://github.com/angular/angular.js/issues/13495))
## Breaking Changes
- **$compile:** due to [f35f334b](https://github.com/angular/angular.js/commit/f35f334bd3197585bdf034f4b6d9ffa3122dac62),
`link[href]` attributes are now protected via `$sce`, which prevents interpolated values that fail
the `RESOURCE_URL` context tests from being used in interpolation. For example if the application is
running at `https://mydomain.org/` then the following will fail:
```html
<link rel="stylesheet" href="{{ 'https://otherdomain.org/unsafe.css' }}" />
```
By default, `RESOURCE_URL` safe URLs are only allowed from the same domain and protocol as the
application document. To use URLs from other domains and/or protocols, you may either whitelist them
using `$sceDelegateProvider.resourceUrlWhitelist(...)` or wrap them into a trusted value by calling
`$sce.trustAsResourceUrl(url)`.
- **ngSanitize:** due to [234053fc](https://github.com/angular/angular.js/commit/234053fc9ad90e0d05be7e8359c6af66be94c094),
The `$sanitize` service will now remove instances of the `usemap` attribute from any elements passed
to it.
This attribute is used to reference another element by `name` or `id`. Since the `name` and `id`
attributes are already blacklisted, a sanitized `usemap` attribute could only reference unsanitized
content, which is a security risk.
<a name="1.5.7"></a>
# 1.5.7 hexagonal-circumvolution (2016-06-15)
## Bug Fixes
- **$compile:**
- don't add merged attributes twice to $attrs
([ebbaa4ac](https://github.com/angular/angular.js/commit/ebbaa4ac5e3559df9267203438a0bb18c2d3b7d8),
[#8159](https://github.com/angular/angular.js/issues/8159), [#14737](https://github.com/angular/angular.js/issues/14737))
- cope with `$onChanges` hooks throwing
([3749c858](https://github.com/angular/angular.js/commit/3749c85829406ca57cc5729e80696c7f34134068),
[#14444](https://github.com/angular/angular.js/issues/14444), [#14463](https://github.com/angular/angular.js/issues/14463))
- **$parse:** allow arguments to contain filter chains
([cc6dcb4b](https://github.com/angular/angular.js/commit/cc6dcb4bc28aadff4f62d76d6451b0f80b928e69),
[#4175](https://github.com/angular/angular.js/issues/4175), [#4168](https://github.com/angular/angular.js/issues/4168), [#14720](https://github.com/angular/angular.js/issues/14720))
- **$routeProvider:** do not deep-copy route definition objects
([6d0dcca6](https://github.com/angular/angular.js/commit/6d0dcca6f18a353a12c356dc96e05475d351b795),
[#14478](https://github.com/angular/angular.js/issues/14478), [#14699](https://github.com/angular/angular.js/issues/14699), [#14750](https://github.com/angular/angular.js/issues/14750))
- **input[email]:** improve email address validation
([f423dc6b](https://github.com/angular/angular.js/commit/f423dc6b51a9b4a09728a287a2cabda7d45f192e),
[#14719](https://github.com/angular/angular.js/issues/14719))
- **ngMessages:** create new scope for ngMessage, clean it up correctly
([56f33af8](https://github.com/angular/angular.js/commit/56f33af89045e2ec18d144d9d1ef73affbe51959),
[#14307](https://github.com/angular/angular.js/issues/14307))
- **ngMessagesInclude:** don't break on empty (or whitespace-only) templates
([01b8772c](https://github.com/angular/angular.js/commit/01b8772cca55916376355a2ae58d3ab7832a4bc2),
[#12941](https://github.com/angular/angular.js/issues/12941), [#14726](https://github.com/angular/angular.js/issues/14726))
- **ngMock#$controller:** properly assign bindings to all types of controllers (e.g. class-based)
([db58801a](https://github.com/angular/angular.js/commit/db58801a55c91df755414387dc00fee5902bb5f3),
[#14437](https://github.com/angular/angular.js/issues/14437), [#14439](https://github.com/angular/angular.js/issues/14439))
- **ngMockE2E:** allow $httpBackend.passThrough() to work when ngMock is loaded
([6685043a](https://github.com/angular/angular.js/commit/6685043ad40acc50d7088f87e2a71f76d2265306),
[#1434](https://github.com/angular/angular.js/issues/1434), [#13124](https://github.com/angular/angular.js/issues/13124))
- **ngSanitize:** call attribute setter in linky for all links
([c4fad6ad](https://github.com/angular/angular.js/commit/c4fad6ad617af025984ca401054f7b402aa28f1d),
[#14707](https://github.com/angular/angular.js/issues/14707))
## Features
- **limitTo:** add support for array-like objects
([b03957f0](https://github.com/angular/angular.js/commit/b03957f0047fcfe996abc8efe97f9e2be2c2e13a),
[#14657](https://github.com/angular/angular.js/issues/14657), [#14694](https://github.com/angular/angular.js/issues/14694))
- **orderBy:** add support for custom comparators
([2e33d170](https://github.com/angular/angular.js/commit/2e33d170be7eb72f40fd57a8f66631583572fbf2),
[#13238](https://github.com/angular/angular.js/issues/13238), [#14455](https://github.com/angular/angular.js/issues/14455), [#5123](https://github.com/angular/angular.js/issues/5123), [#8112](https://github.com/angular/angular.js/issues/8112), [#10368](https://github.com/angular/angular.js/issues/10368), [#14468](https://github.com/angular/angular.js/issues/14468))
<a name="1.5.6"></a>
# 1.5.6 arrow-stringification (2016-05-27)
## Bug Fixes
- **$browser:** set the url even if the browser transforms it
([743b33ab](https://github.com/angular/angular.js/commit/743b33ab9d8cdb8aa6ff28028a54ac0023f4dc15),
[#14427](https://github.com/angular/angular.js/issues/14427), [#14499](https://github.com/angular/angular.js/issues/14499))
- **$compile:**
- properly bind context to linking functions for directives with `templateUrl`
([5ae72dc7](https://github.com/angular/angular.js/commit/5ae72dc7affb252774b7c4cbf278269c5e178f38))
- always use the DDO as `this` in pre-/post-linking functions
([47bc98a1](https://github.com/angular/angular.js/commit/47bc98a1eafd7853babc1a7dc54f13363a3f4a43),
[#9306](https://github.com/angular/angular.js/issues/9306))
- don't run unnecessary update to one-way bindings
([30479647](https://github.com/angular/angular.js/commit/304796471292f9805b9cf77e51aacc9cfbb09921),
[#14546](https://github.com/angular/angular.js/issues/14546), [#14580](https://github.com/angular/angular.js/issues/14580))
- removing unnecessary white space in element-transclusion comments
([1116a96d](https://github.com/angular/angular.js/commit/1116a96df662c95aa83ab527c479b644a14532b6),
[#14549](https://github.com/angular/angular.js/issues/14549), [#14550](https://github.com/angular/angular.js/issues/14550))
- properly handle setting `srcset` to undefined
([308f22ba](https://github.com/angular/angular.js/commit/308f22ba9a803967ce84c995d810990c80478f6f),
[#14470](https://github.com/angular/angular.js/issues/14470), [#14493](https://github.com/angular/angular.js/issues/14493))
- **$injector:**
- add workaround for class stringification in Chrome v50/51
([7550b7bd](https://github.com/angular/angular.js/commit/7550b7bda8d992cc6ed3c65b1f9f7e323a09cae4),
[#14531](https://github.com/angular/angular.js/issues/14531))
- add workaround for fat-arrow stringification in Chrome v50/51
([bb730704](https://github.com/angular/angular.js/commit/bb730704f2f3dea4620e7b40083dcd65f208e2b2),
[#14487](https://github.com/angular/angular.js/issues/14487), [#14495](https://github.com/angular/angular.js/issues/14495))
- **$templateRequest:** trust empty templates in `$templateCache` as well
([10955969](https://github.com/angular/angular.js/commit/10955969620248bd31a0bd37bc9fd7cc3b1f5e88),
[#14479](https://github.com/angular/angular.js/issues/14479), [#14496](https://github.com/angular/angular.js/issues/14496))
- **filters:** always call `splice()` with 2 arguments or more
([6f1fae8a](https://github.com/angular/angular.js/commit/6f1fae8a916cc6f26725f64869f86fcd4991e819),
[#14467](https://github.com/angular/angular.js/issues/14467), [#14489](https://github.com/angular/angular.js/issues/14489))
- **ng-bind-html:** watch the unwrapped value using `$sce.valueOf()` (instead of `toString()`)
([1c1c9b27](https://github.com/angular/angular.js/commit/1c1c9b27cbb57b3219d4c9765eeea8a11553d297),
[#14526](https://github.com/angular/angular.js/issues/14526), [#14527](https://github.com/angular/angular.js/issues/14527))
- **ngAnimate:**
- don't break on anchored animations without duration
([21b76aed](https://github.com/angular/angular.js/commit/21b76aed06d9cc04bd25a99c23ba852af782b357),
[#14641](https://github.com/angular/angular.js/issues/14641), [#14645](https://github.com/angular/angular.js/issues/14645))
- properly handle empty jqLite collections
([9f480257](https://github.com/angular/angular.js/commit/9f4802573348401ee24090f815f3138fa17c161d),
[#14558](https://github.com/angular/angular.js/issues/14558), [#14559](https://github.com/angular/angular.js/issues/14559))
- **ngMessages:** do not compile ngMessagesInclude template if scope is destroyed
([69d9072d](https://github.com/angular/angular.js/commit/69d9072d5b5aba988ac8a9717c92ce1fac465cbe),
[#12695](https://github.com/angular/angular.js/issues/12695), [#14640](https://github.com/angular/angular.js/issues/14640))
- **ngMock:**
- match HTTP request regardless of the order of query params
([363fb16c](https://github.com/angular/angular.js/commit/363fb16c1076a806bb31006daaf583ab5c82e467),
[#12762](https://github.com/angular/angular.js/issues/12762))
- pass eventHandlers to $httpBackend if passThrough is active
([147650b3](https://github.com/angular/angular.js/commit/147650b3beae7ad8b556a930129887d187a0f5d3),
[#14471](https://github.com/angular/angular.js/issues/14471))
## Features
- **$compile:** support omitting required controller name if same as the local name
([8965d57a](https://github.com/angular/angular.js/commit/8965d57aa0788c96859cf3cfa0f78b46d2cebedb),
[#14513](https://github.com/angular/angular.js/issues/14513))
- **$parse:** Add support for ES6 object initializers
([1c9b9e24](https://github.com/angular/angular.js/commit/1c9b9e24ede7efc10ce4d53c6ab5528cc77e79d7))
## Performance Improvements
- **$rootScope:** make queues more efficient
([51b78f61](https://github.com/angular/angular.js/commit/51b78f61f954d94dc937f57190c9f881df9ab3ad),
[#14545](https://github.com/angular/angular.js/issues/14545))
- **ngAnimate:** listen for document visibility changes
([b377d6b0](https://github.com/angular/angular.js/commit/b377d6b043db9c8da4e68493892f75a0f9759464),
[#14568](https://github.com/angular/angular.js/issues/14568))
- **ngClass:** improve even-odd checking
([cf1b1755](https://github.com/angular/angular.js/commit/cf1b175508a72d4b7e3d64b8506903096c6ec25f))
<a name="1.5.5"></a>
# 1.5.5 material-conspiration (2016-04-18)
## Reverts
- **$compile:** move setting of controller data to single location
Reverted commit [21d148ae](https://github.com/angular/angular.js/commit/21d148aedc29c7efba4131ff2ef6383b4700868c)
since it caused the Angular Material tabs directive to fail.
- **ngRoute:** allow `ngView` to be included in an asynchronously loaded template
Eagerly loading `$route`, could break tests, because it might request the root or default route
template (something `$httpBackend` would know nothing about).
It will be re-applied for `v1.6.x`, with a breaking change notice and possibly a way to disable
the feature in tests.
([8237482d](https://github.com/angular/angular.js/commit/8237482d49e76e2c4994fe6207e3c9799ef04163),
[#1213](https://github.com/angular/angular.js/issues/1213), [#6812](https://github.com/angular/angular.js/issues/6812),
[#14088](https://github.com/angular/angular.js/issues/14088))
## Bug Fixes
- **$compile:**
- do not use `noop()` as controller for multiple components
([4c8aeefb](https://github.com/angular/angular.js/commit/4c8aeefb624de7436ad95f3cd525405e0c3f493e),
[#14391](https://github.com/angular/angular.js/issues/14391), [#14402](https://github.com/angular/angular.js/issues/14402))
- still trigger `$onChanges` even if the inner value already matches the new value
([d9448dcb](https://github.com/angular/angular.js/commit/d9448dcb9f901ceb04deda1d5f3d5aac8442a718),
[#14406](https://github.com/angular/angular.js/issues/14406))
- handle boolean attributes in `@` bindings
([499e1b2a](https://github.com/angular/angular.js/commit/499e1b2adf27f32d671123f8dceadb3df2ad84a9),
[#14070](https://github.com/angular/angular.js/issues/14070))
- don't throw if controller is named
([e72990dc](https://github.com/angular/angular.js/commit/e72990dc3714c8b847185ddb64fd5fd00e5cceab))
- ensure that `$onChanges` hook is called correctly
([0ad2b708](https://github.com/angular/angular.js/commit/0ad2b70862d49ecc4355a16d767c0ca9358ecc3e),
[#14355](https://github.com/angular/angular.js/issues/14355), [#14359](https://github.com/angular/angular.js/issues/14359))
- **$injector:** ensure functions with overridden `toString()` are annotated properly
([d384834f](https://github.com/angular/angular.js/commit/d384834fdee140a716298bd065f304f8fba4725e),
[#14361](https://github.com/angular/angular.js/issues/14361))
- **ngAnimate:**
- remove event listeners only after all listeners have been called
([79604f46](https://github.com/angular/angular.js/commit/79604f462899c118a99d610995083ff82d38aa35),
[#14321](https://github.com/angular/angular.js/issues/14321))
- fire callbacks when document is hidden
([c7a92d2a](https://github.com/angular/angular.js/commit/c7a92d2a9a436dddd65de721c9837a93e915d939),
[#14120](https://github.com/angular/angular.js/issues/14120))
- fire callbacks in the correct order for certain skipped animations
([90da3059](https://github.com/angular/angular.js/commit/90da3059cecfefaecf136b01cd87aee6775a8778))
- **ngClass:** fix watching of an array expression containing an object
([f975d8d4](https://github.com/angular/angular.js/commit/f975d8d4481e0b8cdba553f0e5ad9ec1688adae8),
[#14405](https://github.com/angular/angular.js/issues/14405))
- **ngMock:** fix collecting stack trace in `inject()` on IE10+, PhantomJS
([e9c718a4](https://github.com/angular/angular.js/commit/e9c718a465d28b9f2691e3acab944f7c31aa9fb6),
[#13591](https://github.com/angular/angular.js/issues/13591), [#13592](https://github.com/angular/angular.js/issues/13592), [#13593](https://github.com/angular/angular.js/issues/13593))
- **ngOptions:** set select value when model matches disabled option
([832eba5f](https://github.com/angular/angular.js/commit/832eba5fc952312e6b99127123e6e75bdf729006),
[#12756](https://github.com/angular/angular.js/issues/12756))
- **$http:** pass event object to `eventHandlers`/`uploadEventHandlers`
([25d4e5cc](https://github.com/angular/angular.js/commit/25d4e5cca4fa615e49d65976223c6deb5b485b4c),
[#14436](https://github.com/angular/angular.js/issues/14436))
## Features
- **$compile:**
- put custom annotations on DDO
([f338e96c](https://github.com/angular/angular.js/commit/f338e96ccc739efc4b24022eae406c3d5451d422),
[#14369](https://github.com/angular/angular.js/issues/14369), [#14279](https://github.com/angular/angular.js/issues/14279), [#14284](https://github.com/angular/angular.js/issues/14284))
- add `isFirstChange()` method to onChanges object
([8d43d8b8](https://github.com/angular/angular.js/commit/8d43d8b8e7aacf97ddb9aa48bff25db57249cdd5),
[#14318](https://github.com/angular/angular.js/issues/14318), [#14323](https://github.com/angular/angular.js/issues/14323))
- **$componentController:** provide isolated scope if none is passed (#14425)
([33f817b9](https://github.com/angular/angular.js/commit/33f817b99cb20e566b381e7202235fe99b4a742a),
[#14425](https://github.com/angular/angular.js/issues/14425))
- **$http:**
- support handling additional XHR events
([01b18450](https://github.com/angular/angular.js/commit/01b18450882da9bb9c903d43c0daddbc03c2c35d) and
[56c861c9](https://github.com/angular/angular.js/commit/56c861c9e114c45790865e5635eaae8d32eb649a),
[#14367](https://github.com/angular/angular.js/issues/14367), [#11547](https://github.com/angular/angular.js/issues/11547), [#1934](https://github.com/angular/angular.js/issues/1934))
- **$parse:** add the ability to define the identifier characters
([3e7fa191](https://github.com/angular/angular.js/commit/3e7fa19197c54a764225ad27c0c0bf72263daa8d))
- **ngAnimate:** let $animate.off() remove all listeners for an element
([bf6cb8ab](https://github.com/angular/angular.js/commit/bf6cb8ab0d157083a1ed55743e3fffe728daa6f3))
- **ngAria:** add support for aria-readonly based on ngReadonly
([ec0baadc](https://github.com/angular/angular.js/commit/ec0baadcb68a4fa8da27d76b7e6a4e0840acd7fa),
[#14140](https://github.com/angular/angular.js/issues/14140), [#14077](https://github.com/angular/angular.js/issues/14077))
- **ngParseExt:** new ngParseExt module
([d08f5c69](https://github.com/angular/angular.js/commit/d08f5c698624f6243685b16f2d458cb9a980ebde))
## Performance Improvements
- **$compile:** use createMap() for directive bindings to allow fast `forEach`
([c115b37c](https://github.com/angular/angular.js/commit/c115b37c336f3a5936187279057b29c76078caf2),
[#12529](https://github.com/angular/angular.js/issues/12529))
- **ngOptions:** use `documentFragment` to populate `select` options
([6a4124d0](https://github.com/angular/angular.js/commit/6a4124d0fb17cd7fc0e8bf5a1ca4d785a1d11c1c),
[#13607](https://github.com/angular/angular.js/issues/13607), [#13239](https://github.com/angular/angular.js/issues/13239), [#12076](https://github.com/angular/angular.js/issues/12076))
<a name="1.5.4"></a>
# 1.5.4 graduated-sophistry (2016-04-14)
This was a partially published release that you should ignore.
<a name="1.5.3"></a>
# 1.5.3 diplohaplontic-meiosis (2016-03-25)
## Bug Fixes
- **$compile:** workaround a GC bug in Chrome < 50
([513199ee](https://github.com/angular/angular.js/commit/513199ee9f1c8eef1240983d6e52c824404adb98),
[#14041](https://github.com/angular/angular.js/issues/14041), [#14286](https://github.com/angular/angular.js/issues/14286))
- **$sniffer:** fix history sniffing in Chrome Packaged Apps
([457fd21a](https://github.com/angular/angular.js/commit/457fd21a1a0c10c66245c32a73602f3a09038bda),
[#11932](https://github.com/angular/angular.js/issues/11932), [#13945](https://github.com/angular/angular.js/issues/13945))
- **formatNumber:** handle small numbers correctly when `gSize` !== `lgSize`
([3277b885](https://github.com/angular/angular.js/commit/3277b885c4dec3edd51b8e8c3d1776057d6d4d1d),
[#14289](https://github.com/angular/angular.js/issues/14289), [#14290](https://github.com/angular/angular.js/issues/14290))
- **ngAnimate:** run structural animations with cancelled out class changes
([c7813e9e](https://github.com/angular/angular.js/commit/c7813e9ebf793fe89380dcad54e8e002fafdd985),
[#14249](https://github.com/angular/angular.js/issues/14249))
- **ngMessages:** don't crash when nested messages are removed
([ef91b04c](https://github.com/angular/angular.js/commit/ef91b04cdd794f308617bca7ebd0b1b747e4f7de),
[#14183](https://github.com/angular/angular.js/issues/14183), [#14242](https://github.com/angular/angular.js/issues/14242))
## Features
- **$compile:** add more lifecycle hooks to directive controllers
([9cd9956d](https://github.com/angular/angular.js/commit/9cd9956dcbc8382e8e8757a805398bd251bbc67e),
[#14127](https://github.com/angular/angular.js/issues/14127), [#14030](https://github.com/angular/angular.js/issues/14030), [#14020](https://github.com/angular/angular.js/issues/14020), [#13991](https://github.com/angular/angular.js/issues/13991), [#14302](https://github.com/angular/angular.js/issues/14302))
<a name="1.5.2"></a>
# 1.5.2 differential-recovery (2016-03-18)
This release reverts a breaking change that accidentally made it into the 1.5.1 release. See [fee7bac3](https://github.com/angular/angular.js/commit/fee7bac392db24b6006d6a57ba71526f3afa102c) for more info.
This release reverts a breaking change that accidentally made it into the 1.5.1 release. See
[fee7bac3](https://github.com/angular/angular.js/commit/fee7bac392db24b6006d6a57ba71526f3afa102c)
for more info.
## Bug Fixes
@@ -10,9 +405,81 @@ This release reverts a breaking change that accidentally made it into the 1.5.1
([ce7f4000](https://github.com/angular/angular.js/commit/ce7f400011e1e2e1b9316f18ce87b87b79d878b4))
## Breaking Changes
<a name="1.5.1"></a>
# 1.5.1 equivocal-sophistication (2016-03-16)
## Bug Fixes
- **core:** only call `console.log` when `window.console` exists
([ce138f3c](https://github.com/angular/angular.js/commit/ce138f3c552f8bf741721ab8d10994ed35a4b2f5),
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
- **$compile:** allow directives to have decorators
([0728cc2f](https://github.com/angular/angular.js/commit/0728cc2f2bb04d5dbdfca41f3afacea16c75ee07))
- **$resource:** fix parse errors on older Android WebViews
([df8db7b4](https://github.com/angular/angular.js/commit/df8db7b446b5bae83afef457d706d2805e597f29),
[#13989](https://github.com/angular/angular.js/issues/13989))
- **$routeProvider:** properly handle optional eager path named groups
([c0797c68](https://github.com/angular/angular.js/commit/c0797c68866c9ef8ff3c2f6985e6eb9374346151),
[#14011](https://github.com/angular/angular.js/issues/14011))
- **copy:** add support for copying `Blob` objects
([e9d579b6](https://github.com/angular/angular.js/commit/e9d579b608c2be8fdcf0326d0679a76bb9ae5b6e),
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
- **dateFilter:** correctly format BC years
([e36205f5](https://github.com/angular/angular.js/commit/e36205f5af82b69362def7d2b6eeeb038f592311))
- **formatNumber:** allow negative fraction size
([e046c170](https://github.com/angular/angular.js/commit/e046c170bcf677f26e61af6470cb5fd2f751c969),
[#13913](https://github.com/angular/angular.js/issues/13913))
- **input:** re-validate when partially editing date-family inputs
([e383804c](https://github.com/angular/angular.js/commit/e383804c4ab62278fbaf4fdfaa03caeacff77fc4),
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
- **input\[date\]:** support years with more than 4 digits
([d76951f1](https://github.com/angular/angular.js/commit/d76951f1747abd2da6e320d4ff9019f170d9793f),
[#13735](https://github.com/angular/angular.js/issues/13735), [#13905](https://github.com/angular/angular.js/issues/13905))
- **ngOptions:** always set the 'selected' attribute for selected options
([9f5a1722](https://github.com/angular/angular.js/commit/9f5a172291ff6926dcd246f0972288916a4c9bf6),
[#14115](https://github.com/angular/angular.js/issues/14115))
- **ngRoute:** allow `ngView` to be included in an asynchronously loaded template
([8237482d](https://github.com/angular/angular.js/commit/8237482d49e76e2c4994fe6207e3c9799ef04163),
[#1213](https://github.com/angular/angular.js/issues/1213), [#6812](https://github.com/angular/angular.js/issues/6812), [#14088](https://github.com/angular/angular.js/issues/14088))
- **ngMock:**
- attach `$injector` to `$rootElement` and prevent memory leak due to attached data
([75373dd4](https://github.com/angular/angular.js/commit/75373dd4bdae6c6035272942c69444c386f824cd),
[#14022](https://github.com/angular/angular.js/issues/14022), [#14094](https://github.com/angular/angular.js/issues/14094), [#14098](https://github.com/angular/angular.js/issues/14098))
- don't break if `$rootScope.$destroy()` is not a function
([50ed8712](https://github.com/angular/angular.js/commit/50ed8712566d601c9fb76b71f7b534b5bc803a36),
[#14106](https://github.com/angular/angular.js/issues/14106), [#14107](https://github.com/angular/angular.js/issues/14107))
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
([d16faf9f](https://github.com/angular/angular.js/commit/d16faf9f2b9bd2b85d95e71d902cec0269282f2c),
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
## Features
- **$compile:** add custom annotations to the controller
([0c800930](https://github.com/angular/angular.js/commit/0c8009300b819c39c5e4892856724a731a8dcda6),
[#14114](https://github.com/angular/angular.js/issues/14114))
- **$controllerProvider:** add a `has()` method for checking the existence of a controller
([bb9575db](https://github.com/angular/angular.js/commit/bb9575dbd3428176216355df7b2933d2a72783cd),
[#13951](https://github.com/angular/angular.js/issues/13951), [#14109](https://github.com/angular/angular.js/issues/14109))
- **dateFilter:** add support for STANDALONEMONTH in format (`LLLL`)
([3e5b25b3](https://github.com/angular/angular.js/commit/3e5b25b33f278376def432698c704b1807fdb8c0),
[#13999](https://github.com/angular/angular.js/issues/13999), [#14013](https://github.com/angular/angular.js/issues/14013))
- **ngMock:** add `sharedInjector()` to `angular.mock.module`
([a46ab60f](https://github.com/angular/angular.js/commit/a46ab60fd5bf94896f0761e858ef38b998eb0f80),
[#14093](https://github.com/angular/angular.js/issues/14093), [#10238](https://github.com/angular/angular.js/issues/10238))
## Performance Improvements
- **ngRepeat:** avoid duplicate jqLite wrappers
([632e15a3](https://github.com/angular/angular.js/commit/632e15a3afdcd30168700cec1367bd81966400d4))
- **ngAnimate:**
- avoid jqLite/jQuery for upward DOM traversal
([35251bd4](https://github.com/angular/angular.js/commit/35251bd4ce23251b5e9a2860cf414726c194721e))
- avoid `$.fn.data` overhead with jQuery
([15915e60](https://github.com/angular/angular.js/commit/15915e606fdf5114592db1a0a5e3f12e639d7cdb))
<a name="1.4.10"></a>
# 1.4.10 benignant-oscillation (2016-03-16)
@@ -102,82 +569,6 @@ This release reverts a breaking change that accidentally made it into the 1.5.1
([86416bcb](https://github.com/angular/angular.js/commit/86416bcbee2192fa31c017163c5d856763182ade))
<a name="1.5.1"></a>
# 1.5.1 equivocal-sophistication (2016-03-16)
## Bug Fixes
- **core:** only call `console.log` when `window.console` exists
([ce138f3c](https://github.com/angular/angular.js/commit/ce138f3c552f8bf741721ab8d10994ed35a4b2f5),
[#14006](https://github.com/angular/angular.js/issues/14006), [#14007](https://github.com/angular/angular.js/issues/14007), [#14047](https://github.com/angular/angular.js/issues/14047))
- **$compile:** allow directives to have decorators
([0728cc2f](https://github.com/angular/angular.js/commit/0728cc2f2bb04d5dbdfca41f3afacea16c75ee07))
- **$resource:** fix parse errors on older Android WebViews
([df8db7b4](https://github.com/angular/angular.js/commit/df8db7b446b5bae83afef457d706d2805e597f29),
[#13989](https://github.com/angular/angular.js/issues/13989))
- **$routeProvider:** properly handle optional eager path named groups
([c0797c68](https://github.com/angular/angular.js/commit/c0797c68866c9ef8ff3c2f6985e6eb9374346151),
[#14011](https://github.com/angular/angular.js/issues/14011))
- **copy:** add support for copying `Blob` objects
([e9d579b6](https://github.com/angular/angular.js/commit/e9d579b608c2be8fdcf0326d0679a76bb9ae5b6e),
[#9669](https://github.com/angular/angular.js/issues/9669), [#14064](https://github.com/angular/angular.js/issues/14064))
- **dateFilter:** correctly format BC years
([e36205f5](https://github.com/angular/angular.js/commit/e36205f5af82b69362def7d2b6eeeb038f592311))
- **formatNumber:** allow negative fraction size
([e046c170](https://github.com/angular/angular.js/commit/e046c170bcf677f26e61af6470cb5fd2f751c969),
[#13913](https://github.com/angular/angular.js/issues/13913))
- **input:** re-validate when partially editing date-family inputs
([e383804c](https://github.com/angular/angular.js/commit/e383804c4ab62278fbaf4fdfaa03caeacff77fc4),
[#12207](https://github.com/angular/angular.js/issues/12207), [#13886](https://github.com/angular/angular.js/issues/13886))
- **input\[date\]:** support years with more than 4 digits
([d76951f1](https://github.com/angular/angular.js/commit/d76951f1747abd2da6e320d4ff9019f170d9793f),
[#13735](https://github.com/angular/angular.js/issues/13735), [#13905](https://github.com/angular/angular.js/issues/13905))
- **ngOptions:** always set the 'selected' attribute for selected options
([9f5a1722](https://github.com/angular/angular.js/commit/9f5a172291ff6926dcd246f0972288916a4c9bf6),
[#14115](https://github.com/angular/angular.js/issues/14115))
- **ngRoute:** allow `ngView` to be included in an asynchronously loaded template
([8237482d](https://github.com/angular/angular.js/commit/8237482d49e76e2c4994fe6207e3c9799ef04163),
[#1213](https://github.com/angular/angular.js/issues/1213), [#6812](https://github.com/angular/angular.js/issues/6812), [#14088](https://github.com/angular/angular.js/issues/14088))
- **ngMock:**
- attach `$injector` to `$rootElement` and prevent memory leak due to attached data
([75373dd4](https://github.com/angular/angular.js/commit/75373dd4bdae6c6035272942c69444c386f824cd),
[#14022](https://github.com/angular/angular.js/issues/14022), [#14094](https://github.com/angular/angular.js/issues/14094), [#14098](https://github.com/angular/angular.js/issues/14098))
- don't break if `$rootScope.$destroy()` is not a function
([50ed8712](https://github.com/angular/angular.js/commit/50ed8712566d601c9fb76b71f7b534b5bc803a36),
[#14106](https://github.com/angular/angular.js/issues/14106), [#14107](https://github.com/angular/angular.js/issues/14107))
- **ngMockE2E:** pass `responseType` to `$delegate` when using `passThrough`
([d16faf9f](https://github.com/angular/angular.js/commit/d16faf9f2b9bd2b85d95e71d902cec0269282f2c),
[#5415](https://github.com/angular/angular.js/issues/5415), [#5783](https://github.com/angular/angular.js/issues/5783))
## Features
- **$compile:** add custom annotations to the controller
([0c800930](https://github.com/angular/angular.js/commit/0c8009300b819c39c5e4892856724a731a8dcda6),
[#14114](https://github.com/angular/angular.js/issues/14114))
- **$controllerProvider:** add a `has()` method for checking the existence of a controller
([bb9575db](https://github.com/angular/angular.js/commit/bb9575dbd3428176216355df7b2933d2a72783cd),
[#13951](https://github.com/angular/angular.js/issues/13951), [#14109](https://github.com/angular/angular.js/issues/14109))
- **dateFilter:** add support for STANDALONEMONTH in format (`LLLL`)
([3e5b25b3](https://github.com/angular/angular.js/commit/3e5b25b33f278376def432698c704b1807fdb8c0),
[#13999](https://github.com/angular/angular.js/issues/13999), [#14013](https://github.com/angular/angular.js/issues/14013))
- **ngMock:** add `sharedInjector()` to `angular.mock.module`
([a46ab60f](https://github.com/angular/angular.js/commit/a46ab60fd5bf94896f0761e858ef38b998eb0f80),
[#14093](https://github.com/angular/angular.js/issues/14093), [#10238](https://github.com/angular/angular.js/issues/10238))
## Performance Improvements
- **ngRepeat:** avoid duplicate jqLite wrappers
([632e15a3](https://github.com/angular/angular.js/commit/632e15a3afdcd30168700cec1367bd81966400d4))
- **ngAnimate:**
- avoid jqLite/jQuery for upward DOM traversal
([35251bd4](https://github.com/angular/angular.js/commit/35251bd4ce23251b5e9a2860cf414726c194721e))
- avoid `$.fn.data` overhead with jQuery
([15915e60](https://github.com/angular/angular.js/commit/15915e606fdf5114592db1a0a5e3f12e639d7cdb))
<a name="1.5.0"></a>
# 1.5.0 ennoblement-facilitation (2016-02-05)
@@ -296,7 +687,8 @@ changes section for more information
- **ngSanitize:** due to [234053fc](https://github.com/angular/angular.js/commit/234053fc9ad90e0d05be7e8359c6af66be94c094),
The `$sanitize` service will now remove instances of the `usemap` attribute from any elements passed to it.
The `$sanitize` service will now remove instances of the `usemap` attribute from any elements passed
to it.
This attribute is used to reference another element by `name` or `id`. Since the `name` and `id`
attributes are already blacklisted, a sanitized `usemap` attribute could only reference unsanitized
@@ -872,9 +1264,6 @@ is `$locals`.
[#13236](https://github.com/angular/angular.js/issues/13236))
## Breaking Changes
<a name="1.5.0-beta.2"></a>
# 1.5.0-beta.2 effective-delegation (2015-11-17)
@@ -1194,9 +1583,6 @@ requirement more strict and alerts the developer explicitly.
[#2318](https://github.com/angular/angular.js/issues/2318), [#9319](https://github.com/angular/angular.js/issues/9319), [#12159](https://github.com/angular/angular.js/issues/12159))
## Breaking Changes
<a name="1.3.20"></a>
# 1.3.20 shallow-translucence (2015-09-29)
@@ -1207,9 +1593,6 @@ requirement more strict and alerts the developer explicitly.
([d434f3db](https://github.com/angular/angular.js/commit/d434f3db53d6209eb140b904e83bbde401686c16))
## Breaking Changes
<a name="1.2.29"></a>
# 1.2.29 ultimate-deprecation (2015-09-29)
@@ -1236,7 +1619,15 @@ requirement more strict and alerts the developer explicitly.
[#9936](https://github.com/angular/angular.js/issues/9936))
## Breaking Changes
<a name="1.5.0-beta.0"></a>
# 1.5.0-beta.0 intialization-processation (2015-09-17)
## Bug Fixes
- **jqLite:**: properly handle dash-delimited node names in `jqLiteBuildFragment`
([cdd1227a3](https://github.com/angular/angular.js/commit/cdd1227a308edd34d31b67f338083b6e0c4c0db9),
[#10617](https://github.com/angular/angular.js/issues/10617))
<a name="1.4.6"></a>
@@ -1789,6 +2180,44 @@ describe('$q.when', function() {
[#11580](https://github.com/angular/angular.js/issues/11580), [#12234](https://github.com/angular/angular.js/issues/12234))
## Breaking Changes
- **ngInclude:** due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
Before:
```html
<div ng-include="findTemplate('https://example.com/myTemplate.html')"></div>
```
```js
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
```
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
whitelist in the `config()` function:
After:
```js
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/**'])
});
```
<a name="1.3.17"></a>
# 1.3.17 tsktskskly-euouae (2015-07-06)
@@ -2067,9 +2496,7 @@ describe('$q.when', function() {
## Breaking Changes
### ngAnimate
- **$animateCss:** due to [d5683d21](https://github.com/angular/angular.js/commit/d5683d21165e725bc5a850e795f681b0a8a008f5),
- **ngAnimate** - $animateCss: due to [d5683d21](https://github.com/angular/angular.js/commit/d5683d21165e725bc5a850e795f681b0a8a008f5),
The $animateCss service will now always return an
object even if the animation is not set to run. If your code is using
$animateCss then please consider the following code change:
@@ -2153,9 +2580,7 @@ $animateProvider.classNameFilter(/ng-animate-special/);
```
### ngOptions
- ** due to [dfa722a8](https://github.com/angular/angular.js/commit/dfa722a8a6864793fd9580d8ae704a06d10b5509),
- **ngOptions**: due to [dfa722a8](https://github.com/angular/angular.js/commit/dfa722a8a6864793fd9580d8ae704a06d10b5509),
Although it is unlikely that anyone is using it in this way, this change does change the
+1 -1
View File
@@ -280,7 +280,7 @@ You can find out more detailed information about contributing in the
[groups]: https://groups.google.com/forum/?fromgroups#!forum/angular
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
[jsfiddle]: http://jsfiddle.net/
[list]: https://groups.google.com/forum/?fromgroups#!forum/angular
[ngDocs]: https://github.com/angular/angular.js/wiki/Writing-AngularJS-Documentation
+28 -5
View File
@@ -17,6 +17,10 @@ module.exports = function(grunt) {
NG_VERSION.cdn = versionInfo.cdnVersion;
var dist = 'angular-'+ NG_VERSION.full;
if (versionInfo.cdnVersion == null) {
throw new Error('Unable to read CDN version, are you offline or has the CDN not been properly pushed?');
}
//config
grunt.initConfig({
NG_VERSION: NG_VERSION,
@@ -79,6 +83,8 @@ module.exports = function(grunt) {
tests: {
jqlite: 'karma-jqlite.conf.js',
jquery: 'karma-jquery.conf.js',
'jquery-2.2': 'karma-jquery-2.2.conf.js',
'jquery-2.1': 'karma-jquery-2.1.conf.js',
docs: 'karma-docs.conf.js',
modules: 'karma-modules.conf.js'
},
@@ -87,6 +93,8 @@ module.exports = function(grunt) {
autotest: {
jqlite: 'karma-jqlite.conf.js',
jquery: 'karma-jquery.conf.js',
'jquery-2.2': 'karma-jquery-2.2.conf.js',
'jquery-2.1': 'karma-jquery-2.1.conf.js',
modules: 'karma-modules.conf.js',
docs: 'karma-docs.conf.js'
},
@@ -135,6 +143,9 @@ module.exports = function(grunt) {
ngMock: {
files: { src: 'src/ngMock/**/*.js' },
},
ngParseExt: {
files: { src: 'src/ngParseExt/**/*.js' },
},
ngResource: {
files: { src: 'src/ngResource/**/*.js' },
},
@@ -231,7 +242,11 @@ module.exports = function(grunt) {
dest: 'build/angular-aria.js',
src: util.wrap(files['angularModules']['ngAria'], 'module')
},
'promises-aplus-adapter': {
parseext: {
dest: 'build/angular-parse-ext.js',
src: util.wrap(files['angularModules']['ngParseExt'], 'module')
},
"promises-aplus-adapter": {
dest:'tmp/promises-aplus-adapter++.js',
src:['src/ng/q.js', 'lib/promises-aplus/promises-aplus-test-adapter.js']
}
@@ -249,7 +264,8 @@ module.exports = function(grunt) {
resource: 'build/angular-resource.js',
route: 'build/angular-route.js',
sanitize: 'build/angular-sanitize.js',
aria: 'build/angular-aria.js'
aria: 'build/angular-aria.js',
parseext: 'build/angular-parse-ext.js'
},
@@ -264,12 +280,17 @@ module.exports = function(grunt) {
],
options: {
disallowed: [
'fit',
'iit',
'xit',
'fthey',
'tthey',
'xthey',
'fdescribe',
'ddescribe',
'xdescribe'
'xdescribe',
'it.only',
'describe.only'
]
}
},
@@ -344,10 +365,12 @@ module.exports = function(grunt) {
//alias tasks
grunt.registerTask('test', 'Run unit, docs and e2e tests with Karma', ['jshint', 'jscs', 'package', 'test:unit', 'test:promises-aplus', 'tests:docs', 'test:protractor']);
grunt.registerTask('test:jqlite', 'Run the unit tests with Karma' , ['tests:jqlite']);
grunt.registerTask('test:jquery', 'Run the jQuery unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:jquery', 'Run the jQuery (latest) unit tests with Karma', ['tests:jquery']);
grunt.registerTask('test:jquery-2.2', 'Run the jQuery 2.2 unit tests with Karma', ['tests:jquery-2.2']);
grunt.registerTask('test:jquery-2.1', 'Run the jQuery 2.1 unit tests with Karma', ['tests:jquery-2.1']);
grunt.registerTask('test:modules', 'Run the Karma module tests with Karma', ['build', 'tests:modules']);
grunt.registerTask('test:docs', 'Run the doc-page tests with Karma', ['package', 'tests:docs']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:modules']);
grunt.registerTask('test:unit', 'Run unit, jQuery and Karma module tests with Karma', ['test:jqlite', 'test:jquery', 'test:jquery-2.2', 'test:jquery-2.1', 'test:modules']);
grunt.registerTask('test:protractor', 'Run the end to end tests with Protractor and keep a test server running in the background', ['webdriver', 'connect:testserver', 'protractor:normal']);
grunt.registerTask('test:travis-protractor', 'Run the end to end tests with Protractor for Travis CI builds', ['connect:testserver', 'protractor:travis']);
grunt.registerTask('test:ci-protractor', 'Run the end to end tests with Protractor for Jenkins CI builds', ['webdriver', 'connect:testserver', 'protractor:jenkins']);
+19 -13
View File
@@ -8,20 +8,22 @@ synchronizes data from your UI (view) with your JavaScript objects (model) throu
binding. To help you structure your application better and make it easy to test, AngularJS teaches
the browser how to do dependency injection and inversion of control.
Oh yeah and it helps with server-side communication, taming async callbacks with promises and
deferreds. It also makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
piece of cake. Best of all?? It makes development fun!
It also helps with server-side communication, taming async callbacks with promises and deferreds,
and it makes client-side navigation and deeplinking with hashbang urls or HTML5 pushState a
piece of cake. Best of all? It makes development fun!
* Web site: http://angularjs.org
* Tutorial: http://docs.angularjs.org/tutorial
* API Docs: http://docs.angularjs.org/api
* Developer Guide: http://docs.angularjs.org/guide
* Web site: https://angularjs.org
* Tutorial: https://docs.angularjs.org/tutorial
* API Docs: https://docs.angularjs.org/api
* Developer Guide: https://docs.angularjs.org/guide
* Contribution guidelines: [CONTRIBUTING.md](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md)
* Dashboard: http://dashboard.angularjs.org
* Dashboard: https://dashboard.angularjs.org
##### Looking for Angular 2? Go here: https://github.com/angular/angular
Building AngularJS
---------
[Once you have your environment set up](http://docs.angularjs.org/misc/contribute) just run:
[Once you have set up your environment](https://docs.angularjs.org/misc/contribute), just run:
grunt package
@@ -37,8 +39,12 @@ To execute end-to-end (e2e) tests, use:
grunt package
grunt test:e2e
To learn more about the grunt tasks, run `grunt --help` and also read our
[contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
To learn more about the grunt tasks, run `grunt --help`
Contribute & Develop
--------------------
We've set up a separate document for our [contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).
[![Analytics](https://ga-beacon.appspot.com/UA-8594346-11/angular.js/README.md?pixel)](https://github.com/igrigorik/ga-beacon)
@@ -48,7 +54,7 @@ What to use AngularJS for and when to use it
AngularJS is the next generation framework where each component is designed to work with every other component in an interconnected way like a well-oiled machine. AngularJS is JavaScript MVC made easy and done right. (Well it is not really MVC, read on, to understand what this means.)
#### MVC, no, MV* done the right way!
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-_Whatever_. The _Whatever_ is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
MVC, short for Model-View-Controller, is a design pattern, i.e. how the code should be organized and how the different parts of an application separated for proper readability and debugging. Model is the data and the database. View is the user interface and what the user sees. Controller is the main link between Model and View. These are the three pillars of major programming frameworks present on the market today. On the other hand AngularJS works on MV*, short for Model-View-_Whatever_. The _Whatever_ is AngularJS's way of telling that you may create any kind of linking between the Model and the View here.
Unlike other frameworks in any programming language, where MVC, the three separate components, each one has to be written and then connected by the programmer, AngularJS helps the programmer by asking him/her to just create these and everything else will be taken care of by AngularJS.
+21
View File
@@ -5,6 +5,7 @@ var angularFiles = {
'src/minErr.js',
'src/Angular.js',
'src/loader.js',
'src/shallowCopy.js',
'src/stringify.js',
'src/AngularPublic.js',
'src/jqLite.js',
@@ -27,6 +28,7 @@ var angularFiles = {
'src/ng/httpBackend.js',
'src/ng/interpolate.js',
'src/ng/interval.js',
'src/ng/jsonpCallbacks.js',
'src/ng/locale.js',
'src/ng/location.js',
'src/ng/log.js',
@@ -120,10 +122,15 @@ var angularFiles = {
'ngMessages': [
'src/ngMessages/messages.js'
],
'ngParseExt': [
'src/ngParseExt/ucd.js',
'src/ngParseExt/module.js'
],
'ngResource': [
'src/ngResource/resource.js'
],
'ngRoute': [
'src/shallowCopy.js',
'src/ngRoute/route.js',
'src/ngRoute/routeParams.js',
'src/ngRoute/directive/ngView.js'
@@ -171,6 +178,7 @@ var angularFiles = {
'test/auto/*.js',
'test/ng/**/*.js',
'test/ngAnimate/*.js',
'test/ngMessageFormat/*.js',
'test/ngMessages/*.js',
'test/ngCookies/*.js',
'test/ngResource/*.js',
@@ -208,7 +216,9 @@ var angularFiles = {
'test/modules/no_bootstrap.js',
'src/ngScenario/browserTrigger.js',
'test/helpers/*.js',
'test/ngAnimate/*.js',
'test/ngMessageFormat/*.js',
'test/ngMessages/*.js',
'test/ngMock/*.js',
'test/ngCookies/*.js',
'test/ngRoute/**/*.js',
@@ -235,6 +245,17 @@ var angularFiles = {
]
};
['2.1', '2.2'].forEach(function (jQueryVersion) {
angularFiles['karmaJquery' + jQueryVersion] = []
.concat(angularFiles.karmaJquery)
.map(function (path) {
if (path.startsWith('bower_components/jquery')) {
return path.replace(/^bower_components\/jquery/, 'bower_components/jquery-' + jQueryVersion);
}
return path;
});
});
angularFiles['angularSrcModules'] = [].concat(
angularFiles['angularModules']['ngAnimate'],
angularFiles['angularModules']['ngMessageFormat'],
+3 -1
View File
@@ -2,7 +2,9 @@
"name": "AngularJS",
"license": "MIT",
"devDependencies": {
"jquery": "2.1.1",
"jquery": "3.1.0",
"jquery-2.2": "jquery#2.2.4",
"jquery-2.1": "jquery#2.1.4",
"closure-compiler": "https://dl.google.com/closure-compiler/compiler-20140814.zip",
"ng-closure-runner": "https://raw.github.com/angular/ng-closure-runner/v0.2.3/assets/ng-closure-runner.zip"
}
-2
View File
@@ -148,8 +148,6 @@ var writeChangelog = function(stream, commits, version) {
breaks: {}
};
sections.breaks[EMPTY_COMPONENT] = [];
commits.forEach(function(commit) {
var section = sections[commit.type];
var component = commit.component || EMPTY_COMPONENT;
+5
View File
@@ -647,6 +647,11 @@ ul.events > li {
padding-top: 50px;
}
.diagram {
margin-bottom: 10px;
margin-top: 30px;
}
@media only screen and (min-width: 769px) and (max-width: 991px) {
.main-body-grid {
margin-top: 160px;
+4 -1
View File
@@ -19,9 +19,12 @@
"dump": false,
/* e2e */
"protractor": false,
"browser": false,
"element": false,
"by": false,
"$": false,
"$$": false,
/* testabilityPatch / matchers */
"inject": false,
@@ -39,4 +42,4 @@
"browserTrigger": false,
"jqLiteCacheSize": false
}
}
}
+6 -6
View File
@@ -39,7 +39,7 @@ describe('docs.angularjs.org', function () {
it('should change the page content when clicking a link to a service', function () {
browser.get('build/docs/index.html');
browser.get('build/docs/index-production.html');
var ngBindLink = element(by.css('.definition-table td a[href="api/ng/directive/ngClick"]'));
ngBindLink.click();
@@ -51,33 +51,33 @@ describe('docs.angularjs.org', function () {
it('should be resilient to trailing slashes', function() {
browser.get('build/docs/index.html#!/api/ng/function/angular.noop/');
browser.get('build/docs/index-production.html#!/api/ng/function/angular.noop/');
var pageBody = element(by.css('h1'));
expect(pageBody.getText()).toEqual('angular.noop');
});
it('should be resilient to trailing "index"', function() {
browser.get('build/docs/index.html#!/api/ng/function/angular.noop/index');
browser.get('build/docs/index-production.html#!/api/ng/function/angular.noop/index');
var pageBody = element(by.css('h1'));
expect(pageBody.getText()).toEqual('angular.noop');
});
it('should be resilient to trailing "index/"', function() {
browser.get('build/docs/index.html#!/api/ng/function/angular.noop/index/');
browser.get('build/docs/index-production.html#!/api/ng/function/angular.noop/index/');
var pageBody = element(by.css('h1'));
expect(pageBody.getText()).toEqual('angular.noop');
});
it('should display formatted error messages on error doc pages', function() {
browser.get('build/docs/index.html#!error/ng/areq?p0=Missing&p1=not%20a%20function,%20got%20undefined');
browser.get('build/docs/index-production.html#!error/ng/areq?p0=Missing&p1=not%20a%20function,%20got%20undefined');
expect(element(by.css('.minerr-errmsg')).getText()).toEqual("Argument 'Missing' is not a function, got undefined");
});
it("should display an error if the page does not exist", function() {
browser.get('build/docs/index.html#!/api/does/not/exist');
browser.get('build/docs/index-production.html#!/api/does/not/exist');
expect(element(by.css('h1')).getText()).toBe('Oops!');
});
+1 -1
View File
@@ -153,7 +153,7 @@ angular.module('examples', [])
postData.description = ctrl.example.name;
formPostData('http://plnkr.co/edit/?p=preview', newWindow, postData);
formPostData('https://plnkr.co/edit/?p=preview', newWindow, postData);
});
};
+4 -3
View File
@@ -5,7 +5,8 @@ angular.module('tutorials', [])
'',
'step_00', 'step_01', 'step_02', 'step_03', 'step_04',
'step_05', 'step_06', 'step_07', 'step_08', 'step_09',
'step_10', 'step_11', 'step_12', 'the_end'
'step_10', 'step_11', 'step_12', 'step_13', 'step_14',
'the_end'
];
return {
scope: {},
@@ -43,7 +44,7 @@ angular.module('tutorials', [])
'<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}}" title="See diff on Github">GitHub</a>\n' +
'<a ng-href="https://github.com/angular/angular-phonecat/compare/step-{{step ? (step - 1): \'0~1\'}}...step-{{step}}" title="See diff on Github">GitHub</a>.\n' +
'</p>'
};
});
});
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "AngularJS-docs-app",
"dependencies": {
"jquery": "2.1.1",
"jquery": "2.2.3",
"lunr.js": "0.5.12",
"open-sans-fontface": "1.0.4",
"google-code-prettify": "1.0.1",
+1
View File
@@ -54,6 +54,7 @@ module.exports = new Package('angularjs', [
.config(function(parseTagsProcessor) {
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/tutorial-step'));
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/sortOrder'));
parseTagsProcessor.tagDefinitions.push(require('./tag-defs/installation'));
})
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
name: 'installation'
};
@@ -0,0 +1,66 @@
{% extends "base.template.html" %}
{% block content %}
<a href='https://github.com/{$ git.info.owner $}/{$ git.info.repo $}/tree/{$ git.version.isSnapshot and 'master' or git.version.raw $}/{$ doc.fileInfo.projectRelativePath $}#L{$ doc.startingLine $}' class='view-source pull-right btn btn-primary'>
<i class="glyphicon glyphicon-zoom-in">&nbsp;</i>View Source
</a>
{% block header %}
<header class="api-profile-header">
<h1 class="api-profile-header-heading">{$ doc.name $}</h1>
<ol class="api-profile-header-structure naked-list step-list">
{% block related_components %}{% endblock %}
<li>
- {$ doc.docType $} in module <a href="{$ doc.moduleDoc.path $}">{$ doc.moduleDoc.name $}</a>
</li>
</ol>
</header>
{% endblock %}
{% block description %}
<div class="api-profile-description">
{$ doc.description | marked $}
</div>
{% endblock %}
{% if doc.knownIssues %}
<h2 id="known-issues">Known Issues</h2>
{% for issue in doc.knownIssues -%}
<div class="known-issue">
{$ issue | marked $} {% if not loop.last %}<hr>{% endif %}
</div>
{% endfor -%}
{% endif %}
{% if doc.deprecated %}
<fieldset class="deprecated">
<legend>Deprecated API</legend>
{$ doc.deprecated| marked $}
</fieldset>
{% endif %}
<div>
{% block dependencies %}
{%- if doc.requires %}
<h2 id="dependencies">Dependencies</h2>
<ul>
{% for require in doc.requires %}<li>{$ require | link $}</li>{% endfor %}
</ul>
{% endif -%}
{% endblock %}
{% block additional %}
{% endblock %}
{% block examples %}
{%- if doc.examples %}
<h2 id="example">Example</h2>
{%- for example in doc.examples -%}
{$ example | marked $}
{%- endfor -%}
{% endif -%}
{% endblock %}
</div>
{% endblock %}
@@ -0,0 +1,100 @@
{% extends "base.template.html" %}
{% block content %}
<h1>
{% if doc.title %}{$ doc.title | marked $}{% else %}{$ doc.name | code $}{% endif %}
</h1>
<h2>Installation</h2>
{% if doc.installation or doc.installation == '' %}
{$ doc.installation | marked $}
{% else %}
<p>First, get the file:</p>
<ul>
<li>
<a href="https://developers.google.com/speed/libraries/devguide#angularjs">Google CDN</a> e.g.
{% code %}"//ajax.googleapis.com/ajax/libs/angularjs/X.Y.Z/{$ doc.packageFile $}"{% endcode %}
</li>
<li>
<a href="https://www.npmjs.com/">NPM</a> e.g.
{% code %}npm install {$ doc.packageName $}@X.Y.Z{% endcode %}
</li>
<li>
<a href="http://bower.io">Bower</a> e.g.
{% code %}bower install {$ doc.packageName $}#X.Y.Z{% endcode %}
</li>
<li>
<a href="https://code.angularjs.org/">code.angularjs.org</a>
(discouraged for production use) e.g.
{% code %}"//code.angularjs.org/X.Y.Z/{$ doc.packageFile $}"{% endcode %}
</li>
</ul>
<p>where X.Y.Z is the AngularJS version you are running.</p>
<p>Then, include {$ doc.packageFile | code $} in your HTML:</p>
{% code %}
<script src="path/to/angular.js"></script>
<script src="path/to/{$ doc.packageFile $}"></script>
{% endcode %}
<p>Finally, load the module in your application by adding it as a dependent module:</p>
{% code %}
angular.module('app', ['{$ doc.name $}']);
{% endcode %}
<p>With that you&apos;re ready to get started!</p>
{% endif %}
{$ doc.description | marked $}
{% if doc.knownIssueDocs %}
<div class="known-issues">
<h2 id="known-issues">Known Issues</h2>
<table class="definition-table">
<tr><th>Name</th><th>Description</th></tr>
{% for issueDoc in doc.knownIssueDocs -%}
<tr>
<td>{$ issueDoc.id | link(issueDoc.name, issueDoc) $}</td>
<td>
{% for issue in issueDoc.knownIssues -%}
{$ issue | marked $} {% if not loop.last %}<hr>{% endif %}
{% endfor -%}
</td>
</tr>
{% endfor -%}
</table>
</div>
{% endif %}
{% if doc.componentGroups.length %}
<div class="component-breakdown">
<h2>Module Components</h2>
{% for componentGroup in doc.componentGroups %}
<div>
<h3 class="component-heading" id="{$ componentGroup.groupType | dashCase $}">{$ componentGroup.groupType | title $}</h3>
<table class="definition-table">
<tr>
<th>Name</th>
<th>Description</th>
</tr>
{% for component in componentGroup.components %}
<tr>
<td>{$ component.id | link(component.name, component) $}</td>
<td>{$ component.description | firstParagraph | marked $}</td>
</tr>
{% endfor %}
</table>
</div>
{% endfor %}
</div>
{% endif %}
{% if doc.usage %}
<h2>Usage</h2>
{$ doc.usage | marked $}
{% endif %}
{% endblock %}
@@ -228,10 +228,10 @@
)
</p>
<p>
Code licensed under the
<a href="https://github.com/angular/angular.js/blob/master/LICENSE" target="_blank">The
MIT License</a>. Documentation licensed under <a
href="http://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a>.
Code licensed under
<a href="https://github.com/angular/angular.js/blob/master/LICENSE" target="_blank">The MIT License</a>.
Documentation licensed under
<a href="http://creativecommons.org/licenses/by/3.0/" target="_blank">CC BY 3.0</a>.
</p>
</div>
</footer>
+2
View File
@@ -8,6 +8,8 @@ Welcome to the AngularJS API docs page. These pages contain the AngularJS refere
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/services services}, {@link guide/filter filters}, {@link guide/providers providers}, {@link guide/templates templates}, global APIs, and testing mocks.
There is also a {@link guide/index guide} with articles on various topics, and a list of external resources.
<div class="alert alert-info">
**Angular Prefixes `$` and `$$`**:
+3 -3
View File
@@ -1,8 +1,8 @@
@ngdoc error
@name $compile:baddir
@fullName Invalid Directive Name
@fullName Invalid Directive/Component Name
@description
This error occurs when the name of a directive is not valid.
This error occurs when the name of a directive or component is not valid.
Directives must start with a lowercase character and must not contain leading or trailing whitespaces.
Directives and Components must start with a lowercase character and must not contain leading or trailing whitespaces.
+30
View File
@@ -0,0 +1,30 @@
@ngdoc error
@name $compile:infchng
@fullName Unstable `$onChanges` hooks
@description
This error occurs when the application's model becomes unstable because some `$onChanges` hooks are causing updates which then trigger
further calls to `$onChanges` that can never complete.
Angular detects this situation and prevents an infinite loop from causing the browser to become unresponsive.
For example, the situation can occur by setting up a `$onChanges()` hook which triggers an event on the component, which subsequently
triggers the component's bound inputs to be updated:
```html
<c1 prop="a" on-change="a = -a"></c1>
```
```js
function Controller1() {}
Controller1.$onChanges = function() {
this.onChange();
};
mod.component('c1', {
controller: Controller1,
bindings: {'prop': '<', onChange: '&'}
}
```
The maximum number of allowed iterations of the `$onChanges` hooks is controlled via TTL setting which can be configured via
{@link ng.$compileProvider#onChangesTtl `$compileProvider.onChangesTtl`}.
+47
View File
@@ -0,0 +1,47 @@
@ngdoc error
@name $compile:reqslot
@fullName Required transclusion slot
@description
This error occurs when a directive or component try to transclude a slot that is not provided.
Transcluded elements must contain something. This error could happen when you try to transclude a self closing tag element.
Also you can make a transclusion slot optional with a `?` prefix.
```js
// In this example the <my-component> must have an <important-component> inside to transclude it.
// If not, a reqslot error will be generated.
var componentConfig = {
template: 'path/to/template.html',
tranclude: {
importantSlot: 'importantComponent', // mandatory transclusion
optionalSlot: '?optionalComponent', // optional transclusion
}
};
angular
.module('doc')
.component('myComponent', componentConfig)
```
```html
<!-- Will not work because <important-component> is missing -->
<my-component>
</my-component>
<my-component>
<optional-component></optional-component>
</my-component>
<!-- Will work -->
<my-component>
<important-component></important-component>
</my-component>
<my-component>
<optional-component></optional-component>
<important-component></important-component>
</my-component>
```
+1 -1
View File
@@ -350,7 +350,7 @@ base and the path that should be handled by the application.
### Base href constraints
The `$location` service is not able to function properly if the current URL is outside the URL given
as the base href. This can have subtle confusing consequencies...
as the base href. This can have subtle confusing consequences...
Consider a base href set as follows: `<base href="/base/">` (i.e. the application exists in the "folder"
called `/base`). The URL `/base` is actually outside the application (it refers to the `base` file found
+90 -69
View File
@@ -10,7 +10,7 @@ The goal of ngAria is to improve Angular's default accessibility by enabling com
[ARIA](http://www.w3.org/TR/wai-aria/) attributes that convey state or semantic information for
assistive technologies used by persons with disabilities.
##Including ngAria
## Including ngAria
Using {@link ngAria ngAria} is as simple as requiring the ngAria module in your application. ngAria hooks into
standard AngularJS directives and quietly injects accessibility support into your application
@@ -20,7 +20,7 @@ at runtime.
angular.module('myApp', ['ngAria'])...
```
###Using ngAria
### Using ngAria
Most of what ngAria does is only visible "under the hood". To see the module in action, once you've
added it as a dependency, you can test a few things:
* Using your favorite element inspector, look for attributes added by ngAria in your own code.
@@ -28,12 +28,13 @@ added it as a dependency, you can test a few things:
* Fire up a screen reader such as VoiceOver or NVDA to check for ARIA support.
[Helpful screen reader tips.](http://webaim.org/articles/screenreader_testing/)
##Supported directives
## Supported directives
Currently, ngAria interfaces with the following directives:
* {@link guide/accessibility#ngmodel ngModel}
* {@link guide/accessibility#ngdisabled ngDisabled}
* {@link guide/accessibility#ngrequired ngRequired}
* {@link guide/accessibility#ngreadonly ngReadonly}
* {@link guide/accessibility#ngvaluechecked ngChecked}
* {@link guide/accessibility#ngvaluechecked ngValue}
* {@link guide/accessibility#ngshow ngShow}
@@ -57,12 +58,62 @@ attributes (if they have not been explicitly specified by the developer):
* aria-valuenow
* aria-invalid
* aria-required
* aria-readonly
###Example
### Example
<example module="ngAria_ngModelExample" deps="angular-aria.js">
<file name="index.html">
<style>
<file name="index.html">
<form ng-controller="formsController">
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
aria-label="Custom Checkbox" show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox
</some-checkbox>
</form>
</file>
<file name="script.js">
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
};
})
.directive('someCheckbox', function(){
return {
restrict: 'E',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
};
})
.directive('showAttrs', function() {
return function($scope, $el, $attrs) {
var pre = document.createElement('pre');
$el.after(pre);
$scope.$watch(function() {
var $attrs = {};
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
if (item.name !== 'show-$attrs') {
$attrs[item.name] = item.value;
}
});
return $attrs;
}, function(newAttrs, oldAttrs) {
pre.textContent = JSON.stringify(newAttrs, null, 2);
}, true);
};
});
</file>
<file name="style.css">
[role=checkbox] {
cursor: pointer;
display: inline-block;
@@ -81,58 +132,7 @@ attributes (if they have not been explicitly specified by the developer):
pre {
white-space: pre-wrap;
}
</style>
<div>
<form ng-controller="formsController">
<some-checkbox role="checkbox" ng-model="checked" ng-class="{active: checked}"
ng-disabled="isDisabled" ng-click="toggleCheckbox()"
aria-label="Custom Checkbox" show-attrs>
<span class="icon" aria-hidden="true"></span>
Custom Checkbox
</some-checkbox>
</form>
</div>
<script>
var app = angular.module('ngAria_ngModelExample', ['ngAria'])
.controller('formsController', function($scope){
$scope.checked = false;
$scope.toggleCheckbox = function(){
$scope.checked = !$scope.checked;
}
})
.directive('someCheckbox', function(){
return {
restrict: 'E',
link: function($scope, $el, $attrs) {
$el.on('keypress', function(event){
event.preventDefault();
if(event.keyCode === 32 || event.keyCode === 13){
$scope.toggleCheckbox();
$scope.$apply();
}
});
}
}
})
.directive('showAttrs', function() {
return function($scope, $el, $attrs) {
var pre = document.createElement('pre');
$el.after(pre);
$scope.$watch(function() {
var $attrs = {};
Array.prototype.slice.call($el[0].attributes, 0).forEach(function(item) {
if (item.name !== 'show-$attrs') {
$attrs[item.name] = item.value;
}
});
return $attrs;
}, function(newAttrs, oldAttrs) {
pre.textContent = JSON.stringify(newAttrs, null, 2);
}, true);
}
});
</script>
</file>
</file>
</example>
ngAria will also add `tabIndex`, ensuring custom elements with these roles will be reachable from
@@ -147,7 +147,7 @@ To ease the transition between native inputs and custom controls, ngAria now sup
The original directives were created for native inputs only, so ngAria extends
support to custom elements by managing `aria-checked` for accessibility.
###Example
### Example
```html
<custom-checkbox ng-checked="val"></custom-checkbox>
@@ -169,7 +169,7 @@ using ngAria with {@link ng.ngDisabled ngDisabled} will also
add `aria-disabled`. This tells assistive technologies when a non-native input is disabled, helping
custom controls to be more accessible.
###Example
### Example
```html
<md-checkbox ng-disabled="disabled"></md-checkbox>
@@ -181,8 +181,10 @@ Becomes:
<md-checkbox disabled aria-disabled="true"></md-checkbox>
```
>You can check whether a control is legitimately disabled for a screen reader by visiting
<div class="alert alert-info">
You can check whether a control is legitimately disabled for a screen reader by visiting
[chrome://accessibility](chrome://accessibility) and inspecting [the accessibility tree](http://www.paciellogroup.com/blog/2015/01/the-browser-accessibility-tree/).
</div>
<h2 id="ngrequired">ngRequired</h2>
@@ -191,7 +193,7 @@ The boolean `required` attribute is only valid for native form controls such as
as required, using ngAria with {@link ng.ngRequired ngRequired} will also add
`aria-required`. This tells accessibility APIs when a custom control is required.
###Example
### Example
```html
<md-checkbox ng-required="val"></md-checkbox>
@@ -203,9 +205,28 @@ Becomes:
<md-checkbox ng-required="val" aria-required="true"></md-checkbox>
```
<h2 id="ngreadonly">ngReadonly</h2>
The boolean `readonly` attribute is only valid for native form controls such as `input` and
`textarea`. To properly indicate custom element directives such as `<md-checkbox>` or `<custom-input>`
as required, using ngAria with {@link ng.ngReadonly ngReadonly} will also add
`aria-readonly`. This tells accessibility APIs when a custom control is read-only.
### Example
```html
<md-checkbox ng-readonly="val"></md-checkbox>
```
Becomes:
```html
<md-checkbox ng-readonly="val" aria-readonly="true"></md-checkbox>
```
<h2 id="ngshow">ngShow</h2>
>The {@link ng.ngShow ngShow} directive shows or hides the
The {@link ng.ngShow ngShow} directive shows or hides the
given HTML element based on the expression provided to the `ngShow` attribute. The element is
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
@@ -222,7 +243,7 @@ screen reader users won't accidentally focus on "mystery elements". Managing tab
child control can be complex and affect performance, so its best to just stick with the default
`display: none` CSS. See the [fourth rule of ARIA use](http://www.w3.org/TR/aria-in-html/#fourth-rule-of-aria-use).
###Example
### Example
```css
.ng-hide {
display: block;
@@ -242,7 +263,7 @@ Becomes:
<h2 id="nghide">ngHide</h2>
>The {@link ng.ngHide ngHide} directive shows or hides the
The {@link ng.ngHide ngHide} directive shows or hides the
given HTML element based on the expression provided to the `ngHide` attribute. The element is
shown or hidden by removing or adding the `.ng-hide` CSS class onto the element.
@@ -283,11 +304,11 @@ Becomes:
<h2 id="ngmessages">ngMessages</h2>
The new ngMessages module makes it easy to display form validation or other messages with priority
The ngMessages module makes it easy to display form validation or other messages with priority
sequencing and animation. To expose these visual messages to screen readers,
ngAria injects `aria-live="assertive"`, causing them to be read aloud any time a message is shown,
regardless of the user's focus location.
###Example
### Example
```html
<div ng-messages="myForm.myName.$error">
@@ -305,7 +326,7 @@ Becomes:
</div>
```
##Disabling attributes
## Disabling attributes
The attribute magic of ngAria may not work for every scenario. To disable individual attributes,
you can use the {@link ngAria.$ariaProvider#config config} method. Just keep in mind this will
tell ngAria to ignore the attribute globally.
@@ -343,7 +364,7 @@ tell ngAria to ignore the attribute globally.
</file>
</example>
##Common Accessibility Patterns
## Common Accessibility Patterns
Accessibility best practices that apply to web apps in general also apply to Angular.
+41 -44
View File
@@ -12,6 +12,13 @@ triggered, will attempt to perform a CSS Transition, CSS Keyframe Animation or a
placed on the given directive). Animations can be placed using vanilla CSS by following the naming conventions set in place by AngularJS
or with JavaScript code when it's defined as a factory.
<div class="alert alert-info">
Note that we have used non-prefixed CSS transition properties in our examples as the major browsers now support non-prefixed
properties. If you intend to support older browsers or certain mobile browsers then you will need to include prefixed
versions of the transition properties. Take a look at http://caniuse.com/#feat=css-transitions for what browsers require prefixes,
and https://github.com/postcss/autoprefixer for a tool that can automatically generate the prefixes for you.
</div>
Animations are not available unless you include the {@link ngAnimate `ngAnimate` module} as a dependency within your application.
Below is a quick example of animations being enabled for `ngShow` and `ngHide`:
@@ -29,18 +36,17 @@ Below is a quick example of animations being enabled for `ngShow` and `ngHide`:
</file>
<file name="animations.css">
.sample-show-hide {
padding:10px;
border:1px solid black;
background:white;
padding: 10px;
border: 1px solid black;
background: white;
}
.sample-show-hide {
-webkit-transition:all linear 0.5s;
transition:all linear 0.5s;
transition: all linear 0.5s;
}
.sample-show-hide.ng-hide {
opacity:0;
opacity: 0;
}
</file>
</example>
@@ -80,11 +86,8 @@ occur when ngRepeat triggers them:
class
*/
.repeated-item.ng-enter, .repeated-item.ng-move {
-webkit-transition:0.5s linear all;
-moz-transition:0.5s linear all;
-o-transition:0.5s linear all;
transition:0.5s linear all;
opacity:0;
transition: all 0.5s linear;
opacity: 0;
}
/*
@@ -95,7 +98,7 @@ occur when ngRepeat triggers them:
*/
.repeated-item.ng-enter.ng-enter-active,
.repeated-item.ng-move.ng-move-active {
opacity:1;
opacity: 1;
}
/*
@@ -104,10 +107,7 @@ occur when ngRepeat triggers them:
that has the .repeated-item class
*/
.repeated-item.ng-leave {
-webkit-animation:0.5s my_animation;
-moz-animation:0.5s my_animation;
-o-animation:0.5s my_animation;
animation:0.5s my_animation;
animation: 0.5s my_animation;
}
@keyframes my_animation {
@@ -115,24 +115,6 @@ occur when ngRepeat triggers them:
to { opacity:0; }
}
/*
Unfortunately each browser vendor requires
its own definition of keyframe animation code...
*/
@-webkit-keyframes my_animation {
from { opacity:1; }
to { opacity:0; }
}
@-moz-keyframes my_animation {
from { opacity:1; }
to { opacity:0; }
}
@-o-keyframes my_animation {
from { opacity:1; }
to { opacity:0; }
}
```
The same approach to animation can be used using JavaScript code (**jQuery is used within to perform animations**):
@@ -217,10 +199,7 @@ The example below shows how to perform animations during class changes:
</file>
<file name="style.css">
.css-class-add, .css-class-remove {
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
-o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.5s;
}
.css-class,
@@ -231,7 +210,7 @@ The example below shows how to perform animations during class changes:
.css-class-remove.css-class-remove-active {
font-size:1.0em;
color:black;
color: black;
}
</file>
</example>
@@ -317,8 +296,8 @@ app.config(function($animateProvider) {
```css
/&#42; prefixed with animate- &#42;/
.animate-fade-add.animate-fade-add-active {
transition:1s linear all;
opacity:0;
transition: all 1s linear;
opacity: 0;
}
```
@@ -334,8 +313,8 @@ This function can be used to enable / disable animations in two different ways:
With a single `boolean` argument, it enables / disables animations globally: `$animate.enabled(false)`
disables all animations in your app.
When the second argument is a native DOM or jQuery element, the function enables / disables
animations on this element *and all its children*: `$animate.enabled(false, myElement)`. This is the
When the first argument is a native DOM or jqLite/jQuery element, the function enables / disables
animations on this element *and all its children*: `$animate.enabled(myElement, false)`. This is the
most flexible way to change the animation state. For example, even if you have used it to disable
animations on a parent element, you can still re-enable it for a child element. And compared to the
`classNameFilter`, you can change the animation status at runtime instead of during the config phase.
@@ -368,6 +347,7 @@ By setting `transition: 0s`, ngAnimate will ignore the existing transition style
animations will still execute, though). This can be used to prevent {@link guide/animations#preventing-collisions-with-existing-animations-and-third-party-libraries
issues with existing animations interfering with ngAnimate}.
## Preventing flicker before an animation starts
When nesting elements with structural animations such as `ngIf` into elements that have class-based
@@ -442,8 +422,25 @@ than any styles that are independent from ngAnimate.
You can also use one of the two other {@link guide/animations#how-to-selectively-enable-disable-and-skip-animations strategies to disable animations}.
### Enable animations for elements outside of the Angular application DOM tree: {@link ng.$animate#pin $animate.pin()}
Before animating, `ngAnimate` checks to see if the element being animated is inside the application DOM tree,
and if it is not, no animation is run. Usually, this is not a problem as most apps use the `ngApp`
attribute / bootstrap the app on the `html` or `body` element.
Problems arise when the application is bootstrapped on a different element, and animations are
attempted on elements that are outside the application tree, e.g. when libraries append popup and modal
elements as the last child in the body tag.
You can use {@link ng.$animate#pin `$animate.pin(elementToAnimate, parentHost)`} to specify that an
element belongs to your application. Simply call it before the element is added to the DOM / before
the animation starts, with the element you want to animate, and the element which should be its
assumed parent.
## More about animations
For a full breakdown of each method available on `$animate`, see the {@link ng.$animate API documentation}.
To see a complete demo, see the {@link tutorial/step_12 animation step within the AngularJS phonecat tutorial}.
To see a complete demo, see the {@link tutorial/step_14 animation step within the AngularJS phonecat tutorial}.
+26 -14
View File
@@ -5,6 +5,11 @@
# Component Router
<div class="alert alert-info">
**Deprecation Notice:** In an effort to keep synchronized with router changes in Angular 2, this implementation of the Component Router (ngComponentRouter module) has been deprecated and will not receive further updates.
We are investigating backporting the Angular 2 Router to Angular 1, but alternatively, use the {@link ngRoute} module or community developed projects (e.g. [ui-router](https://github.com/angular-ui/ui-router)).
</div>
This guide describes the new Component Router for AngularJS 1.5.
<div class="alert alert-info">
@@ -33,7 +38,7 @@ Here is a table of the main concepts used in the Component Router.
## Component-based Applications
It recommended to develop AngularJS applications as a hierarchy of Components. Each Component
It is recommended to develop AngularJS applications as a hierarchy of Components. Each Component
is an isolated part of the application, which is responsible for its own user interface and has
a well defined programmatic interface to the Component that contains it. Take a look at the
{@link guide/component component guide} for more information.
@@ -124,9 +129,9 @@ This process continues until we run out of **Routing Components** or consume the
![Routed Components](img/guide/component-routes.svg)
In the previous diagram can see that the URL `/heros/2` has been matched against the `App`, `Heroes` and
In the previous diagram, we can see that the URL `/heros/4` has been matched against the `App`, `Heroes` and
`HeroDetail` **Routing Components**. The **Routers** for each of the **Routing Components** consumed a part
of the URL: "/", "/heroes" and "/2" respectively.
of the URL: "/", "/heroes" and "/4" respectively.
The result is that we end up with a hierarchy of **Routing Components** rendered in **Outlets**, via the
{@link ngOutlet} directive, in each **Routing Component's** template, as you can see in the following diagram.
@@ -462,13 +467,13 @@ to display list and detail views of Heroes and Crises.
## Install the libraries
It is simplest to use npm to install the **Component Router** module. For this guide we will also install
It is easier to use npm to install the **Component Router** module. For this guide we will also install
AngularJS itself via npm:
```bash
npm init
npm install@1.5.x angular --save
npm install @angular/router --save
npm install angular@1.5.x --save
npm install @angular/router@0.2.0 --save
```
@@ -482,10 +487,18 @@ Just like any Angular application, we load the JavaScript files into our `index.
<script src="/app/app.js"></script>
```
You also need to include ES6 shims for browsers that do not support ES6 code (Internet Explorer,
iOs < 8, Android < 5.0, Windows Mobile < 10):
```html
<!-- IE required polyfills, in this exact order -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.33.3/es6-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.20/system-polyfills.js"></script>
<script src="https://npmcdn.com/angular2/es6/dev/src/testing/shims_for_IE.js"></script>
```
## Create the `app` module
In the app.js file, create the main application module `app` which depends upon the `ngComponentRouter`
In the app.js file, create the main application module `app` which depends on the `ngComponentRouter`
module, which is provided by the **Component Router** script.
```js
@@ -494,10 +507,10 @@ angular.module('app', ['ngComponentRouter'])
We must choose what **Location Mode** the **Router** should use. We are going to use HTML5 mode locations,
so that we will not have hash-based paths. We must rely on the browser to provide `pushState` support,
which is true of most modern browsers. See {@link $locationProvider#html5Mode} for more information.
which is true for most modern browsers. See {@link $locationProvider#html5Mode} for more information.
<div class="alert alert-info">
Using HTML5 mode means that we can have clean URLs for our application routes but it does require that our
Using HTML5 mode means that we can have clean URLs for our application routes. However, HTML5 mode does require that our
web server, which hosts the application, understands that it must respond with the index.html file for
requests to URLs that represent all our application routes. We are going to use the `lite-server` web server
to do this for us.
@@ -550,7 +563,7 @@ Bootstrap the Angular application and add the top level App Component.
# Implementing the AppComponent
In the previous section we created a single top level **App Component**. Let's now create some more
In the previous section we have created a single top level **App Component**. Let's now create some more
**Routing Components** and wire up **Route Config** for those. We start with a Heroes Feature, which
will display one of two views.
@@ -590,7 +603,7 @@ of this view will be rendered.
### ngLink
We have used the `ng-link` directive to create a link to navigate to the Heroes Component. By using this
directive we don't need to know what the actual URL will be. We can leave the Router to generate that for us.
directive we don't need to know what the actual URL will be. We can let the Router generate that for us.
We have included a link to the Crisis Center but have not included the `ng-link` directive as we have not yet
implemented the CrisisCenter component.
@@ -658,7 +671,7 @@ because the `HeroList` and `HeroDetail` will not contain any child routes.
The `component` property in a **Route Definition** defines the **Component** directive that will be rendered
into the DOM via the **Outlet**. For example the `heroDetail` **Component** will be rendered into the page
where the `<ng-outlet></ng-outlet>` lives as `<hero-detail></hero-detail>.
where the `<ng-outlet></ng-outlet>` lives as `<hero-detail></hero-detail>`.
The `name` property is used to reference the **Route Definition** when generating URLs or navigating to
**Routes**. For example this link will `<a ng-link="['Heroes']">Heroes</a>` navigate the **Route Definition**
@@ -765,7 +778,7 @@ function HeroListComponent(heroService) {
Running the application should update the browser's location to `/heroes` and display the list of heroes
returned from the `heroService`.
By returning a promise for the list of heroes from `$routerOnActivate()` we can delay activation of the
By returning a promise for the list of heroes from `$routerOnActivate()` we can delay the activation of the
Route until the heroes have arrived successfully. This is similar to how a `resolve` works in {@link ngRoute}.
@@ -854,7 +867,6 @@ Router itself, which was made available by the binding in the **Component Defini
function HeroDetailComponent(heroService) {
...
this.gotoHeroes = function() {
var heroId = this.hero && this.hero.id;
this.$router.navigate(['HeroList']);
};
```
+64 -36
View File
@@ -29,7 +29,7 @@ and link functions are unavailable
Components can be registered using the `.component()` method of an Angular module (returned by {@link module `angular.module()`}). The method takes two arguments:
* The name of the Component (as string).
* The Component config object (note that, unlike the `.directive()` method, this method does **not** take a factory function.
* The Component config object. (Note that, unlike the `.directive()` method, this method does **not** take a factory function.)
<example name="heroComponentSimple" module="heroApp">
<file name="index.js">
@@ -133,6 +133,8 @@ components should follow a few simple conventions:
For a deletion, that means the component doesn't delete the `hero` itself, but sends it back to
the owner component via the correct event.
```html
<!-- note that we use kebab-case for bindings in the template as usual -->
<editable-field on-update="$ctrl.update('location', value)"></editable-field><br>
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
```
- That way, the parent component can decide what to do with the event (e.g. delete an item or update the properties)
@@ -147,6 +149,30 @@ components should follow a few simple conventions:
}
```
- **Components have a well-defined lifecycle**
Each component can implement "lifecycle hooks". These are methods that will be called at certain points in the life
of the component. The following hook methods can be implemented:
* `$onInit()` - Called on each controller after all the controllers on an element have been constructed and
had their bindings initialized (and before the pre &amp; post linking functions for the directives on
this element). This is a good place to put initialization code for your controller.
* `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys
are the names of the bound properties that have changed, and the values are an object of the form
`{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a component such as
cloning the bound value to prevent accidental mutation of the outer value.
* `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
external resources, watches and event handlers.
* `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
they are waiting for their template to load asynchronously and their own compilation and linking has been
suspended until that occurs.
This hook can be considered analogous to the `ngAfterViewInit` and `ngAfterContentInit` hooks in Angular 2.
Since the compilation process is rather different in Angular 1 there is no direct mapping and care should
be taken when upgrading.
By implementing these methods, your component can hook into its lifecycle.
- **An application is a tree of components:**
Ideally, the whole application should be a tree of components that implement clearly defined inputs
and outputs, and minimize two-way data binding. That way, it's easier to predict when data changes and what the state
@@ -207,9 +233,13 @@ it upwards to the heroList component, which updates the original data.
</file>
<file name="heroDetail.js">
function HeroDetailController($scope, $element, $attrs) {
function HeroDetailController() {
var ctrl = this;
ctrl.delete = function() {
ctrl.onDelete({hero: ctrl.hero});
};
ctrl.update = function(prop, value) {
ctrl.onUpdate({hero: ctrl.hero, prop: prop, value: value});
};
@@ -277,7 +307,7 @@ it upwards to the heroList component, which updates the original data.
<div>
Name: {{$ctrl.hero.name}}<br>
Location: <editable-field field-value="$ctrl.hero.location" field-type="text" on-update="$ctrl.update('location', value)"></editable-field><br>
<button ng-click="$ctrl.onDelete({hero: $ctrl.hero})">Delete</button>
<button ng-click="$ctrl.delete()">Delete</button>
</div>
</file>
<file name="editableField.html">
@@ -417,54 +447,52 @@ angular.module('docsTabsExample', [])
# Unit-testing Component Controllers
The easiest way to unit-test a component controller is by using the {@link ngMock.$componentController $componentController}
that is included in {@link ngMock}. The advantage of this method is that you do not have
to create any DOM elements. The following example shows how to do this for the `heroDetail` component
from above.
The easiest way to unit-test a component controller is by using the
{@link ngMock.$componentController $componentController} that is included in {@link ngMock}. The
advantage of this method is that you do not have to create any DOM elements. The following example
shows how to do this for the `heroDetail` component from above.
The examples use the [Jasmine](http://jasmine.github.io/) testing framework.
**Controller Test:**
```js
describe('component: heroDetail', function() {
var component, scope, hero, $componentController;
var $componentController;
beforeEach(module('simpleComponent'));
beforeEach(inject(function($rootScope, _$componentController_) {
scope = $rootScope.$new();
beforeEach(module('heroApp'));
beforeEach(inject(function(_$componentController_) {
$componentController = _$componentController_;
hero = {name: 'Wolverine'};
}));
it('should set the default values of the hero', function() {
// It's necessary to always pass the scope in the locals, so that the controller instance can be bound to it
component = $componentController('heroDetail', {$scope: scope});
expect(component.hero).toEqual({
name: undefined,
location: 'unknown'
});
});
it('should assign the name bindings to the hero object', function() {
it('should expose a `hero` object', function() {
// Here we are passing actual bindings to the component
var bindings = {hero: {name: 'Wolverine'}};
var ctrl = $componentController('heroDetail', null, bindings);
component = $componentController('heroDetail',
{$scope: scope},
{hero: hero}
);
expect(component.hero.name).toBe('Wolverine');
expect(ctrl.hero).toBeDefined();
expect(ctrl.hero.name).toBe('Wolverine');
});
it('should call the onDelete binding when a hero is deleted', function() {
component = $componentController('heroDetail',
{$scope: scope},
{hero: hero, onDelete: jasmine.createSpy('deleteSpy')}
);
it('should call the `onDelete` binding, when deleting the hero', function() {
var onDeleteSpy = jasmine.createSpy('onDelete');
var bindings = {hero: {}, onDelete: onDeleteSpy};
var ctrl = $componentController('heroDetail', null, bindings);
component.onDelete({hero: component.hero});
expect(spy('deleteSpy')).toHaveBeenCalledWith(component.hero);
ctrl.delete();
expect(onDeleteSpy).toHaveBeenCalledWith({hero: ctrl.hero});
});
it('should call the `onUpdate` binding, when updating a property', function() {
var onUpdateSpy = jasmine.createSpy('onUpdate');
var bindings = {hero: {}, onUpdate: onUpdateSpy};
var ctrl = $componentController('heroDetail', null, bindings);
ctrl.update('foo', 'bar');
expect(onUpdateSpy).toHaveBeenCalledWith({
hero: ctrl.hero,
prop: 'foo',
value: 'bar'
});
});
});
+2 -2
View File
@@ -277,8 +277,8 @@ Now that Angular knows of all the parts of the application, it needs to create t
In the previous section we saw that controllers are created using a factory function.
For services there are multiple ways to define their factory
(see the {@link services service guide}).
In the example above, we are using a function that returns the `currencyConverter` function as the factory
for the service.
In the example above, we are using an anonymous function as the factory function for `currencyConverter` service.
This function should return the `currencyConverter` service instance.
Back to the initial question: How does the `InvoiceController` get a reference to the `currencyConverter` function?
In Angular, this is done by simply defining arguments on the constructor function. With this, the injector
+486
View File
@@ -0,0 +1,486 @@
@ngdoc overview
@name Decorators
@sortOrder 345
@description
# Decorators in AngularJS
<div class="alert alert-warning">
**NOTE:** This guide is targeted towards developers who are already familiar with AngularJS basics.
If you're just getting started, we recommend the {@link tutorial/ tutorial} first.
</div>
## What are decorators?
Decorators are a design pattern that is used to separate modification or *decoration* of a class without modifying the
original source code. In Angular, decorators are functions that allow a service, directive or filter to be modified
prior to its usage.
## How to use decorators
There are two ways to register decorators
- `$provide.decorator`, and
- `module.decorator`
Each provide access to a `$delegate`, which is the instantiated service/directive/filter, prior to being passed to the
service that required it.
### $provide.decorator
The {@link api/auto/service/$provide#decorator decorator function} allows access to a $delegate of the service once it
has been instantiated. For example:
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('$log', [
'$delegate',
function $logDecorator($delegate) {
var originalWarn = $delegate.warn;
$delegate.warn = function decoratedWarn(msg) {
msg = 'Decorated Warn: ' + msg;
originalWarn.apply($delegate, arguments);
};
return $delegate;
}
]);
}]);
```
After the `$log` service has been instantiated the decorator is fired. The decorator function has a `$delegate` object
injected to provide access to the service that matches the selector in the decorator. This `$delegate` will be the
service you are decorating. The return value of the function *provided to the decorator* will take place of the service,
directive, or filter being decorated.
<hr>
The `$delegate` may be either modified or completely replaced. Given a service `myService` with a method `someFn`, the
following could all be viable solutions:
#### Completely Replace the $delegate
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('myService', [
'$delegate',
function myServiceDecorator($delegate) {
var myDecoratedService = {
// new service object to replace myService
};
return myDecoratedService;
}
]);
}]);
```
#### Patch the $delegate
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('myService', [
'$delegate',
function myServiceDecorator($delegate) {
var someFn = $delegate.someFn;
function aNewFn() {
// new service function
someFn.apply($delegate, arguments);
}
$delegate.someFn = aNewFn;
return $delegate;
}
]);
}]);
```
#### Augment the $delegate
```js
angular.module('myApp', [])
.config([ '$provide', function($provide) {
$provide.decorator('myService', [
'$delegate',
function myServiceDecorator($delegate) {
function helperFn() {
// an additional fn to add to the service
}
$delegate.aHelpfulAddition = helperFn;
return $delegate;
}
]);
}]);
```
<div class="alert alert-info">
Note that whatever is returned by the decorator function will replace that which is being decorated. For example, a
missing return statement will wipe out the entire object being decorated.
</div>
<hr>
Decorators have different rules for different services. This is because services are registered in different ways.
Services are selected by name, however filters and directives are selected by appending `"Filter"` or `"Directive"` to
the end of the name. The `$delegate` provided is dictated by the type of service.
| Service Type | Selector | $delegate |
|--------------|-------------------------------|-----------------------------------------------------------------------|
| Service | `serviceName` | The `object` or `function` returned by the service |
| Directive | `directiveName + 'Directive'` | An `Array.<DirectiveObject>`<sub>{@link guide/decorators#drtvArray 1}</sub> |
| Filter | `filterName + 'Filter'` | The `function` returned by the filter |
<small id="drtvArray">1. Multiple directives may be registered to the same selector/name</small>
<div class="alert alert-warning">
**NOTE:** Developers should take care in how and why they are modifying the `$delegate` for the service. Not only
should expectations for the consumer be kept, but some functionality (such as directive registration) does not take
place after decoration, but during creation/registration of the original service. This means, for example, that
an action such as pushing a directive object to a directive `$delegate` will likely result in unexpected behavior.
Furthermore, great care should be taken when decorating core services, directives, or filters as this may unexpectedly
or adversely affect the functionality of the framework.
</div>
### module.decorator
This {@link api/ng/type/angular.Module#decorator function} is the same as the `$provide.decorator` function except it is
exposed through the module API. This allows you to separate your decorator patterns from your module config blocks. The
main caveat here is that you will need to take note the order in which you create your decorators.
Unlike in the module config block (which allows configuration of services prior to their creation), the service must be
registered prior to the decorator (see {@link guide/providers#provider-recipe Provider Recipe}). For example, the
following would not work because you are attempting to decorate outside of the configuration phase and the service
hasn't been created yet:
```js
// will cause an error since 'someService' hasn't been registered
angular.module('myApp').decorator('someService', ...);
angular.module('myApp').factory('someService', ...);
```
## Example Applications
The following sections provide examples each of a service decorator, a directive decorator, and a filter decorator.
### Service Decorator Example
This example shows how we can replace the $log service with our own to display log messages.
<example module="myServiceDecorator" name="service-decorator">
<file name="script.js">
angular.module('myServiceDecorator', []).
controller('Ctrl', [
'$scope',
'$log',
'$timeout',
function($scope, $log, $timeout) {
var types = ['error', 'warn', 'log', 'info' ,'debug'], i;
for (i = 0; i < types.length; i++) {
$log[types[i]](types[i] + ': message ' + (i + 1));
}
$timeout(function() {
$log.info('info: message logged in timeout');
});
}
]).
directive('myLog', [
'$log',
function($log) {
return {
restrict: 'E',
template: '<ul id="myLog"><li ng-repeat="l in myLog" class="{{l.type}}">{{l.message}}</li></ul>',
scope: {},
compile: function() {
return function(scope) {
scope.myLog = $log.stack;
};
}
};
}
]).
config([
'$provide',
function($provide) {
$provide.decorator('$log', [
'$delegate',
function logDecorator($delegate) {
var myLog = {
warn: function(msg) {
log(msg, 'warn');
},
error: function(msg) {
log(msg, 'error');
},
info: function(msg) {
log(msg, 'info');
},
debug: function(msg) {
log(msg, 'debug');
},
log: function(msg) {
log(msg, 'log');
},
stack: []
};
function log(msg, type) {
myLog.stack.push({ type: type, message: msg.toString() });
if (console && console[type]) console[type](msg);
}
return myLog;
}
]);
}
]);
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<h1>Logs</h1>
<my-log></my-log>
</div>
</file>
<file name="style.css">
li.warn { color: yellow; }
li.error { color: red; }
li.info { color: blue }
li.log { color: black }
li.debug { color: green }
</file>
<file name="protractor.js" type="protractor">
it('should display log messages in dom', function() {
element.all(by.repeater('l in myLog')).count().then(function(count) {
expect(count).toEqual(6);
});
});
</file>
</example>
### Directive Decorator Example
Failed interpolated expressions in `ng-href` attributes can easily go unnoticed. We can decorate `ngHref` to warn us of
those conditions.
<example module="urlDecorator" name="directive-decorator">
<file name="script.js">
angular.module('urlDecorator', []).
controller('Ctrl', ['$scope', function ($scope) {
$scope.id = 3;
$scope.warnCount = 0; // for testing
}]).
config(['$provide', function($provide) {
// matchExpressions looks for interpolation markup in the directive attribute, extracts the expressions
// from that markup (if they exist) and returns an array of those expressions
function matchExpressions(str) {
var exps = str.match(/{{([^}]+)}}/g);
// if there isn't any, get out of here
if (exps === null) return;
exps = exps.map(function(exp) {
var prop = exp.match(/[^{}]+/);
return prop === null ? null : prop[0];
});
return exps;
}
// remember: directives must be selected by appending 'Directive' to the directive selector
$provide.decorator('ngHrefDirective', [
'$delegate',
'$log',
'$parse',
function($delegate, $log, $parse) {
// store the original link fn
var originalLinkFn = $delegate[0].link;
// replace the compile fn
$delegate[0].compile = function(tElem, tAttr) {
// store the original exp in the directive attribute for our warning message
var originalExp = tAttr.ngHref;
// get the interpolated expressions
var exps = matchExpressions(originalExp);
// create and store the getters using $parse
var getters = exps.map(function(el) {
if (el) return $parse(el);
});
return function newLinkFn(scope, elem, attr) {
// fire the originalLinkFn
originalLinkFn.apply($delegate[0], arguments);
// observe the directive attr and check the expressions
attr.$observe('ngHref', function(val) {
// if we have getters and getters is an array...
if (getters && angular.isArray(getters)) {
// loop through the getters and process them
angular.forEach(getters, function(g, idx) {
// if val is truthy, then the warning won't log
var val = angular.isFunction(g) ? g(scope) : true;
if (!val) {
$log.warn('NgHref Warning: "' + exps[idx] + '" in the expression "' + originalExp +
'" is falsy!');
scope.warnCount++; // for testing
}
});
}
});
};
};
// get rid of the old link function since we return a link function in compile
delete $delegate[0].link;
// return the $delegate
return $delegate;
}
]);
}]);
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<a ng-href="/products/{{ id }}/view" id="id3">View Product {{ id }}</a>
- <strong>id == 3</strong>, so no warning<br>
<a ng-href="/products/{{ id + 5 }}/view" id="id8">View Product {{ id + 5 }}</a>
- <strong>id + 5 == 8</strong>, so no warning<br>
<a ng-href="/products/{{ someOtherId }}/view" id="someOtherId">View Product {{ someOtherId }}</a>
- <strong style="background-color: #ffff00;">someOtherId == undefined</strong>, so warn<br>
<a ng-href="/products/{{ someOtherId + 5 }}/view" id="someOtherId5">View Product {{ someOtherId + 5 }}</a>
- <strong>someOtherId + 5 == 5</strong>, so no warning<br>
<div>Warn Count: {{ warnCount }}</div>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should warn when an expression in the interpolated value is falsy', function() {
var id3 = element(by.id('id3'));
var id8 = element(by.id('id8'));
var someOther = element(by.id('someOtherId'));
var someOther5 = element(by.id('someOtherId5'));
expect(id3.getText()).toEqual('View Product 3');
expect(id3.getAttribute('href')).toContain('/products/3/view');
expect(id8.getText()).toEqual('View Product 8');
expect(id8.getAttribute('href')).toContain('/products/8/view');
expect(someOther.getText()).toEqual('View Product');
expect(someOther.getAttribute('href')).toContain('/products//view');
expect(someOther5.getText()).toEqual('View Product 5');
expect(someOther5.getAttribute('href')).toContain('/products/5/view');
expect(element(by.binding('warnCount')).getText()).toEqual('Warn Count: 1');
});
</file>
</example>
### Filter Decorator Example
Let's say we have created an app that uses the default format for many of our `Date` filters. Suddenly requirements have
changed (that never happens) and we need all of our default dates to be `'shortDate'` instead of `'mediumDate'`.
<example module="filterDecorator" name="filter-decorator">
<file name="script.js">
angular.module('filterDecorator', []).
controller('Ctrl', ['$scope', function ($scope) {
$scope.genesis = new Date(2010, 0, 5);
$scope.ngConf = new Date(2016, 4, 4);
}]).
config(['$provide', function($provide) {
$provide.decorator('dateFilter', [
'$delegate',
function dateDecorator($delegate) {
// store the original filter
var originalFilter = $delegate;
// return our filter
return shortDateDefault;
// shortDateDefault sets the format to shortDate if it is falsy
function shortDateDefault(date, format, timezone) {
if (!format) format = 'shortDate';
// return the result of the original filter
return originalFilter(date, format, timezone);
}
}
]);
}]);
</file>
<file name="index.html">
<div ng-controller="Ctrl">
<div id="genesis">Initial Commit default to short date: {{ genesis | date }}</div>
<div>ng-conf 2016 default short date: {{ ngConf | date }}</div>
<div id="ngConf">ng-conf 2016 with full date format: {{ ngConf | date:'fullDate' }}</div>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should default date filter to short date format', function() {
expect(element(by.id('genesis')).getText())
.toMatch(/Initial Commit default to short date: \d{1,2}\/\d{1,2}\/\d{2}/);
});
it('should still allow dates to be formatted', function() {
expect(element(by.id('ngConf')).getText())
.toMatch(/ng-conf 2016 with full date format\: [A-Za-z]+, [A-Za-z]+ \d{1,2}, \d{4}/);
});
</file>
</example>
-4
View File
@@ -162,10 +162,6 @@ initialization work here. The function is invoked using
{@link auto.$injector#invoke $injector.invoke} which makes it injectable just like a
controller.
<div class="alert alert-success">
**Best Practice:** Prefer using the definition object over returning a function.
</div>
We'll go over a few common examples of directives, then dive deep into the different options
and compilation process.
+6 -3
View File
@@ -27,6 +27,9 @@ Angular expressions are like JavaScript expressions with the following differenc
* **Forgiving:** In JavaScript, trying to evaluate undefined properties generates `ReferenceError`
or `TypeError`. In Angular, expression evaluation is forgiving to `undefined` and `null`.
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
displaying it.
* **No Control Flow Statements:** You cannot use the following in an Angular expression:
conditionals, loops, or exceptions.
@@ -38,10 +41,10 @@ Angular expressions are like JavaScript expressions with the following differenc
* **No Object Creation With New Operator:** You cannot use `new` operator in an Angular expression.
* **No Comma And Void Operators:** You cannot use `,` or `void` operators in an Angular expression.
* **No Bitwise, Comma, And Void Operators:** You cannot use
[Bitwise](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators),
`,` or `void` operators in an Angular expression.
* **Filters:** You can use {@link guide/filter filters} within expressions to format data before
displaying it.
If you want to run more complex JavaScript code, you should make it a controller method and call
the method from your view. If you want to `eval()` an Angular expression yourself, use the
+147
View File
@@ -0,0 +1,147 @@
@ngdoc overview
@name External Resources
@sortOrder 205
@description
# External Angular 1 Resources
This is a collection of external, 3rd party resources for learning and developing Angular.
## Articles, Videos, and Projects
### Introductory Material
* [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/)
* [10 Reasons Why Developers Should Learn AngularJS](http://wintellect.com/blogs/jlikness/10-reasons-web-developers-should-learn-angularjs)
* [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c)
* [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM)
* [For folks with a jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background)
### Specific Topics
#### Application Structure & Style Guides
* [Angular Styleguide](https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md)
* [Architecture, file structure, components, one-way dataflow and best practices](https://github.com/toddmotto/angular-styleguide)
* [When to use directives, controllers or services](http://kirkbushell.me/when-to-use-directives-controllers-or-services-in-angular/)
* [Service vs Factory](http://blog.thoughtram.io/angular/2015/07/07/service-vs-factory-once-and-for-all.html)
#### Testing
* **Unit testing:** [Using Karma (video)](http://www.youtube.com/watch?v=YG5DEzaQBIc), [Karma in Webstorm](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7/)
#### Mobile
* [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html)
* [Angular and Cordova](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
* [Ionic Framework](http://ionicframework.com/)
#### Deployment
##### General
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ng-annotate automation tool](https://github.com/olov/ng-annotate)
* **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
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
* **FireBase:** [AngularFire](http://angularfire.com/), [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
* **Meteor: **[angular-meteor package](https://github.com/Urigo/angular-meteor)
### Other Languages
* [ES6, Webpack, and JSPM Starter Project](https://github.com/AngularClass/NG6-starter)
* [ES6/Typescript Best Practices](https://codepen.io/martinmcwhorter/post/angularjs-1-x-with-typescript-or-es6-best-practices)
* [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
* [CoffeeScript Tutorial](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html)
### More Topics
* **Security:** [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Internationalization and Localization:** [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
* **Authentication/Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Facebook library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html)
* **Realtime Communication: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
## Tools
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
* **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [Angular Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/)
## Complementary Libraries
This is a list of libraries that enhance Angular, add common UI components or integrate with other libraries.
You can find a larger list of Angular external libraries at [ngmodules.org](http://ngmodules.org/).
* **Advanced Routing:** [UI-Router](https://github.com/angular-ui/ui-router)
* **Authentication:** [Http Auth Interceptor](https://github.com/witoldsz/angular-http-auth)
* **Internationalization:**
- [angular-translate](http://angular-translate.github.io)
- [angular-gettext](http://angular-gettext.rocketeer.be/)
- [angular-localization](http://doshprompt.github.io/angular-localization/)
* **RESTful services:** [Restangular](https://github.com/mgonto/restangular)
* **SQL and NoSQL backends:**
- [BreezeJS](http://www.breezejs.com/)
- [AngularFire](http://angularfire.com/)
* **Data Handling**
- Local Storage and session: [ngStorage](https://github.com/gsklee/ngStorage)
- [angular-cache](https://github.com/jmdobry/angular-cache)
- Data Modeling [JS-Data-Angular](https://github.com/js-data/js-data-angular)
* **Fileupload:**
- [ng-file-upload](https://github.com/danialfarid/ng-file-upload)
- [blueimp-fileupload for Angular](https://blueimp.github.io/jQuery-File-Upload/angularjs.html)
* **General UI Libraries:**
- [Angular Material](https://material.angularjs.org/latest/)
- [Angular UI Bootstrap](http://angular-ui.github.io/)
- [AngularStrap for Bootstrap 3](http://mgcrea.github.io/angular-strap/)
- [KendoUI](http://kendo-labs.github.io/angular-kendo/#/)
- [Wijmo](http://wijmo.com/tag/angularjs-2/)
* **Specific UI Elements:**
- [ngInfiniteScroll](https://sroze.github.io/ngInfiniteScroll/)
- [ngTable](https://github.com/esvit/ng-table)
- [Angular UI Grid](http://angular-ui.github.io/grid)
- [Toaster Notifications](https://github.com/jirikavi/AngularJS-Toaster)
- [textAngular Rich Text Editor / contenteditable](http://textangular.com/) (Rich Text Editor /
binding to contenteditable)
- [Angular UI Map (Google Maps)](https://github.com/angular-ui/ui-map)
## General Learning Resources
### Books
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
### Videos:
* [egghead.io](http://egghead.io/)
### Courses
* **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:**
[The Angular Course (115 videos that show you how to build a full app)](http://watchandcode.com/courses/angular-course/),
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
* **Paid onsite:**
[angularbootcamp.com](http://angularbootcamp.com/)
+24 -7
View File
@@ -5,10 +5,11 @@
# Filters
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.
Filters format the value of an expression for display to the user. They can be used in view
templates, controllers or services. Angular comes with a collection of
[built-in filters](api/ng/filter), but it is easy to define your own as well.
The underlying API is the {@link ng.$filterProvider `filterProvider`}.
The underlying API is the {@link ng.$filterProvider}.
## Using filters in view templates
@@ -31,6 +32,21 @@ Filters may have arguments. The syntax for this is
E.g. the markup `{{ 1234 | number:2 }}` formats the number 1234 with 2 decimal points using the
{@link ng.filter:number `number`} filter. The resulting value is `1,234.00`.
### When filters are executed
In templates, filters are only executed when their inputs have changed. This is more performant than executing
a filter on each {@link ng.$rootScope.Scope#$digest `$digest`} as is the case with {@link guide/expression expressions}.
There are two exceptions to this rule:
1. In general, this applies only to filters that take [primitive values](https://developer.mozilla.org/docs/Glossary/Primitive)
as inputs. Filters that receive [Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Objects)
as input are executed on each `$digest`, as it would be too costly to track if the inputs have changed.
2. Filters that are marked as `$stateful` are also executed on each $digest.
See {@link guide/filter#stateful-filters Stateful filters} for more information. Note that no Angular
core filters are $stateful.
## Using filters in controllers, services, and directives
@@ -93,8 +109,9 @@ as the first argument. Any filter arguments are passed in as additional argument
function.
The filter function should be a [pure function](http://en.wikipedia.org/wiki/Pure_function), which
means that it should be stateless and idempotent. Angular relies on these properties and executes
the filter only when the inputs to the function change.
means that it should be stateless and idempotent, and not rely for example on other Angular services.
Angular relies on this contract and will by default execute a filter only when the inputs to the function change.
{@link guide/filter#stateful-filters Stateful filters} are possible, but less performant.
<div class="alert alert-warning">
**Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
@@ -141,7 +158,7 @@ text upper-case.
</example>
## Stateful filters
### Stateful filters
It is strongly discouraged to write filters that are stateful, because the execution of those can't
be optimized by Angular, which often leads to performance issues. Many stateful filters can be
@@ -183,4 +200,4 @@ means that it will be executed one or more times during the each `$digest` cycle
## Testing custom filters
See the [phonecat tutorial](http://docs.angularjs.org/tutorial/step_09#test) for an example.
See the [phonecat tutorial](http://docs.angularjs.org/tutorial/step_11#testing) for an example.
+9 -2
View File
@@ -29,6 +29,13 @@ To ensure your Angular application works on IE please consider:
1. Use `ng-style` tags instead of `style="{{ someCss }}"`. The latter works in Chrome and Firefox
but does not work in Internet Explorer <= 11 (the most recent version at time of writing).
2. For the `type` attribute of buttons, use `ng-attr-type` tags instead of
`type="{{ someExpression }}"`. If using the latter, Internet Explorer overwrites the expression
2. For the `type` attribute of buttons, use `ng-attr-type` tags instead of
`type="{{ someExpression }}"`. If using the latter, Internet Explorer overwrites the expression
with `type="submit"` before Angular has a chance to interpolate it.
3. For the `value` attribute of progress, use `ng-attr-value` tags instead of
`value="{{ someExpression}}"`. If using the latter, Internet Explorer overwrites the expression
with `value="0"` before Angular has a chance to interpolate it.
4. For the `placeholder` attribute of textarea, use `ng-attr-placeholder` tags instead
of `placeholder="{{ someExpression }}"`. If using the latter, Internet Explorer will error
on accessing the `nodeValue` on a parentless `TextNode` in Internet Explorer 10 & 11
(see [issue 5025](https://github.com/angular/angular.js/issues/5025)).
+40 -107
View File
@@ -2,22 +2,21 @@
@name Developer Guide
@description
# Guide to AngularJS Documentation
# Guide to Angular 1 Documentation
Everything you need to know about AngularJS
On this page, you will find a list of official Angular resources on various topics.
* {@link guide/introduction What is AngularJS?}
Just starting out with Angular 1? Try working through our step by step tutorial or try
building on our seed project.
* {@link tutorial/index Official Angular 1 Tutorial}
* [Angular Seed](https://github.com/angular/angular-seed)
Ready to find out more about Angular 1?
* {@link guide/introduction What is Angular 1?}
* {@link guide/concepts Conceptual Overview}
## Tutorials
* {@link tutorial/index Official AngularJS Tutorial}
* [10 Reasons Why You Should Use AngularJS](http://www.sitepoint.com/10-reasons-use-angularjs/)
* [10 Reasons Why Developers Should Learn AngularJS](http://wintellect.com/blogs/jlikness/10-reasons-web-developers-should-learn-angularjs)
* [Design Principles of AngularJS (video)](https://www.youtube.com/watch?v=HCR7i5F5L8c)
* [Fundamentals in 60 Minutes (video)](http://www.youtube.com/watch?v=i9MHigUZKEM)
* [For folks with a jQuery background](http://stackoverflow.com/questions/14994391/how-do-i-think-in-angularjs-if-i-have-a-jquery-background)
## Core Concepts
### Templates
@@ -26,134 +25,68 @@ In Angular applications, you move the job of filling page templates with data fr
* {@link guide/databinding Data binding}
* {@link guide/expression Expressions}
* {@link guide/interpolation Interpolation}
* {@link guide/directive Directives}
* {@link ngRoute.$route Views and routes (see the example)}
* {@link guide/filter Filters}
* {@link guide/forms Forms} and [Concepts of AngularJS Forms](http://mrbool.com/the-concepts-of-angularjs-forms/29117)
* {@link guide/compiler HTML compiler}
* {@link guide/forms Forms}
### Application Structure
* **Blog post: **[When to use directives, controllers or services](http://kirkbushell.me/when-to-use-directives-controllers-or-services-in-angular/)
* **App wiring:** {@link guide/di Dependency injection}
* **Exposing model to templates:** {@link guide/scope Scopes}
* **Bootstrap:** {@link guide/bootstrap Bootstrapping an app}
* **Communicating with servers:** {@link ng.$http $http}, {@link ngResource.$resource $resource}
### Other AngularJS Features
### Other Features
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}, and [Animation in AngularJS 1.2](http://www.yearofmoo.com/2013/08/remastered-animation-in-angularjs-1-2.html)
* **Animation:** {@link guide/animations Core concepts}, {@link ngAnimate ngAnimate API}
* **Security:** {@link guide/security Security Docs}, {@link ng.$sce Strict Contextual Escaping}, {@link ng.directive:ngCsp Content Security Policy}, {@link ngSanitize.$sanitize $sanitize}, [video](https://www.youtube.com/watch?v=18ifoT-Id54)
* **Internationalization and Localization:** {@link guide/i18n Angular Guide to i18n and l10n}, {@link ng.filter:date date filter}, {@link ng.filter:currency currency filter}, [Creating multilingual support](http://www.novanet.no/blog/hallstein-brotan/dates/2013/10/creating-multilingual-support-using-angularjs/)
* **Mobile:** {@link ngTouch Touch events}
* **Touch events:** {@link ngTouch Touch events}
* **Accessibility:** {@link guide/accessibility ngAria}
### Testing
* **Unit testing:** [Using Karma (video)](http://www.youtube.com/watch?v=YG5DEzaQBIc), {@link guide/unit-testing Unit testing}, {@link guide/services#unit-testing Testing services}, [Karma in Webstorm](http://blog.jetbrains.com/webstorm/2013/10/running-javascript-tests-with-karma-in-webstorm-7/)
* **Scenario testing:** [Protractor](https://github.com/angular/protractor)
* **Unit testing:** [Karma](http://karma-runner.github.io), {@link guide/unit-testing Unit testing}, {@link guide/services#unit-testing Testing services},
* **End-to-End Testing:** [Protractor](https://github.com/angular/protractor), {@link guide/e2e-testing e2e testing guide}
## Specific Topics
* **Login: **[Google example](https://developers.google.com/+/photohunt/python), [AngularJS Facebook library](https://github.com/pc035860/angular-easyfb), [Facebook example](http://blog.brunoscopelliti.com/facebook-authentication-in-your-angularjs-web-app), [authentication strategy](http://blog.brunoscopelliti.com/deal-with-users-authentication-in-an-angularjs-web-app), [unix-style authorization](http://frederiknakstad.com/authentication-in-single-page-applications-with-angular-js/)
* **Mobile:** [Angular on Mobile Guide](http://www.ng-newsletter.com/posts/angular-on-mobile.html), [PhoneGap](http://devgirl.org/2013/06/10/quick-start-guide-phonegap-and-angularjs/)
* **Other Languages:** [CoffeeScript](http://www.coffeescriptlove.com/2013/08/angularjs-and-coffeescript-tutorials.html), [Dart](https://github.com/angular/angular.dart.tutorial/wiki)
* **Realtime: **[Socket.io](http://www.creativebloq.com/javascript/angularjs-collaboration-board-socketio-2132885), [OmniBinder](https://github.com/jeffbcross/omnibinder)
* **Visualization:** [SVG](http://gaslight.co/blog/angular-backed-svgs), [D3.js](http://www.ng-newsletter.com/posts/d3-on-angular.html)
* **Local Storage and session:** [ngStorage](https://github.com/gsklee/ngStorage)
## Community Resources
## Tools
We have set up a guide to many resources provided by the community, where you can find lots
of additional information and material on these topics, a list of complimentary libraries, and much more.
* **Getting Started:** [Comparison of the options for starting a new project](http://www.dancancro.com/comparison-of-angularjs-application-starters/)
* **Debugging:** [Batarang](https://chrome.google.com/webstore/detail/angularjs-batarang/ighdmehidhipcmcojjgiloacoafjmpfk?hl=en)
* **Testing:** [Karma](http://karma-runner.github.io), [Protractor](https://github.com/angular/protractor)
* **Editor support:** [Webstorm](http://plugins.jetbrains.com/plugin/6971) (and [video](http://www.youtube.com/watch?v=LJOyrSh1kDU)), [Sublime Text](https://github.com/angular-ui/AngularJS-sublime-package), [Visual Studio](http://madskristensen.net/post/angularjs-intellisense-in-visual-studio-2012)
* **Workflow:** [Yeoman.io](https://github.com/yeoman/generator-angular) and [Angular Yeoman Tutorial](http://www.sitepoint.com/kickstart-your-angularjs-development-with-yeoman-grunt-and-bower/)
* {@link guide/external-resources External Angular 1 resources}
## Complementary Libraries
This is a short list of libraries with specific support and documentation for working with Angular. You can find a full list of all known Angular external libraries at [ngmodules.org](http://ngmodules.org/).
* **Internationalization:** [angular-translate](http://angular-translate.github.io), [angular-gettext](http://angular-gettext.rocketeer.be/), [angular-localization](http://doshprompt.github.io/angular-localization/)
* **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/), [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
* **Docs Page:** {@link guide/production Running an AngularJS App in Production}
* **Javascript minification: **[Background](http://thegreenpizza.github.io/2013/05/25/building-minification-safe-angular.js-applications/), [ng-annotate automation tool](https://github.com/olov/ng-annotate)
* **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
* **Django:** [Tutorial](http://blog.mourafiq.com/post/55034504632/end-to-end-web-app-with-django-rest-framework), [Integrating AngularJS with Django](http://django-angular.readthedocs.org/en/latest/integration.html), [Getting Started with Django Rest Framework and AngularJS](http://blog.kevinastone.com/getting-started-with-django-rest-framework-and-angularjs.html)
* **FireBase:** [AngularFire](http://angularfire.com/), [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/), [Realtime Apps with AngularJS and FireBase (video)](http://www.youtube.com/watch?v=C7ZI7z7qnHU)
* **Google Cloud Platform: **[with Cloud Endpoints](https://cloud.google.com/developers/articles/angularjs-cloud-endpoints-recipe-for-building-modern-web-applications/), [with Go](https://github.com/GoogleCloudPlatform/appengine-angular-gotodos)
* **Hood.ie:** [60 Minutes to Awesome](http://www.roberthorvick.com/2013/06/30/todomvc-angularjs-hood-ie-60-minutes-to-awesome/)
* **MEAN Stack: **[Blog post](http://blog.mongodb.org/post/49262866911/the-mean-stack-mongodb-expressjs-angularjs-and), [Setup](http://thecodebarbarian.wordpress.com/2013/07/22/introduction-to-the-mean-stack-part-one-setting-up-your-tools/), [GDL Video](https://developers.google.com/live/shows/913996610)
* **Rails: **[Tutorial](http://coderberry.me/blog/2013/04/22/angularjs-on-rails-4-part-1/), [AngularJS with Rails4](https://shellycloud.com/blog/2013/10/how-to-integrate-angularjs-with-rails-4), [angularjs-rails](https://github.com/hiravgandhi/angularjs-rails)
* **PHP: **[Building a RESTful web service](http://blog.brunoscopelliti.com/building-a-restful-web-service-with-angularjs-and-php-more-power-with-resource), [End to End with Laravel 4 (video)](http://www.youtube.com/watch?v=hqAyiqUs93c)
* **Meteor: **[angular-meteor package](https://github.com/Urigo/angular-meteor)
## Learning Resources
###Books
* [AngularJS: Up and Running](http://www.amazon.com/AngularJS-Running-Enhanced-Productivity-Structured/dp/1491901942) by Brad Green and Shyam Seshadri
* [Mastering Web App Development](http://www.amazon.com/Mastering-Web-Application-Development-AngularJS/dp/1782161821) by Pawel Kozlowski and Pete Bacon Darwin
* [AngularJS Directives](http://www.amazon.com/AngularJS-Directives-Alex-Vanston/dp/1783280336) by Alex Vanston
* [Recipes With AngularJS](http://www.amazon.co.uk/Recipes-Angular-js-Frederik-Dietz-ebook/dp/B00DK95V48) by Frederik Dietz
* [Developing an AngularJS Edge](http://www.amazon.com/Developing-AngularJS-Edge-Christopher-Hiller-ebook/dp/B00CJLFF8K) by Christopher Hiller
* [ng-book: The Complete Book on AngularJS](http://ng-book.com/) by Ari Lerner
* [AngularJS : Novice to Ninja](http://www.amazon.in/AngularJS-Novice-Ninja-Sandeep-Panda/dp/0992279453) by Sandeep Panda
* [AngularJS UI Development](http://www.amazon.com/AngularJS-UI-Development-Amit-Ghart-ebook/dp/B00OXVAK7A) by Amit Gharat and Matthias Nehlsen
* [Responsive Web Design with AngularJS](http://www.amazon.com/Responsive-Design-AngularJS-Sandeep-Kumar/dp/178439842X) by Sandeep Kumar Patel
* [Professional AngularJS](http://www.amazon.com/Professional-AngularJS-Valeri-Karpov/dp/1118832078/)
###Videos:
* [egghead.io](http://egghead.io/)
* [Angular on YouTube](http://youtube.com/angularjs)
* [Firebase Foundations for AngularJS](http://blog.watchandcode.com/firebase-foundations/)
### Courses
* **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:**
[The Angular Course (115 videos that show you how to build a full app)](http://watchandcode.com/courses/angular-course/),
[Pluralsite (3 courses)](http://www.pluralsight.com/training/Courses/Find?highlight=true&searchTerm=angularjs),
[Tuts+](https://tutsplus.com/course/easier-js-apps-with-angular/),
[lynda.com](http://www.lynda.com/AngularJS-tutorials/Up-Running-AngularJS/133318-2.html),
[WintellectNOW (4 lessons)](http://www.wintellectnow.com/Course/Detail/mastering-angularjs)
* **Paid onsite:**
[angularbootcamp.com](http://angularbootcamp.com/)
## Getting Help
The recipe for getting help on your unique issue is to create an example that could work (even if it doesn't) in a shareable example on [Plunker](http://plnkr.co/), [JSFiddle](http://jsfiddle.net/), or similar site and then post to one of the following:
* [Stackoverflow.com](http://stackoverflow.com/search?q=angularjs)
* [AngularJS mailing list](https://groups.google.com/forum/#!forum/angular)
* [AngularJS IRC channel](http://webchat.freenode.net/?channels=angularjs&uio=d4)
* [Angular 1 mailing list](https://groups.google.com/forum/#!forum/angular)
* [Angular 1 IRC channel](http://webchat.freenode.net/?channels=angularjs&uio=d4)
## Social Channels
## Official Communications
* **Daily updates:** [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs)
* **Weekly newsletter:** [ng-newsletter](http://www.ng-newsletter.com/)
* **Meetups: **[meetup.com](http://www.meetup.com/find/?keywords=angularJS&radius=Infinity&userFreeform=San+Francisco%2C+CA&mcId=z94108&mcName=San+Francisco%2C+CA&sort=member_count&eventFilter=mysugg)
* **Official news and releases: **[AngularJS Blog](http://blog.angularjs.org/)
Official announcements, news and releases are posted to our blog, G+ and Twitter:
## Contributing to AngularJS
* [Angular Blog](http://blog.angularjs.org/)
* [Google+](https://plus.google.com/u/0/+AngularJS)
* [Twitter](https://twitter.com/angularjs)
* [Angular on YouTube](http://youtube.com/angularjs)
Though we have a core group of core contributors at Google, Angular is an open source project with hundreds of contributors. We'd love you to be one of them. When you're ready, please read the {@link misc/contribute Guide for contributing to AngularJS}.
## Contributing to Angular 1
## Final Bits
Though we have a core group of core contributors at Google, Angular is an open source project with hundreds of contributors.
We'd love you to be one of them. When you're ready, please read the {@link misc/contribute Guide for contributing to Angular}.
Didn't find what you're looking for here? Check out [AngularJS-Learning](https://github.com/jmcunningham/AngularJS-Learning) for an even more comprehensive list of links to videos, tutorials, and blog posts.
## Something Missing?
If you have awesome AngularJS resources that belong on this page, please tell us about them on [Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs).
Didn't find what you're looking for here? Check out the {@link guide/external-resources External Angular 1 resources guide}.
If you have awesome Angular 1 resources that belong on that page, please tell us about them on
[Google+](https://plus.google.com/u/0/+AngularJS) or [Twitter](https://twitter.com/angularjs).
+15 -2
View File
@@ -37,7 +37,7 @@ would be lost, because the browser ignores the attribute value.
In the following example, the interpolation information would be ignored and the browser would simply
interpret the attribute as present, meaning that the button would always be disabled.
```html
```html
Disabled: <input type="checkbox" ng-model="isDisabled" />
<button disabled="{{isDisabled}}">Disabled</button>
```
@@ -49,7 +49,7 @@ For this reason, Angular provides special `ng`-prefixed directives for the follo
These directives take an expression inside the attribute, and set the corresponding boolean attribute
to true when the expression evaluates to truthy.
```html
```html
Disabled: <input type="checkbox" ng-model="isDisabled" />
<button ng-disabled="isDisabled">Disabled</button>
```
@@ -104,7 +104,20 @@ can be used with `ngAttr` instead. The following is a list of known problematic
- **size** in `<select>` elements (see [issue 1619](https://github.com/angular/angular.js/issues/1619))
- **placeholder** in `<textarea>` in Internet Explorer 10/11 (see [issue 5025](https://github.com/angular/angular.js/issues/5025))
- **type** in `<button>` in Internet Explorer 11 (see [issue 14117](https://github.com/angular/angular.js/issues/5025))
- **value** in `<progress>` in Internet Explorer = 11 (see [issue 7218](https://github.com/angular/angular.js/issues/7218))
## Known Issues
### Dynamically changing an interpolated value
You should avoid dynamically changing the content of an interpolated string (e.g. attribute value
or text node). Your changes are likely to be overwriten, when the original string gets evaluated.
This restriction applies to both directly changing the content via JavaScript or indirectly using a
directive.
For example, you should not use interpolation in the value of the `style` attribute (e.g.
`style="color: {{ 'orange' }}; font-weight: {{ 'bold' }};"`) **and** at the same time use a
directive that changes the content of that attributte, such as `ngStyle`.
### Embedding interpolation markup inside expressions
+39 -6
View File
@@ -514,7 +514,7 @@ angular.module('myApp').directive('form', function() {
});
```
### Templating (`ngRepeat`, `$compile`)
### Templating (`ngRepeat`, `$compile`, `ngInclude`)
#### ngRepeat
@@ -545,6 +545,44 @@ Due to [62d514b](https://github.com/angular/angular.js/commit/62d514b06937cc7dd8
returning an object from a controller constructor function will now override the scope. Views that use the
controllerAs method will no longer get the this reference, but the returned object.
#### ngInclude
Due to [3c6e8ce044446735eb2e70d0061db8c6db050289](https://github.com/angular/angular.js/commit/3c6e8ce044446735eb2e70d0061db8c6db050289), the `src` attribute of ngInclude no longer accepts an
expression that returns the result of `$sce.trustAsResourceUrl`. This will now cause an infinite digest:
Before:
```html
<div ng-include="findTemplate('https://example.com/templates/myTemplate.html')"></div>
```
```js
$scope.findTemplate = function(templateName) {
return $sce.trustAsResourceUrl(templateName);
};
```
To migrate, either cache the result of `trustAsResourceUrl()`, or put the template url in the resource
whitelist in the `config()` function:
After:
```js
var templateCache = {};
$scope.findTemplate = function(templateName) {
if (!templateCache[templateName]) {
templateCache[templateName] = $sce.trustAsResourceUrl(templateName);
}
return templateCache[templateName];
};
// Alternatively, use `$sceDelegateProvider.resourceUrlWhitelist()`, which means you don't
// have to use `$sce.trustAsResourceUrl()` at all:
angular.module('myApp', []).config(function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist(['self', 'https://example.com/templates/**'])
});
```
### Cookies (`ngCookies`)
@@ -583,8 +621,6 @@ has been moved to `$cookies`, to which `$cookieStore` now simply
delegates calls.
### Server Requests (`$http`)
Due to [5da1256](https://github.com/angular/angular.js/commit/5da1256fc2812d5b28fb0af0de81256054856369),
@@ -616,8 +652,6 @@ $http.get(url, {
```
### Filters (`filter`, `limitTo`)
#### `filter` filter
@@ -636,7 +670,6 @@ Now, instead of returning empty object/array, it returns unchanged input.
## Migrating from 1.2 to 1.3
### Controllers
+2 -2
View File
@@ -75,8 +75,8 @@ that you break your application to multiple modules like this:
* And an application level module which depends on the above modules and contains any
initialization code.
You can find a community
[style guide](https://github.com/johnpapa/angular-styleguide) to help yourself when application grows.
You can find a community [style guide](https://github.com/johnpapa/angular-styleguide) to help
yourself when application grows.
The above is a suggestion. Tailor it to your needs.
+1 -1
View File
@@ -257,7 +257,7 @@ the `$digest` phase. This delay is desirable, since it coalesces multiple model
2. **Watcher registration**
During template linking directives register {@link
During template linking, directives register {@link
ng.$rootScope.Scope#$watch watches} on the scope. These watches will be
used to propagate model values to the DOM.
+4
View File
@@ -87,6 +87,10 @@ Protection from JSON Hijacking is provided if the server prefixes all JSON reque
Angular will automatically strip the prefix before processing it as JSON.
For more information please visit {@link $http#json-vulnerability-protection JSON Hijacking Protection}.
Bear in mind that calling `$http.jsonp`, like in [our Yahoo! finance example](https://docs.angularjs.org/guide/concepts#accessing-the-backend),
gives the remote server (and, if the request is not secured, any Man-in-the-Middle attackers)
instant remote code execution in your application: the result of these requests is handed off
to the browser as regular `<script>` tag.
## Strict Contextual Escaping
+2
View File
@@ -3,6 +3,8 @@
@sortOrder 410
@description
# Unit Testing
JavaScript is a dynamically typed language which comes with great power of expression, but it also
comes with almost no help from the compiler. For this reason we feel very strongly that any code
written in JavaScript needs to come with a strong set of tests. We have built many features into
+3 -12
View File
@@ -26,9 +26,9 @@ machine:
* [Git](http://git-scm.com/): The [Github Guide to
Installing Git](https://help.github.com/articles/set-up-git) is a good source of information.
* [Node.js](http://nodejs.org): We use Node to generate the documentation, run a
* [Node.js v4.x](http://nodejs.org): We use Node to generate the documentation, run a
development web server, run tests, and generate distributable files. Depending on your system, you can install Node either from source or as a
pre-packaged bundle.
pre-packaged bundle. (Currently our build does not work properly on Node v5 or greater - please use v4.x.)
* [Java](http://www.java.com): We minify JavaScript using our
[Closure Tools](https://developers.google.com/closure/) jar. Make sure you have Java (version 7 or higher) installed
@@ -40,15 +40,6 @@ and included in your [PATH](http://docs.oracle.com/javase/tutorial/essential/env
npm install -g grunt-cli
```
* [Bower](http://bower.io/): We use Bower to manage client-side packages for the docs. Install the `bower` command-line tool globally with:
```shell
npm install -g bower
```
**Note:** You may need to use sudo (for OSX, *nix, BSD etc) or run your command shell as Administrator (for Windows) to install Grunt &amp;
Bower globally.
## Forking Angular on Github
To create a Github account, follow the instructions [here](https://github.com/signup/free).
@@ -62,7 +53,7 @@ minified AngularJS files:
```shell
# Clone your Github repository:
git clone "git@github.com:<github username>/angular.js.git"
git clone https://github.com/<github username>/angular.js.git
# Go to the AngularJS directory:
cd angular.js
+88 -51
View File
@@ -2,85 +2,122 @@
@name Downloading
@description
# Including angular scripts from the Google CDN
The quickest way to get started is to point your html `<script>` tag to a Google CDN URL.
# Including Angular scripts from the Google CDN
The quickest way to get started is to point your html `<script>` tag to a
[Google CDN](https://developers.google.com/speed/libraries/#angularjs) URL.
This way, you don't have to download anything or maintain a local copy.
There are two types of angular script URLs you can point to, one for development and one for
There are two types of Angular script URLs you can point to, one for development and one for
production:
* __angular.js__ — This is the human-readable, non-minified version, suitable for web
development.
* __angular.js__ — This is the human-readable, non-minified version, suitable for web development.
* __angular.min.js__ — This is the minified version, which we strongly suggest you use in
production.
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.4.5:
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.5.6:
```
```html
<!doctype html>
<html ng-app>
<head>
<title>My Angular App</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
</head>
<body>
</body>
</html>
```
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
hosted code source. If you're still using the angular server you should switch to the CDN version
for even faster loading times.
<div class="alert alert-info">
Note that only versions 1.0.1 and above are available on the CDN. If you need an earlier version
(which you shouldn't) you can use the https://code.angularjs.org/ URL, which was the previous
recommended location for hosted code source. If you're still using the Angular server you should
switch to the CDN version for even faster loading times.
</div>
<br />
# Downloading and hosting angular files locally
This option is for those who want to work with angular offline, or those who want to host the
angular files on their own servers.
This option is for those who want to work with Angular offline, or those who want to host the
Angular files on their own servers.
If you navigate to <http://code.angularjs.org/>, you'll see a directory listing with all of the
angular versions since we started releasing versioned build artifacts (quite late in the project
lifetime). Each directory contains all artifacts that we released for a particular version.
Download the version you want and have fun.
If you navigate to https://code.angularjs.org/, you'll see a directory listing with all of the
Angular versions since we started releasing versioned build artifacts. Each directory contains all
artifacts that we released for a particular version. Download the version you want and have fun.
Each directory under <http://code.angularjs.org/> includes the following set of files:
<div class="alert alert-warning">
You can ignore directories starting with `2.` (e.g. `2.0.0-beta.17`) — they are not related to
AngularJS. They contain build artifacts from [Angular 2](https://angular.io) versions.
</div>
* __`angular.js`__ — This file is non-obfuscated, non-minified, and human-readable by
opening it in any editor or browser. In order to get better error messages during development, you
should always use this non-minified angular script.
<br />
Each directory under https://code.angularjs.org/ includes a set of files that comprise the
corresponding version. All JavaScript files (except for `angular-mocks` which is only used during
development) come in two flavors — one suitable for development, the other for production:
* __`angular.min.js`__ — This is a minified and obfuscated version of
`angular.js` created with the Closure compiler. Use this version for production in order
to minimize the size of the application that is downloaded by your user's browser.
* __`<filename>.js`__ — These files are non-obfuscated, non-minified, and human-readable by opening
them in any editor or browser. In order to get better error messages during development, you
should always use these non-minified scripts.
* __`angular.zip`__ — This is a zip archive that contains all of the files released
for this angular version. Use this file to get everything in a single download.
* __`<filename>.min.js`__ — These are minified and obfuscated versions, created with the
[Closure compiler](https://developers.google.com/closure/compiler/). Use these versions for
production in order to minimize the size of the application that is downloaded by your user's
browser.
* __`angular-mocks.js`__ — This file contains an implementation of mocks that makes
testing angular apps even easier. Your unit/integration test harness should load this file after
`angular.js` is loaded.
* __`<filename>.min.js.map`__ — These are
[sourcemap files](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/). You can
serve them alongside the `.min.js` files in order to be able to debug the minified code (e.g. on a
production deployment) more easily, but without impacting performance.
* __`angular-scenario.js`__ — This file is a very nifty JavaScript file that allows you
to write and execute end-to-end tests for angular applications.
<br />
The set of files included in each version directory are:
* __`angular-loader.min.js`__ — Module loader for Angular modules. If you are loading multiple script files containing
Angular modules, you can load them asynchronously and in any order as long as you load this file first. Often the
contents of this file are copy&pasted into the `index.html` to avoid even the initial request to `angular-loader.min.js`.
See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for an example of usage.
* __`angular.zip`__ — This is a zip archive that contains all of the files released for this Angular
version. Use this file to get everything in a single download.
* __Additional Angular modules:__ optional modules with additional functionality. These files should be loaded
after the core `angular.js` file:
* __`angular-animate.js`__ - Enable animation support
* __`angular-cookies.js`__ - A convenient wrapper for reading and writing browser cookies
* __`angular-resource.js`__ - Interaction support with RESTful services via the $resource service
* __`angular-route.js`__ - Routing and deeplinking services and directives for angular apps
* __`angular-sanitize.js`__ - Functionality to sanitize HTML
* __`angular-touch.js`__ - Touch events and other helpers for touch-enabled devices
* __`angular.js`__ — The core Angular framework. This is all you need to get your Angular app
running.
* __`angular-csp.css`__ — You only need this file if you are using
[CSP (Content Security Policy)](https://developer.mozilla.org/en/Security/CSP). See
{@link directive:ngCsp here} for more info.
* __`angular-mocks.js`__ This file contains an implementation of mocks that makes testing angular
apps even easier. Your unit/integration test harness should load this file after `angular.js` is
loaded.
* __`angular-loader.js`__ — Module loader for Angular modules. If you are loading multiple
script files containing Angular modules, you can load them asynchronously and in any order as long
as you load this file first. Often the contents of this file are copy&pasted into the `index.html`
to avoid even the initial request to `angular-loader[.min].js`.
See [angular-seed](https://github.com/angular/angular-seed/blob/master/app/index-async.html) for
an example of usage.
* __Additional Angular modules:__ Optional modules with additional functionality. These files
should be loaded after the core `angular[.min].js` file:
* __`angular-animate.js`__ — Enable animation support. ({@link module:ngAnimate API docs})
* __`angular-aria.js`__ — Make your apps [accessible](http://www.w3.org/TR/wai-aria/) to users of
assistive technologies. ({@link module:ngAria API docs})
* __`angular-cookies.js`__ — A convenient wrapper for reading and writing browser cookies.
({@link module:ngCookies API docs})
* __`angular-message-format.js`__ — Enhanced support for pluralization and gender specific
messages in interpolated text. ({@link module:ngMessageFormat API docs})
* __`angular-messages.js`__ — Enhanced support for displaying validation messages.
({@link module:ngMessages API docs})
* __`angular-parse-ext.js`__ — Allow Unicode characters in identifiers inside Angular expressions.
({@link module:ngParseExt API docs})
* __`angular-resource.js`__ — Easy interaction with RESTful services.
({@link module:ngResource API docs})
* __`angular-route.js`__ — Routing and deep-linking services and directives for Angular apps.
({@link module:ngRoute API docs})
* __`angular-sanitize.js`__ — Functionality to sanitize HTML. ({@link module:ngSanitize API docs})
* __`angular-touch.js`__ — Touch events and other helpers for touch-enabled devices.
({@link module:ngTouch API docs})
* __`docs`__ — this directory contains all the files that compose the
<http://docs.angularjs.org/> documentation app. These files are handy to see the older version of
our docs, or even more importantly, view the docs offline.
* __`docs/`__ — This directory contains all the files that compose the https://docs.angularjs.org/
documentation app. These files are handy to see the older versions of our docs, or even more
importantly, view the docs offline.
* __`i18n`__ - this directory contains locale specific `ngLocale` angular modules to override the defaults
defined in the `ng` module.
* __`i18n/`__ - This directory contains [locale specific](https://docs.angularjs.org/guide/i18n)
`ngLocale` Angular modules to override the defaults defined in the main `ng` module.
+4 -4
View File
@@ -16,9 +16,9 @@ becoming an Angular expert.
starter app with a directory layout, test harness, and scripts to begin building your application.
#Further Steps
# Further Steps
##Watch Videos
## Watch Videos
If you havent had a chance to watch the videos from the homepage, please check out:
@@ -29,13 +29,13 @@ If you havent had a chance to watch the videos from the homepage, please chec
And visit our [YouTube channel](http://www.youtube.com/user/angularjs) for more AngularJS video presentations and
tutorials.
##Subscribe
## Subscribe
* Subscribe to the [mailing list](http://groups.google.com/forum/?fromgroups#!forum/angular). Ask questions here!
* Follow us on [Twitter](https://twitter.com/intent/follow?original_referer=http%3A%2F%2Fangularjs.org%2F&region=follow_link&screen_name=angularjs&source=followbutton&variant=2.0)
* Add us to your circles on [Google+](https://plus.google.com/110323587230527980117/posts)
##Read more
## Read more
The AngularJS documentation includes the {@link guide/index Developer Guide} covering concepts and the
{@link ./api API Reference} for syntax and usage.
+141 -96
View File
@@ -6,12 +6,12 @@
# PhoneCat Tutorial App
A great way to get introduced to AngularJS is to work through this tutorial, which walks you through
the construction of an AngularJS web app. The app you will build is a catalog that displays a list
the construction of an Angular web app. The app you will build is a catalog that displays a list
of Android devices, lets you filter the list to see only devices that interest you, and then view
details for any device.
<img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413" alt="demo
application running in the browser">
<img class="diagram" src="img/tutorial/catalog_screen.png" width="488" height="413"
alt="demo application running in the browser">
Follow the tutorial to see how Angular makes browsers smarter — without the use of native
extensions or plug-ins:
@@ -28,10 +28,11 @@ When you finish the tutorial you will be able to:
* Create a dynamic application that works in all modern browsers.
* 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.
* Create and run end-to-end tests, with Protractor.
* Move application logic out of the template and into components and controllers.
* Get data from a server using Angular services.
* Apply animations to your application, using ngAnimate.
* Apply animations to your application, using the `ngAnimate` module.
* Structure your Angular applications in a modular way that scales well for larger projects.
* Identify resources for learning more about AngularJS.
The tutorial guides you through the entire process of building a simple application, including
@@ -42,16 +43,18 @@ 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
# Environment Setup
The rest of this page explains how you can set up your local machine for development.
If you just want to read the tutorial then you can just go straight to the first step:
If you just want to _read_ the tutorial, you can go straight to the first step:
[Step 0 - Bootstrapping](tutorial/step_00).
# Working with the code
## Working with the Code
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
This way, you can get hands-on practice of really writing Angular code and also on using the
recommended testing tools.
The tutorial relies on the use of the [Git][git] versioning system for source code management.
@@ -62,10 +65,11 @@ a few git commands.
### Install 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:
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.
- `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
@@ -73,13 +77,14 @@ Clone the [angular-phonecat repository][angular-phonecat] located at GitHub by r
command:
```
git clone --depth=14 https://github.com/angular/angular-phonecat.git
git clone --depth=16 https://github.com/angular/angular-phonecat.git
```
This command creates the `angular-phonecat` directory in your current directory.
This command creates an `angular-phonecat` sub-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 class="alert alert-info">
The `--depth=16` option tells Git to pull down only the last 16 commits.
This makes the download much smaller and faster.
</div>
Change your current directory to `angular-phonecat`.
@@ -88,16 +93,16 @@ Change your current directory to `angular-phonecat`.
cd angular-phonecat
```
The tutorial instructions, from now on, assume you are running all commands from the
The tutorial instructions, from now on, assume you are running all commands from within the
`angular-phonecat` directory.
### Install Node.js
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].
If you want to run the preconfigured local web server and the test tools then you will also need
[Node.js v4+][node].
You can download a Node.js installer for your operating system from http://nodejs.org/download/.
You can download a Node.js installer for your operating system from https://nodejs.org/en/download/.
Check the version of Node.js that you have installed by running the following command:
@@ -105,7 +110,7 @@ Check the version of Node.js that you have installed by running the following co
node --version
```
In Debian based distributions, there is a name clash with another utility called `node`. The
In Debian based distributions, there might be 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`.
@@ -115,12 +120,9 @@ nodejs --version
npm --version
```
<div class="alert alert-info">If you need to run 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 class="alert alert-info">
If you need to run different versions of Node.js in your local environment, consider installing
[Node Version Manager (nvm)][nvm] or [Node Version Manager (nvm) for Windows][nvm-windows].
</div>
Once you have Node.js installed on your machine, you can download the tool dependencies by running:
@@ -129,30 +131,32 @@ Once you have Node.js installed on your machine, you can download the tool depen
npm install
```
This command reads angular-phonecat's `package.json` file and downloads the following tools
into the `node_modules` directory:
This command reads angular-phonecat's `package.json` file and downloads the following tools into the
`node_modules` directory:
- [Bower][bower] - client-side code package manager
- [Http-Server][http-server] - simple local static web server
- [Karma][karma] - unit test runner
- [Protractor][protractor] - end to end (E2E) test runner
* [Bower][bower] - client-side code package manager
* [Http-Server][http-server] - simple local static web server
* [Karma][karma] - unit test runner
* [Protractor][protractor] - end-to-end (E2E) test runner
Running `npm install` will also automatically use bower to download the Angular framework into the
Running `npm install` will also automatically use bower to download the AngularJS 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.
to follow the tutorial. See [Installing Helper Tools](tutorial/#install-helper-tools-optional-)
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:
- `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
* `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.
### Install Helper Tools (optional)
@@ -167,7 +171,7 @@ For instance, to install the Bower command line executable you would do:
sudo npm install -g bower
```
*(Omit the sudo if running on Windows)*
_(Omit the sudo if running on Windows)_
Then you can run the bower tool directly, such as:
@@ -176,10 +180,10 @@ bower install
```
### Running Development Web Server
### Running the 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
browser directly from the file system, it is better to serve them from an 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.
@@ -190,70 +194,64 @@ application during development. Start the web server by running:
npm start
```
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
```
This will create a local web server that is listening to port 8000 on your local machine.
You can now browse to the application at http://localhost:8000/index.html.
<div class="alert alert-info">
To serve the web app on a different IP address or port, edit the "start" script within package.json.
You can use `-a` to set the address and `-p` to set the port.
To serve the web app on a different IP address or port, edit the "start" script within
`package.json`. You can use `-a` to set the address and `-p` to set the port. You also need to
update the `baseUrl` configuration property in `e2e-test/protractor.conf.js`.
</div>
### Running Unit Tests
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.
Unit tests focus on testing small isolated parts of the application. The unit tests are kept in test
files (specs) side-by-side with the application code. This way it's easier to find them and keep
them up-to-date with the code under test. It also makes refactoring our app structure easier, since
tests are moved together with the source code.
The angular-phonecat project is configured to use [Karma][karma] to run the unit tests for the
application. Start Karma by running:
application. Start Karma by running:
```
npm test
```
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:
This will start the Karma unit test runner. Karma will read the configuration file `karma.conf.js`,
located at the root of the project directory. This configuration file tells Karma to:
- 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
* Open up instances of the Chrome and Firefox browsers and connect them to Karma.
* Execute all the unit tests in these browsers.
* 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 E2E Tests
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.
We use E2E (end-to-end) tests to ensure that the application as a whole operates as expected.
E2E 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 E2E tests are kept in the `e2e-tests` 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:
The angular-phonecat project is configured to use [Protractor][protractor] to run the E2E 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 should only need to do this once.)*
You will need to have Java present on your dev machine to allow the Selenium standalone to be started.
Check if you already have java installed by opening a terminal/command line window and typing
'''
java -version
'''
If java is already installed and exists in the PATH then you will be shown the version installed,
if, however you receive a message that "java is not recognized as an internal command or external
command" you will need to install [java].
<div class="alert alert-info">
You don't have to manually run this command. Our npm scripts are configured so that it will be
automatically executed as part of the command that runs the E2E tests.
</div>
Since Protractor works by interacting with a running application, we need to start our web server:
@@ -261,32 +259,79 @@ Since Protractor works by interacting with a running application, we need to sta
npm start
```
Then in a separate terminal/command line window, we can run the Protractor test scripts against the
application by running:
Then, in a _separate_ terminal/command line window, we can run the Protractor test scripts against
the application by running:
```
npm run protractor
```
Protractor will read the configuration file at `test/protractor-conf.js`. This configuration tells
Protractor to:
Protractor will read the configuration file at `e2e-tests/protractor.conf.js`. This configuration
file 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
* Open up a Chrome browser and connect it to the application.
* Execute all the E2E tests in this browser.
* Report the results of these tests in the terminal/command line window.
* Close 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. It is very common to run End to End tests
before pushing a new commit of changes to a remote repository.
It is good to run the E2E tests whenever you make changes to the HTML views or want to check that
the application as a whole is executing correctly. It is very common to run E2E tests before pushing
a new commit of changes to a remote repository.
### Common Issues
<br />
**Firewall / Proxy issues**
Git and other tools, often use the `git:` protocol for accessing files in remote repositories.
Some firewall configurations are blocking `git://` URLs, which leads to errors when trying to clone
repositories or download dependencies. (For example corporate firewalls are "notorious" for blocking
`git:`.)
If you run into this issue, you can force the use of `https:` instead, by running the following
command: `git config --global url."https://".insteadOf git://`
<br />
**Updating WebDriver takes too long**
Running `update-webdriver` for the first time may take from several seconds up to a few minutes
(depending on your hardware and network connection). If you cancel the operation (e.g. using
`Ctrl+C`), you might get errors, when trying to run Protractor later.
In that case, you can delete the `node_modules/` directory and run `npm install` again.
<br />
**Protractor dependencies**
Under the hood, Protractor uses the [Selenium Stadalone Server][selenium], which in turn requires
the [Java Development Kit (JDK)][jdk] to be installed on your local machine. Check this by running
`java -version` from the command line.
If JDK is not already installed, you can download it [here][jdk-download].
<br />
**Error running the web server**
The web server is configured to use port 8000. If the port is already in use (for example by another
instance of a running web server) you will get an `EADDRINUSE` error. Make sure the port is
available, before running `npm start`.
<hr />
Now that you have set up your local machine, let's get started with the tutorial:
{@link step_00 Step 0 - Bootstrapping}
Now that you have set up your local machine, let's get started with the tutorial: {@link step_00 Step 0 - Bootstrapping}
[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/
[git]: http://git-scm.com/
[http-server]: https://github.com/nodeapps/http-server
[karma]: https://github.com/karma-runner/karma
[java]: https://www.java.com/en/download/help/download_options.xml
[jdk]: https://en.wikipedia.org/wiki/Java_Development_Kit
[jdk-download]: http://www.oracle.com/technetwork/java/javase/downloads/index.html
[karma]: https://karma-runner.github.io/
[node]: http://nodejs.org/
[nvm]: https://github.com/creationix/nvm
[nvm-windows]: https://github.com/coreybutler/nvm-windows
[protractor]: https://github.com/angular/protractor
[selenium]: http://docs.seleniumhq.org/
+87 -72
View File
@@ -7,11 +7,12 @@
In this step of the tutorial, you will become familiar with the most important source code files of
the AngularJS phonecat app. You will also learn how to start the development servers bundled with
angular-seed, and run the application in the browser.
the AngularJS Phonecat App. You will also learn how to start the development servers bundled with
[angular-seed][angular-seed], and run the application in the browser.
Before you continue, make sure you have set up your development environment and installed all necessary
dependencies, as described in {@link index#get-started Get Started}.
Before you continue, make sure you have set up your development environment and installed all
necessary dependencies, as described in the {@link tutorial/#environment-setup Environment Setup}
section.
In the `angular-phonecat` directory, run this command:
@@ -19,118 +20,130 @@ In the `angular-phonecat` directory, run this command:
git checkout -f step-0
```
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:
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/" target="_blank" title="Open app on localhost">`http://localhost:8000/app/`</a>
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
http://localhost:8000/index.html.
Note that if you already ran the master branch app prior to checking out step-0, you may see the cached
master version of the app in your browser window at this point. Just hit refresh to re-load the page.
Note that if you already ran the master branch app prior to checking out step-0, you may see the
cached master version of the app in your browser window at this point. Just hit refresh to re-load
the page.
You can now see the page in your browser. It's not very exciting, but that's OK.
The HTML page that displays "Nothing here yet!" was constructed with the HTML code shown below.
The code contains some key Angular elements that we will need as we progress.
__`app/index.html`:__
**`app/index.html`:**
```html
<!doctype html>
<html lang="en" ng-app>
<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">
<script src="bower_components/angular/angular.js"></script>
</head>
<body>
<head>
<meta charset="utf-8">
<title>My HTML File</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<script src="bower_components/angular/angular.js"></script>
</head>
<body>
<p>Nothing here {{'yet' + '!'}}</p>
<p>Nothing here {{'yet' + '!'}}</p>
</body>
</body>
</html>
```
## What is the code doing?
**`ng-app` directive:**
<br />
**`ng-app` attribute:**
<html ng-app>
```html
<html ng-app>
```
The `ng-app` attribute represents an Angular directive named `ngApp` (Angular uses
`spinal-case` for its custom attributes and `camelCase` for the corresponding directives
which implement them).
This directive is used to flag the html element that Angular should consider to be the root element
of our application.
This gives application developers the freedom to tell Angular if the entire html page or only a
portion of it should be treated as the Angular application.
The `ng-app` attribute represents an Angular directive, named `ngApp` (Angular uses `kebab-case` for
its custom attributes and `camelCase` for the corresponding directives which implement them). This
directive is used to flag the HTML element that Angular should consider to be the root element of
our application. This gives application developers the freedom to tell Angular if the entire HTML
page or only a portion of it should be treated as the AngularJS application.
**AngularJS script tag:**
For more info on `ngApp`, check out the {@link ngApp API Reference}.
<script src="bower_components/angular/angular.js">
<br />
**`angular.js` script tag:**
This code downloads the `angular.js` script which registers a callback that will be executed by the
```html
<script src="bower_components/angular/angular.js"></script>
```
This code downloads the `angular.js` script which registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
looks for the {@link ng.directive:ngApp ngApp} directive. If
Angular finds the directive, it will bootstrap the application with the root of the application DOM
being the element on which the `ngApp` directive was defined.
looks for the {@link ngApp ngApp} directive. If Angular finds the directive, it will bootstrap the
application with the root of the application DOM being the element on which the `ngApp` directive
was defined.
For more info on bootstrapping your app, checkout the [Bootstrap](guide/bootstrap) section of the
Developer Guide.
<br />
**Double-curly binding with an expression:**
Nothing here {{'yet' + '!'}}
```html
Nothing here {{'yet' + '!'}}
```
This line demonstrates two core features of Angular's templating capabilities:
* a binding, denoted by double-curlies `{{ }}`
* a simple expression `'yet' + '!'` used in this binding.
* A binding, denoted by double-curlies: `{{ }}`
* A simple expression used in this binding: `'yet' + '!'`
The binding tells Angular that it should evaluate an expression and insert the result into the
DOM in place of the binding. Rather than a one-time insert, as we'll see in the next steps, a
binding will result in efficient continuous updates whenever the result of the expression
evaluation changes.
The binding tells Angular that it should evaluate an expression and insert the result into the DOM
in place of the binding. As we will see in the next steps, rather than a one-time insert, a binding
will result in efficient continuous updates whenever the result of the expression evaluation
changes.
{@link guide/expression Angular expression} is a JavaScript-like code snippet that is
evaluated by Angular in the context of the current model scope, rather than within the scope of
the global context (`window`).
{@link guide/expression Angular expressions} are JavaScript-like code snippets that are evaluated by
Angular in the context of the current model scope, rather than within the scope of the global
context (`window`).
As expected, once this template is processed by Angular, the html page contains the text:
"Nothing here yet!".
As expected, once this template is processed by Angular, the HTML page contains the text:
## Bootstrapping AngularJS apps
```
Nothing here yet!
```
Bootstrapping AngularJS apps automatically using the `ngApp` directive is very easy and suitable
for most cases. In advanced cases, such as when using script loaders, you can use the
{@link guide/bootstrap imperative / manual way} to bootstrap the app.
## Bootstrapping Angular Applications
There are 3 important things that happen during the app bootstrap:
Bootstrapping Angular applications automatically using the `ngApp` directive is very easy and
suitable for most cases. In advanced cases, such as when using script loaders, you can use the
{@link guide/bootstrap#manual-initialization imperative/manual way} to bootstrap the application.
There are 3 important things that happen during the bootstrap phase:
1. The {@link auto.$injector injector} that will be used for dependency injection is created.
2. The injector will then create the {@link ng.$rootScope root scope} that will
become the context for the model of our application.
2. The injector will then create the {@link ng.$rootScope root scope} that will become the context
for the model of our application.
3. Angular will then "compile" the DOM starting at the `ngApp` root element, processing any
directives and bindings found along the way.
Once an application is bootstrapped, it will then wait for incoming browser events (such as mouse
click, key press or incoming HTTP response) that might change the model. Once such an event occurs,
Angular detects if it caused any model changes and if changes are found, Angular will reflect them
in the view by updating all of the affected bindings.
clicks, key presses or incoming HTTP responses) that might change the model. Once such an event
occurs, Angular detects if it caused any model changes and if changes are found, Angular will
reflect them in the view by updating all of the affected bindings.
The structure of our application is currently very simple. The template contains just one directive
and one static binding, and our model is empty. That will soon change!
@@ -140,27 +153,29 @@ 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][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`).
Most of the files in your working directory come from the [angular-seed project][angular-seed],
which is typically used to bootstrap new AngularJS projects. The seed project is pre-configured to
install the AngularJS framework (via `bower` into the `app/bower_components/` directory) and tools
for developing and testing a typical web application (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/`
* Removed the example app.
* Removed unused dependencies.
* Added phone images to `app/img/phones/`.
* Added phone data files (JSON) to `app/phones/`.
* Added a dependency on [Bootstrap](http://getbootstrap.com) in the `bower.json` file.
# Experiments
* Try adding a new expression to the `index.html` that will do some math:
<div></div>
<p>1 + 2 = {{ 1 + 2 }}</p>
* Try adding a new expression to `index.html` that will do some math:
```html
<p>1 + 2 = {{1 + 2}}</p>
```
# Summary
+9 -5
View File
@@ -12,11 +12,11 @@ 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.
* The page now contains a list with information about two phones.
<div doc-tutorial-reset="1"></div>
<br />
**`app/index.html`:**
```html
@@ -39,15 +39,19 @@ In this step you will add some basic information about two cell phones to an HTM
# Experiments
<div></div>
* Try adding more static HTML to `index.html`. For example:
<p>Total number of phones: 2</p>
```html
<p>Total number of phones: 2</p>
```
# Summary
This addition to your app uses static HTML to display the list. Now, let's go to {@link step_02
step 2} to learn how to use AngularJS to dynamically generate the same list.
This addition to your app uses static HTML to display the list. Now, let's go to
{@link step_02 step 2} to learn how to use Angular to dynamically generate the same list.
<ul doc-tutorial-nav="1"></ul>
+171 -114
View File
@@ -6,37 +6,38 @@
<ul doc-tutorial-nav="2"></ul>
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.
Now, it's time to make the web page dynamic — with AngularJS. We will 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 applications, we encourage
the use of the [Model-View-Controller (MVC) design pattern][mvc-pattern] to decouple the code and
separate concerns. With that in mind, let's use a little Angular and JavaScript to add models,
views, and controllers to our app.
- The list of three phones is now generated dynamically from data
* The list of three phones is now generated dynamically from data
<div doc-tutorial-reset="2"></div>
## View and Template
In Angular, the __view__ is a projection of the model through the HTML __template__. This means that
In Angular, the **view** is a projection of the model through the HTML **template**. This means that
whenever the model changes, Angular refreshes the appropriate binding points, which updates the
view.
The view component is constructed by Angular from this template:
The view is constructed by Angular from this template.
__`app/index.html`:__
<br />
**`app/index.html`:**
```html
<html ng-app="phonecatApp">
<head>
...
<script src="bower_components/angular/angular.js"></script>
<script src="js/controllers.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="PhoneListCtrl">
<body ng-controller="PhoneListController">
<ul>
<li ng-repeat="phone in phones">
@@ -49,97 +50,117 @@ __`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}:
We replaced the hard-coded phone list with the {@link ngRepeat ngRepeat} directive and two
{@link guide/expression Angular expressions}:
* 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.
* The `ng-repeat="phone in phones"` attribute on 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 values of the expressions.
We have added a new directive, called `ng-controller`, which attaches a `PhoneListCtrl`
__controller__ to the &lt;body&gt; tag. At this point:
We have also added a new directive, called {@link ngController ngController}, which attaches a
`PhoneListController` **controller** to the `<body>` tag. At this point:
* 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.
* `PhoneListController` is in charge of the DOM sub-tree under (and including) the `<body>` element.
* 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 `PhoneListController` controller.
<div class="alert alert-info">
Note: We have specified an {@link angular.Module Angular Module} to load using `ng-app="phonecatApp"`,
where `phonecatApp` is the name of our module. This module will contain the `PhoneListCtrl`.
Note: We have specified an {@link angular.Module Angular Module} to load using
`ng-app="phonecatApp"`, where `phonecatApp` is the name of our module. This module will contain
the `PhoneListController`.
</div>
<img class="diagram" src="img/tutorial/tutorial_02.png">
## Model and Controller
The data __model__ (a simple array of phones in object literal notation) is now instantiated within
the `PhoneListCtrl` __controller__. The __controller__ is simply a constructor function that takes a
`$scope` parameter:
The data **model** (a simple array of phones in object literal notation) is now instantiated within
the `PhoneListController` **controller**. The **controller** is simply a constructor function that
takes a `$scope` parameter:
__`app/js/controllers.js`:__
<br />
**`app/app.js`:**
```js
// Define the `phonecatApp` module
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope) {
// Define the `PhoneListController` controller on the `phonecatApp` module
phonecatApp.controller('PhoneListController', function PhoneListController($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.'},
{'name': 'Motorola XOOM™ with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.'},
{'name': 'MOTOROLA XOOM™',
'snippet': 'The Next, Next Generation tablet.'}
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.'
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.'
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.'
}
];
});
```
Here we declared a controller called `PhoneListCtrl` and registered it in an AngularJS
module, `phonecatApp`. Notice that our `ng-app` directive (on the `<html>` tag) now specifies the `phonecatApp`
module name as the module to load when bootstrapping the Angular application.
Here we declared a controller called `PhoneListController` and registered it in an Angular module,
`phonecatApp`. Notice that our `ngApp` directive (on the `<html>` tag) now specifies the
`phonecatApp` module name as the module to load when bootstrapping the application.
Although the controller is not yet doing very much, it plays a crucial role. By providing context
for our data model, the controller allows us to establish data-binding between
the model and the view. We connected the dots between the presentation, data, and logic components
as follows:
for our data model, the controller allows us to establish data-binding between the model and the
view. We connected the dots between the presentation, data, and logic components as follows:
* The {@link ng.directive:ngController ngController} directive, located on the `<body>` tag,
references the name of our controller, `PhoneListCtrl` (located in the JavaScript file
`controllers.js`).
* The {@link ngController ngController} directive, located on the `<body>` tag, references the name
of our controller, `PhoneListController` (located in the JavaScript file `app.js`).
* The `PhoneListController` controller attaches the phone data to the `$scope` that was injected
into our controller function. This _scope_ is a prototypal descendant of the _root scope_ that was
created when the application was defined. This controller scope is available to all bindings
located within the `<body ng-controller="PhoneListController">` tag.
* The `PhoneListCtrl` controller attaches the phone data to the `$scope` that was injected into our
controller function. This *scope* is a prototypical descendant of the *root scope* that was created
when the application was defined. This controller scope is available to all bindings located within
the `<body ng-controller="PhoneListCtrl">` tag.
### Scope
The concept of a scope in Angular is crucial. A scope can be seen as the glue which allows the
template, model and controller to work together. Angular uses scopes, along with the information
template, model, and controller to work together. Angular uses scopes, along with the information
contained in the template, data model, and controller, to keep models and views separate, but in
sync. Any changes made to the model are reflected in the view; any changes that occur in the view
are reflected in the model.
To learn more about Angular scopes, see the {@link ng.$rootScope.Scope angular scope documentation}.
<img class="diagram" src="img/tutorial/tutorial_02.png">
## Tests
<div class="alert alert-warning">
<p>
Angular scopes prototypally inherit from their parent scope, all the way up to the *root scope*
of the application. As a result, assigning values directly on the scope makes it easy to share
data across different parts of the page and create interactive applications.
While this approach works for prototypes and smaller applications, it quickly leads to tight
coupling and difficulty to reason about changes in our data model.
</p>
<p>
In the next step, we will learn how to better organize our code, by "packaging" related pieces
of application and presentation logic into isolated, reusable entities, called components.
</p>
</div>
# Testing
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 could simply instantiate it
with a mock `scope` object:
__`test/e2e/scenarios.js`:__
developed. If our controller were available on the global namespace, we could simply instantiate it
with a mock scope object:
<br />
```js
describe('PhoneListCtrl', function(){
describe('PhoneListController', function() {
it('should create "phones" model with 3 phones', function() {
var scope = {},
ctrl = new PhoneListCtrl(scope);
it('should create a `phones` model with 3 phones', function() {
var scope = {};
var ctrl = new PhoneListController(scope);
expect(scope.phones.length).toBe(3);
});
@@ -147,30 +168,31 @@ describe('PhoneListCtrl', function(){
});
```
The test instantiates `PhoneListCtrl` and verifies that the phones array property on the scope
contains three records. This example demonstrates how easy it is to create a unit test for code in
Angular. Since testing is such a critical part of software development, we make it easy to create
tests in Angular so that developers are encouraged to write them.
The test instantiates `PhoneListController` and verifies that the phones array property on the
scope contains three records. This example demonstrates how easy it is to create a unit test for
code in Angular. Since testing is such a critical part of software development, we make it easy to
create 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,
you can see that we have registered it via an anonymous constructor function on the `phonecatApp`
module.
## Testing non-global Controllers
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 a 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`:__
<br />
**`app/app.spec.js`:**
```js
describe('PhoneListCtrl', function(){
describe('PhoneListController', function() {
beforeEach(module('phonecatApp'));
it('should create "phones" model with 3 phones', inject(function($controller) {
var scope = {},
ctrl = $controller('PhoneListCtrl', {$scope:scope});
it('should create a `phones` model with 3 phones', inject(function($controller) {
var scope = {};
var ctrl = $controller('PhoneListController', {$scope: scope});
expect(scope.phones.length).toBe(3);
}));
@@ -179,29 +201,46 @@ describe('PhoneListCtrl', function(){
```
* 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`
* We ask Angular to `inject` the `$controller` service into our test function.
* We use `$controller` to create an instance of the `PhoneListController`.
* With this instance, we verify that the phones array property on the scope contains three records.
<div class="alert alert-info">
<p>**A note on file naming:**</p>
<p>
As already mentioned in the [introduction](tutorial/#running-unit-tests), the unit test files
(specs) are kept side-by-side with the application code. We name our specs after the file
containing the code to be tested plus a specific suffix to distinguish them from files
containing application code. Note that test files are still plain JavaScript files, so they have
a `.js` file extension.
</p>
<p>
In this tutorial, we are using the `.spec` suffix. So the test file corresponding to
`something.js` would be called `something.spec.js`.
(Another common convention is to use a `_spec` or `_test` suffix.)
</p>
</div>
### 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 v1.3. You can learn about Jasmine on the [Jasmine home page][jasmine] and
at the [Jasmine docs][jasmine-docs].
## Writing and Running Tests
The angular-seed project is pre-configured to run unit tests using [Karma][karma] but you will need
Many Angular developers prefer the syntax of
[Jasmine's Behavior-Driven Development (BDD) framework][jasmine-home], when writing tests. Although
Angular does not require you to use Jasmine, we wrote all of the tests in this tutorial in Jasmine
v2.4. You can learn about Jasmine on the [Jasmine home page][jasmine-home] and at the
[Jasmine docs][jasmine-docs].
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 tests, and then watch the files for changes execute: `npm test`
* Karma will start new instances of Chrome and Firefox browsers automatically. Just ignore them and
let them run in the background. Karma will use these browsers for test execution.
* If you only have one of the browsers installed on your machine (either Chrome or Firefox), make
sure to update the karma configuration file before running the test. Locate the configuration file
in `test/karma.conf.js`, then update the `browsers` property.
sure to update the karma configuration file (`karma.conf.js`), before running the test. Locate the
configuration file in the root directory and update the `browsers` property.
E.g. if you only have Chrome installed:
<pre>
@@ -213,23 +252,27 @@ To run the tests, and then watch the files for changes: `npm test`.
* You should see the following or similar output in the terminal:
<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)
INFO [karma]: Karma server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 49.0]: Connected on socket ... with id ...
Chrome 49.0: Executed 1 of 1 SUCCESS (0.05 secs / 0.04 secs)
</pre>
Yay! The test passed! Or not...
* 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?
<div class="alert alert-info">
Make sure you don't minimize the browser that Karma opened. On some OS, memory assigned to a minimized
browser is limited, which results in your karma tests running extremely slow.
Make sure you don't minimize the browser that Karma opened. On some OS, memory assigned to a
minimized browser is limited, which results in your karma tests running extremely slow.
</div>
# Experiments
<div></div>
* Add another binding to `index.html`. For example:
```html
@@ -238,46 +281,60 @@ browser is limited, which results in your karma tests running extremely slow.
* Create a new model property in the controller and bind to it from the template. For example:
$scope.name = "World";
```js
$scope.name = 'world';
```
Then add a new binding to `index.html`:
<p>Hello, {{name}}!</p>
```html
<p>Hello, {{name}}!</p>
```
Refresh your browser and verify that it says "Hello, World!".
Refresh your browser and verify that it says 'Hello, world!'.
* Update the unit test for the controller in `./test/unit/controllersSpec.js` to reflect the previous change. For example by adding:
* Update the unit test for the controller in `app/app.spec.js` to reflect the previous change.
For example by adding:
expect(scope.name).toBe('World');
```js
expect(scope.name).toBe('world');
```
* Create a repeater in `index.html` that constructs a simple table:
<table>
<tr><th>row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
</table>
```html
<table>
<tr><th>Row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i}}</td></tr>
</table>
```
Now, make the list 1-based by incrementing `i` by one in the binding:
<table>
<tr><th>row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>
```html
<table>
<tr><th>Row number</th></tr>
<tr ng-repeat="i in [0, 1, 2, 3, 4, 5, 6, 7]"><td>{{i+1}}</td></tr>
</table>
```
Extra points: try and make an 8x8 table using an additional `ng-repeat`.
Extra points: Try and make an 8x8 table using an additional `ng-repeat`.
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use `toBe(4)`.
* Make the unit test fail by changing `expect(scope.phones.length).toBe(3)` to instead use
`toBe(4)`.
# Summary
You now have a dynamic app that features separate model, view, and controller components, and you
are testing as you go. Now, let's go to {@link step_03 step 3} to learn how to add full text search
to the app.
We now have a dynamic application which separates models, views, and controllers, and we are testing
as we go. Let's go to {@link step_03 step 3} to learn how to improve our application's architecture,
by utilizing components.
<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/
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
[jasmine-home]: http://jasmine.github.io/
[karma]: https://karma-runner.github.io/
[mvc-pattern]: http://en.wikipedia.org/wiki/ModelViewController
+230 -171
View File
@@ -1,223 +1,282 @@
@ngdoc tutorial
@name 3 - Filtering Repeaters
@name 3 - Components
@step 3
@description
<ul doc-tutorial-nav="3"></ul>
We did a lot of work in laying a foundation for the app in the last step, so now we'll do something
simple; we will add full text search (yes, it will be simple!). We will also write an end-to-end
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.
In the previous step, we saw how a controller and a template worked together to convert a static
HTML page into a dynamic view. This is a very common pattern in Single-Page Applications in general
(and Angular applications in particular):
* Instead of creating a static HTML page on the server, the client-side code "takes over" and
interacts dynamically with the view, updating it instantly to reflect changes in model data or
state, usually as a result of user interaction (we'll see an example shortly in
{@link step_05 step 5}).
The **template** (the part of the view containing the bindings and presentation logic) acts as a
blueprint for how our data should be organized and presented to the user.
The **controller** provides the context in which the bindings are evaluated and applies behavior
and logic to our template.
There are still a couple of areas we can do better:
1. What if we want to reuse the same functionality in a different part of our application ?<br />
We would need to duplicate the whole template (including the controller). This is error-prone and
hurts maintainability.
2. The scope, that glues our controller and template together into a dynamic view, is not isolated
from other parts of the page. What this means is that a random, unrelated change in a different
part of the page (e.g. a property-name conflict) could have unexpected and hard-to-debug side
effects on our view.
(OK, this might not be a real concern in our minimal example, but it **is** a valid concern for
bigger, real-world applications.)
* 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.
<div doc-tutorial-reset="3"></div>
## Controller
## Components to the rescue!
We made no changes to the controller.
Since this combination (template + controller) is such a common and recurring pattern, Angular
provides an easy and concise way to combine them together into reusable and isolated entities,
known as _components_.
Additionally, Angular will create a so called _isolate scope_ for each instance of our component,
which means no prototypal inheritance and no risk of our component affecting other parts of the
application or vice versa.
<div class="alert alert-info">
<p>
Since this is an introductory tutorial, we are not going to dive deep into all features provided
by Angular **components**. You can read more about components and their usage patterns in the
[Components](guide/component) section of the Developer Guide.
</p>
<p>
In fact, one could think of components as an opinionated and stripped-down version of their more
complex and verbose (but powerful) siblings, **directives**, which are Angular's way of teaching
HTML new tricks. You can read all about them in the [Directives](guide/directive) section of the
Developer Guide.
</p>
<p>
(**Note:** Directives are an advanced topic, so you might want to postpone studying them, until
you have mastered the basics.)
</p>
</div>
## Template
To create a component, we use the {@link angular.Module#component .component()} method of an
{@link module Angular module}. We must provide the name of the component and the Component
Definition Object (CDO for short).
__`app/index.html`:__
Remember that (since components are also directives) the name of the component is in `camelCase`,
but we will use `kebab-case`, when referring to it in our HTML.
```html
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
In its simplest form, the CDO will just contain a template and a controller. (We can actually omit
the controller and Angular will create a dummy controller for us. This is useful for simple
"presentational" components, that don't attach any behavior to the template.)
Search: <input ng-model="query">
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
```
We added a standard HTML `<input>` tag and used Angular's
{@link ng.filter:filter filter} function to process the input for the
{@link ng.directive:ngRepeat ngRepeat} directive.
This lets a user enter search criteria and immediately see the effects of their search on the phone
list. This new code demonstrates the following:
* Data-binding: This is one of the core features in Angular. When the page loads, Angular binds the
name of the input box to a variable of the same name in the data model and keeps the two in sync.
In this code, the data that a user types into the input box (named __`query`__) is immediately
available as a filter input in the list repeater (`phone in phones | filter:`__`query`__). When
changes to the data model cause the repeater's input to change, the repeater efficiently updates
the DOM to reflect the current state of the model.
<img class="diagram" src="img/tutorial/tutorial_03.png">
* Use of the `filter` filter: The {@link ng.filter:filter filter} function uses the
`query` value to create a new array that contains only those records that match the `query`.
`ngRepeat` automatically updates the view in response to the changing number of phones returned
by the `filter` filter. The process is completely transparent to the developer.
## Test
In Step 2, we learned how to write and run unit tests. Unit tests are perfect for testing
controllers and other components of our application written in JavaScript, but they can't easily
test DOM manipulation or the wiring of our application. For these, an end-to-end test is a much
better choice.
The search feature was fully implemented via templates and data-binding, so we'll write our first
end-to-end test, to verify that the feature works.
__`test/e2e/scenarios.js`:__
Let's see an example:
```js
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
angular.
module('myApp').
component('greetUser', {
template: 'Hello, {{$ctrl.user}}!',
controller: function GreetUserController() {
this.user = 'world';
}
});
```
Now, every time we include `<greet-user></greet-user>` in our view, Angular will expand it into a
DOM sub-tree constructed using the provided `template` and managed by an instance of the specified
controller.
But wait, where did that `$ctrl` come from and what does it refer to ?
For reasons already mentioned (and for other reasons that are out of the scope of this tutorial), it
is considered a good practice to avoid using the scope directly. We can (and should) use our
controller instance; i.e. assign our data and methods on properties of our controller (the "`this`"
inside the controller constructor), instead of directly to the scope.
From the template, we can refer to our controller instance using an alias. This way, the context of
evaluation for our expressions is even more clear. By default, components use `$ctrl` as the
controller alias, but we can override it, should the need arise.
There are more options available, so make sure you check out the
{@link ng.$compileProvider#component API Reference}, before using `.component()` in your own
applications.
it('should filter the phone list as a user types into the search box', function() {
## Using Components
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
Now that we know how to create components, let's refactor the HTML page to make use of our newly
acquired skill.
expect(phoneList.count()).toBe(3);
<br />
**`app/index.html`:**
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
```html
<html ng-app="phonecatApp">
<head>
...
<script src="bower_components/angular/angular.js"></script>
<script src="app.js"></script>
<script src="phone-list.component.js"></script>
</head>
<body>
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
<!-- Use a custom component to render a list of phones -->
<phone-list></phone-list>
</body>
</html>
```
<br />
**`app/app.js`:**
```js
// Define the `phonecatApp` module
angular.module('phonecatApp', []);
```
<br />
**`app/phone-list.component.js`:**
```js
// Register `phoneList` component, along with its associated controller and template
angular.
module('phonecatApp').
component('phoneList', {
template:
'<ul>' +
'<li ng-repeat="phone in $ctrl.phones">' +
'<span>{{phone.name}}</span>' +
'<p>{{phone.snippet}}</p>' +
'</li>' +
'</ul>',
controller: function PhoneListController() {
this.phones = [
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.'
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.'
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.'
}
];
}
});
```
Voilà! The resulting output should look the same, but let's see what we have gained:
* Our phone list is reusable. Just drop `<phone-list></phone-list>` anywhere in the page to get a
list of phones.
* Our main view (`index.html`) is cleaner and more declarative. Just by looking at it, we know there
is a list of phones. We are not bothered with implementation details.
* Our component is isolated and safe from "external influences". Likewise, we don't have to worry,
that we might accidentally break something in some other part of the application. What happens
inside our component, stays inside our component.
* It's easier to test our component in isolation.
<img class="diagram" src="img/tutorial/tutorial_03.png">
<div class="alert alert-info">
<p>**A note on file naming:**</p>
<p>
It is a good practice to distinguish different types of entities by suffix. In this tutorial, we
are using the `.component` suffix for components, so the definition of a `someComponent`
component would be in a file named `some-component.component.js`.
</p>
</div>
# Testing
Although we have combined our controller with a template into a component, we still can (and should)
unit test the controller separately, since this is where our application logic and data reside.
In order to retrieve and instantiate a component's controller, Angular provides the
{@link ngMock.$componentController $componentController} service.
<div class="alert alert-info">
The `$controller` service that we used in the previous step can only instantiate controllers that
were registered by name, using the `.controller()` method. We could have registered our component
controller this way, too, if we wanted to. Instead, we chose to define it inline &mdash; inside
the CDO &mdash; to keep things localized, but either way works equally well.
</div>
<br />
**`app/phone-list.component.spec.js`:**
```js
describe('phoneList', function() {
// Load the module that contains the `phoneList` component before each test
beforeEach(module('phonecatApp'));
// Test the controller
describe('PhoneListController', function() {
it('should create a `phones` model with 3 phones', inject(function($componentController) {
var ctrl = $componentController('phoneList');
expect(ctrl.phones.length).toBe(3);
}));
});
});
```
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.
The test retrieves the controller associated with the `phoneList` component, instantiates it and
verifies that the phones array property on it contains three records. Note that the data is now on
the controller instance itself, not on a `scope`.
### 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 [Protractor](https://github.com/angular/protractor). Read
about the Protractor APIs at http://angular.github.io/protractor/#/api.
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.
## Running Tests
<div class="alert alert-info">
Note: You must ensure your application is being served via a web-server to test with protractor.
You can do this using `npm start`.
You also need to 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>
Same as before, execute `npm test` to run the tests and then watch the files for changes.
# Experiments
### 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.
<div></div>
### 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.
* Try the experiments from the previous step, this time on the `phoneList` component.
* Add an end-to-end test into the `describe` block, `test/e2e/scenarios.js` should look like this:
* Add a couple more phone lists on the page, by just adding more `<phone-list></phone-list>`
elements in `index.html`. Now add another binding to the `phoneList` component's template:
```js
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html');
});
var phoneList = element.all(by.repeater('phone in phones'));
var query = element(by.model('query'));
it('should filter the phone list as a user types into the search box', function() {
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
it('should display the current filter value in the title bar', function() {
query.clear();
expect(browser.getTitle()).toMatch(/Google Phone Gallery:\s*$/);
query.sendKeys('nexus');
expect(browser.getTitle()).toMatch(/Google Phone Gallery: nexus$/);
});
});
});
template:
'<p>Total number of phones: {{$ctrl.phones.length}}</p>' +
'<ul>' +
...
```
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:
<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:
<html ng-app="phonecatApp" ng-controller="PhoneListCtrl">
Be sure to __remove__ the `ng-controller` declaration from the body element.
* 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:
<title ng-bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
Reload the page and watch the new "feature" propagate to all phone lists. In real-world
applications, where the phone lists could appear on several different pages, being able to change
or add something in one place (e.g. a component's template) and have that change propagate
throughout the application, is a big win.
# Summary
We have now added full text search and included a test to verify that search works! Now let's go on
to {@link step_04 step 4} to learn how to add sorting capability to the phone app.
You have learned how to organize your application and presentation logic into isolated, reusable
components. Let's go to {@link step_04 step 4} to learn how to organize our code in directories and
files, so it remains easy to locate as our application grows.
<ul doc-tutorial-nav="3"></ul>
[jasmine-docs]: http://jasmine.github.io/2.4/introduction.html
[jasmine-home]: http://jasmine.github.io/
[karma]: https://karma-runner.github.io/
[mvc-pattern]: http://en.wikipedia.org/wiki/ModelViewController
+261 -145
View File
@@ -1,199 +1,315 @@
@ngdoc tutorial
@name 4 - Two-way Data Binding
@name 4 - Directory and File Organization
@step 4
@description
<ul doc-tutorial-nav="4"></ul>
In this step, you will add a feature to let your users control the order of the items in the phone
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 this step, we will not be adding any new functionality to our application. Instead, we are going
to take a step back, refactor our codebase and move files and code around, in order to make our
application more easily expandable and maintainable.
* 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.
In the previous step, we saw how to architect our application to be modular and testable. What's
equally important though, is organizing our codebase in a way that makes it easy (both for us and
other developers on our team) to navigate through the code and quickly locate the pieces that are
relevant to a specific feature or section of the application.
To that end, we will explain why and how we:
* Put each entity in its **own file**.
* Organize our code by **feature area**, instead of by function.
* Split our code into **modules** that other modules can depend on.
<div class="alert alert-info">
We will keep it short, not going into great detail on every good practice and convention. These
principles are explained in great detail in the [Angular Style Guide][styleguide], which also
contains many more techniques for effectively organizing Angular codebases.
</div>
<div doc-tutorial-reset="4"></div>
## Template
## One Feature per File
__`app/index.html`:__
It might be tempting, for the sake of simplicity, to put everything in one file, or have one file
per type; e.g. all controllers is one file, all components in another file, all services in a third
file, and so on.
This might seem to work well in the beginning, but as our application grows it becomes a burden to
maintain. As we add more and more features, our files will get bigger and bigger and it will be
difficult to navigate and find the code we are looking for.
```html
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
Instead we should put each feature/entity in its own file. Each stand-alone controller will be
defined in its own file, each component will be defined in each own file, etc.
Luckily, we don't need to change anything with respect to that guideline in our code, since we have
already defined our `phoneList` component in its own `phone-list.component.js` file. Good job!
We will keep this in mind though, as we add more features.
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
## Organizing by Feature
So, now that we learned we should put everything in its own file, our `app/` directory will soon be
full with dozens of files and specs (remember we keep our unit test files next to the corresponding
source code files). What's more important, logically related files will not be grouped together; it
will be really difficult of locate all files related to a specific section of the application and
make a change or fix a bug.
So, what shall we do?
Well, we are going to group our files into directories _by feature_. For example, since we have a
section in our application that lists phones, we will put all related files into a `phone-list/`
directory under `app/`. We are soon to find out that certain features are used across different
parts of the application. We will put those inside `app/core/`.
<div class="alert alert-info">
<p>
Other typical names for our `core` directory are `shared`, `common` and `components`. The last
one is kind of misleading though, as it will contain other things than components as well.
</p>
<p>
(This is mostly a relic of the past, when "components" just meant the generic building blocks of
an application.)
</p>
</div>
Based on what we have discussed so far, here is our directory/file layout for the `phoneList`
"feature":
```
app/
phone-list/
phone-list.component.js
phone-list.component.spec.js
app.js
```
We made the following changes to the `index.html` template:
* First, we added a `<select>` html element named `orderProp`, so that our users can pick from the
two provided sorting options.
## Using Modules
<img class="diagram" src="img/tutorial/tutorial_04.png">
As previously mentioned, one of the benefits of having a modular architecture is code reuse &mdash;
not only inside the same application, but across applications too. There is one final step in making
this code reuse frictionless:
* We then chained the `filter` filter with {@link ng.filter:orderBy `orderBy`}
filter to further process the input into the repeater. `orderBy` is a filter that takes an input
array, copies it and reorders the copy which is then returned.
* Each feature/section should declare its own module and all related entities should register
themselves on that module.
Angular creates a two way data-binding between the select element and the `orderProp` model.
`orderProp` is then used as the input for the `orderBy` filter.
As we discussed in the section about data-binding and the repeater in step 3, whenever the model
changes (for example because a user changes the order with the select drop down menu), Angular's
data-binding will cause the view to automatically update. No bloated DOM manipulation code is
necessary!
## Controller
__`app/js/controllers.js`:__
Let's take the `phoneList` feature as an example. Previously, the `phoneList` component would
register itself on the `phonecatApp` module:
```js
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', function ($scope) {
$scope.phones = [
{'name': 'Nexus S',
'snippet': 'Fast just got faster with Nexus S.',
'age': 1},
{'name': 'Motorola XOOM™ with Wi-Fi',
'snippet': 'The Next, Next Generation tablet.',
'age': 2},
{'name': 'MOTOROLA XOOM™',
'snippet': 'The Next, Next Generation tablet.',
'age': 3}
];
$scope.orderProp = 'age';
});
angular.
module('phonecatApp').
component('phoneList', ...);
```
* We modified the `phones` model - the array of phones - and added an `age` property to each phone
record. This property is used to order phones by age.
Similarly, the accompanying spec file loads the `phonecatApp` module before each test (because
that's where our component is registered). Now, imagine that we need a list of phones on another
project that we are working on. Thanks to our modular architecture, we don't have to reinvent the
wheel; we simply copy the `phone-list/` directory on our other project and add the necessary script
tags in our `index.html` file and we are done, right?
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
not set a default value here, the `orderBy` filter would remain uninitialized until our
user picked an option from the drop down menu.
Well, not so fast. The new project doesn't know anything about a `phonecatApp` module. So, we would
have to replace all references to `phonecatApp` with the name of this project's main module. As you
can imagine this is both laborious and error-prone.
This is a good time to talk about two-way data-binding. Notice that when the app is loaded in the
browser, "Newest" is selected in the drop down menu. This is because we set `orderProp` to `'age'`
in the controller. So the binding works in the direction from our model to the UI. Now if you
select "Alphabetically" in the drop down menu, the model will be updated as well and the phones
will be reordered. That is the data-binding doing its job in the opposite direction — from the UI
to the model.
Yeah, you guessed it: There is a better way!
Each feature/section, will declare its own module and have all related entities registered there.
The main module (`phonecatApp`) will declare a dependency on each feature/section module. Now,
all it takes to reuse the same code on a new project is copying the feature directory over and
adding the feature module as a dependency in the new project's main module.
Here is what our `phoneList` feature will look like after this change:
## Test
<br />
**`/`:**
The changes we made should be verified with both a unit test and an end-to-end test. Let's look at
the unit test first.
```
app/
phone-list/
phone-list.module.js
phone-list.component.js
phone-list.component.spec.js
app.module.js
```
__`test/unit/controllersSpec.js`:__
<br />
**`app/phone-list/phone-list.module.js`:**
```js
describe('PhoneCat controllers', function() {
// Define the `phoneList` module
angular.module('phoneList', []);
```
describe('PhoneListCtrl', function(){
var scope, ctrl;
<br />
**`app/phone-list/phone-list.component.js`:**
beforeEach(module('phonecatApp'));
```js
// Register the `phoneList` component on the `phoneList` module,
angular.
module('phoneList').
component('phoneList', {...});
```
beforeEach(inject(function($controller) {
scope = {};
ctrl = $controller('PhoneListCtrl', {$scope:scope});
}));
<br />
**`app/app.module.js`:**<br />
_(since `app/app.js` now only contains the main module declaration, we gave it a `.module` suffix)_
it('should create "phones" model with 3 phones', function() {
expect(scope.phones.length).toBe(3);
});
```js
// Define the `phonecatApp` module
angular.module('phonecatApp', [
// ...which depends on the `phoneList` module
'phoneList'
]);
```
By passing `phoneList` inside the dependencies array when defining the `phonecatApp` module, Angular
will make all entities registered on `phoneList` available on `phonecatApp` as well.
<div class="alert alert-info">
<p>
Don't forget to also update your `index.html` adding a `<script>` tag for each JavaScript file
we have created. This might seem tedious, but is totally worth it.
</p>
<p>
In a production-ready application, you would concatenate and minify all your JavaScript files
anyway (for performance reasons), so this won't be an issue any more.
</p>
</div>
<div class="alert alert-warning">
Note that files defining a module (i.e. `.module.js`) need to be included before other files that
add features (e.g. components, controllers, services, filters) to that module.
</div>
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
## External Templates
Since we are at refactoring, let's do one more thing. As we learned, components have templates,
which are basically fragments of HTML code that dictate how our data is laid out and presented to
the user. In {@link step_03 step 3}, we saw how we can specify the template for a component as a
string using the `template` property of the CDO (Component Definition Object).
Having HTML code in a string isn't ideal, especially for bigger templates. It would be much better,
if we could have our HTML code in `.html` files. This way, we would get all the support our
IDE/editor has to offer (e.g. HTML-specific color-highlighting and auto-completion) and also keep
our component definitions cleaner.
So, while it's perfectly fine to keep our component templates inline (using the `template` property
of the CDO), we are going to use an external template for our `phoneList` component. In order to
denote that we are using an external template, we use the `templateUrl` property and specify the URL
that our template will be loaded from. Since we want to keep our template close to where the
component is defined, we place it inside `app/phone-list/`.
We copied the contents of the `template` property (the HTML code) into
`app/phone-list/phone-list.template.html` and modified our CDO like this:
<br />
**`app/phone-list/phone-list.component.js`:**
```js
angular.
module('phoneList').
component('phoneList', {
// Note: The URL is relative to our `index.html` file
templateUrl: 'phone-list/phone-list.template.html',
controller: ...
});
```
At runtime, when Angular needs to create an instance of the `phoneList` component, it will make an
HTTP request to get the template from `app/phone-list/phone-list.template.html`.
<div class="alert alert-info">
Keeping inline with our convention, we will be using the `.template` suffix for external
templates. Another common convention is to just have the `.html` extension
(e.g. `phone-list.html`).
</div>
<div class="alert alert-warning">
<p>
Using an external template like this, will result in more HTTP requests to the server (one for
each external template). Although Angular takes care not to make extraneous requests (e.g.
fetching the templates lazily, caching the results, etc), additional requests do have a cost
(especially on mobile devices and data-plan connections).
</p>
<p>
Luckily, there are ways to avoid the extra costs (while still keeping your templates external).
A detailed discussion of the subject is outside the scope of this tutorial, but you can take a
look at the {@link ng.$templateRequest $templateRequest} and
{@link ng.$templateCache $templateCache} services for more info on how Angular manages external
templates.
</p>
</div>
## Final Directory/File Layout
After all the refactorings that took place, this is how our application looks from the outside:
<br />
**`/`:**
```
app/
phone-list/
phone-list.component.js
phone-list.component.spec.js
phone-list.module.js
phone-list.template.html
app.css
app.module.js
index.html
```
# Testing
Since this was just a refactoring step (no actual code addition/deletions), we shouldn't need to
change much (if anything) as far as our specs are concerned.
One thing that we can (and should) change is the name of the module to be loaded before each test in
`app/phone-list/phone-list.component.spec.js`. We don't need to pull in the whole `phonecatApp`
module (which will soon grow to depend on more stuff). All we want to test is already included in
the much smaller `phoneList` module, so it suffices to just load that.
This is one extra benefit that we get out of our modular architecture for free.
<br />
**`app/phone-list/phone-list.component.spec.js`:**
```js
describe('phoneList', function() {
// Load the module that contains the `phoneList` component before each test
beforeEach(module('phoneList'));
...
});
```
If not already done so, run the tests (using the `npm test` command) and verify that they still
pass.
The unit test now verifies that the default ordering property is set.
<div class="alert alert-success">
One of the great things about tests is the confidence they provide, when refactoring your
application. It's easy to break something as you start moving files around and re-arranging
modules. Having good test coverage is the quickest, easiest and most reliable way of knowing that
your application will continue to work as expected.
</div>
We used Jasmine's API to extract the controller construction into a `beforeEach` block, which is
shared by all tests in the parent `describe` block.
You should now see the following output in the Karma tab:
<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.
__`test/e2e/scenarios.js`:__
```js
...
it('should be possible to control phone order via the drop down select box', function() {
var phoneNameColumn = element.all(by.repeater('phone in phones').column('phone.name'));
var query = element(by.model('query'));
function getNames() {
return phoneNameColumn.map(function(elm) {
return elm.getText();
});
}
query.sendKeys('tablet'); //let's narrow the dataset to make the test assertions shorter
expect(getNames()).toEqual([
"Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"
]);
element(by.model('orderProp')).element(by.css('option[value="name"]')).click();
expect(getNames()).toEqual([
"MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"
]);
});...
```
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
You can now rerun `npm run protractor` to see the tests run.
# Experiments
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
you'll see that Angular will temporarily add a new blank ("unknown") option to the drop-down list and the
ordering will default to unordered/natural order.
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
text.
* Reverse the sort order by adding a `-` symbol before the sorting value: `<option value="-age">Oldest</option>`
# Summary
Now that you have added list sorting and tested the app, go to {@link step_05 step 5} to learn
about Angular services and how Angular uses dependency injection.
Even if we didn't add any new and exciting functionality to our application, we have made a great
step towards a well-architected and maintainable application. Time to spice things up. Let's go to
{@link step_05 step 5} to learn how to add full-text search to the application.
<ul doc-tutorial-nav="4"></ul>
[angular-seed]: https://github.com/angular/angular-seed
[styleguide]: https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md
+122 -231
View File
@@ -1,277 +1,168 @@
@ngdoc tutorial
@name 5 - XHRs & Dependency Injection
@name 5 - Filtering Repeaters
@step 5
@description
<ul doc-tutorial-nav="5"></ul>
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
from our server using one of Angular's built-in {@link guide/services services} called {@link
ng.$http $http}. We will use Angular's {@link guide/di dependency
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
We did a lot of work in laying a foundation for the app in the previous steps, so now we'll do
something simple; we will add full-text search (yes, it will be simple!). We will also write an
end-to-end (E2E) test, because a good E2E test is a good friend. It stays with your app, keeps an
eye on it, and quickly detects regressions.
* 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.
* There is now a list of 20 phones, loaded from the server.
<div doc-tutorial-reset="5"></div>
## 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.
## Component Controller
Following is a sample of the file:
We made no changes to the component's controller.
```js
[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]
## Component Template
<br />
**`app/phone-list/phone-list.template.html`:**
```html
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="$ctrl.query" />
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
```
We added a standard HTML `<input>` tag and used Angular's {@link ng.filter:filter filter} function
to process the input for the {@link ngRepeat ngRepeat} directive.
## Controller
By virtue of the {@link ngModel ngModel} directive, this lets a user enter search criteria and
immediately see the effects of their search on the phone list. This new code demonstrates the
following:
We'll use Angular's {@link ng.$http $http} service in our controller to make an HTTP
request to your web server to fetch the data in the `app/phones/phones.json` file. `$http` is just
one of several built-in {@link guide/services Angular services} that handle common operations
in web apps. Angular injects these services for you where you need them.
* Data-binding: This is one of the core features in Angular. When the page loads, Angular binds the
value of the input box to the data model variable specified with `ngModel` and keeps the two in
sync.
Services are managed by Angular's {@link guide/di DI subsystem}. Dependency injection
helps to make your web apps both well-structured (e.g., separate components for presentation, data,
and control) and loosely coupled (dependencies between components are not resolved by the
components themselves, but by the DI subsystem).
In this code, the data that a user types into the input box (bound to **`$ctrl.query`**) is
immediately available as a filter input in the list repeater
(`phone in $ctrl.phones | filter:`**`$ctrl.query`**). When changes to the data model cause the
repeater's input to change, the repeater efficiently updates the DOM to reflect the current state
of the model.
__`app/js/controllers.js:`__
<img class="diagram" src="img/tutorial/tutorial_05.png">
* Use of the `filter` filter: The {@link ng.filter:filter filter} function uses the `$ctrl.query`
value to create a new array that contains only those records that match the query.
`ngRepeat` automatically updates the view in response to the changing number of phones returned
by the `filter` filter. The process is completely transparent to the developer.
# Testing
In previous steps, we learned how to write and run unit tests. Unit tests are perfect for testing
controllers and other parts of our application written in JavaScript, but they can't easily
test templates, DOM manipulation or interoperability of components and services. For these, an
end-to-end (E2E) test is a much better choice.
The search feature was fully implemented via templates and data-binding, so we'll write our first
E2E test, to verify that the feature works.
<br />
**`e2e-tests/scenarios.js`:**
```js
var phonecatApp = angular.module('phonecatApp', []);
describe('PhoneCat Application', function() {
describe('phoneList', function() {
beforeEach(function() {
browser.get('index.html');
});
it('should filter the phone list as a user types into the search box', function() {
var phoneList = element.all(by.repeater('phone in $ctrl.phones'));
var query = element(by.model('$ctrl.query'));
expect(phoneList.count()).toBe(3);
query.sendKeys('nexus');
expect(phoneList.count()).toBe(1);
query.clear();
query.sendKeys('motorola');
expect(phoneList.count()).toBe(2);
});
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
});
```
`$http` makes an HTTP GET request to our web server, asking for `phones/phones.json` (the url is
relative to our `index.html` file). The server responds by providing the data in the json file.
(The response might just as well have been dynamically generated by a backend server. To the
browser and our app, they both look the same. For the sake of simplicity, we used a json file in this
tutorial.)
The `$http` service returns a {@link ng.$q promise object} with a `success`
method. We call this method to handle the asynchronous response and assign the phone data to the
scope controlled by this controller, as a model called `phones`. Notice that Angular detected the
json response and parsed it for us!
To use a service in Angular, you simply declare the names of the dependencies you need as arguments
to the controller's constructor function, as follows:
phonecatApp.controller('PhoneListCtrl', function ($scope, $http) {...}
Angular's dependency injector provides services to your controller when the controller is being
constructed. The dependency injector also takes care of creating any transitive dependencies the
service may have (services often depend upon other services).
Note that the names of arguments are significant, because the injector uses these to look up the
dependencies.
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write E2E tests in Angular. Although this example is for a simple test, it really is
that easy to set up any functional, readable, E2E test.
<img class="diagram" src="img/tutorial/tutorial_05.png">
## Running E2E Tests with Protractor
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the E2E test uses APIs of [Protractor][protractor]. Read about the Protractor APIs in the
[Protractor API Docs][protractor-docs].
### `$` Prefix Naming Convention
You can create your own services, and in fact we will do exactly that in step 11. As a naming
convention, Angular's built-in services, Scope methods and a few other Angular APIs have a `$`
prefix in front of the name.
The `$` prefix is there to namespace Angular-provided services.
To prevent collisions it's best to avoid naming your services and models anything that begins with a `$`.
If you inspect a Scope, you may also notice some properties that begin with `$$`. These
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.
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:
* 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 our example, we would write:
```js
function PhoneListCtrl($scope, $http) {...}
PhoneListCtrl.$inject = ['$scope', '$http'];
phonecatApp.controller('PhoneListCtrl', PhoneListCtrl);
```
* 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]);
```
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.
When using the second method, it is common to provide the constructor function inline as an
anonymous function when registering the controller:
```js
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http', function($scope, $http) {...}]);
```
From this point onward, we're going to use the inline method in the tutorial. With that in mind,
let's add the annotations to our `PhoneListCtrl`:
__`app/js/controllers.js:`__
```js
var phonecatApp = angular.module('phonecatApp', []);
phonecatApp.controller('PhoneListCtrl', ['$scope', '$http',
function ($scope, $http) {
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
$scope.orderProp = 'age';
}]);
```
## Test
__`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, 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() {
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
// Load our app module definition before each test.
beforeEach(module('phonecatApp'));
// 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 in order to avoid a name conflict.
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
```
Note: Because we loaded Jasmine and `angular-mocks.js` in our test environment, we got two helper
methods {@link angular.mock.module module} and {@link angular.mock.inject inject} that we'll
use to access and configure the injector.
We created the controller in the test environment, as follows:
* We used the `inject` helper method to inject instances of
{@link ng.$rootScope $rootScope},
{@link ng.$controller $controller} and
{@link ng.$httpBackend $httpBackend} services into the Jasmine's `beforeEach`
function. These instances come from an injector which is recreated from scratch for every single
test. This guarantees that each test starts from a well known starting point and each test is
isolated from the work done in other tests.
* We created a new scope for our controller by calling `$rootScope.$new()`
* We called the injected `$controller` function passing the name of the `PhoneListCtrl` controller
and the created scope as parameters.
Because our code now uses the `$http` service to fetch the phone list data in our controller, before
we create the `PhoneListCtrl` child scope, we need to tell the testing harness to expect an
incoming request from the controller. To do this we:
* Request `$httpBackend` service to be injected into our `beforeEach` function. This is a mock
version of the service that in a production environment facilitates all XHR and JSONP requests.
The mock version of this service allows you to write tests without having to deal with
native APIs and the global state associated with them — both of which make testing a nightmare.
* Use the `$httpBackend.expectGET` method to train the `$httpBackend` service to expect an incoming
HTTP request and tell it what to respond with. Note that the responses are not returned until we call
the `$httpBackend.flush` method.
Now we will make assertions to verify that the `phones` model doesn't exist on `scope` before
the response is received:
```js
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toBeUndefined();
$httpBackend.flush();
expect(scope.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
```
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
promise returned by the `$http` service to be resolved with the trained response. See
'Flushing HTTP requests' in the {@link ngMock.$httpBackend mock $httpBackend} documentation for
a full explanation of why this is necessary.
* We make the assertions, verifying that the phone model now exists on the scope.
Finally, we verify that the default value of `orderProp` is set correctly:
```js
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
```
You should now see the following output in the Karma tab:
<pre>Chrome 22.0: Executed 2 of 2 SUCCESS (0.028 secs / 0.007 secs)</pre>
Much like Karma is the test runner for unit tests, we use Protractor to run E2E tests. Try it with
`npm run protractor`. E2E tests take time, so unlike with unit tests, Protractor will exit after the
tests run and will not automatically rerun the test suite on every file change.
To rerun the test suite, execute `npm run protractor` again.
<div class="alert alert-info">
**Note:** In order for protractor to access and run tests against your application, it must be
served via a web server. In a different terminal/command line window, run `npm start` to fire up
the web server.
</div>
# Experiments
* At the bottom of `index.html`, add a `<pre>{{phones | filter:query | orderBy:orderProp | json}}</pre>`
binding to see the list of phones displayed in json format.
<div></div>
* In the `PhoneListCtrl` controller, pre-process the http response by limiting the number of phones
to the first 5 in the list. Use the following code in the `$http` callback:
* Display the current value of the `query` model by adding a `{{$ctrl.query}}` binding into the
`phone-list.template.html` template and see how it changes, when you type in the input box.
$scope.phones = data.splice(0, 5);
You might also try to add the `{{$ctrl.query}}` binding to `index.html`. 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 `<phone-list>` component.<br />
Component isolation at work!
# Summary
Now that you have learned how easy it is to use Angular services (thanks to Angular's dependency
injection), go to {@link step_06 step 6}, where you will add some
thumbnail images of phones and some links.
We have now added full-text search and included a test to verify that it works! Now let's go on to
{@link step_06 step 6} to learn how to add sorting capabilities to the PhoneCat application.
<ul doc-tutorial-nav="5"></ul>
[protractor]: https://github.com/angular/protractor
[protractor-docs]: https://angular.github.io/protractor/#/api
+197 -64
View File
@@ -1,108 +1,241 @@
@ngdoc tutorial
@name 6 - Templating Links & Images
@name 6 - Two-way Data Binding
@step 6
@description
<ul doc-tutorial-nav="6"></ul>
In this step, you will add thumbnail images for the phones in the phone list, and links that, for
now, will go nowhere. In subsequent steps you will use the links to display additional information
about the phones in the catalog.
In this step, we will add a feature to let our users control the order of the items in the phone
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 application displays a drop-down menu that allows users to
control the order in which the phones are listed.
* There are now links and images of the phones in the list.
<div doc-tutorial-reset="6"></div>
## Data
Note that the `phones.json` file contains unique IDs and image URLs for each of the phones. The
URLs point to the `app/img/phones/` directory.
## Component Template
__`app/phones/phones.json`__ (sample snippet):
```js
[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
]
```
## Template
__`app/index.html`:__
<br />
**`app/phone-list/phone-list.template.html`:**
```html
...
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
<p>
Search:
<input ng-model="$ctrl.query">
</p>
<p>
Sort by:
<select ng-model="$ctrl.orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</p>
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}"></a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp">
<span>{{phone.name}}</span>
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</div>
</div>
</div>
```
To dynamically generate links that will in the future lead to phone detail pages, we used the
now-familiar double-curly brace binding in the `href` attribute values. In step 2, we added the
`{{phone.name}}` binding as the element content. In this step the `{{phone.id}}` binding is used in
the element attribute.
We made the following changes to the `phone-list.template.html` template:
We also added phone images next to each record using an image tag with the {@link
ng.directive:ngSrc ngSrc} directive. That directive prevents the
browser from treating the Angular `{{ expression }}` markup literally, and initiating a request to
an invalid URL `http://localhost:8000/app/{{phone.imageUrl}}`, which it would have done if we had
only specified an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`).
Using the `ngSrc` directive prevents the browser from making an http request to an invalid location.
* First, we added a `<select>` element bound to `$ctrl.orderProp`, so that our users can pick from
the two provided sorting options.
<img class="diagram" src="img/tutorial/tutorial_06.png">
* We then chained the `filter` filter with the {@link orderBy orderBy} filter to further process the
input for the repeater. `orderBy` is a filter that takes an input array, copies it and reorders
the copy which is then returned.
Angular creates a two way data-binding between the select element and the `$ctrl.orderProp` model.
`$ctrl.orderProp` is then used as the input for the `orderBy` filter.
As we discussed in the section about data-binding and the repeater in {@link step_05 step 5},
whenever the model changes (for example because a user changes the order with the select drop-down
menu), Angular's data-binding will cause the view to automatically update. No bloated DOM
manipulation code is necessary!
## Test
## Component Controller
__`test/e2e/scenarios.js`__:
<br />
**`app/phone-list/phone-list.components.js`:**
```js
...
it('should render phone specific links', function() {
var query = element(by.model('query'));
query.sendKeys('nexus');
element.all(by.css('.phones li a')).first().click();
browser.getLocationAbsUrl().then(function(url) {
expect(url).toBe('/phones/nexus-s');
});
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: function PhoneListController() {
this.phones = [
{
name: 'Nexus S',
snippet: 'Fast just got faster with Nexus S.',
age: 1
}, {
name: 'Motorola XOOM™ with Wi-Fi',
snippet: 'The Next, Next Generation tablet.',
age: 2
}, {
name: 'MOTOROLA XOOM™',
snippet: 'The Next, Next Generation tablet.',
age: 3
}
];
this.orderProp = 'age';
}
});
...
```
We added a new end-to-end test to verify that the app is generating correct links to the phone
views that we will implement in the upcoming steps.
* We modified the `phones` model - the array of phones - and added an `age` property to each phone
record. This property is used to order the phones by age.
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
not set a default value here, the `orderBy` filter would remain uninitialized until the user
picked an option from the drop-down menu.
This is a good time to talk about two-way data-binding. Notice that when the application is loaded
in the browser, "Newest" is selected in the drop-down menu. This is because we set `orderProp` to
`'age'` in the controller. So the binding works in the direction from our model to the UI. Now if
you select "Alphabetically" in the drop-down menu, the model will be updated as well and the phones
will be reordered. That is the data-binding doing its job in the opposite direction — from the UI to
the model.
# Testing
The changes we made should be verified with both a unit test and an E2E test. Let's look at the unit
test first.
<br />
**`app/phone-list/phone-list.component.spec.js`:**
```js
describe('phoneList', function() {
// Load the module that contains the `phoneList` component before each test
beforeEach(module('phoneList'));
// Test the controller
describe('PhoneListController', function() {
var ctrl;
beforeEach(inject(function($componentController) {
ctrl = $componentController('phoneList');
}));
it('should create a `phones` model with 3 phones', function() {
expect(ctrl.phones.length).toBe(3);
});
it('should set a default value for the `orderProp` model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
});
```
The unit test now verifies that the default ordering property is set.
We used Jasmine's API to extract the controller construction into a `beforeEach` block, which is
shared by all tests in the parent `describe` block.
You should now see the following output in the Karma tab:
```
Chrome 49.0: Executed 2 of 2 SUCCESS (0.136 secs / 0.08 secs)
```
Let's turn our attention to the E2E tests.
<br />
**`e2e-tests/scenarios.js`:**
```js
describe('PhoneCat Application', function() {
describe('phoneList', function() {
...
it('should be possible to control phone order via the drop-down menu', function() {
var queryField = element(by.model('$ctrl.query'));
var orderSelect = element(by.model('$ctrl.orderProp'));
var nameOption = orderSelect.element(by.css('option[value="name"]'));
var phoneNameColumn = element.all(by.repeater('phone in $ctrl.phones').column('phone.name'));
function getNames() {
return phoneNameColumn.map(function(elem) {
return elem.getText();
});
}
queryField.sendKeys('tablet'); // Let's narrow the dataset to make the assertions shorter
expect(getNames()).toEqual([
'Motorola XOOM\u2122 with Wi-Fi',
'MOTOROLA XOOM\u2122'
]);
nameOption.click();
expect(getNames()).toEqual([
'MOTOROLA XOOM\u2122',
'Motorola XOOM\u2122 with Wi-Fi'
]);
});
...
```
The E2E test verifies that the ordering mechanism of the select box is working correctly.
You can now rerun `npm run protractor` to see the tests run.
# Experiments
* Replace the `ng-src` directive with a plain old `src` attribute. Using tools such as Firebug,
or Chrome's Web Inspector, or inspecting the webserver access logs, confirm that the app is indeed
making an extraneous request to `/app/%7B%7Bphone.imageUrl%7D%7D` (or
`/app/{{phone.imageUrl}}`).
<div></div>
The issue here is that the browser will fire a request for that invalid image address as soon as
it hits the `img` tag, which is before Angular has a chance to evaluate the expression and inject
the valid address.
* In the `phoneList` component's controller, remove the statement that sets the `orderProp` value
and you'll see that Angular will temporarily add a new blank ("unknown") option to the drop-down
list and the ordering will default to unordered/natural order.
* Add a `{{$ctrl.orderProp}}` binding into the `phone-list.template.html` template to display its
current value as text.
* Reverse the sort order by adding a `-` symbol before the sorting value:
`<option value="-age">Oldest</option>`
# Summary
Now that you have added phone images and links, go to {@link step_07 step 7} to learn about Angular
layout templates and how Angular makes it easy to create applications that have multiple views.
Now that you have added list sorting and tested the application, go to {@link step_07 step 7} to
learn about Angular services and how Angular uses dependency injection.
<ul doc-tutorial-nav="6"></ul>
+261 -329
View File
@@ -1,380 +1,312 @@
@ngdoc tutorial
@name 7 - Routing & Multiple Views
@name 7 - XHR & Dependency Injection
@step 7
@description
<ul doc-tutorial-nav="7"></ul>
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, using an Angular module called 'ngRoute'.
Enough of building an app with three phones in a hard-coded dataset! Let's fetch a larger dataset
from our server using one of Angular's built-in {@link guide/services services} called
{@link ng.$http $http}. We will use Angular's {@link guide/di dependency injection (DI)} to provide
the service to the `phoneList` component's controller.
* There is now a list of 20 phones, loaded from the server.
* 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 that specific phone and the stub of a
phone detail page is displayed.
<div doc-tutorial-reset="7"></div>
## Dependencies
The routing functionality added by this step is provided by angular in the `ngRoute` module, which
is distributed separately from the core Angular framework.
## Data
We are using [Bower][bower] to install client-side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
The `app/phones/phones.json` file in our project is a dataset that contains a larger list of phones,
stored in JSON format.
Following is a sample of the file:
```json
{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.1.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x"
}
}
[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]
```
The new dependency `"angular-route": "1.4.x"` tells bower to install a version of the
angular-route component that is compatible with version 1.4.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:
## Component Controller
```
npm install
```
We will use Angular's {@link ng.$http $http} service in our controller for making an HTTP request to
our web server to fetch the data in the `app/phones/phones.json` file. `$http` is just one of
several built-in {@link guide/services Angular services} that handle common operations in web
applications. Angular injects these services for you, right where you need them.
<div class="alert alert-warning">
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then you may have a
problem with the `bower install` due to a conflict between the versions of angular.js that need to
be installed. If you get this then simply delete your `app/bower_components` folder before running
`npm install`.
</div>
Services are managed by Angular's {@link guide/di DI subsystem}. Dependency injection helps to make
your web applications both well-structured (e.g. separate entities for presentation, data, and
control) and loosely coupled (dependencies between entities are not resolved by the entities
themselves, but by the DI subsystem). As a result, applications are easier to test as well.
<div class="alert alert-info">
**Note:** 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>
## Multiple Views, Routing and Layout Template
Our app is slowly growing and becoming more complex. Before step 7, the app provided our users with
a single view (the list of all phones), and all of the template code was located in the
`index.html` file. The next step in building the app is to add a view that will show detailed
information about each of the devices in our list.
To add the detailed view, we could expand the `index.html` file to contain template code for both
views, but that would get messy very quickly. Instead, we are going to turn the `index.html`
template into what we call a "layout template". This is a template that is common for all views in
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.
### A Note About DI, Injector and Providers
As you {@link tutorial/step_05 noticed}, {@link guide/di dependency injection} (DI) is at the core of
AngularJS, so it's important for you to understand a thing or two about how it works.
When the application bootstraps, Angular creates an injector that will be used to find and inject all
of the services that are required by your app. The injector itself doesn't know anything about what
`$http` or `$route` services do. In fact, the injector doesn't even know about the existence of these services
unless it is configured with proper module definitions.
The injector only carries out the following steps :
* load the module definition(s) that you specify in your app
* register all Providers defined in these module definitions
* when asked to do so, inject a specified function and any necessary dependencies (services) that
it lazily instantiates via their Providers.
Providers are objects that provide (create) instances of services and expose configuration APIs
that can be used to control the creation and runtime behavior of a service. In case of the `$route`
service, the `$routeProvider` exposes APIs that allow you to define routes for your application.
<div class="alert alert-warning">
**Note:** Providers can only be injected into `config` functions. Thus you could not inject
`$routeProvider` into `PhoneListCtrl`.
</div>
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 fulfill their goals.
To deepen your understanding of DI on Angular, see
[Understanding Dependency Injection](https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection).
## Template
The `$route` service is usually used in conjunction with the {@link ngRoute.directive:ngView
ngView} directive. The role of the `ngView` directive is to include the view template for the current
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 additional `angular-route.js` file, which we download via Bower above.
</div>
__`app/index.html`:__
```html
<!doctype html>
<html lang="en" ng-app="phonecatApp">
<head>
...
<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>
<body>
<div ng-view></div>
</body>
</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:
__`app/partials/phone-list.html`:__
```html
<div class="container-fluid">
<div class="row">
<div class="col-md-2">
<!--Sidebar content-->
Search: <input ng-model="query">
Sort by:
<select ng-model="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</div>
<div class="col-md-10">
<!--Body content-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp" class="thumbnail">
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
</div>
</div>
</div>
```
<div style="display:none">
TODO!
<img class="diagram" src="img/tutorial/tutorial_07_final.png">
</div>
We also added a placeholder template for the phone details view:
__`app/partials/phone-detail.html`:__
```html
TBD: detail view for <span>{{phoneId}}</span>
```
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 as 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`:__
<br />
**`app/phone-list/phone-list.component.js:`**
```js
var phonecatApp = angular.module('phonecatApp', [
'ngRoute',
'phonecatControllers'
]);
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: function PhoneListController($http) {
var self = this;
self.orderProp = 'age';
...
```
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 '/phones/: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;
$http.get('phones/phones.json').then(function(response) {
self.phones = response.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.
`$http` makes an HTTP GET request to our web server, asking for `phones.json` (the URL is relative
to our `index.html` file). The server responds by providing the data in the JSON file.
(The response might just as well have been dynamically generated by a backend server. To the
browser and our app, they both look the same. For the sake of simplicity, we will use JSON files
in this tutorial.)
Because our example app is relatively small, we'll just add all of our controllers to the
`phonecatControllers` module.
The `$http` service returns a {@link ng.$q promise object}, which has a `then()` method. We call
this method to handle the asynchronous response and assign the phone data to the controller, as a
property called `phones`. Notice that Angular detected the JSON response and parsed it for us into
the `data` property of the `response` object passed to our callback!
Since we are making the assignment of the `phones` property in a callback function, where the `this`
value is not defined, we also introduce a local variable called `self` that points back to the
controller instance.
## Test
To automatically verify that everything is wired properly, we wrote end-to-end tests that navigate
to various URLs and verify that the correct view was rendered.
To use a service in Angular, you simply declare the names of the dependencies you need as arguments
to the controller's constructor function, as follows:
```js
...
it('should redirect index.html to index.html#/phones', function() {
browser.get('app/index.html');
browser.getLocationAbsUrl().then(function(url) {
expect(url).toEqual('/phones');
});
function PhoneListController($http) {...}
```
Angular's dependency injector provides services to your controller, when the controller is being
constructed. The dependency injector also takes care of creating any transitive dependencies the
service may have (services often depend upon other services).
Note that the names of arguments are significant, because the injector uses these to look up the
dependencies.
<img class="diagram" src="img/tutorial/tutorial_05.png">
### `$`-prefix Naming Convention
You can create your own services, and in fact we will do exactly that a few steps down the road. As
a naming convention, Angular's built-in services, Scope methods and a few other Angular APIs have a
`$` prefix in front of the name.
The `$` prefix is there to namespace Angular-provided services. To prevent collisions it's best to
avoid naming your services and models anything that begins with a `$`.
If you inspect a Scope, you may also notice some properties that begin with `$$`. These 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][minification] the JavaScript code for the
`PhoneListController` controller, all of its function arguments would be minified as well, and the
dependency injector would not be able to identify services correctly.
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:
* Create an `$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 our example, we would write:
```js
function PhoneListController($http) {...}
PhoneListController.$inject = ['$http'];
...
.component('phoneList', {..., controller: PhoneListController});
```
* 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 as the last item
of the array.
```js
function PhoneListController($http) {...}
...
.component('phoneList', {..., controller: ['$http', PhoneListController]});
```
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.
When using the second method, it is common to provide the constructor function inline, when
registering the controller:
```js
.component('phoneList', {..., controller: ['$http', function PhoneListController($http) {...}]});
```
From this point onwards, we are going to use the inline method in the tutorial. With that in mind,
let's add the annotations to our `PhoneListController`:
<br />
**`app/phone-list/phone-list.component.js`**
```js
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: ['$http',
function PhoneListController($http) {
var self = this;
self.orderProp = 'age';
$http.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
}
]
});
```
# Testing
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, 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`:
<br />
**`app/phone-list/phone-list.component.spec.js`:**
```js
describe('phoneList', function() {
beforeEach(module('phoneList'));
describe('controller', function() {
var $httpBackend, ctrl;
// The injector ignores leading and trailing underscores here (i.e. _$httpBackend_).
// This allows us to inject a service and assign it to a variable with the same name
// as the service while avoiding a name conflict.
beforeEach(inject(function($componentController, _$httpBackend_) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json')
.respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
ctrl = $componentController('phoneList');
}));
...
});
describe('Phone list view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones');
});
...
describe('Phone detail view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones/nexus-s');
});
it('should display placeholder page with phoneId', function() {
expect(element(by.binding('phoneId')).getText()).toBe('nexus-s');
});
});
});
```
<div class="alert alert-info">
**Note:** Because we loaded Jasmine and `angular-mocks.js` in our test environment, we got two
helper methods {@link angular.mock.module module} and {@link angular.mock.inject inject} that we
can use to access and configure the injector.
</div>
You can now rerun `npm run protractor` to see the tests run.
We created the controller in the test environment, as follows:
* We used the `inject()` helper method to inject instances of
{@link ngMock.$componentController $componentController} and {@link ng.$httpBackend $httpBackend}
services into Jasmine's `beforeEach()` function. These instances come from an injector which is
recreated from scratch for every single test. This guarantees that each test starts from a well
known starting point and each test is isolated from the work done in other tests.
* We called the injected `$componentController` function passing the name of the `phoneList`
component (whose controller we wanted to instantiate) as a parameter.
Because our code now uses the `$http` service to fetch the phone list data in our controller, before
we create the `PhoneListController`, we need to tell the testing harness to expect an incoming
request from the controller. To do this we:
* Inject the `$httpBackend` service into the `beforeEach()` function. This is a
{@link ngMock.$httpBackend mock version} of the service that in a production environment
facilitates all XHR and JSONP requests. The mock version of this service allows us to write tests
without having to deal with native APIs and the global state associated with them — both of which
make testing a nightmare. It also overcomes the asynchronous nature of these calls, which would
slow down unit tests.
* Use the `$httpBackend.expectGET()` method to train the `$httpBackend` service to expect an
incoming HTTP request and tell it what to respond with. Note that the responses are not returned
until we call the `$httpBackend.flush()` method.
Now we will make assertions to verify that the `phones` property doesn't exist on the controller
before the response is received:
```js
it('should create a `phones` property with 2 phones fetched with `$http`', function() {
expect(ctrl.phones).toBeUndefined();
$httpBackend.flush();
expect(ctrl.phones).toEqual([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
});
```
* We flush the request queue in the browser by calling `$httpBackend.flush()`. This causes the
promise returned by the `$http` service to be resolved with the trained response. See
{@link ngMock.$httpBackend#flushing-http-requests Flushing HTTP requests} in the mock
`$httpBackend` documentation for a full explanation of why this is necessary.
* We make the assertions, verifying that the `phones` property now exists on the controller.
Finally, we verify that the default value of `orderProp` is set correctly:
```js
it('should set a default value for the `orderProp` property', function() {
expect(ctrl.orderProp).toBe('age');
});
```
You should now see the following output in the Karma tab:
```
Chrome 49.0: Executed 2 of 2 SUCCESS (0.133 secs / 0.097 secs)
```
# Experiments
* Try to add an `{{orderProp}}` binding to `index.html`, and you'll see that nothing happens even
when you are in the phone list view. This is because the `orderProp` model is visible only in the
scope managed by `PhoneListCtrl`, which is associated with the `<div ng-view>` element. If you add
the same binding into the `phone-list.html` template, the binding will work as expected.
<div></div>
<div style="display: none">
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
`PhoneListCtrl`, let's shadow it with `this.hero = 'Batman'`. In `PhoneDetailCtrl`, we'll use
`this.hero = "Captain Proton"`. Then add the `<p>hero = {{hero}}</p>` to all three of our templates
(`index.html`, `phone-list.html`, and `phone-detail.html`). Open the app and you'll see scope
inheritance and model property shadowing do some wonders.
</div>
* At the bottom of `phone-list.template.html`, add a
`<pre>{{$ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp | json}}</pre>` binding to see
the list of phones displayed in JSON format.
* In the `PhoneListController` controller, pre-process the HTTP response by limiting the number of
phones to the first 5 in the list. Use the following code in the `$http` callback:
```js
self.phones = response.data.slice(0, 5);
```
# Summary
With the routing set up and the phone list view implemented, we're ready to go to {@link step_08
step 8} to implement the phone details view.
Now that you have learned how easy it is to use Angular services (thanks to Angular's dependency
injection), go to {@link step_08 step 8}, where you will add some thumbnail images of phones and
some links.
<ul doc-tutorial-nav="7"></ul>
[bower]: http://bower.io
[minification]: https://en.wikipedia.org/wiki/Minification_(programming)
+69 -149
View File
@@ -1,197 +1,117 @@
@ngdoc tutorial
@name 8 - More Templating
@name 8 - Templating Links & Images
@step 8
@description
<ul doc-tutorial-nav="8"></ul>
In this step, you will implement the phone details view, which is displayed when a user clicks on a
phone in the phone list.
In this step, we will add thumbnail images for the phones in the phone list, and links that, for
now, will go nowhere. In subsequent steps, we will use the links to display additional information
about the phones in the catalog.
* When you click on a phone on the list, the phone details page with phone-specific information
is displayed.
* There are now links and images of the phones in the list.
To implement the phone details view we are going to use {@link ng.$http $http} to fetch our data,
and then flesh out the `phone-detail.html` view template.
<div doc-tutorial-reset="8"></div>
## Data
In addition to `phones.json`, the `app/phones/` directory also contains one JSON file for each
phone:
Note that the `phones.json` file contains unique IDs and image URLs for each of the phones. The
URLs point to the `app/img/phones/` directory.
__`app/phones/nexus-s.json`:__ (sample snippet)
<br />
**`app/phones/phones.json`** (sample snippet):
```js
{
"additionalFeatures": "Contour Display, Near Field Communications (NFC),...",
"android": {
"os": "Android 2.3",
"ui": "Android"
```json
[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}
]
```
Each of these files describes various properties of the phone using the same data structure. We'll
show this data in the phone detail view.
## Component Template
## Controller
We'll expand the `PhoneDetailCtrl` by using the `$http` service to fetch the JSON files. This works
the same way as the phone list controller.
__`app/js/controllers.js`:__
```js
var phonecatControllers = angular.module('phonecatControllers',[]);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
});
}]);
```
To construct the URL for the HTTP request, we use `$routeParams.phoneId` extracted from the current
route by the `$route` service.
## Template
The TBD placeholder line has been replaced with lists and bindings that comprise the phone details.
Note where we use the Angular `{{expression}}` markup and `ngRepeat` to project phone data from
our model into the view.
__`app/partials/phone-detail.html`:__
<br />
**`app/phone-list/phone-list.template.html`:**
```html
<img ng-src="{{phone.images[0]}}" class="phone">
<h1>{{phone.name}}</h1>
<p>{{phone.description}}</p>
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}">
...
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp" class="thumbnail">
<a href="#/phones/{{phone.id}}" class="thumb">
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" />
</a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
...
```
<ul class="specs">
<li>
<span>Availability and Networks</span>
<dl>
<dt>Availability</dt>
<dd ng-repeat="availability in phone.availability">{{availability}}</dd>
</dl>
</li>
To dynamically generate links that will in the future lead to phone detail pages, we used the
now-familiar double-curly brace binding in the `href` attribute values. In step 2, we added the
`{{phone.name}}` binding as the element content. In this step the `{{phone.id}}` binding is used in
the element attribute.
We also added phone images next to each record using an image tag with the {@link ngSrc ngSrc}
directive. That directive prevents the browser from treating the Angular `{{ expression }}` markup
literally, and initiating a request to an invalid URL (`http://localhost:8000/{{phone.imageUrl}}`),
which it would have done if we had only specified an attribute binding in a regular `src` attribute
(`<img src="{{phone.imageUrl}}">`). Using the `ngSrc` directive, prevents the browser from making an
HTTP request to an invalid location.
# Testing
<br />
**`e2e-tests/scenarios.js`**:
```js
...
<li>
<span>Additional Features</span>
<dd>{{phone.additionalFeatures}}</dd>
</li>
</ul>
```
<div style="display: none">
TODO!
<img class="diagram" src="img/tutorial/tutorial_08-09_final.png">
</div>
it('should render phone specific links', function() {
var query = element(by.model('$ctrl.query'));
query.sendKeys('nexus');
## Test
We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in
step 5.
__`test/unit/controllersSpec.js`:__
```js
beforeEach(module('phonecatApp'));
...
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl;
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond({name:'phone xyz'});
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));
it('should fetch phone detail', function() {
expect(scope.phone).toBeUndefined();
$httpBackend.flush();
expect(scope.phone).toEqual({name:'phone xyz'});
});
});
...
```
You should now see the following output in the Karma tab:
<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
heading on the page is "Nexus S".
__`test/e2e/scenarios.js`:__
```js
...
describe('Phone detail view', function() {
beforeEach(function() {
browser.get('app/index.html#/phones/nexus-s');
element.all(by.css('.phones li a')).first().click();
expect(browser.getLocationAbsUrl()).toBe('/phones/nexus-s');
});
it('should display nexus-s page', function() {
expect(element(by.binding('phone.name')).getText()).toBe('Nexus S');
});
});
...
...
```
We added a new E2E test to verify that the application is generating correct links to the phone
views, that we will implement in the upcoming steps.
You can now rerun `npm run protractor` to see the tests run.
# Experiments
* Using the [Protractor API](http://angular.github.io/protractor/#/api),
write a test that verifies that we display 4 thumbnail images on the Nexus S details page.
<div></div>
* Replace the `ngSrc` directive with a plain old `src` attribute. Using tools such as your browser's
developer tools or inspecting the web server access logs, confirm that the application is indeed
making an extraneous request to `%7B%7Bphone.imageUrl%7D%7D` (or `{{phone.imageUrl}}`).
The issue here is that the browser will fire a request for that invalid image address as soon as
it hits the `<img>` tag, which is before Angular has a chance to evaluate the expression and
inject the valid address.
# Summary
Now that the phone details view is in place, proceed to {@link step_09 step 9} to learn how to
write your own custom display filter.
Now that you have added phone images and links, go to {@link step_09 step 9} to learn about Angular
layout templates and how Angular makes it easy to create applications that have multiple views.
<ul doc-tutorial-nav="8"></ul>
+374 -89
View File
@@ -1,144 +1,429 @@
@ngdoc tutorial
@name 9 - Filters
@name 9 - Routing & Multiple Views
@step 9
@description
<ul doc-tutorial-nav="9"></ul>
In this step you will learn how to create your own custom display filter.
* 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.
In this step, you will learn how to create a layout template and how to build an application that
has multiple views by adding routing, using an Angular module called {@link ngRoute ngRoute}.
* When you now navigate to `/index.html`, you are redirected to `/index.html#!/phones` and the phone
list appears in the browser.
* When you click on a phone link, the URL changes to that specific phone and the stub of a phone
detail page is displayed.
<div doc-tutorial-reset="9"></div>
## Custom Filter
## Dependencies
In order to create a new filter, you are going to create a `phonecatFilters` module and register
your custom filter with this module:
The routing functionality added in this step is provided by Angular in the `ngRoute` module, which
is distributed separately from the core Angular framework.
__`app/js/filters.js`:__
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
`bower.json` configuration file to include the new dependency:
```js
angular.module('phonecatFilters', []).filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';
};
});
<br />
**`bower.json`:**
```json
{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.5.x",
"angular-mocks": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x"
}
}
```
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
return one of the two unicode characters we have chosen to represent true (`\u2713` -> ✓) or false (`\u2718` -> ✘).
The new dependency `"angular-route": "1.5.x"` tells bower to install a version of the angular-route
module that is compatible with version 1.5.x of Angular. We must tell bower to download and install
this dependency.
Now that our filter is ready, we need to register the `phonecatFilters` module as a dependency for
our main `phonecatApp` module.
__`app/js/app.js`:__
```js
...
angular.module('phonecatApp', ['ngRoute','phonecatControllers','phonecatFilters']);
...
```
npm install
```
<div class="alert alert-info">
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
we have preconfigured `npm install` to run bower for us.
</div>
<div class="alert alert-warning">
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then
you may have a problem with the `bower install` due to a conflict between the versions of
angular.js that need to be installed. If you run into this issue, simply delete your
`app/bower_components` directory and then run `npm install`.
</div>
## Multiple Views, Routing and Layout Templates
Our app is slowly growing and becoming more complex. Prior to this step, the app provided our users
with a single view (including the list of all phones), and all of the template code was located in
the `phone-list.template.html` file. The next step in building the application is to add a view that
will show detailed information about each of the devices in our list.
To add the detailed view, we are going to turn `index.html` into what we call a "layout template".
This is a template that is common for all views in 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][deep-linking], which lets us utilize the browser's history
(back and forward navigation) and bookmarks.
<div class="alert alert-success">
<p>
`ngRoute` lets us associate a controller and a template with a specific URL (or URL
pattern). This is pretty close to what we did with `ngController` and `index.html` back in
{@link step_02 step 2}.
</p>
<p>
Since we have already learned that components allow us to combine controllers with templates in
a modular, testable way, we are going to use components for routing as well.
Each route will be associated with a component and that component will be in charge of providing
the view template and the controller.
</p>
</div>
### A Note about DI, Injector and Providers
As you {@link step_07 noticed}, {@link guide/di dependency injection} (DI) is at the core of
AngularJS, so it's important for you to understand a thing or two about how it works.
When the application bootstraps, Angular creates an injector that will be used to find and inject
all of the services that are required by your application. The injector itself doesn't know anything
about what the `$http` or `$route` services do. In fact, the injector doesn't even know about the
existence of these services, unless it is configured with proper module definitions.
The injector only carries out the following steps:
* Load the module definition(s) that you specify in your application.
* Register all Providers defined in these module definition(s).
* When asked to do so, lazily instantiate services and their dependencies, via their Providers, as
parameters to an injectable function.
Providers are objects that provide (create) instances of services and expose configuration APIs,
that can be used to control the creation and runtime behavior of a service. In case of the `$route`
service, the `$routeProvider` exposes APIs that allow you to define routes for your application.
<div class="alert alert-warning">
**Note:** Providers can only be injected into `config` functions. Thus you could not inject
`$routeProvider` into `PhoneListController` at runtime.
</div>
Angular modules solve the problem of removing global variables 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 fulfill their goals.
To deepen your understanding on Angular's DI, see [Understanding Dependency Injection][wiki-di].
## Template
Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our
layout template.
The `$route` service is usually used in conjunction with the {@link ngRoute.directive:ngView ngView}
directive. The role of the `ngView` directive is to include the view template for the current route
into the layout template. This makes it a perfect fit for our `index.html` template.
__`app/index.html`:__
<br />
**`app/index.html`:**
```html
...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
...
<head>
...
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="app.module.js"></script>
<script src="app.config.js"></script>
...
<script src="phone-detail/phone-detail.module.js"></script>
<script src="phone-detail/phone-detail.component.js"></script>
</head>
<body>
<div ng-view></div>
</body>
```
The syntax for using filters in Angular templates is as follows:
We have added four new `<script>` tags in our `index.html` file to load some extra JavaScript files
into our application:
{{ expression | filter }}
* `angular-route.js`: Defines the Angular `ngRoute` module, which provides us with routing.
* `app.config.js`: Configures the providers available to our main module (see
[below](tutorial/step_09#configuring-a-module)).
* `phone-detail.module.js`: Defines a new module containing a `phoneDetail` component.
* `phone-detail.component.js`: Defines a dummy `phoneDetail` component (see
[below](tutorial/step_09#the-phonedetail-component)).
Let's employ the filter in the phone details template:
Note that we removed the `<phone-list></phone-list>` line from the `index.html` template and
replaced it with a `<div>` with the `ng-view` attribute.
<img class="diagram" src="img/tutorial/tutorial_09.png">
## Configuring a Module
__`app/partials/phone-detail.html`:__
A module's {@link ng.angular.Module#config .config()} method gives us access to the available
providers for configuration. To make the providers, services and directives defined in `ngRoute`
available to our application, we need to add `ngRoute` as a dependency of our `phonecatApp` module.
```html
...
<dl>
<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
...
```
## Test
Filters, like any other component, should be tested and these tests are very easy to write.
__`test/unit/filtersSpec.js`:__
<br />
**`app/app.module.js`:**
```js
describe('filter', function() {
beforeEach(module('phonecatFilters'));
describe('checkmark', function() {
it('should convert boolean values to unicode checkmark or cross',
inject(function(checkmarkFilter) {
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
}));
});
});
angular.module('phonecatApp', [
'ngRoute',
...
]);
```
We must call `beforeEach(module('phonecatFilters'))` before any of
our filter tests execute. This call loads our `phonecatFilters` module into the injector
for this test run.
Now, in addition to the core services and directives, we can also configure the `$route` service
(using it's provider) for our application. In order to be able to quickly locate the configuration
code, we put it into a separate file and used the `.config` suffix.
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()}.
<br />
**`app/app.config.js`:**
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.
```js
angular.
module('phonecatApp').
config(['$locationProvider', '$routeProvider',
function config($locationProvider, $routeProvider) {
$locationProvider.hashPrefix('!');
You should now see the following output in the Karma tab:
$routeProvider.
when('/phones', {
template: '<phone-list></phone-list>'
}).
when('/phones/:phoneId', {
template: '<phone-detail></phone-detail>'
}).
otherwise('/phones');
}
]);
```
<pre>Chrome 22.0: Executed 4 of 4 SUCCESS (0.034 secs / 0.012 secs)</pre>
Using the `.config()` method, we request the necessary providers (for example the `$routeProvider`)
to be injected into our configuration function and then use their methods to specify the behavior of
the corresponding services. Here, we use the
{@link ngRoute.$routeProvider#when $routeProvider.when()} and
{@link ngRoute.$routeProvider#otherwise $routeProvider.otherwise()} methods to define our
application routes.
<div class="alert alert-success">
<p>
We also used {@link $locationProvider#hashPrefix $locationProvider.hashPrefix()} to set the
hash-prefix to `!`. This prefix will appear in the links to our client-side routes, right after
the hash (`#`) symbol and before the actual path (e.g. `index.html#!/some/path`).
</p>
<p>
Setting a prefix is not necessary, but it is considered a good practice (for reasons that are
outside the scope of this tutorial). `!` is the most commonly used prefix.
</p>
</div>
Our routes are defined as follows:
* `when('/phones')`: Determines the view that will be shown, when the URL hash fragment is
`/phones`. According to the specified template, Angular will create an instance of the `phoneList`
component to manage the view. Note that this is the same markup that we used to have in the
`index.html` file.
* `when('/phones/:phoneId')`: Determines the view that will be shown, when the URL hash fragment
matches `/phones/<phoneId>`, where `<phoneId>` is a variable part of the URL. In charge of the
view will be the `phoneDetail` component.
* `otherwise('/phones')`: Defines a fallback route to redirect to, when no route definition matches
the current URL.(Here it will redirect to `/phones`.)
We reused the `phoneList` component that we have already built and a new "dummy" `phoneDetail`
component. For now, the `phoneDetail` component will just display the selected phone's ID.
(Not too impressive, but we will enhance it in the {@link step_10 next step}.)
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 `:` prefix are extracted into the (injectable)
{@link ngRoute.$routeParams $routeParams} object.
## The `phoneDetail` Component
We created a `phoneDetail` component to handle the phone details view. We followed the same
conventions as with `phoneList`, using a separate directory and creating a `phoneDetail` module,
which we added as a dependency of the `phonecatApp` module.
<br />
**`app/phone-detail/phone-detail.module.js`:**
```js
angular.module('phoneDetail', [
'ngRoute'
]);
```
<br />
**`app/phone-detail/phone-detail.component.js`:**
```js
angular.
module('phoneDetail').
component('phoneDetail', {
template: 'TBD: Detail view for <span>{{$ctrl.phoneId}}</span>',
controller: ['$routeParams',
function PhoneDetailController($routeParams) {
this.phoneId = $routeParams.phoneId;
}
]
});
```
<br />
**`app/app.module.js`:**
```js
angular.module('phonecatApp', [
...
'phoneDetail',
...
]);
```
### A Note on Sub-module Dependencies
The `phoneDetail` module depends on the `ngRoute` module for providing the `$routeParams` object,
which is used in the `phoneDetail` component's controller. Since `ngRoute` is also a dependency of
the main `phonecatApp` module, its services and directives are already available everywhere in the
application (including the `phoneDetail` component).
This means that our application would continue to work even if we didn't include `ngRoute` in the
list of dependencies for the `phoneDetail` component. Although it might be tempting to omit
dependencies of a sub-module that are already imported by the main module, it breaks our hard-earned
modularity.
<div class="alert alert-warning">
Imagine what would happen if we decided to copy the `phoneDetail` feature over to another project
that does not declare a dependency on `ngRoute`. The injector would not be able to provide
`$routeParams` and our application would break.
</div>
The takeaway here is:
* Always be explicit about the dependecies of a sub-module. Do not rely on dependencies inherited
from a parent module (because that parent module might not be there some day).
<div class="alert alert-success">
Declaring the same dependency in multiple modules does not incur extra "cost", because Angular
will still load each dependency once. For more info on modules and their dependencies take a look
at the [Modules](guide/module) section of the Developer Guide.
</div>
# Testing
Since some of our modules depend on {@link ngRoute ngRoute} now, it is necessary to update the Karma
configuration file with angular-route. Other than that, the unit tests should (still) pass without
any modification.
<br />
**`karma.conf.js`:**
```js
files: [
'bower_components/angular/angular.js',
'bower_components/angular-route/angular-route.js',
...
],
```
<br />
To automatically verify that everything is wired properly, we wrote E2E tests for navigating to
various URLs and verifying that the correct view was rendered.
<br />
**`e2e-tests/scenarios.js`**
```js
...
it('should redirect `index.html` to `index.html#!/phones', function() {
browser.get('index.html');
expect(browser.getLocationAbsUrl()).toBe('/phones');
});
...
describe('View: Phone list', function() {
beforeEach(function() {
browser.get('index.html#!/phones');
});
...
});
...
describe('View: Phone details', function() {
beforeEach(function() {
browser.get('index.html#!/phones/nexus-s');
});
it('should display placeholder page with `phoneId`', function() {
expect(element(by.binding('$ctrl.phoneId')).getText()).toBe('nexus-s');
});
});
...
```
You can now rerun `npm run protractor` to see the tests run (and hopefully pass).
# Experiments
* Let's experiment with some of the {@link api/ng/filter built-in Angular filters} and add the
following bindings to `index.html`:
* `{{ "lower cap string" | uppercase }}`
* `{{ {foo: "bar", baz: 23} | json }}`
* `{{ 1304375948024 | date }}`
* `{{ 1304375948024 | date:"MM/dd/yyyy @ h:mma" }}`
<div></div>
* We can also create a model with an input element, and combine it with a filtered binding. Add
the following to index.html:
* Try to add a `{{$ctrl.phoneId}` binding in the template string for the phone details view:
```html
<input ng-model="userInput"> Uppercased: {{ userInput | uppercase }}
```js
when('/phones/:phoneId', {
template: '{{$ctrl.phoneId}} <phone-detail></phone-detail>'
...
```
You will see that nothing happens, even when you are in the phone details view. This is because
the `phoneId` model is visible only in the context set by the `phoneDetail` component. Again,
component isolation at work!
# Summary
Now that you have learned how to write and test a custom filter, go to {@link step_10 step 10} to
learn how we can use Angular to enhance the phone details page further.
With the routing set up and the phone list view implemented, we are ready to go to
{@link step_10 step 10} and implement a proper phone details view.
<ul doc-tutorial-nav="9"></ul>
[bower]: http://bower.io
[deep-linking]: https://en.wikipedia.org/wiki/Deep_linking
[wiki-di]: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
+153 -119
View File
@@ -1,180 +1,214 @@
@ngdoc tutorial
@name 10 - Event Handlers
@name 10 - More Templating
@step 10
@description
<ul doc-tutorial-nav="10"></ul>
In this step, you will add a clickable phone image swapper to the phone details page.
In this step, we 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 are going to use {@link ng.$http $http} to fetch our data,
and then flesh out the `phoneDetail` component's template.
* 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.
<div doc-tutorial-reset="10"></div>
## Controller
__`app/js/controllers.js`:__
## Data
```js
...
var phonecatControllers = angular.module('phonecatControllers',[]);
In addition to `phones.json`, the `app/phones/` directory also contains one JSON file for each
phone:
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', '$http',
function($scope, $routeParams, $http) {
$http.get('phones/' + $routeParams.phoneId + '.json').success(function(data) {
$scope.phone = data;
$scope.mainImageUrl = data.images[0];
});
<br />
**`app/phones/nexus-s.json`:** (sample snippet)
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
};
}]);
```json
{
"additionalFeatures": "Contour Display, Near Field Communications (NFC), ...",
"android": {
"os": "Android 2.3",
"ui": "Android"
},
...
"images": [
"img/phones/nexus-s.0.jpg",
"img/phones/nexus-s.1.jpg",
"img/phones/nexus-s.2.jpg",
"img/phones/nexus-s.3.jpg"
],
"storage": {
"flash": "16384MB",
"ram": "512MB"
}
}
```
In the `PhoneDetailCtrl` controller, we created the `mainImageUrl` model property and set its
default value to the first phone image URL.
We also created a `setImage` event handler function that will change the value of `mainImageUrl`.
Each of these files describes various properties of the phone using the same data structure. We will
show this data in the phone details view.
## Template
## Component Controller
__`app/partials/phone-detail.html`:__
We will expand the `phoneDetail` component's controller by using the `$http` service to fetch the
appropriate JSON files. This works the same way as the `phoneList` component's controller.
<br />
**`app/phone-detail/phone-detail.component.js`:**
```js
angular.
module('phoneDetail').
component('phoneDetail', {
templateUrl: 'phone-detail/phone-detail.template.html',
controller: ['$http', '$routeParams',
function PhoneDetailController($http, $routeParams) {
var self = this;
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
self.phone = response.data;
});
}
]
});
```
To construct the URL for the HTTP request, we use `$routeParams.phoneId`, which is extracted from
the current route by the `$route` service.
## Component Template
The inline, TBD placeholder template has been replaced with a full blown external template,
including lists and bindings that comprise the phone details. Note how we use the Angular
`{{expression}}` markup and `ngRepeat` to project phone data from our model into the view.
<br />
**`app/phone-detail/phone-detail.template.html`:**
```html
<img ng-src="{{mainImageUrl}}" class="phone">
<img ng-src="{{$ctrl.phone.images[0]}}" class="phone" />
...
<h1>{{$ctrl.phone.name}}</h1>
<p>{{$ctrl.phone.description}}</p>
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}" ng-click="setImage(img)">
<li ng-repeat="img in $ctrl.phone.images">
<img ng-src="{{img}}" />
</li>
</ul>
<ul class="specs">
<li>
<span>Availability and Networks</span>
<dl>
<dt>Availability</dt>
<dd ng-repeat="availability in $ctrl.phone.availability">{{availability}}</dd>
</dl>
</li>
...
<li>
<span>Additional Features</span>
<dd>{{$ctrl.phone.additionalFeatures}}</dd>
</li>
</ul>
...
```
We bound the `ngSrc` directive of the large image to the `mainImageUrl` property.
<img class="diagram" src="img/tutorial/tutorial_10.png">
We also registered an {@link ng.directive:ngClick `ngClick`}
handler with thumbnail images. When a user clicks on one of the thumbnail images, the handler will
use the `setImage` event handler function to change the value of the `mainImageUrl` property to the
URL of the thumbnail image.
<div style="display: none">
TODO!
<img class="diagram" src="img/tutorial/tutorial_10-11_final.png">
</div>
# Testing
## Test
We wrote a new unit test that is similar to the one we wrote for the `phoneList` component's
controller in {@link step_07#testing step 7}.
To verify this new feature, we added two end-to-end tests. One verifies that the main image is set
to the first phone image by default. The second test clicks on several thumbnail images and
verifies that the main image changed appropriately.
__`test/e2e/scenarios.js`:__
<br />
**`app/phone-detail/phone-detail.component.spec.js`:**
```js
...
describe('Phone detail view', function() {
describe('phoneDetail', function() {
...
// Load the module that contains the `phoneDetail` component before each test
beforeEach(module('phoneDetail'));
it('should display the first phone image as the main phone image', function() {
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
// Test the controller
describe('PhoneDetailController', function() {
var $httpBackend, ctrl;
it('should swap main image if a thumbnail image is clicked on', function() {
element(by.css('.phone-thumbs li:nth-child(3) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
element(by.css('.phone-thumbs li:nth-child(1) img')).click();
expect(element(by.css('img.phone')).getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
});
```
You can now rerun `npm run protractor` to see the tests run.
You also have to refactor one of your unit tests because of the addition of the `mainImageUrl`
model property to the `PhoneDetailCtrl` controller. Below, we create the function `xyzPhoneData`
which returns the appropriate json with the `images` attribute in order to get the test to pass.
__`test/unit/controllersSpec.js`:__
```js
...
beforeEach(module('phonecatApp'));
...
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl,
xyzPhoneData = function() {
return {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
}
};
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
beforeEach(inject(function($componentController, _$httpBackend_, $routeParams) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
$httpBackend.expectGET('phones/xyz.json').respond({name: 'phone xyz'});
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
ctrl = $componentController('phoneDetail');
}));
it('should fetch the phone details', function() {
expect(ctrl.phone).toBeUndefined();
it('should fetch phone detail', function() {
expect(scope.phone).toBeUndefined();
$httpBackend.flush();
expect(scope.phone).toEqual(xyzPhoneData());
expect(ctrl.phone).toEqual({name: 'phone xyz'});
});
});
});
```
Your unit tests should now be passing.
You should now see the following output in the Karma tab:
```
Chrome 49.0: Executed 3 of 3 SUCCESS (0.159 secs / 0.136 secs)
```
We also added a new E2E test that navigates to the 'Nexus S' details page and verifies that the
heading on the page is "Nexus S".
<br />
**`e2e-tests/scenarios.js`**
```js
...
describe('View: Phone detail', function() {
beforeEach(function() {
browser.get('index.html#!/phones/nexus-s');
});
it('should display the `nexus-s` page', function() {
expect(element(by.binding('$ctrl.phone.name')).getText()).toBe('Nexus S');
});
});
...
```
You can run the tests with `npm run protractor`.
# Experiments
* Let's add a new controller method to `PhoneDetailCtrl`:
<div></div>
$scope.hello = function(name) {
alert('Hello ' + (name || 'world') + '!');
}
and add:
<button ng-click="hello('Elmo')">Hello</button>
to the `phone-detail.html` template.
<div style="display: none">
TODO!
The controller methods are inherited between controllers/scopes, so you can use the same snippet
in the `phone-list.html` template as well.
* Move the `hello` method from `PhoneCatCtrl` to `PhoneListCtrl` and you'll see that the button
declared in `index.html` will stop working, while the one declared in the `phone-list.html`
template remains operational.
</div>
* Using [Protractor's API][protractor-docs], write a test that verifies that we display 4 thumbnail
images on the 'Nexus S' details page.
# Summary
With the phone image swapper in place, we're ready for {@link step_11 step 11} to
learn an even better way to fetch data.
Now that the phone details view is in place, proceed to {@link step_11 step 11} to learn how to
write your own custom display filter.
<ul doc-tutorial-nav="10"></ul>
[protractor-docs]: https://angular.github.io/protractor/#/api
+119 -236
View File
@@ -1,293 +1,176 @@
@ngdoc tutorial
@name 11 - REST and Custom Services
@name 11 - Custom Filters
@step 11
@description
<ul doc-tutorial-nav="11"></ul>
In this step, you will change the way our app fetches data.
In this step you will learn how to create your own custom display filter.
* We define 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.
* In the previous step, the details page displayed either "true" or "false" to indicate whether
certain phone features were present or not. In this step, we are using a custom filter to convert
those text strings into glyphs: ✓ for "true", and ✘ for "false".
Let's see what the filter code looks like.
<div doc-tutorial-reset="11"></div>
## Dependencies
The RESTful functionality is provided by Angular in the `ngResource` module, which is distributed
separately from the core Angular framework.
## The `checkmark` Filter
We are using [Bower][bower] to install client side dependencies. This step updates the
`bower.json` configuration file to include the new dependency:
Since this filter is generic (i.e. it is not specific to any view or component), we are going to
register it in a `core` module, which contains "application-wide" features.
```
{
"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.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.1.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x",
"angular-resource": "1.4.x"
}
}
<br />
**`app/core/core.module.js`:**
```js
angular.module('core', []);
```
The new dependency `"angular-resource": "1.4.x"` tells bower to install a version of the
angular-resource component that is compatible with version 1.4.x. We must ask bower to download
and install this dependency. We can do this by running:
<br />
**`app/core/checkmark/checkmark.filter.js`:**
```js
angular.
module('core').
filter('checkmark', function() {
return function(input) {
return input ? '\u2713' : '\u2718';
};
});
```
npm install
```
<div class="alert alert-warning">
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then you may have a
problem with the `bower install` due to a conflict between the versions of angular.js that need to
be installed. If you get this then simply delete your `app/bower_components` folder before running
`npm install`.
</div>
<div class="alert alert-info">
**Note:** 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.
As you may have noticed, we (unsurprisingly) gave our file a `.filter` suffix.
</div>
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
return one of the two unicode characters we have chosen to represent true (`\u2713` -> ✓) and false
(`\u2718` -> ✘).
## Template
Now that our filter is ready, we need to register the `core` module as a dependency of our main
`phonecatApp` module.
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 module:ngResource ngResource} module:
<br />
**`app/app.module.js`:**
__`app/index.html`.__
```js
angular.module('phonecatApp', [
...
'core',
...
]);
```
## Templates
Since we have created two new files (**core.module.js**, **checkmark.filter.js**), we need to
include them in our layout template.
<br />
**`app/index.html`:**
```html
...
<script src="bower_components/angular-resource/angular-resource.js"></script>
<script src="js/services.js"></script>
...
...
<script src="core/core.module.js"></script>
<script src="core/checkmark/checkmark.filter.js"></script>
...
```
## Service
The syntax for using filters in Angular templates is as follows:
We create our own service to provide access to the phone data on the server:
__`app/js/services.js`.__
```js
var phonecatServices = angular.module('phonecatServices', ['ngResource']);
phonecatServices.factory('Phone', ['$resource',
function($resource){
return $resource('phones/:phoneId.json', {}, {
query: {method:'GET', params:{phoneId:'phones'}, isArray:true}
});
}]);
```
{{expression | filter}}
```
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 to be injected via function
arguments. The Phone service declared a dependency on the `$resource` service.
Let's employ the filter in the phone details template:
The {@link ngResource.$resource `$resource`} service makes it easy to create a
[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.
<br />
**`app/phone-detail/phone-detail.template.html`:**
__`app/js/app.js`.__
```js
...
angular.module('phonecatApp', ['ngRoute', 'phonecatControllers','phonecatFilters', 'phonecatServices']).
...
```html
...
<dl>
<dt>Infrared</dt>
<dd>{{$ctrl.phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{$ctrl.phone.connectivity.gps | checkmark}}</dd>
</dl>
...
```
We need to add the 'phonecatServices' module dependency to 'phonecatApp' module's requires array.
# Testing
## Controller
Filters, like any other code, should be tested. Luckily, these tests are very easy to write.
We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
lower-level {@link ng.$http $http} service, replacing it with a new service called
`Phone`. Angular's {@link ngResource.$resource `$resource`} service is easier to
use than `$http` for interacting with data sources exposed as RESTful resources. It is also easier
now to understand what the code in our controllers is doing.
__`app/js/controllers.js`.__
<br />
**`app/core/checkmark/checkmark.filter.spec.js`:**
```js
var phonecatControllers = angular.module('phonecatControllers', []);
describe('checkmark', function() {
...
beforeEach(module('core'));
phonecatControllers.controller('PhoneListCtrl', ['$scope', 'Phone', function($scope, Phone) {
$scope.phones = Phone.query();
$scope.orderProp = 'age';
}]);
it('should convert boolean values to unicode checkmark or cross',
inject(function(checkmarkFilter) {
expect(checkmarkFilter(true)).toBe('\u2713');
expect(checkmarkFilter(false)).toBe('\u2718');
})
);
phonecatControllers.controller('PhoneDetailCtrl', ['$scope', '$routeParams', 'Phone', function($scope, $routeParams, Phone) {
$scope.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
$scope.mainImageUrl = phone.images[0];
});
$scope.setImage = function(imageUrl) {
$scope.mainImageUrl = imageUrl;
}
}]);
```
Notice how in `PhoneListCtrl` we replaced:
$http.get('phones/phones.json').success(function(data) {
$scope.phones = data;
});
with:
$scope.phones = Phone.query();
This is a simple statement that we want to query for all phones.
An important thing to notice in the code above is that we don't pass any callback functions when
invoking methods of our Phone service. Although it looks as if the result were returned
synchronously, that is not the case at all. What is returned synchronously is a "future" — an
object, which will be filled with data when the XHR response returns. Because of the data-binding
in Angular, we can use this future and bind it to our template. Then, when the data arrives, the
view will automatically update.
Sometimes, relying on the future object and data-binding alone is not sufficient to do everything
we require, so in these cases, we can add a callback to process the server response. The
`PhoneDetailCtrl` controller illustrates this by setting the `mainImageUrl` in a callback.
## Test
Because we're now using the {@link ngResource ngResource} module, it's necessary 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.
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][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() {
beforeEach(function(){
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
beforeEach(module('phonecatApp'));
beforeEach(module('phonecatServices'));
describe('PhoneListCtrl', function(){
var scope, ctrl, $httpBackend;
beforeEach(inject(function(_$httpBackend_, $rootScope, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/phones.json').
respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
scope = $rootScope.$new();
ctrl = $controller('PhoneListCtrl', {$scope: scope});
}));
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(scope.phones).toEqualData([]);
$httpBackend.flush();
expect(scope.phones).toEqualData(
[{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
});
it('should set the default value of orderProp model', function() {
expect(scope.orderProp).toBe('age');
});
});
describe('PhoneDetailCtrl', function(){
var scope, $httpBackend, ctrl,
xyzPhoneData = function() {
return {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
}
};
beforeEach(inject(function(_$httpBackend_, $rootScope, $routeParams, $controller) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData());
$routeParams.phoneId = 'xyz';
scope = $rootScope.$new();
ctrl = $controller('PhoneDetailCtrl', {$scope: scope});
}));
it('should fetch phone detail', function() {
expect(scope.phone).toEqualData({});
$httpBackend.flush();
expect(scope.phone).toEqualData(xyzPhoneData());
});
});
});
```
The call to `beforeEach(module('core'))` loads the `core` module (which contains the `checkmark`
filter) into the injector, before every test.
Note that we call the helper function `inject(function(checkmarkFilter) {...})`, to get access to
the filter that we want to test. See also {@link angular.mock.inject angular.mock.inject()}.
<div class="alert alert-info">
When injecting a filter, we need to suffix the filter name with 'Filter'. For example, our
`checkmark` filter is injected as `checkmarkFilter`.
See the [Filters](guide/filter#using-filters-in-controllers-services-and-directives) section of
the Developer Guide for more info.
</div>
You should now see the following output in the Karma tab:
<pre>Chrome 22.0: Executed 5 of 5 SUCCESS (0.038 secs / 0.01 secs)</pre>
```
Chrome 49.0: Executed 4 of 4 SUCCESS (0.091 secs / 0.075 secs)
```
# Experiments
<div></div>
* Let's experiment with some of the {@link api/ng/filter built-in Angular filters}.
Add the following bindings to `index.html`:
* `{{'lower cap string' | uppercase}}`
* `{{{foo: 'bar', baz: 42} | json}}`
* `{{1459461289000 | date}}`
* `{{1459461289000 | 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`:
```html
<input ng-model="userInput" /> Uppercased: {{userInput | uppercase}}
```
# Summary
Now that we've seen how to build a custom service as a RESTful client, we're ready for {@link step_12 step 12} (the last step!) to
learn how to improve this application with animations.
Now that we have learned how to write and test a custom filter, let's go to {@link step_12 step 12}
to learn how we can use Angular to enhance the phone details page further.
<ul doc-tutorial-nav="11"></ul>
[restful]: http://en.wikipedia.org/wiki/Representational_State_Transfer
[jasmine-matchers]: http://jasmine.github.io/1.3/introduction.html#section-Matchers
[bower]: http://bower.io/
+131 -484
View File
@@ -1,538 +1,185 @@
@ngdoc tutorial
@name 12 - Applying Animations
@name 12 - Event Handlers
@step 12
@description
<ul doc-tutorial-nav="12"></ul>
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.
In this step, you will add a clickable phone image swapper to the phone details page.
* 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.
* We now use the `ngAnimate` module to enable animations throughout the application.
* We also use common `ng` directives to 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 ngRepeat `ngRepeat`} or adding and removing classes on
{@link ngClass `ngClass`}).
<div doc-tutorial-reset="12"></div>
## Dependencies
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.
## Component Controller
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.4.x",
"angular-mocks": "1.4.x",
"jquery": "~2.1.1",
"bootstrap": "~3.1.1",
"angular-route": "1.4.x",
"angular-resource": "1.4.x",
"angular-animate": "1.4.x"
}
}
```
* `"angular-animate": "1.4.x"` tells bower to install a version of the
angular-animate component that is compatible with version 1.4.x.
* `"jquery": "~2.1.1"` tells bower to install the 2.1.1 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-warning">
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then you may have a
problem with the `bower install` due to a conflict between the versions of angular.js that need to
be installed. If you get this then simply delete your `app/bower_components` folder before running
`npm install`.
</div>
<div class="alert alert-info">
**Note:** 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`
To get an idea of how animations work with AngularJS, please read the
{@link guide/animations AngularJS Animation Guide} first.
## Template
The changes required within the HTML template code is to link the asset files which define the animations as
well as the `angular-animate.js` file. The animation module, known as {@link module:ngAnimate `ngAnimate`}, is
defined within `angular-animate.js` and contains the code necessary to make your application become animation
aware.
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="bower_components/jquery/dist/jquery.js"></script>
...
<!-- required module to enable animation support in AngularJS -->
<script src="bower_components/angular-animate/angular-animate.js"></script>
<!-- for JavaScript Animations -->
<script src="js/animations.js"></script>
...
```
<div class="alert alert-error">
**Important:** Be sure to use jQuery version 2.1 or newer when using Angular 1.4; jQuery 1.x is
not officially supported.
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`).
But before we start, let's create a new module which uses the ngAnimate module as a dependency just like we did before
with `ngResource`.
## Module & Animations
__`app/js/animations.js`.__
<br />
**`app/phone-detail/phone-detail.component.js`:**
```js
angular.module('phonecatAnimations', ['ngAnimate']);
// ...
// this module will later be used to define animations
// ...
...
controller: ['$http', '$routeParams',
function PhoneDetailController($http, $routeParams) {
var self = this;
self.setImage = function setImage(imageUrl) {
self.mainImageUrl = imageUrl;
};
$http.get('phones/' + $routeParams.phoneId + '.json').then(function(response) {
self.phone = response.data;
self.setImage(self.phone.images[0]);
});
}
]
...
```
And now let's attach this module to our application module...
In the `phoneDetail` component's controller, we created the `mainImageUrl` model property and set
its default value to the first phone image URL.
__`app/js/app.js`.__
```js
// ...
angular.module('phonecatApp', [
'ngRoute',
'phonecatAnimations',
'phonecatControllers',
'phonecatFilters',
'phonecatServices',
]);
// ...
```
Now, the phonecat module is animation aware. Let's make some animations!
We also created a `setImage()` method (to be used as event handler), that will change the value of
`mainImageUrl`.
## Animating ngRepeat with CSS Transition Animations
## Component Template
We'll start off by adding CSS transition animations to our `ngRepeat` directive present on the `phone-list.html` page.
First let's add an extra CSS class to our repeated element so that we can hook into it with our CSS animation code.
__`app/partials/phone-list.html`.__
<br />
**`app/phone-detail/phone-detail.template.html`:**
```html
<!--
Let's change the repeater HTML to include a new CSS class
which we will later use for animations:
-->
<ul class="phones">
<li ng-repeat="phone in phones | filter:query | orderBy:orderProp"
class="thumbnail phone-listing">
<a href="#/phones/{{phone.id}}" class="thumb"><img ng-src="{{phone.imageUrl}}"></a>
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
```
Notice how we added the `phone-listing` CSS class? This is all we need in our HTML code to get animations working.
Now for the actual CSS transition animation code:
__`app/css/animations.css`__
```css
.phone-listing.ng-enter,
.phone-listing.ng-leave,
.phone-listing.ng-move {
-webkit-transition: 0.5s linear all;
-moz-transition: 0.5s linear all;
-o-transition: 0.5s linear all;
transition: 0.5s linear all;
}
.phone-listing.ng-enter,
.phone-listing.ng-move {
opacity: 0;
height: 0;
overflow: hidden;
}
.phone-listing.ng-move.ng-move-active,
.phone-listing.ng-enter.ng-enter-active {
opacity: 1;
height: 120px;
}
.phone-listing.ng-leave {
opacity: 1;
overflow: hidden;
}
.phone-listing.ng-leave.ng-leave-active {
opacity: 0;
height: 0;
padding-top: 0;
padding-bottom: 0;
}
```
As you can see our `phone-listing` CSS class is combined together with the animation hooks that occur when items are
inserted into and removed from the list:
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered on the page.
* The `ng-move` class is applied when items are moved around in the list.
* The `ng-leave` class is applied when they're removed from the list.
The phone listing items are added and removed depending on the data passed to the `ng-repeat` attribute.
For example, if the filter data changes, the items will be animated in and out of the repeat list.
Something important to note is that when an animation occurs, two sets of CSS classes
are added to the element:
1. a "starting" class that represents the style at the beginning of the animation
2. an "active" class that represents the style at the end of the animation
The name of the starting class is the name of the event that is fired (like `enter`, `move` or `leave`) prefixed with
`ng-`. So an `enter` event will result in a class called `ng-enter`.
The active class name is the same as the starting class's but with an `-active` suffix.
This two-class CSS naming convention allows the developer to craft an animation, beginning to end.
In our example above, elements are expanded from a height of **0** to **120 pixels** when they're added to the
list and are collapsed back down to **0 pixels** before being removed from the list.
There's also a nice fade-in and fade-out effect that occurs at the same time. All of this is handled
by the CSS transition declarations at the top of the example code above.
Although most modern browsers have good support for [CSS transitions](http://caniuse.com/#feat=css-transitions)
and [CSS animations](http://caniuse.com/#feat=css-animation), IE9 and earlier do not.
If you want animations that are backwards-compatible with older browsers, consider using JavaScript-based animations,
which are described in detail below.
## Animating `ngView` with CSS Keyframe Animations
Next let's add an animation for transitions between route changes in {@link ngRoute.directive:ngView `ngView`}.
To start, let's add a new CSS class to our HTML like we did in the example above.
This time, instead of the `ng-repeat` element, let's add it to the element containing the `ng-view` directive.
In order to do this, we'll have to make some small changes to the HTML code so that we can have more control over our
animations between view changes.
__`app/index.html`.__
```html
<div class="view-container">
<div ng-view class="view-frame"></div>
</div>
```
With this change, the `ng-view` directive is nested inside a parent element with
a `view-container` CSS class. This class adds a `position: relative` style so that the positioning of the `ng-view`
is relative to this parent as it animates transitions.
With this in place, let's add the CSS for this transition animation to our `animations.css` file:
__`app/css/animations.css`.__
```css
.view-container {
position: relative;
}
.view-frame.ng-enter, .view-frame.ng-leave {
background: white;
position: absolute;
top: 0;
left: 0;
right: 0;
}
.view-frame.ng-enter {
-webkit-animation: 0.5s fade-in;
-moz-animation: 0.5s fade-in;
-o-animation: 0.5s fade-in;
animation: 0.5s fade-in;
z-index: 100;
}
.view-frame.ng-leave {
-webkit-animation: 0.5s fade-out;
-moz-animation: 0.5s fade-out;
-o-animation: 0.5s fade-out;
animation: 0.5s fade-out;
z-index:99;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@-moz-keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@-webkit-keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@-moz-keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@-webkit-keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
/* 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, causing it to rerender and
reposition 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).
## Animating `ngClass` with JavaScript
Let's add another animation to our application. Switching to our `phone-detail.html` page,
we see that we have a nice thumbnail swapper. By hovering over 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 hover over 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.
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. Notice that we
have changed the way we display our large image:
__`app/partials/phone-detail.html`.__
```html
<!-- We're only changing the top of the file -->
<div class="phone-images">
<img ng-src="{{img}}"
class="phone"
ng-repeat="img in phone.images"
ng-class="{active:mainImageUrl==img}">
</div>
<h1>{{phone.name}}</h1>
<p>{{phone.description}}</p>
<img ng-src="{{$ctrl.mainImageUrl}}" class="phone" />
...
<ul class="phone-thumbs">
<li ng-repeat="img in phone.images">
<img ng-src="{{img}}" ng-mouseenter="setImage(img)">
<li ng-repeat="img in $ctrl.phone.images">
<img ng-src="{{img}}" ng-click="$ctrl.setImage(img)" />
</li>
</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.
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;
}
```
We bound the `ngSrc` directive of the large image to the `$ctrl.mainImageUrl` property.
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.
We also registered an {@link ng.directive:ngClick ngClick} handler with thumbnail images. When a
user clicks on one of the thumbnail images, the handler will use the `$ctrl.setImage()` method
callback to change the value of the `$ctrl.mainImageUrl` property to the URL of the clicked
thumbnail image.
__`app/js/animations.js`.__
<img class="diagram" src="img/tutorial/tutorial_12.png">
# Testing
To verify this new feature, we added two E2E tests. One verifies that `mainImageUrl` is set to the
first phone image URL by default. The second test clicks on several thumbnail images and verifies
that the main image URL changes accordingly.
<br />
**`e2e-tests/scenarios.js`:**
```js
var phonecatAnimations = angular.module('phonecatAnimations', ['ngAnimate']);
...
phonecatAnimations.animation('.phone', function() {
describe('View: Phone detail', function() {
var animateUp = function(element, className, done) {
if(className != 'active') {
return;
}
element.css({
position: 'absolute',
top: 500,
left: 0,
display: 'block'
...
it('should display the first phone image as the main phone image', function() {
var mainImage = element(by.css('img.phone'));
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
jQuery(element).animate({
top: 0
}, done);
it('should swap the main image when clicking on a thumbnail image', function() {
var mainImage = element(by.css('img.phone'));
var thumbnails = element.all(by.css('.phone-thumbs img'));
return function(cancel) {
if(cancel) {
element.stop();
}
};
}
thumbnails.get(2).click();
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.2.jpg/);
var animateDown = function(element, className, done) {
if(className != 'active') {
return;
}
element.css({
position: 'absolute',
left: 0,
top: 0
thumbnails.get(0).click();
expect(mainImage.getAttribute('src')).toMatch(/img\/phones\/nexus-s.0.jpg/);
});
jQuery(element).animate({
top: -500
}, done);
});
return function(cancel) {
if(cancel) {
element.stop();
}
};
}
return {
addClass: animateUp,
removeClass: animateDown
};
});
...
```
Note that we're using [jQuery](http://jquery.com/) to implement the animation. jQuery
isn't required to do JavaScript animations with AngularJS, but we're going to use it because writing
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/).
You can now rerun the tests with `npm run protractor`.
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
`done` callback function. The purpose of `done` is so you can let Angular know when the JavaScript
animation has ended by calling it.
We also have to refactor one of our unit tests, because of the addition of the `mainImageUrl` model
property to the controller. As previously, we will use a mocked response.
The `removeClass` callback works the same way, but instead gets triggered when a class is removed
from the element.
<br />
**`app/phone-detail/phone-detail.component.spec.js`:**
Within your JavaScript callback, you create the animation by manipulating the DOM. In the code above,
that's what the `element.css()` and the `element.animate()` are doing. The callback positions the next
element with an offset of `500 pixels` and animates both the previous and the new items together by
shifting each item up `500 pixels`. This results in a conveyor-belt like animation. After the `animate`
function does its business, it calls `done`.
```js
...
Notice that `addClass` and `removeClass` each return a function. This is an **optional** function that's
called when the animation is cancelled (when another animation takes place on the same element)
as well as when the animation has completed. A boolean parameter is passed into the function which
lets the developer know if the animation was cancelled or not. This function can be used to
do any cleanup necessary for when the animation finishes.
describe('controller', function() {
var $httpBackend, ctrl
var xyzPhoneData = {
name: 'phone xyz',
images: ['image/url1.png', 'image/url2.png']
};
beforeEach(inject(function($componentController, _$httpBackend_, _$routeParams_) {
$httpBackend = _$httpBackend_;
$httpBackend.expectGET('phones/xyz.json').respond(xyzPhoneData);
...
}));
it('should fetch phone details', function() {
expect(ctrl.phone).toBeUndefined();
$httpBackend.flush();
expect(ctrl.phone).toEqual(xyzPhoneData);
});
});
...
```
Our unit tests should now be passing again.
# Experiments
<div></div>
* Similar to the `ngClick` directive, which binds an Angular expression to the `click` event, there
are built-in directives for all native events, such as `dblclick`, `focus`/`blur`, mouse and key
events, etc.
Let's add a new controller method to the `phoneDetail` component's controller:
```js
self.onDblclick = function onDblclick(imageUrl) {
alert('You double-clicked image: ' + imageUrl);
};
```
and add the following to the `<img>` element in `phone-detail.template.html`:
```html
<img ... ng-dblclick="$ctrl.onDblclick(img)" />
```
Now, whenever you double-click on a thumbnail, an alert pops-up. Pretty annoying!
# Summary
There you have it! We have created a web app in a relatively short amount of time. In the {@link
the_end closing notes} we'll cover where to go from here.
With the phone image swapper in place, we are ready for {@link step_13 step 13} to learn an even
better way to fetch data.
<ul doc-tutorial-nav="12"></ul>
[bower]: http://bower.io/
+321
View File
@@ -0,0 +1,321 @@
@ngdoc tutorial
@name 13 - REST and Custom Services
@step 13
@description
<ul doc-tutorial-nav="13"></ul>
In this step, we will change the way our application fetches data.
* We define a custom service that represents a [RESTful][restful] client. Using this client we can
make requests for data to the server 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="13"></div>
## Dependencies
The RESTful functionality is provided by Angular in the {@link ngResource ngResource} module, which
is distributed separately from the core Angular framework.
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
`bower.json` configuration file to include the new dependency:
<br />
**`bower.json`:**
```
{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.5.x",
"angular-mocks": "1.5.x",
"angular-resource": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x"
}
}
```
The new dependency `"angular-resource": "1.5.x"` tells bower to install a version of the
angular-resource module that is compatible with version 1.5.x of Angular. We must tell bower to
download and install this dependency.
```
npm install
```
<div class="alert alert-info">
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
we have preconfigured `npm install` to run bower for us.
</div>
<div class="alert alert-warning">
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then
you may have a problem with the `bower install` due to a conflict between the versions of
angular.js that need to be installed. If you run into this issue, simply delete your
`app/bower_components` directory and then run `npm install`.
</div>
## Service
We create our own service to provide access to the phone data on the server. We will put the service
in its own module, under `core`, so we can explicitly declare its dependency on `ngResource`:
<br />
**`app/core/phone/phone.module.js`:**
```js
angular.module('core.phone', ['ngResource']);
```
<br />
**`app/core/phone/phone.service.js`:**
```js
angular.
module('core.phone').
factory('Phone', ['$resource',
function($resource) {
return $resource('phones/:phoneId.json', {}, {
query: {
method: 'GET',
params: {phoneId: 'phones'},
isArray: true
}
});
}
]);
```
We used the {@link angular.Module module API} to register a custom service using a factory function.
We passed in the name of the service &mdash; `'Phone'` &mdash; and the factory function. The factory
function is similar to a controller's constructor in that both can declare dependencies to be
injected via function arguments. The `Phone` service declares a dependency on the `$resource`
service, provided by the `ngResource` module.
The {@link ngResource.$resource $resource} service makes it easy to create a [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.
<br />
**`app/core/core.module.js`:**
```js
angular.module('core', ['core.phone']);
```
We need to add the `core.phone` module as a dependency of the `core` module.
## Template
Our custom resource service will be defined in `app/core/phone/phone.service.js`, so we need to
include this file and the associated `.module.js` file in our layout template. Additionally, we also
need to load the `angular-resource.js` file, which contains the `ngResource` module:
<br />
**`app/index.html`:**
```html
<head>
...
<script src="bower_components/angular-resource/angular-resource.js"></script>
...
<script src="core/phone/phone.module.js"></script>
<script src="core/phone/phone.service.js"></script>
...
</head>
```
## Component Controllers
We can now simplify our component controllers (`PhoneListController` and `PhoneDetailController`) by
factoring out the lower-level `$http` service, replacing it with the new `Phone` service. Angular's
`$resource` service is easier to use than `$http` for interacting with data sources exposed as
RESTful resources. It is also easier now to understand what the code in our controllers is doing.
<br />
**`app/phone-list/phone-list.module.js`:**
```js
angular.module('phoneList', ['core.phone']);
```
<br />
**`app/phone-list/phone-list.component.js`:**
```js
angular.
module('phoneList').
component('phoneList', {
templateUrl: 'phone-list/phone-list.template.html',
controller: ['Phone',
function PhoneListController(Phone) {
this.phones = Phone.query();
this.orderProp = 'age';
}
]
});
```
<br />
**`app/phone-detail/phone-detail.module.js`:**
```js
angular.module('phoneDetail', ['core.phone']);
```
<br />
**`app/phone-detail/phone-detail.component.js`:**
```js
angular.
module('phoneDetail').
component('phoneDetail', {
templateUrl: 'phone-detail/phone-detail.template.html',
controller: ['$routeParams', 'Phone',
function PhoneDetailController($routeParams, Phone) {
var self = this;
self.phone = Phone.get({phoneId: $routeParams.phoneId}, function(phone) {
self.setImage(phone.images[0]);
});
self.setImage = function setImage(imageUrl) {
self.mainImageUrl = imageUrl;
};
}
]
});
```
Notice how in `PhoneListController` we replaced:
```js
$http.get('phones/phones.json').then(function(response) {
self.phones = response.data;
});
```
with just:
```js
this.phones = Phone.query();
```
This is a simple and declarative statement that we want to query for all phones.
An important thing to notice in the code above is that we don't pass any callback functions, when
invoking methods of our `Phone` service. Although it looks as if the results were returned
synchronously, that is not the case at all. What is returned synchronously is a "future" — an
object, which will be filled with data, when the XHR response is received. Because of the
data-binding in Angular, we can use this future and bind it to our template. Then, when the data
arrives, the view will be updated automatically.
Sometimes, relying on the future object and data-binding alone is not sufficient to do everything
we require, so in these cases, we can add a callback to process the server response. The
`phoneDetail` component's controller illustrates this by setting the `mainImageUrl` in a callback.
## Testing
Because we are now using the {@link ngResource ngResource} module, it is necessary to update the
Karma configuration file with angular-resource.
<br />
**`karma.conf.js`:**
```js
files: [
'bower_components/angular/angular.js',
'bower_components/angular-resource/angular-resource.js',
...
],
```
We have added a unit test to verify that our new service is issuing HTTP requests and returns the
expected "future" objects/arrays.
The {@link ngResource.$resource $resource} service augments the response object with extra methods
&mdash; e.g. for updating and deleting the resource &mdash; and properties (some of which are only
meant to be accessed by Angular). If we were to use Jasmine's standard `.toEqual()` matcher, our
tests would fail, because the test values would not match the responses exactly.
To solve the problem, we instruct Jasmine to use a [custom equality tester][jasmine-equality] for
comparing objects. We specify {@link angular.equals angular.equals} as our equality tester, which
ignores functions and `$`-prefixed properties, such as those added by the `$resource` service.<br />
(Remember that Angular uses the `$` prefix for its proprietary API.)
<br />
**`app/core/phone/phone.service.spec.js`:**
```js
describe('Phone', function() {
...
var phonesData = [...];
// Add a custom equality tester before each test
beforeEach(function() {
jasmine.addCustomEqualityTester(angular.equals);
});
// Load the module that contains the `Phone` service before each test
...
// Instantiate the service and "train" `$httpBackend` before each test
...
// Verify that there are no outstanding expectations or requests after each test
afterEach(function () {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
it('should fetch the phones data from `/phones/phones.json`', function() {
var phones = Phone.query();
expect(phones).toEqual([]);
$httpBackend.flush();
expect(phones).toEqual(phonesData);
});
});
```
Here we are using `$httpBackend`'s
{@link ngMock.$httpBackend#verifyNoOutstandingExpectation verifyNoOutstandingExpectation()} and
{@link ngMock.$httpBackend#verifyNoOutstandingExpectation verifyNoOutstandingRequest()} methods to
verify that all expected requests have been sent and that no extra request is scheduled for later.
Note that we have also modified our component tests to use the custom matcher when appropriate.
You should now see the following output in the Karma tab:
```
Chrome 49.0: Executed 5 of 5 SUCCESS (0.123 secs / 0.104 secs)
```
# Summary
Now that we have seen how to build a custom service as a RESTful client, we are ready for
{@link step_14 step 14} to learn how to enhance the user experience with animations.
<ul doc-tutorial-nav="13"></ul>
[bower]: http://bower.io/
[jasmine-equality]: https://jasmine.github.io/2.4/custom_equality.html
[restful]: https://en.wikipedia.org/wiki/Representational_State_Transfer
+564
View File
@@ -0,0 +1,564 @@
@ngdoc tutorial
@name 14 - Animations
@step 14
@description
<ul doc-tutorial-nav="14"></ul>
In this step, we will enhance our web application by adding CSS and JavaScript animations on top of
the template code we created earlier.
* We now use the {@link ngAnimate ngAnimate} module to enable animations throughout the application.
* We also rely on built-in directives to automatically trigger hooks for animations to tap into.
* When an animation is found, it will run along with the actual DOM operation that is being issued
on the element at the given time (e.g. inserting/removing nodes on {@link ngRepeat ngRepeat} or
adding/removing classes on {@link ngClass ngClass}).
<div doc-tutorial-reset="14"></div>
## Dependencies
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][jquery] in this project
to do extra JavaScript animations.
Since we are using [Bower][bower] to install client-side dependencies, this step updates the
`bower.json` configuration file to include the new dependencies:
<br />
**`bower.json`:**
```
{
"name": "angular-phonecat",
"description": "A starter project for AngularJS",
"version": "0.0.0",
"homepage": "https://github.com/angular/angular-phonecat",
"license": "MIT",
"private": true,
"dependencies": {
"angular": "1.5.x",
"angular-animate": "1.5.x",
"angular-mocks": "1.5.x",
"angular-resource": "1.5.x",
"angular-route": "1.5.x",
"bootstrap": "3.3.x",
"jquery": "2.2.x"
}
}
```
* `"angular-animate": "1.5.x"` tells bower to install a version of the angular-animate module that
is compatible with version 1.5.x of Angular.
* `"jquery": "2.2.x"` tells bower to install the latest patch release of the 2.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.
Now, we must tell bower to download and install these dependencies.
```
npm install
```
<div class="alert alert-info">
**Note:** If you have bower installed globally, you can run `bower install`, but for this project
we have preconfigured `npm install` to run bower for us.
</div>
<div class="alert alert-warning">
**Warning:** If a new version of Angular has been released since you last ran `npm install`, then
you may have a problem with the `bower install` due to a conflict between the versions of
angular.js that need to be installed. If you run into this issue, simply delete your
`app/bower_components` directory and then run `npm install`.
</div>
## How Animations work with `ngAnimate`
To get an idea of how animations work with AngularJS, you might want to read the
[Animations](guide/animations) section of the Developer Guide first.
## Template
In order to enable animations, we need to update `index.html`, loading the necessary dependencies
(**angular-animate.js** and **jquery.js**) and the files that contain the CSS and JavaScript code
used in CSS/JavaScript animations. The animation module, {@link ngAnimate ngAnimate}, contains the
code necessary to make your application "animation aware".
<br />
**`app/index.html`:**
```html
...
<!-- Defines CSS necessary for animations -->
<link rel="stylesheet" href="app.animations.css" />
...
<!-- Used for JavaScript animations (include this before angular.js) -->
<script src="bower_components/jquery/dist/jquery.js"></script>
...
<!-- Adds animation support in AngularJS -->
<script src="bower_components/angular-animate/angular-animate.js"></script>
<!-- Defines JavaScript animations -->
<script src="app.animations.js"></script>
...
```
<div class="alert alert-error">
**Important:** Be sure to use jQuery version 2.1 or newer, when using Angular 1.5; jQuery 1.x is
not officially supported.
In order for Angular to detect jQuery and take advantage of it, make sure to include `jquery.js`
before `angular.js`.
</div>
Animations can now be created within the CSS code (`app.animations.css`) as well as the JavaScript
code (`app.animations.js`).
## Dependencies
We need to add a dependency on `ngAnimate` to our main module first:
<br />
**`app/app.module.js`:**
```js
angular.
module('phonecatApp', [
'ngAnimate',
...
]);
```
Now that our application is "animation aware", let's create some fancy animations!
## CSS Transition Animations: Animating `ngRepeat`
We will start off by adding CSS transition animations to our `ngRepeat` directive present on the
`phoneList` component's template. We need to add an extra CSS class to our repeated element, in
order to be able to hook into it with our CSS animation code.
<br />
**`app/phone-list/phone-list.template.html`:**
```html
...
<ul class="phones">
<li ng-repeat="phone in $ctrl.phones | filter:$ctrl.query | orderBy:$ctrl.orderProp"
class="thumbnail phone-list-item">
<a href="#!/phones/{{phone.id}}" class="thumb">
<img ng-src="{{phone.imageUrl}}" alt="{{phone.name}}" />
</a>
<a href="#!/phones/{{phone.id}}">{{phone.name}}</a>
<p>{{phone.snippet}}</p>
</li>
</ul>
...
```
Did you notice the added `phone-list-item` CSS class? This is all we need in our HTML code to get
animations working.
Now for the actual CSS transition animation code:
<br />
**`app/app.animations.css`:**
```css
.phone-list-item.ng-enter,
.phone-list-item.ng-leave,
.phone-list-item.ng-move {
transition: 0.5s linear all;
}
.phone-list-item.ng-enter,
.phone-list-item.ng-move {
height: 0;
opacity: 0;
overflow: hidden;
}
.phone-list-item.ng-enter.ng-enter-active,
.phone-list-item.ng-move.ng-move-active {
height: 120px;
opacity: 1;
}
.phone-list-item.ng-leave {
opacity: 1;
overflow: hidden;
}
.phone-list-item.ng-leave.ng-leave-active {
height: 0;
opacity: 0;
padding-bottom: 0;
padding-top: 0;
}
```
As you can see, our `phone-list-item` CSS class is combined together with the animation hooks that
occur when items are inserted into and removed from the list:
* The `ng-enter` class is applied to the element when a new phone is added to the list and rendered
on the page.
* The `ng-move` class is applied to the element when a phone's relative position in the list
changes.
* The `ng-leave` class is applied to the element when a phone is removed from the list.
The phone list items are added and removed based on the data passed to the `ngRepeat` directive.
For example, if the filter data changes, the items will be animated in and out of the repeat list.
Something important to note is that, when an animation occurs, two sets of CSS classes are added to
the element:
1. A "starting" class that represents the style at the beginning of the animation.
2. An "active" class that represents the style at the end of the animation.
The name of the starting class is the name of the event that is fired (like `enter`, `move` or
`leave`) prefixed with `ng-`. So an `enter` event will result in adding the `ng-enter` class.
The active class name is derived from the starting class by appending an `-active` suffix.
This two-class CSS naming convention allows the developer to craft an animation, beginning to end.
In the example above, animated elements are expanded from a height of **0px** to **120px** when they
are added to the list and are collapsed back down to **0px** before being removed from the list.
There is also a catchy fade-in/fade-out effect that occurs at the same time. All this is handled by
the CSS transition declaration at the top of the CSS file.
<div class="alert alert-warning">
Although all modern browsers have good support for [CSS transitions][caniuse-css-transitions] and
[CSS animations][caniuse-css-animation], IE9 and earlier IE versions do not.
If you want animations that are backwards-compatible with older browsers, consider using
JavaScript-based animations, which are demonstrated below.
</div>
## CSS Keyframe Animations: Animating `ngView`
Next, let's add an animation for transitions between route changes in
{@link ngRoute.directive:ngView ngView}.
Again, we need to prepare our HTML template by adding a new CSS class, this time to the `ng-view`
element. In order to gain more "expressive power" for our animations, we will also wrap the
`[ng-view]` element in a container element.
<br />
**`app/index.html`:**
```html
<div class="view-container">
<div ng-view class="view-frame"></div>
</div>
```
We have applied a `position: relative` CSS style to the `.view-container` wrapper, so that it is
easier for us to manage the `.view-frame` element's positioning during the animation.
With our preparation code in place, let's move on to the actual CSS styles for this transition
animation.
<br />
**`app/app.animations.css`:**
```css
...
.view-container {
position: relative;
}
.view-frame.ng-enter,
.view-frame.ng-leave {
background: white;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.view-frame.ng-enter {
animation: 1s fade-in;
z-index: 100;
}
.view-frame.ng-leave {
animation: 1s fade-out;
z-index: 99;
}
@keyframes fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
/* Older browsers might need vendor-prefixes for keyframes and animation! */
```
Nothing fancy here! Just a simple fade-in/fade-out effect between pages. The only thing out of the
ordinary here is that we are using absolute positioning to position the entering page (identified
by the `ng-enter` class) on top of the leaving page (identified by the `ng-leave` class). At the
same time a cross-fade animation is performed. 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, the element is removed from the DOM. Likewise, once the `enter`
animation is complete, the `ng-enter` and `ng-enter-active` CSS classes are removed from the
element, causing it to rerender and reposition itself with its default CSS styles (so no more
absolute positioning once the animation is over). This works fluidly and the pages flow naturally
between route changes, without anything jumping around.
The applied CSS classes are much the same as with `ngRepeat`. Each time a new page is loaded the
`ngView` 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][webplatform-animations].
## Animating `ngClass` with JavaScript
Let's add another animation to our application. On our `phone-detail.template.html` view, we have a
nice thumbnail swapper. By clicking on the thumbnails listed on the page, the profile phone image
changes. But how can we incorporate animations?
Let's give it some thought first. Basically, when a user clicks on a thumbnail image, they are
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 &mdash; when we used a CSS
class to drive the animation &mdash; this time the animation will occur when the CSS class itself
changes.
Every time a phone thumbnail is selected, the state changes and the `.selected` CSS class is added
to the matching profile image. This will trigger the animation.
We will start by tweaking our HTML code in `phone-detail.template.html`. Notice that we have changed
the way we display our large image:
<br />
**`app/phone-detail/phone-detail.template.html`:**
```html
<div class="phone-images">
<img ng-src="{{img}}" class="phone"
ng-class="{selected: img === $ctrl.mainImageUrl}"
ng-repeat="img in $ctrl.phone.images" />
</div>
...
```
Just like with the thumbnails, we are using a repeater to display **all** the profile images as a
list, however we're not animating any repeat-related transitions. Instead, we will be keeping our
eye on each element's classes and especially the `selected` class, since its presence or absence
will determine if the element is visible or hidden. The addition/removal of the `selected` class is
managed by the {@link ngClass ngClass} directive, based on the specified condition
(`img === $ctrl.mainImageUrl`).
In our case, there is always exactly one element that has the `selected` class, and therefore there
will be exactly one phone profile image visible on the screen at all times.
When the `selected` class is added to an element, the `selected-add` and `selected-add-active`
classes are added just before to signal AngularJS to fire off an animation. When the `selected`
class is removed from an element, the `selected-remove` and `selected-remove-active` classes are
applied to the element, triggering another animation.
Finally, in order to ensure that the phone images are displayed correctly when the page is first
loaded, we also tweak the detail page CSS styles:
<br />
**`app/app.css`:**
```css
...
.phone {
background-color: white;
display: none;
float: left;
height: 400px;
margin-bottom: 2em;
margin-right: 3em;
padding: 2em;
width: 400px;
}
.phone:first-child {
display: block;
}
.phone-images {
background-color: white;
float: left;
height: 450px;
overflow: hidden;
position: relative;
width: 450px;
}
...
```
You may be thinking that we are just going to create another CSS-based animation. Although we could
do that, let's take the opportunity to learn how to create JavaScript-based animations with the
{@link ng.angular.Module#animation .animation()} module method.
<br />
**`app/app.animations.js`:**
```js
angular.
module('phonecatApp').
animation('.phone', function phoneAnimationFactory() {
return {
addClass: animateIn,
removeClass: animateOut
};
function animateIn(element, className, done) {
if (className !== 'selected') return;
element.
css({
display: 'block',
position: 'absolute',
top: 500,
left: 0
}).
animate({
top: 0
}, done);
return function animateInEnd(wasCanceled) {
if (wasCanceled) element.stop();
};
}
function animateOut(element, className, done) {
if (className !== 'selected') return;
element.
css({
position: 'absolute',
top: 0,
left: 0
}).
animate({
top: -500
}, done);
return function animateOutEnd(wasCanceled) {
if (wasCanceled) element.stop();
};
}
});
```
We are creating a custom animation by specifying the target elements via a CSS class selector (here
`.phone`) and an animation _factory_ function (here `phoneAnimationFactory()`). The factory function
returns an object associating specific _events_ (object keys) to animation _callbacks_ (object
values). The _events_ correspond to DOM actions that `ngAnimate` recognizes and can hook into, such
as `addClass`/`removeClass`/`setClass`, `enter`/`move`/`leave` and `animate`. The associated
callbacks are called by `ngAnimate` at appropriate times.
For more info on animation factories, check out the
{@link ng.$animateProvider#register API Reference}.
In this case, we are interested in a class getting added to/removed from a `.phone` element, thus we
specify callbacks for the `addClass` and `removeClass` events. When the `selected` class is added to
an element (via the `ngClass` directive), the `addClass` JavaScript callback will be executed with
`element` passed in as a parameter. The last parameter passed in is the `done` callback function. We
call `done()` to let Angular know that our custom JavaScript animation has ended. The `removeClass`
callback works the same way, but instead gets executed when a class is removed.
Note that we are using [jQuery][jquery]'s `animate()` helper to implement the animation. jQuery
isn't required to do JavaScript animations with AngularJS, but we use it here anyway in order to
keep the example simple. More info on `jQuery.animate()` can be found in the
[jQuery documentation][jquery-animate].
Within the event callbacks, we create the animation by manipulating the DOM. In the code above,
this is achieved using `element.css()` and `element.animate()`. As a result the new element is
positioned with an offset of **500px** and then both elements &mdash; the previous and the new
&mdash; are animated together by shifting each one up by **500px**. The outcome is a conveyor-belt
like animation. After the `animate()` function has completed the animation, it calls `done` to
notify Angular.
You may have noticed that each animation callback returns a function. This is an **optional**
function, which (if provided) will be called when the animation ends, either because it ran to
completion or because it was canceled (for example another animation took place on the same
element). A boolean parameter (`wasCanceled`) is passed to the function, letting the developer know
if the animation was canceled or not. Use this function to do any necessary clean-up.
# Experiments
<div></div>
* Reverse the animation, so that the elements animate downwards.
* Make the animation run faster or slower, by passing a `duration` argument to `.animate()`:
```js
element.css({...}).animate({...}, 1000 /* 1 second */, done);
```
* Make the animations "asymmetrical". For example, have the previous element fade out, while the new
element zooms in:
```js
// animateIn()
element.css({
display: 'block',
opacity: 1,
position: 'absolute',
width: 0,
height: 0,
top: 200,
left: 200
}).animate({
width: 400,
height: 400,
top: 0,
left: 0
}, done);
// animateOut()
element.animate({
opacity: 0
}, done);
```
* Go crazy and come up with your own funky animations!
# Summary
Our application is now much more pleasant to use, thanks to the smooth transitions between pages
and UI states.
There you have it! We have created a web application in a relatively short amount of time. In the
{@link the_end closing notes} we will cover where to go from here.
<ul doc-tutorial-nav="14"></ul>
[bower]: http://bower.io/
[caniuse-css-animation]: http://caniuse.com/#feat=css-animation
[caniuse-css-transitions]: http://caniuse.com/#feat=css-transitions
[jquery]: https://jquery.com/
[jquery-animate]: https://api.jquery.com/animate/
[webplatform-animations]: https://docs.webplatform.org/wiki/css/properties/animations
+13 -6
View File
@@ -9,11 +9,18 @@ previous steps using the `git checkout` command.
For more details and examples of the Angular concepts we touched on in this tutorial, see the
{@link guide/ Developer Guide}.
When you are ready to start developing a project using Angular, we recommend that you bootstrap
your development with the [angular-seed](https://github.com/angular/angular-seed) project.
When you are ready to start developing a project using AngularJS, we recommend that you bootstrap
your development with the [angular-seed project][angular-seed].
We hope this tutorial was useful to you and that you learned enough about Angular to make you want
to learn more. We especially hope you are inspired to go out and develop Angular web apps of your
own, and that you might be interested in {@link misc/contribute contributing} to Angular.
We hope this tutorial was useful to you and that you learned enough about AngularJS to make you want
to learn more. We especially hope you are inspired to go out and develop Angular web applications of
your own, and that you might be interested in {@link misc/contribute contributing} to AngularJS.
If you have questions or feedback or just want to say "hi", please post a message at (https://groups.google.com/forum/#!forum/angular).
If you have questions or feedback or just want to say "hi", please post a message to the
[mailing list][mailing-list]. You can also find us on [IRC][irc] or [Gitter][gitter].
[angular-seed]: https://github.com/angular/angular-seed
[gitter]: https://gitter.im/angular/angular.js
[irc]: http://webchat.freenode.net/?channels=angularjs&uio=d4
[mailing-list]: https://groups.google.com/forum/#!forum/angular
+44 -5
View File
@@ -1,5 +1,9 @@
"use strict";
var fs = require('fs');
var _ = require('lodash');
var stripJsonComments = require('strip-json-comments');
var gulp = require('gulp');
var log = require('gulp-util').log;
var concat = require('gulp-concat');
@@ -25,6 +29,23 @@ var ignoredFiles = '!src/angular.bind.js';
var assets = 'app/assets/**/*';
var getJshintConfig = function(filepath) {
return JSON.parse(stripJsonComments(fs.readFileSync(filepath, {encoding: 'utf-8'})));
};
var getMergedJshintConfig = function(filepath) {
// "extends" doesn't work in configuration passed by an object, we need to do the extending ourselves.
var config = getJshintConfig(filepath);
var baseConfig = getJshintConfig('../.jshintrc-base');
_.merge(config, baseConfig);
delete config.extends;
// Examples don't run in strict mode; accept that for now.
config.strict = false;
return config;
};
var copyComponent = function(component, pattern, sourceFolder, packageFile) {
pattern = pattern || '/**/*';
sourceFolder = sourceFolder || bowerFolder;
@@ -90,17 +111,35 @@ gulp.task('assets', ['bower'], function() {
gulp.task('doc-gen', ['bower'], function() {
var dgeni = new Dgeni([require('./config')]);
return dgeni.generate().catch(function(error) {
return dgeni.generate().catch(function() {
process.exit(1);
});
});
// JSHint the example and protractor test files
gulp.task('jshint', ['doc-gen'], function() {
gulp.src([outputFolder + '/ptore2e/**/*.js', outputFolder + '/examples/**/*.js'])
.pipe(jshint())
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail'));
var examplesConfig = getMergedJshintConfig('../docs/app/test/.jshintrc');
// Some tests use `alert` which is not assumed to be available even with `"browser": true`.
examplesConfig.globals.alert = false;
var protractorConfig = getMergedJshintConfig('../docs/app/e2e/.jshintrc');
return merge(
gulp.src([
outputFolder + '/examples/**/*.js',
'!' + outputFolder + '/examples/**/protractor.js',
])
.pipe(jshint(examplesConfig))
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail')),
gulp.src([
outputFolder + '/ptore2e/**/*.js',
outputFolder + '/examples/**/protractor.js',
])
.pipe(jshint(protractorConfig))
.pipe(jshint.reporter('jshint-stylish'))
.pipe(jshint.reporter('fail'))
);
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 141 KiB

+4
View File
@@ -9,4 +9,8 @@ npm run test-i18n
node src/closureSlurper.js
npm run test-i18n-ucd
echo "Generating ngParseExt"
node ucd/src/extract.js
+53
View File
@@ -0,0 +1,53 @@
var extractValues = require('../src/extractValues.js').extractValues;
var stream = require('stream');
function StringStream(str) {
return new stream.Readable({
read: function(n) {
this.push(str);
str = null;
}
});
}
describe('extractValues', function() {
it('should extract the values from the xml', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><char cp="0002" IDS="Y"></char><char cp="0003" IDS="N"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0002' ] ] });
done();
});
});
it('should extract the values from the xml if the last element matches', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><char cp="0002" IDS="Y"></char><char cp="0003" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0003' ] ] });
done();
});
});
it('should support `reserved`', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><reserved first-cp="0002" last-cp="0005" IDS="N"></reserved><char cp="0006" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
done();
});
});
it('should support `surrogate`', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><surrogate first-cp="0002" last-cp="0005" IDS="N"></surrogate><char cp="0006" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
done();
});
});
it('should support `noncharactere`', function(done) {
var str = '<ucd><repertoire><char cp="0000" IDS="N"></char><char cp="0001" IDS="Y"></char><noncharacter first-cp="0002" last-cp="0005" IDS="N"></noncharacter><char cp="0006" IDS="Y"></char></repertoire></ucd>';
extractValues(StringStream(str), {'IDS': 'Y'}, function(values) {
expect(values).toEqual({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ] });
done();
});
});
});
+54
View File
@@ -0,0 +1,54 @@
var generateCodeModule = require('../src/generateCode.js');
var generateCode = generateCodeModule.generateCode;
var generateFunction = generateCodeModule.generateFunction;
describe('generateFunction', function() {
it('should generate function with ranges', function() {
expect(generateFunction([ [ '0001', '0003' ] ], 'IDS_Y')).toEqual('\
function IDS_Y(cp) {\n\
if (0x0001 <= cp && cp <= 0x0003) return true;\n\
return false;\n\
}\n');
});
it('should generate function with multiple ranges', function() {
expect(generateFunction([ [ '0001', '0003' ], [ '0005', '0009'] ], 'IDS_Y')).toEqual('\
function IDS_Y(cp) {\n\
if (0x0001 <= cp && cp <= 0x0003) return true;\n\
if (0x0005 <= cp && cp <= 0x0009) return true;\n\
return false;\n\
}\n');
});
it('should generate function with unique values', function() {
expect(generateFunction([ [ '0001', '0001' ], [ '0005', '0009'] ], 'IDS_Y')).toEqual('\
function IDS_Y(cp) {\n\
if (cp === 0x0001) return true;\n\
if (0x0005 <= cp && cp <= 0x0009) return true;\n\
return false;\n\
}\n');
});
});
describe('generateCode', function() {
it('should generate the function for all the values', function() {
expect(generateCode({ IDS_Y : [ [ '0001', '0001' ], [ '0006', '0006' ] ], IDC_Y : [ [ '0002', '0002' ], [ '0007', '0007' ] ] })).toEqual('\
/******************************************************\n\
* Generated file, do not modify *\n\
* *\n\
*****************************************************/\n\
"use strict";\n\
function IDS_Y(cp) {\n\
if (cp === 0x0001) return true;\n\
if (cp === 0x0006) return true;\n\
return false;\n\
}\n\
function IDC_Y(cp) {\n\
if (cp === 0x0002) return true;\n\
if (cp === 0x0007) return true;\n\
return false;\n\
}\n\
');
});
});
+28
View File
@@ -0,0 +1,28 @@
"use strict";
var fs = require('fs');
var zlib = require('zlib');
var extractValues = require('./extractValues').extractValues;
var generateCode = require('./generateCode').generateCode;
var generateextractValues = require('./extractValues').extractValues;
// ID_Start and ID_Continue
var propertiesToExtract = {'IDS': 'Y', 'IDC': 'Y'};
function main() {
extractValues(
fs.createReadStream('./ucd/src/ucd.all.flat.xml.gz').pipe(zlib.createGunzip()),
propertiesToExtract,
writeFile);
function writeFile(validRanges) {
var code = generateCode(validRanges);
try {
var stats = fs.lstatSync('../src/ngParseExt');
} catch (e) {
fs.mkdirSync('../src/ngParseExt');
}
fs.writeFile('../src/ngParseExt/ucd.js', code);
}
}
main();
+58
View File
@@ -0,0 +1,58 @@
/**
* Extract values from a stream.
*/
exports.extractValues = extractValues;
var sax = require('sax/lib/sax');
var saxStrict = true;
var saxOptions = {};
var validXMLTagNames = { char: 'Y', reserved: 'Y', surrogate: 'Y', noncharacter: 'Y'};
function extractValues(stream, propertiesToExtract, callback) {
var saxStream = sax.createStream(saxStrict, saxOptions);
var firstValid = {};
var lastValid = {};
var keys = Object.keys(propertiesToExtract);
var keyValues = keys.map(function(k) { return propertiesToExtract[k]; });
var validRanges = {};
for (var i in keys) {
validRanges[keys[i] + '_' + keyValues[i]] = [];
}
saxStream.onopentag = onOpenTag;
stream
.pipe(saxStream)
.on('end', doCallback);
function onOpenTag(node) {
var property;
if (validXMLTagNames[node.name]) {
for (var i in keys) {
property = keyValues[i];
if (node.attributes[keys[i]] === property) validProperty(keys[i] + '_' + property, node);
else invalidProperty(keys[i] + '_' + property);
}
}
}
function validProperty(property, node) {
if (!firstValid[property]) firstValid[property] =
node.attributes.cp || node.attributes['first-cp'];
lastValid[property] = node.attributes.cp || node.attributes['last-cp'];
}
function invalidProperty(property) {
if (!firstValid[property]) return;
validRanges[property].push([firstValid[property], lastValid[property]]);
firstValid[property] = null;
}
function doCallback() {
for (var i in keys) {
property = keys[i] + '_' + keyValues[i];
invalidProperty(property);
}
callback(validRanges);
}
}
+33
View File
@@ -0,0 +1,33 @@
exports.generateCode = generateCode;
exports.generateFunction = generateFunction;
function generateCode(validRanges) {
var code = '/******************************************************\n' +
' * Generated file, do not modify *\n' +
' * *\n' +
' *****************************************************/\n' +
'"use strict";\n';
var keys = Object.keys(validRanges);
for (var i in keys) {
code += generateFunction(validRanges[keys[i]], keys[i]);
}
return code;
}
function generateFunction(positiveElements, functionName) {
var result = [];
result.push('function ', functionName, '(cp) {\n');
positiveElements.forEach(function(range) {
if (range[0] === range[1]) {
result.push(' if (cp === 0x', range[0], ')');
} else {
result.push(' if (0x', range[0], ' <= cp && cp <= 0x', range[1], ')');
}
result.push(' return true;\n');
});
result.push(' return false;\n}\n');
return result.join('');
}
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

+3
View File
@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xl="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 576 733" width="48pc" height="733pt" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata> Produced by OmniGraffle 6.5.2 <dc:date>2016-04-12 13:18:31 +0000</dc:date></metadata><defs><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="532.22656" cap-height="719.72656" ascent="770.01953" descent="-229.98047" font-weight="bold"><font-face-src><font-face-name name="Helvetica-Bold"/></font-face-src></font-face><font-face font-family="Helvetica" font-size="12" units-per-em="1000" underline-position="-75.683594" underline-thickness="49.316406" slope="0" x-height="522.94922" cap-height="717.28516" ascent="770.01953" descent="-229.98047" font-weight="500"><font-face-src><font-face-name name="Helvetica"/></font-face-src></font-face></defs><g stroke="none" stroke-opacity="1" stroke-dasharray="none" fill="none" fill-opacity="1"><title>Notes</title><rect fill="white" width="576" height="733"/><g><title>Layer 1</title><text transform="translate(57 78)" fill="black"><tspan font-family="Helvetica" font-size="12" font-weight="bold" x="0" y="11" textLength="106.01367">Guiding Principles</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="25" textLength="126.73828">* Convention over Confi</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="126.73828" y="25" textLength="43.365234">guration</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="39" textLength="158.73047">* Declarative / Self Describing</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="53" textLength="8.0039062">* </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="7.7929688" y="53" textLength="7.330078">T</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="13.792969" y="53" textLength="38.695312">estable</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="67" textLength="25.335938">* DR</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="25.125" y="67" textLength="8.0039062">Y</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="32.917969" y="67" textLength="80.33203"> (Don&apos;t Repeat </tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="113.03906" y="67" textLength="8.0039062">Y</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="119.941406" y="67" textLength="40.013672">ourself)</tspan><tspan font-family="Helvetica" font-size="12" font-weight="500" x="0" y="81" textLength="171.38086">* CRUD ~ 80% -&gt; make it trivial </tspan></text></g></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

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