Compare commits

...

281 Commits

Author SHA1 Message Date
Igor Minar b00262fffe chore(release): cutting the 0.10.6 bubblewrap-cape release 2012-01-17 13:54:18 -08:00
Igor Minar 3f98d6ac99 docs(*): more docs 2012-01-17 12:19:26 -08:00
Igor Minar 22309c312f fix(docs): disable appcache - causing too much trouble 2012-01-17 12:13:29 -08:00
Igor Minar fcf95a47d1 docs(*): more fixes 2012-01-17 11:23:56 -08:00
Konstantin Stepanov e1e7aca9a6 fix($locationProvider) hashPrefix's getter returned html5Mode + doc fix 2012-01-17 11:21:20 -08:00
Vojta Jina 039041e3ae docs: syntax highlight auto bootstrap code example 2012-01-17 11:00:25 -08:00
Igor Minar 3da441b580 docs(release-notes): 0.10.6 bubblewrap-cape release 2012-01-17 09:49:40 -08:00
Igor Minar f9502d2ad3 fix(docs): temp hack to get appcache properly invalidated 2012-01-17 09:49:39 -08:00
Igor Minar 0356c90af8 chore(jquery): make the license header closure friendly 2012-01-17 09:49:39 -08:00
Igor Minar 0d4def452e fix(Rakefile): rewrite version number for mocks.js 2012-01-17 09:49:39 -08:00
Vojta Jina 897d0f1424 docs(guide): update the diagram 2012-01-17 09:49:38 -08:00
Igor Minar 92af30ce6e docs(*): various doc fixes 2012-01-17 09:49:37 -08:00
Igor Minar 54581d36df fix(e2e $httpBackend): use browser.defer instead of $defer
this is necessary to avoid $apply from within $apply situations
2012-01-16 23:26:44 -08:00
Igor Minar b587091b6e feat(jqLite): added injector() helper method 2012-01-16 02:17:27 -08:00
Vojta Jina c49b8a2db5 fix($location): do not $digest if browser's url change fired within $apply/$digest
Chrome (probably other browsers as well) fires 'hashchange' event synchronously, so if you change raw location from within $apply/$digest, we don't want to $apply twice. (It would throw an exception)
2012-01-14 11:23:12 -08:00
Misko Hevery 5cdfe45aa3 feat(module): add runtime block 2012-01-13 14:28:43 -08:00
Igor Minar 16a40c626f style(*): small fixes 2012-01-13 14:28:21 -08:00
Igor Minar b7f4d8c3c3 fix($http): anonnymous response interceptors should be treated as factories 2012-01-13 14:28:20 -08:00
Igor Minar 939c8e8fac docs($http, $httpBackend): docs docs docs 2012-01-13 14:28:20 -08:00
Igor Minar d2ba4c5170 fix(ngdocs): add '=' to type signatures with optional arguments 2012-01-13 13:53:07 -08:00
Igor Minar 46691c2721 fix($http): remove support for PATCH + better whenXXX, expectXXX api
- there are too many unknowns about PATCH, so I'm dropping its support until we know that this is actually useful
- expectGET, expectHEAD and expectJSON (and the same for whenXXX) should not require response data to be specified
2012-01-13 13:53:07 -08:00
Vojta Jina e7a23e4b65 fix(docs): generate correct ids on h elements to get scrolling working 2012-01-13 01:07:17 -08:00
Vojta Jina 15fd735793 refactor($autoScroll): rename to $anchorScroll and allow disabling auto scrolling (links)
Now, that we have autoscroll attribute on ng:include, there is no reason to disable the service completely, so $anchorScrollProvider.disableAutoScrolling() means it won't be scrolling when $location.hash() changes.

And then, it's not $autoScroll at all, it actually scrolls to anchor when it's called, so I renamed
it to $anchorScroll.
2012-01-13 01:07:12 -08:00
Vojta Jina 985d3d7558 refactor($autoScroll): rename method in test + use $apply instead of $digest 2012-01-13 01:05:24 -08:00
Vojta Jina 249c89c091 fix($autoScroll): scroll even if $location is in html5 mode
+ use autoscroll in docs (ng:include)
2012-01-13 01:01:26 -08:00
Vojta Jina 5164ae545b style(mocks): remove console.log 2012-01-13 00:37:16 -08:00
Misko Hevery e1e0ddb910 docs(inject/module): add documentation 2012-01-12 17:10:48 -08:00
Misko Hevery d648d709f3 refactor(module): strict separation between module-config / app-runtime 2012-01-12 13:40:07 -08:00
Vojta Jina 9a8dbfef51 style(mock): make jslint and igor happier 2012-01-11 22:11:06 -08:00
Vojta Jina 28114de8dc refactor(mock.$httpBackend): split (e2e/unit testing version of $httpBackend mock) 2012-01-11 22:11:01 -08:00
Vojta Jina c6ea1be053 fix(mock.$httpBackend): resetExpectations should not create new array 2012-01-11 11:48:03 -08:00
Misko Hevery 5143e7bf06 feat(module): new module loader 2012-01-10 22:27:00 -08:00
Misko Hevery afd25446d2 feat(ngdocs): support for interface documentation 2012-01-10 22:21:54 -08:00
Misko Hevery 3c3e6980b3 chore(specs.js): remove unused dependency 2012-01-10 22:21:54 -08:00
Misko Hevery e0b4b107ee chore(license): update year 2012-01-10 22:21:54 -08:00
Igor Minar 614fd3d55a fix(ng:repeat): support repeating over array with null
typeof null == 'object', but it doesn't behave like an object
because its properties can't be dereferenced, so we need
to special-case it.

Closes #702
2012-01-10 22:21:53 -08:00
Igor Minar 7146f70636 fix($httpBackend): fix for jsonp requests 2012-01-09 14:38:32 -08:00
Igor Minar 11cb9423a7 chore(docs): disable disqus for localhost
it's just slowing down the test runs and debugging
2012-01-09 13:17:49 -08:00
Igor Minar c76a120bfe fix(nodeserver): add dummy favicon.ico to silence 404s 2012-01-09 13:17:49 -08:00
Igor Minar b8960c3710 chore($http): small $http fixes 2012-01-09 13:17:48 -08:00
Igor Minar 67338ce061 feat($http): turn mock backend into a decorator + e2e testing support
- provider -> decorator
- autoflush + passThrough mode
- fix noop -> angular.noop
2012-01-09 13:17:48 -08:00
Igor Minar 23f8da7cbb feat($http): expose req/resp headers to transform fns 2012-01-09 13:17:48 -08:00
Igor Minar b911e303ec feat($httpBackend): add expect/when shortcut methods 2012-01-09 13:17:48 -08:00
Igor Minar a13b5ed3bc fix($http): fix and cleanup $http and friends
$http:
- use promises internally
- get rid of XhrFuture that was previously used internally
- get rid of $browser.defer calls for async stuff (serving from cache),
  promises will take care of asynchronicity
- fix transformation bugs (when caching requested + multiple request
  pending + error is returned)
- get rid of native header parsing and instead just lazily parse the
  header string

$httpBackend:
- don't return raw/mock XMLHttpRequest object (we don't use it for
  anything anymore)
- call the callback with response headers string

mock $httpBackend:
- unify response api for expect and when
- call the callback with response headers string
- changed the expect/when failure error message so that EXPECTED and GOT
  values are aligned

Conflicts:

	src/service/http.js
	test/service/compilerSpec.js
	test/service/httpSpec.js
2012-01-09 13:17:48 -08:00
Igor Minar 63cca9afbc feat(browser.defer): flush should throw exception when queue is empty 2012-01-09 13:17:48 -08:00
Vojta Jina d47ec772c3 docs(fix): select widget name -> ng:model 2012-01-08 15:06:12 -08:00
Vojta Jina 5c19766063 feat(ng:include): enable/disable scrolling through autoscroll attribute 2012-01-07 00:18:22 -08:00
Vojta Jina f2119c7524 fix($httpBackend): respond 200/404 when on file protocol (fix protocol parsing) 2012-01-06 19:20:29 -08:00
Dhruv Manek 08029c7b72 fix(angular.copy): throw Error if source and destination are identical
Closes #693
2012-01-06 12:19:39 -08:00
Igor Minar 0bf611087b feat(scope): throw exception when recursive $apply 2012-01-06 12:19:39 -08:00
Igor Minar acb4338b70 style(widgetsSpec): ws, unused variables, etc 2012-01-06 12:19:39 -08:00
Igor Minar cd9a7b9608 fix(ng:repeat): support repeating over array with null
typeof null == 'object', but it doesn't behave like an object
because its properties can't be dereferenced, so we need
to special-case it.

Closes #702
2012-01-06 12:19:39 -08:00
Vojta Jina 1dccaaaaa2 chore(readme): add some links and update test commands 2012-01-05 19:54:42 -08:00
Igor Minar 9632f5c1c7 style(q): rename src/Deferred.js to src/service/q.js 2012-01-03 17:48:09 -08:00
Igor Minar bb3be87606 style(q): reorganize the file + drop fallback dependencies 2012-01-03 17:48:09 -08:00
Igor Minar 174952e443 docs(q): documentation for all $q apis 2012-01-03 17:31:23 -08:00
Vojta Jina 6f91ffeb91 style: prefer single quotes + some whitespaces 2012-01-03 15:09:00 -08:00
Vojta Jina c594f75b4c refactor: remove old JSTD assertions
So that we can run the tests even without JSTD :-D
2012-01-03 15:09:00 -08:00
Mark Hansen 50eb7f15b8 docs(scope): fix typo $digest -> $watch 2012-01-03 15:01:34 -08:00
Sean Gilligan 212a6ff29a docs(resource): fix params for non-get actions 2012-01-03 14:57:38 -08:00
Igor Minar 871252ab4c docs(guide): fix $xhr -> $http links 2011-12-07 16:59:06 -08:00
bartes 0c534644bc fix(input): bind inputs to the 'input' event
The input event is fired on all non-ie browsers whenever the contents of an input
field changes. This means that we now support cut&paste via mouse which
was previously unsupported.

IE8 and older don't support this events and IE9 has a problematic
support for it, so we can't rely solely on this event and drop keydown
and change events.
2011-12-07 13:07:20 -08:00
Igor Minar c28662d28d fix(filter): remove the $ prefix from filter service ids 2011-12-07 13:07:19 -08:00
Igor Minar b97c6e5f74 style(scopeSpec): clean up scopeSpec.js 2011-12-07 09:41:09 -08:00
Igor Minar 4e3c05b99e feat(injector): add $provide.decorator 2011-12-07 09:41:08 -08:00
Igor Minar 5e4d59adf0 style(injector): cleanup of InjectorSpec.js 2011-12-07 09:41:07 -08:00
Igor Minar fd38655e6c fix(): use angular.callbacks namespace for jsonp callbacks
Previously we used to put callbacks on the window object, but that
causes problems on IE8 where it is not possible to delete properties
from the window object
2011-12-07 07:54:09 -08:00
Vojta Jina b9001e9147 fix(route): $destroy scope after update and reload
When we update route (changing only search param, no route reload) and then reload (change to different
route), it did not $destroy last scope.
2011-12-06 13:35:05 -08:00
Igor Minar d1e7a5394a docs(form): add docs about form submission 2011-12-06 13:32:49 -08:00
Vojta Jina 2090136dd8 docs(ng:submit): update docs example to not add empty items 2011-12-06 13:07:27 -08:00
Vojta Jina c9f2b1eec5 feat(form): do not prevent submission if action attribute present 2011-12-06 13:07:26 -08:00
Igor Minar 163e05ed36 feat($http): allow interceptors to be services 2011-12-05 23:53:26 -08:00
Igor Minar 2986a09c0d fix(jqLite): JQLiteHasClass should work even when minified
closure compiler is smarter than we expected and drops the unused fn
argument - this breaks the meta-programing logic of jqLite.

The fix special cases JQLiteHasClass since its the only fn that needs
this treatment in a way that is minification-proof.
2011-12-05 14:12:00 -08:00
Igor Minar bb2e7488fa fix($httpBackend mock): getResponseHeader should be case insensitive 2011-12-01 18:21:45 -05:00
Igor Minar 44b2f44f93 fix($resource): forwardport exposing headers from 0.9.19 2011-12-01 16:20:08 -05:00
Igor Minar 1d14760c6d fix(ng:include): prevent race conditions by ignoring stale http callbacks
This fix is similar to what I've done in ng:view, if a new template has been requested before the
callback for the previous template returned, ignore it. Otherwise weird race conditions happen
and users might end up getting the content for the previous include rendered instead of the most
recent one.
2011-11-30 14:49:36 -05:00
Igor Minar baa7af0df0 docs($location): add docs for the $location.search setter 2011-11-30 14:49:36 -05:00
Igor Minar f43c226c67 fix(copy,equals): prevent browser crashes with Scope or Window
Scope and Window instances are special and when copied can crash browser. For this reason
it makes sense to compare them only by identity.
2011-11-30 14:49:36 -05:00
Misko Hevery 0e1fa2aefe feat($interpolate): string interpolation function 2011-11-30 14:49:36 -05:00
Igor Minar 3d0ce0ebe9 feat($location): name the watch function to ease debugging 2011-11-30 14:49:36 -05:00
Igor Minar b00da987a9 scope($digest): add new&old val to the infinite $digest log 2011-11-30 14:49:35 -05:00
Igor Minar 188bdf7768 feat($http): add response interceptors 2011-11-30 14:49:35 -05:00
Igor Minar dbd880cc0a feat($http): add promise support
quite messy, some tests are missing, contains an experimental jasmine DI support)
2011-11-30 14:49:35 -05:00
Igor Minar bf8e0540f8 feat(dump): add support for arrays, functions, errors 2011-11-30 14:49:35 -05:00
Igor Minar 78b6e8a446 feat($parse): add support for transparent evaluation of Promises
Parser now builds expressions that can detect promises and transparently
evaluate them to undefined or the promise value.

If promiseA is resolved with value 'A', then {{promiseA}} evals to 'A';
If promiseA is unresolved, then {{promiseA}} evals to undefined;

Following invocations are supported:

- {{promise}}
- {{promise.futureProp}}
- {{[promise][0]}}
- {{object.promise}}
- {{object[promise]}}
- {{array[promise]}}
- {{fn(promise)}}
- combinations of the above
2011-11-30 14:49:35 -05:00
Igor Minar b656552d68 fix(angular-boostrap): add missing jQuery/jqLite binding 2011-11-30 14:49:35 -05:00
Igor Minar 1cdfa3b960 feat(deferreds/promises): Q-like deferred/promise implementation with a ton of specs 2011-11-30 14:49:03 -05:00
Vojta Jina 16363d8000 refactor(ng:view, ng:include): pass cache instance into $http
Instead of doing all the stuff in these widgets (checking cache, etc..) we can rely on $http now...
2011-11-30 14:49:03 -05:00
Vojta Jina 92995bbce9 fix($http): default json transformation should not crash on angular template
The way we determine whether it's json is lame anyway. We need to change that.
We should probably check the content type header...
2011-11-30 11:17:25 -05:00
Vojta Jina b9707d910e style(): get rid off some jsl warnings 2011-11-30 11:17:25 -05:00
Vojta Jina 5bbd64ac65 feat($http): allow passing custom cache instance per request
You can still use cache: true, which will use $http's default cache.
2011-11-30 11:17:25 -05:00
Vojta Jina caeb1bf899 feat($httpBackend): fix 0 status code when "file" protocol
Browsers return always 0 status code for "file" protocol, so we convert them into 200/404.
2011-11-30 11:17:24 -05:00
Vojta Jina 9b4efa73f9 feat(mock.$httpBackend): say which request was expected when unexpected request error 2011-11-30 11:17:24 -05:00
Vojta Jina 4aaa2f7f6b feat(mock.$httpBackend): verify expectations after flush() 2011-11-30 11:17:24 -05:00
Vojta Jina 6290bd4587 refactor(mock.$httpBackend): rename when().then() to when().respond() 2011-11-30 11:17:24 -05:00
Vojta Jina e9f81b6631 fix(mock.$httpBackend): flush() even requests sent during callbacks 2011-11-30 11:17:23 -05:00
Vojta Jina afbe073121 feat(mock.$httpBackend): add verifyNoOutstandingRequest method
+ rename verifyExpectations to verifyNoOutstandingExpectation
2011-11-30 11:17:23 -05:00
Vojta Jina 7b705df2b7 feat($http): broadcast $http.request event 2011-11-30 11:17:23 -05:00
Vojta Jina a4c8ac7126 feat(mock.$httpBackend): throw when nothing to flush, dump data/headers when expected different 2011-11-30 11:17:23 -05:00
Vojta Jina e3e2e4436e fix($http): add .send() alias for .retry() to get better stack trace on error 2011-11-30 11:17:22 -05:00
Vojta Jina 972c3e9be0 refactor($http): change callback matching mechanism 2011-11-30 11:17:22 -05:00
Vojta Jina feacf608ee fix($resource): to work with $http, $httpBackend services
Breaks Disabling $resource caching for the moment.
2011-11-30 11:17:22 -05:00
Vojta Jina fe633dd0cf fix($http): allow multiple json vulnerability prefixes
We strip out both:
)]}',
)]}'
2011-11-30 11:17:22 -05:00
Vojta Jina fdcc2dbfd3 feat($http): expose pendingRequests and configuration object
- $http.pendingRequests is now an array of pending requests
- each request (its future object) has public property configuration
2011-11-30 11:17:22 -05:00
Vojta Jina 5ad0c7d0e4 feat($httpBackend): extract $browser.xhr into separate service
- remove whole $browser.xhr stuff
- remove whole mock $browser.xhr stuff
- add $httpBackend service + migrate unit tests from $browser
- add temporary API to access $browser's outstandingRequests count
2011-11-30 11:17:22 -05:00
Vojta Jina 540701a8d8 feat(mocks.$browser): add simple addJs() method into $browser mock 2011-11-30 11:17:22 -05:00
Vojta Jina 4d2d70e7fb feat($templateCache): add $templateCache - shared by ng:include, ng:view 2011-11-30 11:17:21 -05:00
Vojta Jina cd28a2e952 feat(mocks.$httpBackend): add $httpBackend mock
$httpBackend mock allows:
- expecting (asserting) requests
- stubbing (responding without asserting)

Add empty $httpBackend service (currently just wrapper for $browser.xhr)
2011-11-30 11:12:14 -05:00
Vojta Jina 59adadca08 feat($http): new $http service, removing $xhr.*
Features:
- aborting requests
- more flexible callbacks (per status code)
- custom request headers (per request)
- access to response headers
- custom transform functions (both request, response)
- caching
- shortcut methods (get, head, post, put, delete, patch, jsonp)
- exposing pendingCount()
- setting timeout
Breaks Renaming $xhr to $http
Breaks Takes one parameter now - configuration object
Breaks $xhr.cache removed - use configuration cache: true instead
Breaks $xhr.error, $xhr.bulk removed
Breaks Callback functions get parameters: response, status, headers
Closes #38
Closes #80
Closes #180
Closes #299
Closes #342
Closes #395
Closes #413
Closes #414
Closes #507
2011-11-30 11:12:14 -05:00
Igor Minar 497839f583 feat($cacheFactory): add general purpose $cacheFactory service 2011-11-30 11:03:42 -05:00
Vojta Jina 5487bdb3d1 feat($browser.xhr): add timeout option to abort request
Timeouted request responds internal status code -1, which should be normalized
into 0 by $xhr.
2011-11-30 11:03:42 -05:00
Vojta Jina 3ae3ccf3da fix($browser.xhr): fix IE6, IE7 bug - sync xhr when serving from cache
IE6, IE7 is sync when serving content from cache.
We want consistent api, so we have to use setTimeout to make it async.
2011-11-30 11:03:42 -05:00
Vojta Jina e9b57f9df8 fix($browser.xhr): respond with internal -2 status on jsonp error
If jsonp is not successfull, we return internal status -2.
This internal status should by normalized by $xhr into 0,
but $xhr needs to distinguish between jsonp-error/abort/timeout (all status 0).
2011-11-30 11:03:42 -05:00
Vojta Jina 45f47ff6cd fix($browser.xhr): change method "JSON" to "JSONP"
Breaks "JSON" xhr method is now called "JSONP"
2011-11-30 11:03:41 -05:00
Vojta Jina 0c8b35681e feat($browser): xhr returns raw request object 2011-11-30 11:03:41 -05:00
Igor Minar a035e88397 docs(guide): fix directives guide docs 2011-11-28 18:04:01 -05:00
Vojta Jina 3548fe3139 feat(service.$autoScroll): scroll to hash fragment
- whenever hash part of the url changes
- after ng:view / ng:include load
2011-11-21 17:49:49 -08:00
Codier 29f9e2665d fix(scope): $watch (and angular.equals) should support NaN values
- since NaN !== NaN in javascript digest can get into an infinite loop
  when model value is set to NaN
- angular.equals(NaN, NaN) should return true since that's what we
  expect when comparing primitives or objects containing NaN values

Previously NaN because of its special === properties was used as the
initial value for watches, but that results in issues when NaN is used
as model value.

In order to allow for model to be anything incuding undefined and NaN we
need to mark the initial value differently in a way that would avoid
these issues, allow us to run digest without major perf penalties and
allow for clients to determine if the listener is being called because
the watcher is being initialized or because the model changed. This
implementation covers all of these scenarios.

BREAKING CHANGE: previously to detect if the listener was called because
the watcher was being initialized, it was suggested that clients check
if old value is NaN. With this change, the check should be if the newVal
equals the oldVal.

Closes #657
2011-11-21 15:43:12 -08:00
Igor Minar 8d1944851d fix(docs): specify disqus_url 2011-11-19 15:42:17 -08:00
TEHEK Firefox 3e1a6688c3 chore(browser): rename Browser.js -> browser.js, BrowserSpec.js -> browserSpec.js
And move them to proper service subfolder...
2011-11-18 12:05:55 -08:00
Misko Hevery aba9bb2a24 fix(input): treat all not number model as blank 2011-11-15 11:47:23 -08:00
Misko Hevery 5857c44e0c fix(radio): fix binding to value={{exp}} 2011-11-15 11:44:36 -08:00
Misko Hevery 8adae2fdf2 refactor(injector): removed loadModule/ng:module
- added module property to doc:example
2011-11-14 20:31:21 -08:00
Misko Hevery 955551141d style(angularPublic): move public export definition to AnuglarPublic.js 2011-11-14 20:31:21 -08:00
Misko Hevery 94e1a07b28 doc(rename): NG_LOCALE -> ngLocale 2011-11-14 20:31:20 -08:00
Misko Hevery ac73e8877e add(doc): added missing angular.module.ng namespace 2011-11-14 20:31:19 -08:00
Misko Hevery e88dfb734a refactor(injector): $injector is no longer a function.
- $injector('abc') -> $injector.get('abc');
- $injector(fn) -> $injector.invoke(null, fn);
2011-11-14 20:31:19 -08:00
Misko Hevery 8d6dc0b9a7 del($eager): removed the support for $eager services 2011-11-14 20:31:19 -08:00
Vojta Jina acbd7cdf32 style(docs): make jslint happy - fix some warnings 2011-11-14 20:31:19 -08:00
Misko Hevery 035c751076 fix(doc) cleanup all dev guide doc link warnings 2011-11-14 20:31:18 -08:00
Misko Hevery 186a840cd3 feat(bootstrap): added angular.bootstrap method 2011-11-14 20:31:18 -08:00
Misko Hevery b09595a3c1 fix(doc) cleanup all api doc link warnings 2011-11-14 20:31:18 -08:00
Misko Hevery f6d98f1472 fix(doc): make output less noisy 2011-11-14 20:31:17 -08:00
Vojta Jina 5279de0e70 fix($location): links without path segment should not change the path
Closes #648
2011-11-14 20:31:17 -08:00
Misko Hevery 8fe77b69e8 refactor(docs): improved the visual rendering of the documentation pages 2011-11-14 20:31:17 -08:00
Misko Hevery 1cc6bee4ce docs(browser): moved and migrate browser removed unneeded files. 2011-11-14 20:31:17 -08:00
Misko Hevery a8aa193c6b doc($rootScope): scope documentation changes 2011-11-14 20:31:17 -08:00
Misko Hevery e45b013143 doc(locale): migrate docs to new format 2011-11-14 20:31:16 -08:00
Misko Hevery ea18f4548d doc($formFactory): migrated the $formFactory documentation 2011-11-14 20:31:16 -08:00
Misko Hevery 57c37a21d1 doc(service): update docs for the moved services 2011-11-14 20:31:16 -08:00
Misko Hevery 74fac45f48 doc($filter): added $filter documentation 2011-11-14 20:31:16 -08:00
Misko Hevery f0fa5e6376 doc(AUTO, NG_MOCK): Documenting the AUTO and NG_MOCK module 2011-11-14 20:31:16 -08:00
Misko Hevery c283bf6035 refactor($location): merged $locationConfig service into $locationProvider 2011-11-14 20:31:15 -08:00
Misko Hevery b3c17f3fdc chore(scope): remove $service 2011-11-14 20:31:15 -08:00
Misko Hevery 9c06394376 chore(scenario tests): make scenario tests pass again 2011-11-14 20:31:15 -08:00
Misko Hevery 085e3c611f new(directive): added ng:module directive for loading modules 2011-11-14 20:31:15 -08:00
Misko Hevery 4b35a59c6a refactor(scenario): fix scenario bootstrap & publish injector for inspection 2011-11-14 20:31:14 -08:00
Misko Hevery 7cb03c5ab9 chore(angular): clean up unused constants 2011-11-14 20:31:14 -08:00
Misko Hevery 78c7066422 refactor(angular): isDate / isArray test iframe independent fix 2011-11-14 20:31:14 -08:00
Misko Hevery 923da410bd fix(i18n): updated the way locale is being registered 2011-11-14 20:31:14 -08:00
Misko Hevery a87f2fb9e4 refactor(mock): moved mocks into its own module 2011-11-14 20:31:14 -08:00
Misko Hevery c27aba4354 refactor(api): remove type augmentation
BREAK:
  - remove angular.[Object/Array/String/Function]
  - in templates [].$filter(predicate) and friends need to change to [] | filter:predicate
2011-11-14 20:31:14 -08:00
Misko Hevery dd9151e522 refacter(filters): convert filter/limitTo/orderBy from type augmentation to filters 2011-11-14 20:31:13 -08:00
Misko Hevery 3972d2a89b refactor(json): break dependence on api.js 2011-11-14 20:31:13 -08:00
Misko Hevery cb6f832f38 refactor(filter): filters are now injectable and services
BREAK:
 - removed CSS support from filters
2011-11-14 20:31:09 -08:00
Misko Hevery 6022f3df39 move(filters): appease the History God 2011-11-14 16:39:34 -08:00
Misko Hevery 7c11531902 refactor(parser): turn parser into a service (keep compatibility hack) 2011-11-14 16:39:33 -08:00
Misko Hevery c6d2549a52 fix(JSON): json date parsing same native/ngular parser 2011-11-14 16:39:33 -08:00
Misko Hevery bee6060e4b move(parser): appease the History God 2011-11-14 16:39:33 -08:00
Misko Hevery 16597e8b52 refactor($service): removed almost all references to scope.$service
- still need to remove from factory
2011-11-14 16:39:33 -08:00
Misko Hevery f684f20c99 cleanup(parser): removed unused method 2011-11-14 16:39:33 -08:00
Misko Hevery bd04316a89 refactor(services): migrate angular.service -> module 2011-11-14 16:39:33 -08:00
Misko Hevery ed36b9da3b refactor(injector): switch to injector 2.0 introduce modules 2011-11-14 16:39:32 -08:00
Misko Hevery c925f8a657 new(injector): new injector v2.0
- not connected to keep the CL small
2011-11-14 16:39:32 -08:00
Misko Hevery 4c10d33eb4 refactor(api): cleaned up the externalization of angular API methods 2011-11-14 16:39:32 -08:00
Misko Hevery 9062996a0e feat(injector): support $inject(fn($service){}) function invocation 2011-11-14 16:39:32 -08:00
Misko Hevery 411c1ae77e feat(injector): support ['$service', function($service){}] annotations for function invocation. 2011-11-14 16:39:32 -08:00
Misko Hevery d12df0d360 refactor(compiler) turn compiler into a service
BREAK
- remove angular.compile() since the compile method is now a service and needs to be injected
2011-11-14 16:39:32 -08:00
Misko Hevery d9b58f23f6 move(compiler): appease the History God
-	renamed:    src/Compiler.js -> src/service/compiler.js
-	renamed:    test/CompilerSpec.js -> test/service/compilerSpec.js
2011-11-14 16:39:32 -08:00
Misko Hevery 03dd8c4f4c feat(injector): Service look up failures include dependency path 2011-11-14 16:39:32 -08:00
Misko Hevery 48697a2b86 refactor(injector): turn scope into a service
- turn scope into a $rootScope service.
- injector is now a starting point for creating angular application.
- added inject() method which wraps jasmine its/beforeEach/afterEach,
  and which allows configuration and injection of services.
- refactor tests to use inject() where possible

BREAK:
- removed angular.scope() method
2011-11-14 16:39:31 -08:00
Misko Hevery 93b777c916 move(scope): appease the History God
-	renamed:    src/Scope.js -> src/service/scope.js
-	renamed:    test/ScopeSpec.js -> test/service/scopeSpec.js
2011-11-14 16:39:31 -08:00
Vojta Jina 5c70ff72e2 style(docs): make jslint happy - fix some warnings 2011-11-13 16:40:31 -08:00
Vojta Jina 5e663c3dc7 fix(docs.guide): fix $location example 2011-11-13 16:40:31 -08:00
Vojta Jina 260725efcd feat(docs): allow examples with hidden source code 2011-11-13 16:40:31 -08:00
Vojta Jina 4afad1da29 feat(docs): allow custom attributes on <doc:source>
Allow any attributes, not only jsfiddle...
2011-11-13 16:40:31 -08:00
Dhruv Manek eb01fe593d feat(jquery): upgrade to jQuery 1.7
no breaking changes AFAICT
2011-11-13 16:30:21 -08:00
Misko Hevery fc7834f9ac cleanup(parser): remove unused method 2011-11-11 13:04:26 -08:00
Misko Hevery e4303a1f3a chore(test): remove stale bit-rotten code
- deleted:    test/manual.html
2011-11-09 08:54:04 -08:00
Igor Minar 1e00db8daa fix(directives): make directive names case-insensitive
+ tests
+ added docs for angular.directive
2011-11-08 21:44:46 -08:00
Igor Minar aaa0179758 chore(release): preparing the 0.10.6 bubblewrap-cape iteration 2011-11-08 16:39:19 -08:00
Igor Minar f5ef3724ce chore(release): cutting the 0.10.5 steel-fist release 2011-11-08 04:29:07 -08:00
Igor Minar e60601be4f docs(changelog): release notes for 0.10.5 steel-fist 2011-11-08 04:29:07 -08:00
Dhruv Manek e2663f62b0 feat(ng:style): compatibility + perf improvements
- better compatibility with 3rd party code - we clober 3rd party
  style only if it direcrtly collides with 3rd party styles
- better perf since it doesn't execute stuff on every digest
- lots of tests
2011-11-08 02:25:22 -08:00
Igor Minar 9f9ed4c5ff docs(ng:bind-attr): improve examples 2011-11-08 02:23:51 -08:00
Igor Minar 66fc268aeb docs(css): fixing positioning of disqus comments
float:left causes the comments to shift to the left when content is longer
than the sidebar
2011-11-05 22:30:50 -07:00
Vojta Jina 1d966f8a65 style(angularFiles): just missing semi-colon and extra comma 2011-11-01 14:14:52 -07:00
Vojta Jina ddf6f1143f test(mocks): test mocks with compiled angular
Unfortunately, there people in our team (me), who are not able to use angular.* namespace prefix
when writing angular-mocks code, so we need to test it...
2011-10-31 18:22:38 -07:00
Vojta Jina 2636105c5e feat(matchers): extract jasmine matchers into separate file for future reuse
Prefix all used functions with angular.* so that they can be used with compiled angular as well...
2011-10-31 18:22:21 -07:00
Igor Minar c0b557a96c test(scope): infinite digest test should pass on all browsers
Different browsers print function body differently, and best of all IE doesn't have
function.name property.
2011-10-31 12:46:12 -07:00
Igor Minar 84873e7f4e fix(angular-bootstrap): fix boostrap scripts that broke w\ 5a2dcb9a
Commit 5a2dcb9a doesn't properly modify angular-boostrap.js.

This fix resolves issues and makes both the regular and scenario
version of angular-boostrap.js functional.
2011-10-31 11:34:25 -07:00
Igor Minar 95fdb1231f chore(watchr): watchr scripts should output logs to terminal
Having the extra level of indirection by using logs/*.log file
only makes it more difficult to use these scripts. After this
change it will be enough to just start the watchr and watch the
terminal it was started in.
2011-10-31 11:34:25 -07:00
Igor Minar ef875ad0cf feat(scope): better logging of infinite digest error
Feedback team has often problems debugging inifinite digest errors, this change
should reveal info about what watchers are causing the infinite loop
2011-10-31 11:34:25 -07:00
Igor Minar 615841a5d3 style(widgets): fix typo in a spec comment 2011-10-31 07:19:33 -07:00
Vojta Jina 7d0c256ecd docs(jqlite): add missing methods 2011-10-30 22:31:03 -07:00
Igor Minar 6cbe096dbf feat(docs): remove the Report Issue link - duplication of disqus 2011-10-30 22:27:25 -07:00
Igor Minar 21602b5cd6 fix(docs): special case url generation for index pages 2011-10-30 22:27:25 -07:00
Igor Minar 4ae671ac88 fix(docs): increment load counter only when a valid page is requested 2011-10-30 22:27:25 -07:00
dandoyon 28ed5ba465 feat(docs): disqus integration
- add style for disqus threads in css
- add loadDisqus method to be called afterPartialLoaded
- add div container for disqus threads
2011-10-30 22:27:25 -07:00
Igor Minar 02dc81bae0 fix(css): ng:form should always be a block element 2011-10-30 22:26:32 -07:00
Igor Minar 445680f601 fix(nodeserver): properly escape all, not just first char 2011-10-27 08:44:14 -07:00
Vojta Jina bf729d550b feat(docs): Add scenario runner into dev guide
And update the link in the tutorial as well...
2011-10-26 15:58:59 -07:00
Vojta Jina dc8ffa51b7 fix(scenario.dsl): Fix dsl for $location
New $location does not have hashSearch, hashPath.
The old dsl was mixing $location / window.location so this solves the problem as well...
2011-10-26 15:58:59 -07:00
Igor Minar d7ba5bc83b feat(bootstrap): drop angular.js file name restrictions for autobind
The last script element in the dom is always us if the script that
contains angular is loaded synchronously.

For async loading manual bootstrap needs to be performed.

Close #621
2011-10-26 12:57:15 -07:00
Igor Minar 950d02b4d4 fix(docs): remove unused $browser dependency 2011-10-26 12:57:14 -07:00
Igor Minar 578e38e0af fix(example): fixes for personalLog e2e tests
Looks like this got broken with scope rewrite.
2011-10-26 12:57:14 -07:00
Igor Minar af7c51ee1d style(Angular.js): various code style fixes 2011-10-26 12:57:14 -07:00
Igor Minar 25d1822bd8 style(HashQueueMap): fixing a typo in the comment 2011-10-26 12:54:00 -07:00
Igor Minar 3945f884c5 fix(ng:repeat) with array ignore properties not representing array elements
Along the way I also changed the repeater impl to use for loop instead
of for in loop.

Iteration over objects is handled by creating an array of keys, which is
sorted and this array then determines the order of iteration over an
element. This makes repeating over objects deterministic and
cross-browser compatible.
2011-10-26 12:54:00 -07:00
Igor Minar d5ccabce60 fix(ng:view): ignore stale xhr callbacks
A lot of badness happens when we don't ignore stale xhrs. These
raceconditions are only apparent when user clicks through the app very
quckly without waiting for routes to fully load.

Closes #619
2011-10-26 12:15:07 -07:00
Igor Minar bb948176aa test(ng:view): spec cleanup
- remove optional controller definition from specs
- remove extranious digest calls
2011-10-26 12:02:30 -07:00
Dhruv Manek 163c799eff fix(angular.widget): Allow widgets to be styled in IE8 and below
Closes #584
2011-10-24 23:28:36 -07:00
Vojta Jina 7da70af1ae fix(scenario): Change title to "AngularJS" 2011-10-24 14:03:50 -07:00
Igor Minar 836e4c1428 chore(release): preparing the 0.10.5 steel-fist iteration 2011-10-24 09:16:33 -07:00
Igor Minar eabedba34d chore(release): cutting the 0.10.4 human-torch release 2011-10-22 21:39:39 -07:00
Igor Minar b4add97c17 docs(changelog): update changelog with 0.10.4 release notes 2011-10-22 21:39:39 -07:00
Igor Minar bacc31bea9 fix(defer.cancel): should return false instead of undefined 2011-10-22 21:32:48 -07:00
Igor Minar ad90c3574f feat($defer): add $defer.cancel
This functionality was previously available only as obscure $browser.defer.cancel.

I also added docs and tests and fixed an issue in .defer.cancel mock.
2011-10-22 21:32:48 -07:00
Igor Minar e28171d5e4 fix(docs): key-binding used old scope apis 2011-10-22 21:32:48 -07:00
Igor Minar ce73ed091b feat(docs): add "Loading..." notification 2011-10-22 21:32:47 -07:00
Igor Minar 90ac8d57b0 fix(docs): update page title only when content loads 2011-10-22 21:32:47 -07:00
Igor Minar 6eb1179505 style(docs): change "this" to "scope" in the controller 2011-10-22 21:32:47 -07:00
Vojta Jina 9b85757102 fix($location): rewrite links with nested elements
For example:
<a href="some/link">inner <span>text</span></a>

If you click on "text", then the span element is event.target, so we need to traverse the DOM.
2011-10-22 15:35:18 -07:00
Igor Minar c6c3949b14 feat(filter.date): use mediumDate as default
Breaking change!

Previously the default was fullDate.
2011-10-20 16:51:28 -07:00
Igor Minar e175db37c6 fix(date filter): default to fullDate format
The browser's behave inconsistently, so we should just stick to one format
when the format is not specified by the developer

Closes #605
2011-10-20 16:51:28 -07:00
Igor Minar f38010d3a2 fix(compiler): revert 8611ebe6 - calling \$digest after linking
Change introduced by me in 8611ebe6 results in considerable inefficiencies when the compiler
and linker is used from within a widget, in which case, we call $digest unnecessary since it
will be called by the $apply which called the directive/widget in the first place.

There are only two places when the extra $digest call can be useful - when manually bootstrapping
the app or in tests. However even in tests this behavior can result in unwanted results (especially
when ng:controller is involved). So it is better to leave it for the developer to call $digest
when it is really needed.
2011-10-20 15:51:14 -07:00
Misko Hevery 7fc18b263d fix(radio): allows data-binding on value property. Closes#316 2011-10-20 11:30:40 -07:00
Igor Minar fabc9f77a3 feat(sanitizer): add html5 elements to the whitelist
Closes #89
2011-10-20 09:44:52 -07:00
Igor Minar c17c731fdc style(select): cleaning up select.js 2011-10-19 22:52:14 -07:00
TEHEK Firefox 3692885810 fix(ng:options): compile null/blank option tag
Fixes #562
2011-10-19 22:52:14 -07:00
Igor Minar 5d43439dbe fix(ng:pattern): correctly parse out inlined regexp 2011-10-19 21:52:20 -07:00
Igor Minar a46f2a0db3 docs(textarea): add docs for angular.widget.textarea 2011-10-19 16:49:34 -07:00
Igor Minar 3217a249e1 style(input): fix style violations in the input.js file 2011-10-19 16:49:34 -07:00
Konstantin Stepanov 78f394fd17 feat(input): add ng:minlength and ng:maxlength validation
notes(igor): I also e2e tests and refactorred the e2e test example to be
more clear about what is a variable and what is an html/framework api.
2011-10-19 16:49:20 -07:00
Konstantin Stepanov e82e64d57b fix(input): recognize 'password' as an html input type 2011-10-19 10:36:36 -07:00
Vojta Jina 8978e066b5 fix(gen-docs): require files without touching PATH
So that it works on latest revision of node...
New version of Node (v0.5.x) does not support require.paths.push().
2011-10-18 22:23:52 -07:00
Igor Minar 833eb3c844 fix(ng:repeat): repeater should ignore $ and $$ properties 2011-10-18 17:27:43 -07:00
Igor Minar 07926ff1ef chore(version.yaml): add missing snapshot version suffix 2011-10-18 17:25:10 -07:00
Igor Minar e801faba2e chore(jstd adapter): switch to our version with backported fixes
sha of the version: da92db714142b49f9cf61db664e782bb0ccad80b
2011-10-18 16:21:52 -07:00
TEHEK Firefox ee6af9a978 fix(ng:options): select correct element when '?'-option was previously selected
Closes #599
2011-10-18 14:02:54 -07:00
Igor Minar 74379df6c4 chore(release): preparing the 0.10.4 human-torch iteration 2011-10-14 15:29:17 -07:00
Igor Minar fe65dd926c chore(release): cutting the 0.10.3 shattering-heartbeat release 2011-10-14 08:31:39 -07:00
Igor Minar 669b53ede2 fix(docs): fix jsfiddle integration
this got accidentally messed up during the forms refactoring and mass renaming
2011-10-14 08:31:00 -07:00
Igor Minar b0c3f28e8f docs(forms): fix devguide forms example code 2011-10-14 08:30:02 -07:00
Igor Minar 9810dc0993 docs(cookbook): disable jsfiddle for deeplinking example
it uses extra resources, so it won't work on fiddle
2011-10-14 08:29:33 -07:00
Vojta Jina ab5df20dfa chore(libs): update libs (jasmine, jstd, jasmine-jstd adapter) 2011-10-13 17:36:11 -07:00
Igor Minar d83a92c121 fix(checkbox): prefix true-value & false-value with ng: 2011-10-13 17:35:00 -07:00
Igor Minar d0425de29e chore(release): preparing release notes for 0.10.3 2011-10-13 16:54:30 -07:00
Igor Minar ad5e42cf82 docs($location): Html5 -> HTML5 2011-10-13 14:42:49 -07:00
Igor Minar 9ed1126adb docs($location): update replace() docs 2011-10-13 14:37:37 -07:00
Igor Minar 7a19eb84aa docs($location): fix $config -> $locationConfig in docs 2011-10-13 14:07:48 -07:00
Igor Minar 718741acab chore(logo): add hi-res ng logo 2011-10-13 13:44:38 -07:00
Vojta Jina ec8bb675b4 fix(docs): set proper base href when hashbang url requested 2011-10-13 11:10:20 -07:00
Vojta Jina 8e32f3fd35 fix(nodeserver): docs rewriting
There can be url /api which does not end with / and we want to rewrite this url as well...
2011-10-13 11:10:20 -07:00
Vojta Jina 02332107e5 feat(nodeserver): if index.html exists, serve it instead of directory listing 2011-10-13 11:10:20 -07:00
Vojta Jina afc81b554e fix($location): do not rewrite link when meta key pressed 2011-10-13 11:10:19 -07:00
Vojta Jina 26e8ab3693 feat(scenario): allow key pressing when triggering browser event
Add parameter to our browserTriger function to allow specifying which keys are pressed.

Note, this does not work on IE<9 !
2011-10-13 11:10:19 -07:00
Igor Minar 28ccc76aa1 docs(dev_guide.bootstrap.auto_bootstrap): fixing a typo 2011-10-12 23:04:48 -07:00
Igor Minar b3c4cb7cff docs(*): remove @workInProgress from everywhere
it's not useful any more and it only makes the docs look ugly
2011-10-12 23:04:48 -07:00
Misko Hevery 4af4378b11 fix(forms): broken tests on jQuery and ie8&9 2011-10-12 23:04:48 -07:00
Igor Minar 8611ebe6a0 fix(compiler): linking function should call $digest
The linked scope should be $digest-ed but only if a $digest isn't
already running on it.
2011-10-12 23:04:48 -07:00
Igor Minar 8f46a3c9ac fix(jqLite): attr for boolean attribute should lowercase value 2011-10-12 23:04:47 -07:00
Igor Minar 66fdb36ecb refactor(ng:bind-attr): simplify impl by leveraging jquery 2011-10-12 23:04:47 -07:00
Igor Minar f0f5ffa9aa test(jstd-config): add widget specs to jsTestDriver-jquery.conf 2011-10-12 23:04:47 -07:00
Igor Minar 2bc7afd3ba fix(ng:class): ignore undefined or NaN classnames 2011-10-12 23:04:47 -07:00
Igor Minar a4b45397e0 docs(forms): add ng:change docs and other fixes 2011-10-12 11:49:42 -07:00
Igor Minar de4e06ed73 test(checkbox): add test for ng:change 2011-10-12 11:11:10 -07:00
Misko Hevery fd822bdaf9 chore(formating): clean code to be function() { 2011-10-11 11:01:46 -07:00
Misko Hevery 4f78fd692c feat(forms): new and improved forms 2011-10-11 11:01:45 -07:00
Misko Hevery df6d2ba326 style(examples): clean up dead examples 2011-10-11 10:53:07 -07:00
Misko Hevery ccda436f94 style(.gitignore) added IDEA 2011-10-11 10:53:07 -07:00
Misko Hevery e86c435349 refactor(bindings): remove the decoration of the DOM with errors.
Only $exceptionHandler gets notified now.
2011-10-11 10:53:07 -07:00
Misko Hevery 1942861472 refactor(hover): delete hover service 2011-10-11 10:53:06 -07:00
Misko Hevery b96e978178 fix(jqlite): removeClass would clobber class names 2011-10-11 10:53:06 -07:00
Misko Hevery bda2bba2be feat(jqlite): added .inheritedData method and $destroy event.
- refactored .scope() to use .inheritedData() instead.
- .bind('$destroy', callback) will call when the DOM element is removed
2011-10-11 10:53:05 -07:00
Misko Hevery ca08c004c8 feat(jqlite): support required as a no-value attribute 2011-10-11 10:53:05 -07:00
Misko Hevery 25a62b58db refactor(injection) infer injection args in ng:controller only
Because only controllers don't have currying, we can infer its arguments, all other APIs needing currying, automatic inference complicates the matters unecessary.
2011-10-11 10:53:04 -07:00
Misko Hevery 97e3ec4d1b style(gitignore): added xproject to ignore list 2011-10-11 10:53:04 -07:00
Misko Hevery 75f11f1fc4 feat(ng:repeat) collection items and DOM elements affinity / stability 2011-10-11 10:53:04 -07:00
Misko Hevery e134a8335f fix(filter): make json filter ignore private properties 2011-10-11 10:53:03 -07:00
Igor Minar 8ee32a75f0 chore(release): prepare the 0.10.3 shattering-heartbeat iteration 2011-10-10 11:23:08 -07:00
461 changed files with 31132 additions and 23350 deletions
+2
View File
@@ -11,3 +11,5 @@ performance/temp*.html
angular.js.tmproj
node_modules
jsTestDriver*.conf
angular.xcodeproj
.idea
+282 -36
View File
@@ -1,7 +1,223 @@
- The Latest Stable Release: <a href="#0.9.19">0.9.19 canine-psychokinesis</a>
- The Latest Unstable Release: <a href="#0.10.2">0.10.2 sneaky-seagull</a>
- The Latest Unstable Release: <a href="#0.10.6">0.10.6 bubblewrap-cape</a>
<a name="0.10.2"><a/>
<a name="0.10.6"></a>
# 0.10.6 bubblewrap-cape (in-progress) #
## Features:
- [Dependency injection subsystem][guide2.di] rewrite. This is a huge change to the Angular core
that was necessary for many reasons. Please read the full
[design doc](https://docs.google.com/document/d/1hJnIqWhSt7wCacmWBB01Bmc6faZ8XdXJAEeiJwjZmqs/edit?hl=en_US)
to understand the changes and reasoning behind them.
- Added [angular.bootstrap] for manual bootstrapping of the app. Also see
[Initializing Angular App][bootstrapping] doc.
- Helper functions [inject] and [module] that make testing with DI and jasmine a lot easier.
- [jqLite][jqLite2] and jQuery were extended with helper method `injector()` that simplifies the
access to the application injector during debugging.
- Rewrite of $xhr service and its dependencies, which was replaced with [$http] service.
The $browser.xhr and its mock were replaced by [$httpBackend] and its
[unit testing][unit-testing $httpBackend] and [end-to-end testing][e2e-testing $httpBackend]
mocks. The $resource service api and functionality was preserved, with the exception of caching,
which is not happening automatically as it used it in the past (verifyCache has no effect).
- [$q] - Q-like deferred/promise implementation
([commit](https://github.com/angular/angular.js/commit/1cdfa3b9601c199ec0b45096b38e26350eca744f))
- Transparent data-binding to promises in templates. [Example](http://jsfiddle.net/IgorMinar/aNSWu/)
([commit](https://github.com/angular/angular.js/commit/78b6e8a446c0e38075c14b724f3cdf345c01fa06))
- New [$anchorScroll] service that watches url hash and navigates to the html anchor even if the
content was loaded via [ng:view] (for [ng:include] you have to opt into this behavior using
autoscroll attribute)
- New LRU cache factory - [$cacheFactory] service
- jQuery 1.7 compatibility
## Bug Fixes:
- Directive names are now case insensitive
([commit](https://github.com/angular/angular.js/commit/1e00db8daa5c09e7f8f9134f5c94b9a18c7dc425))
- $location#url setter fix (Issue [#648](https://github.com/angular/angular.js/issues/648))
- [ng:include] - prevent race conditions by ignoring stale http callbacks
([commit](https://github.com/angular/angular.js/commit/1d14760c6d3eefb676f5670bc323b2a7cadcdbfa))
- [ng:repeat] - support repeating over array with null
([commit](https://github.com/angular/angular.js/commit/cd9a7b9608707c34bec2316ee8c789a617d22a7b))
- [angular.copy] - throw Error if source and destination are identical
([commit](https://github.com/angular/angular.js/commit/08029c7b72a857ffe52f302ed79ae12db9efcc08))
- Forms should not prevent POST submission if the action attribute is present
([commit](https://github.com/angular/angular.js/commit/c9f2b1eec5e8a9eaf10faae8a8accf0b771096e0))
## Breaking Changes:
- App bootstrapping works differently (see [angular.bootstrap] and [ng:app] and [bootstrapping])
- scope.$service is no more (because injector creates scope and not the other way around),
if you really can't get services injected and need to fetch them manually then, get hold of
[$injector] service and call $injector.get('serviceId')
- angular.service style service registration was replaced with module system, please see
[angular.module] api and [DI documentation][guide2.di] for more info.
- the $xhr service was replaced with [$http] with promise based apis.
- [unit-testing $httpBackend]'s expect method (the replacement for $browser.xhr.expect) is stricter -
the order of requests matters and a single request expectation can handle only a single request.
- compiler
- compiler is a service, so use [$compile] instead of angular.compile to compile templates
- $compile (nee angular.compile) returns the linking function which takes one mandatory argument -
the scope. previously this argument was optional and if missing, the compiler would create a new
root scope, this was a source of bugs and was removed
- filters
- filters need to be registered either via [moduleName.filter][angular.Module] or
[$filterProvider.filter][$filterProvider]
- filters don't have access to the dom element
- currency filter doesn't make negative values red
- json filter doesn't print out stuff in monospace
- type augmentation via angular.Array, and angular.Object is gone. As a replacement use filters
([filter], [limitTo], [orderBy]) or ES5 apis (e.g. Array#indexOf)
- [$browser.defer.flush] now throws an exception when queue is empty
([commit](https://github.com/angular/angular.js/commit/63cca9afbcf7a772086eb4582d2f409c39e0ed12))
- scope.$apply and scope.$digest throws an exception if called while $apply or $digest is already
in progress (this is a programming error, you should never need to do this)
([commit](https://github.com/angular/angular.js/commit/0bf611087b2773fd36cf95c938d1cda8e65ffb2b))
<a name="0.10.5"></a>
# 0.10.5 steel-fist (11-11-08) #
## Features:
- [ng:autobind]: drop angular.js file name restrictions
([commit](https://github.com/angular/angular.js/commit/d7ba5bc83ba9a8937384ea677331c5156ed6772d))
- [Scope]: better logging of infinite digest error
([commit](https://github.com/angular/angular.js/commit/ef875ad0cf4349144cb4674e050dd160564f6dd9),
issue [#621](https://github.com/angular/angular.js/issues/621))
- enable [widget] styling in IE8 and below using
[html5shiv](http://code.google.com/p/html5shiv/)-like approach
([commit](https://github.com/angular/angular.js/commit/163c799effd5cfadc57990f4d4127651bae3fbdb),
issue [#584](https://github.com/angular/angular.js/issues/584))
- [ng:style]: compatibility + perf improvements
([commit](https://github.com/angular/angular.js/commit/e2663f62b0fbb8b9ce2e706b821a135e0bc7e885))
## Bug Fixes:
- [ng:view]: ignore stale xhr callbacks - fixes issues caused by race-conditions which occured when
user navigated to a new route before the current route finished loading
(issue [#619](https://github.com/angular/angular.js/issues/619))
- [ng:form] should always be a block level (css) element
([commit](https://github.com/angular/angular.js/commit/02dc81bae0011b7ae4190363be5fdd5db420aca9))
- Fixes for [e2e test runner]'s `$location` dsl
([commit](https://github.com/angular/angular.js/commit/dc8ffa51b7ebe5fb9bc1c89087c8b3c9e65d1006))
- [ng:repeat] when iterating over arrays ignore non-array properties + when iterating over objects
sort keys alphabetically
([commit](https://github.com/angular/angular.js/commit/3945f884c5777e629b57c9ab0e93b9d02b9840d0))
## Docs:
- experimental [disqus.com](http://disqus.com/) integration for all docs-next.angularjs.org pages
([commit](https://github.com/angular/angular.js/commit/28ed5ba46595a371bd734b92a6e4bb40d1013741),
contributed by Dan Doyon)
- [e2e test runner] docs were moved to the dev guide
<a name="0.10.4"></a>
# 0.10.4 human-torch (2011-10-22) #
## Features:
- New validation options for
[input widgets](http://docs-next.angularjs.org/api/angular.widget.input): `ng:minlength` and
`ng:maxlength`
([commit](https://github.com/angular/angular.js/commit/78f394fd17be581c84ecd526bb786ed1681d35cb))
(contributed by Konstantin Stepanov)
- HTML sanitizer was updated to recognize all safe HTML5 elements
(Issue [#89](https://github.com/angular/angular.js/issues/89))
- [ng:options]' blank option is now compiled and data-bound as any other template
(Issue [#562](https://github.com/angular/angular.js/issues/562))
(contributed by tehek)
- [$defer](http://docs-next.angularjs.org/api/angular.service.$defer) service now exposes `cancel`
method for task cancellation
([commit](https://github.com/angular/angular.js/commit/ad90c3574f8365ee4a1a973d5e43c64fe9fcda2c))
## Bug Fixes:
- [ng:options] should select correct element when '?'-option (invalid value) was previously selected
(Issue [#599](https://github.com/angular/angular.js/issues/599)) (contributed by Tehek)
- Fix data-binding of radio button's value property
(Issue [#316](https://github.com/angular/angular.js/issues/316))
- Input with type `password` should no be turned into a readable text field
([commit](https://github.com/angular/angular.js/commit/e82e64d57b65d9f3c4f2e8831f30b615a069b7f6))
(contributed by Konstantin Stepanov)
- [ng:repeat] should ignore object properties starting with `$`
([commit](https://github.com/angular/angular.js/commit/833eb3c84445110dc1dad238120573f08ed8d102))
- Correctly parse out inlined regexp from the input field's `ng:pattern` attribute.
([commit](https://github.com/angular/angular.js/commit/5d43439dbe764a4c7227f51b34a81b044f13901b))
- $location service in html5 mode should correctly rewrite links that contain nested elements
([commit](https://github.com/angular/angular.js/commit/9b85757102fbd44e88d0a3909fdf8b90f191b593))
## Breaking Changes:
- the [date] filter now uses 'mediumDate' format if none is specified. This was done to deal with
browser inconsistencies (each browser used to use different format)
(Issue [#605](https://github.com/angular/angular.js/issues/605),
[commit](https://github.com/angular/angular.js/commit/c6c3949b14f4003ecab291243edfca61262f2c3d),
[commit](https://github.com/angular/angular.js/commit/e175db37c6f52bba4080efeec22a7120a896099e))
- calling the linker function returned by [angular.compile][compile] doesn't automatically run
`$digest` on the linked scope any more. This behavior was briefly introduced in 0.10.3 but was
causing issues and inefficiencies in production apps so we reverted it. See:
[commit](https://github.com/angular/angular.js/commit/f38010d3a2f457a53798212ef72418637dabe189)
<a name="0.10.3"></a>
# 0.10.3 shattering-heartbeat (2011-10-13) #
## Features:
- New forms, validation, support for HTML5 input widgets. Please check out:
- [Forms overview](http://docs-next.angularjs.org/guide/dev_guide.forms)
- [form widget](http://docs-next.angularjs.org/api/angular.widget.form)
- [input widget](http://docs-next.angularjs.org/api/angular.widget.input)
- [$formFactory service](http://docs-next.angularjs.org/api/angular.service.$formFactory)
- [angular.inputType](http://docs-next.angularjs.org/api/angular.inputType)
- [commit](https://github.com/angular/angular.js/commit/4f78fd692c0ec51241476e6be9a4df06cd62fdd6)
- [ng:repeat] now has element-model affinity, which makes it more friendly to third-party code that
is not aware of angular's DOM manipulation. This is also the pre-requisite for supporting
animations.
([commit](https://github.com/angular/angular.js/commit/75f11f1fc46c35a28c0905f7316ea6779145e2fb))
## Bug Fixes:
- The select widget with [ng:options] directive now correctly displays selected option (regression
from 0.10.2).
- Fix for jqLite's removeClass, which under certain circumstances could clobber class names.
([commit](https://github.com/angular/angular.js/commit/b96e978178a6acbf048aa6db466ed845e1395445))
- Other small fixes and documentation improvements.
## Breaking Changes:
- Due to changes in how forms and validation works the following were replaced with new apis:
- `angular.formatter` - use `angular.inputType` or form's `$createWidget`
- `angular.validator` - use `angular.inputType` or form's `$createWidget`
- changes to `<input>` and `<select>` elements
- `ng:model` directive is now required for data-binding to kick in
- the `name` attribute is now optional and is used only as an alias when accessing the input
widget via the form object.
- view can't affect the model without a user interaction, so the `value` attribute of the
`<input>` element and `selected` attribute of the `<option>` element if specified in the
template is ignored.
- Removed decoration of DOM elements when:
- an exception occurs - when an exception happens, it will be passed to the $exceptionHandler
service, which can decide what to do with it.
- an input widget contains invalid input - in this case the forms validation apis can be used to
display a customized error message.
- The $hover service was removed (it was needed only for the DOM decoration described above).
<a name="0.10.2"></a>
# 0.10.2 sneaky-seagull (2011-10-08) #
## Features:
@@ -46,17 +262,20 @@
- If Angular is being used with jQuery older than 1.6, some features might not work properly. Please
upgrade to jQuery version 1.6.4.
## Breaking Changes
- ng:repeat no longer has ng:repeat-index property. This is because the elements now have
affinity to the underlying collection, and moving items around in the collection would move
ng:repeat-index property rendering it meaningless.
<a name="0.10.1"><a/>
<a name="0.10.1"></a>
# 0.10.1 inexorable-juggernaut (2011-09-09) #
## Features
- complete rewrite of the $location service with HTML5 support, many API and semantic changes.
Please see:
- [$location service API docs](http://docs-next.angularjs.org/#!/api/angular.service.$location)
- [$location service API docs](http://docs-next.angularjs.org/#!/api/angular.module.ng.$location)
- [$location service dev guide article](http://docs-next.angularjs.org/#!/guide/dev_guide.services.$location)
- [location.js source file](https://github.com/angular/angular.js/blob/master/src/service/location.js)
- breaking changes section of this changelog
@@ -82,7 +301,7 @@
- $location.hashPath -> $location.path()
- $location.hashSearch -> $location.search()
- $location.search -> no equivalent, use $window.location.search (this is so that we can work in
hashBang and html5 mode at the same time, check out the docs)
hashBang and html5 mode at the same time, check out the docs)
- $location.update() / $location.updateHash() -> use $location.url()
- n/a -> $location.replace() - new api for replacing history record instead of creating a new one
@@ -95,7 +314,7 @@
<a name="0.10.0"><a/>
<a name="0.10.0"></a>
# 0.10.0 chicken-hands (2011-09-02) #
## Features
@@ -150,7 +369,7 @@
<a name="0.9.19"><a/>
<a name="0.9.19"></a>
# 0.9.19 canine-psychokinesis (2011-08-20) #
## Features
@@ -186,7 +405,7 @@
`css('display', 'block'/'inline'/..)` instead
<a name="0.9.18"><a/>
<a name="0.9.18"></a>
# 0.9.18 jiggling-armfat (2011-07-29) #
### Features
@@ -219,7 +438,7 @@
### Bug Fixes
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
- make injector compatible with Rhino (HtmlUnit) (contributed by Mårten Dolk)
[commit](https://github.com/angular/angular.js/commit/77ba539f630c57b17d71dbf1e9c5667a7eb603b7)
- `ie-compat.js` fixes and improvements related to fetching this file on the fly on legacy browsers
- [jqLite]
@@ -256,7 +475,7 @@
<a name="0.9.17"><a/>
<a name="0.9.17"></a>
# <angular/> 0.9.17 vegetable-reanimation (2011-06-30) #
### New Features
@@ -291,7 +510,7 @@
<a name="0.9.16"><a/>
<a name="0.9.16"></a>
# <angular/> 0.9.16 weather-control (2011-06-07) #
### Features
@@ -322,7 +541,7 @@
<a name="0.9.15"><a/>
<a name="0.9.15"></a>
# <angular/> 0.9.15 lethal-stutter (2011-04-11) #
### Features
@@ -341,7 +560,7 @@
<a name="0.9.14"><a/>
<a name="0.9.14"></a>
# <angular/> 0.9.14 key-maker (2011-04-01) #
### Performance
@@ -362,12 +581,12 @@
<a name="0.9.13"><a/>
<a name="0.9.13"></a>
# <angular/> 0.9.13 curdling-stare (2011-03-13) #
### New Features
- Added XSRF protection for the [$xhr] service. (commit c578f8c3)
- Targeted auto-bootstrap [ng:autobind] now takes an optional value which specifies an element id
- Targeted auto-bootstrap [ng:autobind] now takes an optional value which specifies an element id
to be compiled instead of compiling the entire html document. (commit 9d5c5337)
@@ -389,7 +608,7 @@
<a name="0.9.12"><a/>
<a name="0.9.12"></a>
# <angular/> 0.9.12 thought-implanter (2011-03-03) #
### API
@@ -446,7 +665,7 @@
<a name="0.9.11"><a/>
<a name="0.9.11"></a>
# <angular/> 0.9.11 snow-maker (2011-02-08) #
### Documentation
@@ -477,7 +696,7 @@
request via the `$xhr` service or remove all unneeded `flush()` calls.
<a name="0.9.10"><a/>
<a name="0.9.10"></a>
# <angular/> 0.9.10 flea-whisperer (2011-01-26) #
### Features
@@ -501,7 +720,7 @@ with the `$route` service
- lots of improvements related to formatting of the content of docs.anguarjs.org
<a name="0.9.9"><a/>
<a name="0.9.9"></a>
# <angular/> 0.9.9 time-shift (2011-01-13) #
### Security
@@ -532,7 +751,7 @@ with the `$route` service
- angular.filter.date now properly handles some corner-cases (issue #159 - fix contributed by Vojta)
### Breaking changes
- API for accessing registered services `scope.$inject` was renamed to
- API for accessing registered services — `scope.$inject` — was renamed to
[`scope.$service`](http://docs.angularjs.org/#!/api/angular.scope.$service). (commit b2631f61)
- Support for `eager-published` services was removed. This change was done to make explicit
@@ -577,7 +796,7 @@ with the `$route` service
- The `toString` method of the `angular.service.$location` service was removed. (commit 23875cb3)
<a name="0.9.8"><a/>
<a name="0.9.8"></a>
# <angular/> 0.9.8 astral-projection (2010-12-23) #
### Docs/Getting started
@@ -591,7 +810,7 @@ with the `$route` service
- Ignore input widgets which have no name (issue #153)
<a name="0.9.7"><a/>
<a name="0.9.7"></a>
# <angular/> 0.9.7 sonic-scream (2010-12-10) #
### Bug Fixes
@@ -610,7 +829,7 @@ with the `$route` service
your controllers. (commit e5e69d9b90850eb653883f52c76e28dd870ee067)
<a name="0.9.6"><a/>
<a name="0.9.6"></a>
# <angular/> 0.9.6 night-vision (2010-12-06) #
### Security
@@ -640,7 +859,7 @@ with the `$route` service
- The HTML sanitizer is slightly more strinct now. Please see info in the "Security" section above.
<a name="0.9.5"><a/>
<a name="0.9.5"></a>
# <angular/> 0.9.5 turkey-blast (2010-11-25) #
### Docs
@@ -650,7 +869,7 @@ with the `$route` service
- added `angular.Array.limitTo` to make it easy to select first or last few items of an array
<a name="0.9.4"><a/>
<a name="0.9.4"></a>
# <angular/> 0.9.4 total-recall (2010-11-18) #
### Docs
@@ -667,7 +886,7 @@ with the `$route` service
- Better error handling - compilation exception now contain stack trace (commit b2d63ac4)
<a name="0.9.3"><a/>
<a name="0.9.3"></a>
# <angular/> 0.9.3 cold-resistance (2010-11-10) #
### Docs
@@ -695,7 +914,7 @@ with the `$route` service
simple RegExp validator.
<a name="0.9.2"><a/>
<a name="0.9.2"></a>
# <angular/> 0.9.2 faunal-mimicry (2010-11-03) #
### Docs
@@ -733,7 +952,7 @@ with the `$route` service
implements HEAD
<a name="0.9.1"><a/>
<a name="0.9.1"></a>
# <angular/> 0.9.1 repulsion-field (2010-10-26) #
### Security
@@ -760,7 +979,7 @@ with the `$route` service
- html filter now sanitizes html content for XSS attacks which may result in different behavior
<a name="0.9.0"><a/>
<a name="0.9.0"></a>
# <angular/> 0.9.0 dragon-breath (2010-10-20) #
### Security
@@ -815,12 +1034,13 @@ with the `$route` service
[ng:class]: http://docs.angularjs.org/#!/api/angular.directive.ng:class
[ng:src]: http://docs.angularjs.org/#!/api/angular.directive.ng:src
[ng:href]: http://docs.angularjs.org/#!/api/angular.directive.ng:href
[$defer]: http://docs.angularjs.org/#!/api/angular.service.$defer
[$cookies]: http://docs.angularjs.org/#!/api/angular.service.$cookies
[$xhr]: http://docs.angularjs.org/#!/api/angular.service.$xhr
[$xhr.cache]: http://docs.angularjs.org/#!/api/angular.service.$xhr.cache
[$resource]: http://docs.angularjs.org/#!/api/angular.service.$resource
[$route]: http://docs.angularjs.org/#!/api/angular.service.$route
[ng:style]: http://docs.angularjs.org/#!/api/angular.directive.ng:style
[$defer]: http://docs.angularjs.org/#!/api/angular.module.ng.$defer
[$cookies]: http://docs.angularjs.org/#!/api/angular.module.ng.$cookies
[$xhr]: http://docs.angularjs.org/#!/api/angular.module.ng.$xhr
[$xhr.cache]: http://docs.angularjs.org/#!/api/angular.module.ng.$xhr.cache
[$resource]: http://docs.angularjs.org/#!/api/angular.module.ng.$resource
[$route]: http://docs.angularjs.org/#!/api/angular.module.ng.$route
[$orderBy]: http://docs.angularjs.org/#!/api/angular.Array.orderBy
[date]: http://docs.angularjs.org/#!/api/angular.filter.date
[number]: http://docs.angularjs.org/#!/api/angular.filter.number
@@ -835,9 +1055,35 @@ with the `$route` service
[Jstd Scenario Adapter]: https://github.com/angular/angular.js/blob/master/src/jstd-scenario-adapter/Adapter.js
[i18n]: http://docs-next.angularjs.org/#!/guide/dev_guide.i18n
[ng:pluralize]: http://docs-next.angularjs.org/#!/api/angular.widget.ng:pluralize
[ng:form]: http://docs-next.angularjs.org/api/angular.widget.form
[ng:cloak]: http://docs-next.angularjs.org/#!/api/angular.directive.ng:cloak
[$on]: http://docs-next.angularjs.org/#!/api/angular.scope.$on
[$emit]: http://docs-next.angularjs.org/#!/api/angular.scope.$emit
[$broadcast]: http://docs-next.angularjs.org/#!/api/angular.scope.$broadcast
[$limitTo]: http://docs-next.angularjs.org/api/angular.Array.limitTo
[$location]: http://docs-next.angularjs.org/api/angular.service.$location
[e2e test runner]: http://docs-next.angularjs.org/guide/dev_guide.e2e-testing
[$injector]: http://docs-next.angularjs.org/api/angular.module.AUTO.$injector
[$http]: http://docs-next.angularjs.org/api/angular.module.ng.$http
[$httpBackend]: http://docs-next.angularjs.org/api/angular.module.ng.$httpBackend
[unit-testing $httpBackend]: http://docs-next.angularjs.org/api/angular.module.ngMock.$httpBackend
[e2e-testing $httpBackend]: http://docs-next.angularjs.org/api/angular.module.ngMockE2E.$httpBackend
[$q]: http://docs-next.angularjs.org/api/angular.module.ng.$q
[angular.bootstrap]: http://docs-next.angularjs.org/api/angular.bootstrap
[$anchorScroll]: http://docs-next.angularjs.org/api/angular.module.ng.$anchorScroll
[$cacheFactory]: http://docs-next.angularjs.org/api/angular.module.ng.$cacheFactory
[bootstrapping]: http://docs-next.angularjs.org/guide/dev_guide.bootstrap
[angular.copy]: http://docs-next.angularjs.org/api/angular.copy
[ng:app]: http://docs-next.angularjs.org/api/angular.directive.ng:app
[$compile]: http://docs-next.angularjs.org/api/angular.module.ng.$compile
[$filterProvider]: http://docs-next.angularjs.org/api/angular.module.ng.$filterProvider
[angular.Module]: http://docs-next.angularjs.org/api/angular.Module
[angular.module]: http://docs-next.angularjs.org/api/angular.module
[filter]: http://docs-next.angularjs.org/api/angular.module.ng.$filter.filter
[limitTo]: http://docs-next.angularjs.org/api/angular.module.ng.$filter.limitTo
[orderBy]: http://docs-next.angularjs.org/api/angular.module.ng.$filter.orderBy
[$browser.defer.flush]: http://docs-next.angularjs.org/api/angular.module.ngMock.$browser#defer.flush
[inject]: http://docs-next.angularjs.org/api/angular.mock.inject
[module]: http://docs-next.angularjs.org/api/angular.mock.module
[guide2.di]: http://docs-next.angularjs.org/guide/dev_guide.di
[jqLite2]: http://docs.angularjs.org/#!/api/angular.element
+11 -4
View File
@@ -1,5 +1,10 @@
Angular
======
AngularJS
=========
* Web site: http://angularjs.org
* Tutorial: http://docs.angularjs.org/#!/tutorial
* API Docs: http://docs.angularjs.org
* Developer Guide: http://docs.angularjs.org/#!/guide
Compiling
---------
@@ -7,6 +12,8 @@ Compiling
Running Tests
-------------
rake server:start
rake test
./server.sh # start the server
open http://localhost:9876/capture # capture browser
./test.sh # run all unit tests
+31 -60
View File
@@ -77,66 +77,8 @@ task :compile_jstd_scenario_adapter => :init do
end
desc 'Generate IE css js patch'
task :generate_ie_compat => :init do
css = File.open('css/angular.css', 'r') {|f| f.read }
# finds all css rules that contain backround images and extracts the rule name(s), content type of
# the image and base64 encoded image data
r = /\n([^\{\n]+)\s*\{[^\}]*background-image:\s*url\("data:([^;]+);base64,([^"]+)"\);[^\}]*\}/
images = css.scan(r)
# create a js file with multipart header containing the extracted images. the entire file *must*
# be CRLF (\r\n) delimited
File.open(path_to('angular-ie-compat.js'), 'w') do |f|
f.write("/*\r\n" +
"Content-Type: multipart/related; boundary=\"_\"\r\n" +
"\r\n")
images.each_index do |idx|
f.write("--_\r\n" +
"Content-Location:img#{idx}\r\n" +
"Content-Transfer-Encoding:base64\r\n" +
"\r\n" +
images[idx][2] + "\r\n")
end
f.write("--_--\r\n" +
"*/\r\n")
# generate a css string containing *background-image rules for IE that point to the mime type
# images in the header
cssString = ''
images.each_index do |idx|
cssString += "#{images[idx][0]}{*background-image:url(\"mhtml:' + jsUri + '!img#{idx}\")}"
end
# generate a javascript closure that contains a function which will append the generated css
# string as a stylesheet to the current html document
jsString = "(function(){ \r\n" +
" var jsUri = document.location.href.replace(/\\/[^\\\/]+(#.*)?$/, '/') + \r\n" +
" document.getElementById('ng-ie-compat').src,\r\n" +
" css = '#{cssString}',\r\n" +
" s = document.createElement('style'); \r\n" +
"\r\n" +
" s.setAttribute('type', 'text/css'); \r\n" +
"\r\n" +
" if (s.styleSheet) { \r\n" +
" s.styleSheet.cssText = css; \r\n" +
" } else { \r\n" +
" s.appendChild(document.createTextNode(css)); \r\n" +
" } \r\n" +
" document.getElementsByTagName('head')[0].appendChild(s); \r\n" +
"})();\r\n"
f.write(jsString)
end
end
desc 'Compile JavaScript'
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :generate_ie_compat] do
task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter] do
deps = [
'src/angular.prefix',
@@ -167,6 +109,27 @@ task :compile => [:init, :compile_scenario, :compile_jstd_scenario_adapter, :gen
--js_output_file #{path_to('angular.min.js')})
FileUtils.cp_r 'i18n/locale', path_to('i18n')
File.open(path_to('angular-loader.js'), 'w') do |f|
concat = 'cat ' + [
'src/loader.prefix',
'src/loader.js',
'src/loader.suffix'].flatten.join(' ')
content = %x{#{concat}}.
gsub('"NG_VERSION_FULL"', NG_VERSION.full).
gsub(/^\s*['"]use strict['"];?\s*$/, '') # remove all file-specific strict mode flags
f.write(content)
end
%x(java -jar lib/closure-compiler/compiler.jar \
--compilation_level SIMPLE_OPTIMIZATIONS \
--language_in ECMASCRIPT5_STRICT \
--js #{path_to('angular-loader.js')} \
--js_output_file #{path_to('angular-loader.min.js')})
end
@@ -192,8 +155,9 @@ task :package => [:clean, :compile, :docs] do
['src/angular-mocks.js',
path_to('angular.js'),
path_to('angular-loader.js'),
path_to('angular.min.js'),
path_to('angular-ie-compat.js'),
path_to('angular-loader.min.js'),
path_to('angular-scenario.js'),
path_to('jstd-scenario-adapter.js'),
path_to('jstd-scenario-adapter-config.js'),
@@ -205,6 +169,13 @@ task :package => [:clean, :compile, :docs] do
FileUtils.cp_r path_to('i18n'), "#{pkg_dir}/i18n-#{NG_VERSION.full}"
FileUtils.cp_r path_to('docs'), "#{pkg_dir}/docs-#{NG_VERSION.full}"
File.open("#{pkg_dir}/angular-mocks-#{NG_VERSION.full}.js", File::RDWR) do |f|
text = f.read
f.truncate 0
f.rewind
f.write text.sub('"NG_VERSION_FULL"', NG_VERSION.full)
end
File.open("#{pkg_dir}/docs-#{NG_VERSION.full}/index.html", File::RDWR) do |f|
text = f.read
f.truncate 0
+41 -25
View File
@@ -1,42 +1,49 @@
angularFiles = {
'angularSrc': [
'src/Angular.js',
'src/loader.js',
'src/AngularPublic.js',
'src/JSON.js',
'src/Compiler.js',
'src/Scope.js',
'src/Injector.js',
'src/parser.js',
'src/Resource.js',
'src/Browser.js',
'src/sanitizer.js',
'src/jqLite.js',
'src/apis.js',
'src/filters.js',
'src/formatters.js',
'src/validators.js',
'src/service/anchorScroll.js',
'src/service/browser.js',
'src/service/cacheFactory.js',
'src/service/compiler.js',
'src/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
'src/service/document.js',
'src/service/exceptionHandler.js',
'src/service/hover.js',
'src/service/invalidWidgets.js',
'src/service/filter.js',
'src/service/filter/filter.js',
'src/service/filter/filters.js',
'src/service/filter/limitTo.js',
'src/service/filter/orderBy.js',
'src/service/formFactory.js',
'src/service/interpolate.js',
'src/service/location.js',
'src/service/log.js',
'src/service/resource.js',
'src/service/parse.js',
'src/service/q.js',
'src/service/route.js',
'src/service/routeParams.js',
'src/service/scope.js',
'src/service/sniffer.js',
'src/service/window.js',
'src/service/xhr.bulk.js',
'src/service/xhr.cache.js',
'src/service/xhr.error.js',
'src/service/xhr.js',
'src/service/http.js',
'src/service/httpBackend.js',
'src/service/locale.js',
'src/directives.js',
'src/markups.js',
'src/widgets.js',
'src/AngularPublic.js',
'src/widget/form.js',
'src/widget/input.js',
'src/widget/select.js'
],
'angularScenario': [
@@ -57,32 +64,33 @@ angularFiles = {
],
'jstd': [
'lib/jasmine-1.0.1/jasmine.js',
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'lib/jquery/jquery.js',
'test/jquery_remove.js',
'@angularSrc',
'example/personalLog/*.js',
'test/testabilityPatch.js',
'test/matchers.js',
'src/scenario/Scenario.js',
'src/scenario/output/*.js',
'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js',
'src/angular-mocks.js',
'test/mocks.js',
'test/scenario/*.js',
'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
'test/service/filter/*.js',
'test/widget/*.js',
'example/personalLog/test/*.js'
],
'jstdExclude': [
'test/jquery_alias.js',
'src/angular-bootstrap.js',
'src/scenario/angular-bootstrap.js',
'src/AngularPublic.js'
'src/scenario/angular-bootstrap.js'
],
'jstdScenario': [
@@ -92,8 +100,17 @@ angularFiles = {
'build/docs/docs-scenario.js'
],
'jstdMocks': [
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'build/angular.js',
'src/angular-mocks.js',
'test/matchers.js',
'test/angular-mocksSpec.js'
],
'jstdPerf': [
'lib/jasmine-1.0.1/jasmine.js',
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'angularSrc',
'src/angular-mocks.js',
@@ -104,36 +121,35 @@ angularFiles = {
'jstdPerfExclude': [
'src/angular-bootstrap.js',
'src/scenario/angular-bootstrap.js',
'src/AngularPublic.js'
'src/scenario/angular-bootstrap.js'
],
'jstdJquery': [
'lib/jasmine-1.0.1/jasmine.js',
'lib/jasmine/jasmine.js',
'lib/jasmine-jstd-adapter/JasmineAdapter.js',
'lib/jquery/jquery.js',
'test/jquery_alias.js',
'@angularSrc',
'example/personalLog/*.js',
'test/testabilityPatch.js',
'test/matchers.js',
'src/scenario/Scenario.js',
'src/scenario/output/*.js',
'src/jstd-scenario-adapter/*.js',
'src/scenario/*.js',
'src/angular-mocks.js',
'test/mocks.js',
'test/scenario/*.js',
'test/scenario/output/*.js',
'test/jstd-scenario-adapter/*.js',
'test/*.js',
'test/service/*.js',
'test/widget/*.js',
'example/personalLog/test/*.js'
],
'jstdJqueryExclude': [
'src/angular-bootstrap.js',
'src/AngularPublic.js',
'src/scenario/angular-bootstrap.js',
'test/jquery_remove.js'
]
}
};
+2 -84
View File
@@ -4,88 +4,6 @@
display: none;
}
.ng-format-negative {
color: red;
}
.ng-exception {
border: 2px solid #FF0000;
font-family: "Courier New", Courier, monospace;
font-size: smaller;
white-space: pre;
}
.ng-validation-error {
border: 2px solid #FF0000;
}
/*****************
* TIP
*****************/
#ng-callout {
margin: 0;
padding: 0;
border: 0;
outline: 0;
font-size: 13px;
font-weight: normal;
font-family: Verdana, Arial, Helvetica, sans-serif;
vertical-align: baseline;
background: transparent;
text-decoration: none;
}
#ng-callout .ng-arrow-left{
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrSLoc/AG8FeUUIN+sGebWAnbKSJodqqlsOxJtqYooU9vvk+vcJIcTkg+QAAA7");
background-repeat: no-repeat;
background-position: left top;
position: absolute;
z-index:101;
left:-12px;
height:23px;
width:10px;
top:-3px;
}
#ng-callout .ng-arrow-right{
background-image: url("data:image/gif;base64,R0lGODlhCwAXAKIAAMzMzO/v7/f39////////wAAAAAAAAAAACH5BAUUAAQALAAAAAALABcAAAMrCLTcoM29yN6k9socs91e5X3EyJloipYrO4ohTMqA0Fn2XVNswJe+H+SXAAA7");
background-repeat: no-repeat;
background-position: left top;
position: absolute;
z-index:101;
height:23px;
width:11px;
top:-2px;
}
#ng-callout {
position: absolute;
z-index:100;
border: 2px solid #CCCCCC;
background-color: #fff;
}
#ng-callout .ng-content{
padding:10px 10px 10px 10px;
color:#333333;
}
#ng-callout .ng-title{
background-color: #CCCCCC;
text-align: left;
padding-left: 8px;
padding-bottom: 5px;
padding-top: 2px;
font-weight:bold;
}
/*****************
* indicators
*****************/
.ng-input-indicator-wait {
background-image: url("data:image/png;base64,R0lGODlhEAAQAPQAAP///wAAAPDw8IqKiuDg4EZGRnp6egAAAFhYWCQkJKysrL6+vhQUFJycnAQEBDY2NmhoaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCgAAACwAAAAAEAAQAAAFdyAgAgIJIeWoAkRCCMdBkKtIHIngyMKsErPBYbADpkSCwhDmQCBethRB6Vj4kFCkQPG4IlWDgrNRIwnO4UKBXDufzQvDMaoSDBgFb886MiQadgNABAokfCwzBA8LCg0Egl8jAggGAA1kBIA1BAYzlyILczULC2UhACH5BAkKAAAALAAAAAAQABAAAAV2ICACAmlAZTmOREEIyUEQjLKKxPHADhEvqxlgcGgkGI1DYSVAIAWMx+lwSKkICJ0QsHi9RgKBwnVTiRQQgwF4I4UFDQQEwi6/3YSGWRRmjhEETAJfIgMFCnAKM0KDV4EEEAQLiF18TAYNXDaSe3x6mjidN1s3IQAh+QQJCgAAACwAAAAAEAAQAAAFeCAgAgLZDGU5jgRECEUiCI+yioSDwDJyLKsXoHFQxBSHAoAAFBhqtMJg8DgQBgfrEsJAEAg4YhZIEiwgKtHiMBgtpg3wbUZXGO7kOb1MUKRFMysCChAoggJCIg0GC2aNe4gqQldfL4l/Ag1AXySJgn5LcoE3QXI3IQAh+QQJCgAAACwAAAAAEAAQAAAFdiAgAgLZNGU5joQhCEjxIssqEo8bC9BRjy9Ag7GILQ4QEoE0gBAEBcOpcBA0DoxSK/e8LRIHn+i1cK0IyKdg0VAoljYIg+GgnRrwVS/8IAkICyosBIQpBAMoKy9dImxPhS+GKkFrkX+TigtLlIyKXUF+NjagNiEAIfkECQoAAAAsAAAAABAAEAAABWwgIAICaRhlOY4EIgjH8R7LKhKHGwsMvb4AAy3WODBIBBKCsYA9TjuhDNDKEVSERezQEL0WrhXucRUQGuik7bFlngzqVW9LMl9XWvLdjFaJtDFqZ1cEZUB0dUgvL3dgP4WJZn4jkomWNpSTIyEAIfkECQoAAAAsAAAAABAAEAAABX4gIAICuSxlOY6CIgiD8RrEKgqGOwxwUrMlAoSwIzAGpJpgoSDAGifDY5kopBYDlEpAQBwevxfBtRIUGi8xwWkDNBCIwmC9Vq0aiQQDQuK+VgQPDXV9hCJjBwcFYU5pLwwHXQcMKSmNLQcIAExlbH8JBwttaX0ABAcNbWVbKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICSRBlOY7CIghN8zbEKsKoIjdFzZaEgUBHKChMJtRwcWpAWoWnifm6ESAMhO8lQK0EEAV3rFopIBCEcGwDKAqPh4HUrY4ICHH1dSoTFgcHUiZjBhAJB2AHDykpKAwHAwdzf19KkASIPl9cDgcnDkdtNwiMJCshACH5BAkKAAAALAAAAAAQABAAAAV3ICACAkkQZTmOAiosiyAoxCq+KPxCNVsSMRgBsiClWrLTSWFoIQZHl6pleBh6suxKMIhlvzbAwkBWfFWrBQTxNLq2RG2yhSUkDs2b63AYDAoJXAcFRwADeAkJDX0AQCsEfAQMDAIPBz0rCgcxky0JRWE1AmwpKyEAIfkECQoAAAAsAAAAABAAEAAABXkgIAICKZzkqJ4nQZxLqZKv4NqNLKK2/Q4Ek4lFXChsg5ypJjs1II3gEDUSRInEGYAw6B6zM4JhrDAtEosVkLUtHA7RHaHAGJQEjsODcEg0FBAFVgkQJQ1pAwcDDw8KcFtSInwJAowCCA6RIwqZAgkPNgVpWndjdyohACH5BAkKAAAALAAAAAAQABAAAAV5ICACAimc5KieLEuUKvm2xAKLqDCfC2GaO9eL0LABWTiBYmA06W6kHgvCqEJiAIJiu3gcvgUsscHUERm+kaCxyxa+zRPk0SgJEgfIvbAdIAQLCAYlCj4DBw0IBQsMCjIqBAcPAooCBg9pKgsJLwUFOhCZKyQDA3YqIQAh+QQJCgAAACwAAAAAEAAQAAAFdSAgAgIpnOSonmxbqiThCrJKEHFbo8JxDDOZYFFb+A41E4H4OhkOipXwBElYITDAckFEOBgMQ3arkMkUBdxIUGZpEb7kaQBRlASPg0FQQHAbEEMGDSVEAA1QBhAED1E0NgwFAooCDWljaQIQCE5qMHcNhCkjIQAh+QQJCgAAACwAAAAAEAAQAAAFeSAgAgIpnOSoLgxxvqgKLEcCC65KEAByKK8cSpA4DAiHQ/DkKhGKh4ZCtCyZGo6F6iYYPAqFgYy02xkSaLEMV34tELyRYNEsCQyHlvWkGCzsPgMCEAY7Cg04Uk48LAsDhRA8MVQPEF0GAgqYYwSRlycNcWskCkApIyEAOwAAAAAAAAAAAA==");
background-position: right;
background-repeat: no-repeat;
ng\:form {
display: block;
}
+92
View File
@@ -0,0 +1,92 @@
@ngdoc overview
@name angular.inputType
@description
Angular {@link guide/dev_guide.forms forms} allow you to build complex widgets. However for
simple widget which are based on HTML input text element a simpler way of providing the validation
and parsing is also provided. `angular.inputType` is a short hand for creating a widget which
already has the DOM listeners and `$render` method supplied. The only thing which needs to
be provided by the developer are the optional `$validate` listener and
`$parseModel` or `$parseModel` methods.
All `inputType` widgets support:
- CSS classes:
- **`ng-valid`**: when widget is valid.
- **`ng-invalid`**: when widget is invalid.
- **`ng-pristine`**: when widget has not been modified by user action.
- **`ng-dirty`**: when has been modified do to user action.
- Widget properties:
- **`$valid`**: When widget is valid.
- **`$invalid`**: When widget is invalid.
- **`$pristine`**: When widget has not been modified by user interaction.
- **`$dirty`**: When user has been modified do to user interaction.
- **`$required`**: When the `<input>` element has `required` attribute. This means that the
widget will have `REQUIRED` validation error if empty.
- **`$disabled`**: When the `<input>` element has `disabled` attribute.
- **`$readonly`**: When the `<input>` element has `readonly` attribute.
- Widget Attribute Validators:
- **`required`**: Sets `REQUIRED` validation error key if the input is empty
- **`ng:pattern`** Sets `PATTERN` validation error key if the value does not match the
RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
patterns defined as scope expressions.
# Example
<doc:example>
<doc:source>
<script>
angular.inputType('json', function() {
this.$parseView = function() {
try {
this.$modelValue = angular.fromJson(this.$viewValue);
if (this.$error.JSON) {
this.$emit('$valid', 'JSON');
}
} catch (e) {
this.$emit('$invalid', 'JSON');
}
}
this.$parseModel = function() {
this.$viewValue = angular.toJson(this.$modelValue);
}
});
function Ctrl() {
this.data = {
framework:'angular',
codenames:'supper-powers'
}
this.required = false;
this.disabled = false;
this.readonly = false;
}
</script>
<div ng:controller="Ctrl">
<form name="myForm">
<input type="json" ng:model="data" size="80"
ng:required="{{required}}" ng:disabled="{{disabled}}"
ng:readonly="{{readonly}}"/><br/>
Required: <input type="checkbox" ng:model="required"> <br/>
Disabled: <input type="checkbox" ng:model="disabled"> <br/>
Readonly: <input type="checkbox" ng:model="readonly"> <br/>
<pre>data={{data}}</pre>
<pre>myForm={{myForm}}</pre>
</form>
</div>
</doc:source>
<doc:scenario>
it('should invalidate on wrong input', function() {
expect(element('form[name=myForm]').prop('className')).toMatch('ng-valid');
input('data').enter('{}');
expect(binding('data')).toEqual('data={\n }');
input('data').enter('{');
expect(element('form[name=myForm]').prop('className')).toMatch('ng-invalid');
});
</doc:scenario>
</doc:example>
+5
View File
@@ -0,0 +1,5 @@
@ngdoc overview
@name angular.module.ng
@description
The `ng` is an angular module which contains all of the core angular services.
-28
View File
@@ -1,28 +0,0 @@
@workInProgress
@ngdoc overview
@name angular.service
@description
The services API provides objects for carrying out common web app tasks. Service objects are
managed by angular's {@link guide/dev_guide.di dependency injection system}.
* {@link angular.service.$browser $browser } - Provides an instance of a browser object
* {@link angular.service.$cookieStore $cookieStore } - Provides key / value storage backed by
session cookies
* {@link angular.service.$cookies $cookies } - Provides read / write access to browser cookies
* {@link angular.service.$defer $defer } - Defers function execution and try / catch block
* {@link angular.service.$document $document } - Provides reference to `window.document` element
* {@link angular.service.$exceptionHandler $exceptionHandler } - Receives uncaught angular
exceptions
* {@link angular.service.$hover $hover } -
* {@link angular.service.$invalidWidgets $invalidWidgets } - Holds references to invalid widgets
* {@link angular.service.$location $location } - Parses the browser location URL
* {@link angular.service.$log $log } - Provides logging service
* {@link angular.service.$resource $resource } - Creates objects for interacting with RESTful
server-side data sources
* {@link angular.service.$route $route } - Provides deep-linking services
* {@link angular.service.$window $window } - References the browsers `window` object
* {@link angular.service.$xhr $xhr} - Generates an XHR request.
For information on how angular services work and how to write your own services, see {@link
guide/dev_guide.services Angular Services} in the angular Developer Guide.
+7 -16
View File
@@ -7,28 +7,25 @@
* {@link angular.widget Widgets} - Angular custom DOM element
* {@link angular.directive Directives} - Angular DOM element attributes
* {@link angular.markup Markup} and {@link angular.attrMarkup Attribute Markup}
* {@link angular.filter Filters} - Angular output filters
* {@link angular.formatter Formatters} - Angular converters for form elements
* {@link angular.validator Validators} - Angular input validators
* {@link angular.compile angular.compile()} - Template compiler
* {@link angular.module.ng.$filter Filters} - Angular output filters
* {@link angular.module.ng.$compile $compile} - Template compiler
## Angular Scope API
* {@link angular.scope Scope Object} - Angular scope object
* {@link angular.module.ng.$rootScope.Scope Scope Object} - Angular scope object
## Angular Services & Dependency Injection API
* {@link angular.service Angular Services}
* {@link angular.module.ng Angular Services}
* {@link angular.injector angular.injector() }
## Angular Testing API
* {@link angular.mock Testing Mocks API} - Mock objects for testing
* {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en_US
Angular Scenario Runner} - Automated scenario testing documentation
* {@link angular.module.ngMock Testing Mocks API} - Mock objects for testing
* {@link guide/dev_guide.e2e-testing Angular Scenario Runner} - Automated scenario testing
documentation
## Angular Utility Functions
@@ -66,9 +63,3 @@ Angular Scenario Runner} - Automated scenario testing documentation
* {@link angular.fromJson angular.fromJson() }
* {@link angular.toJson angular.toJson() }
## Utility methods for JavaScript types
* {@link angular.Object Object API} - Utility functions for JavaScript objects
* {@link angular.Array Array API} - Utility functions for JavaScript arrays
+53 -32
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Advanced Form
@description
@@ -9,9 +8,7 @@ detection, and preventing invalid form submission.
<doc:example>
<doc:source>
<script>
UserForm.$inject = ['$invalidWidgets'];
function UserForm($invalidWidgets){
this.$invalidWidgets = $invalidWidgets;
function UserForm() {
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.master = {
@@ -30,43 +27,67 @@ detection, and preventing invalid form submission.
}
UserForm.prototype = {
cancel: function(){
cancel: function() {
this.form = angular.copy(this.master);
},
save: function(){
save: function() {
this.master = this.form;
this.cancel();
},
addContact: function() {
this.form.contacts.push({type:'', value:''});
},
removeContact: function(contact) {
for ( var i = 0, ii = this.form.contacts.length; i < ii; i++) {
if (contact === this.form.contacts[i]) {
this.form.contacts.splice(i, 1);
}
}
},
isCancelDisabled: function() {
return angular.equals(this.master, this.form);
},
isSaveDisabled: function() {
return this.myForm.$invalid || angular.equals(this.master, this.form);
}
};
</script>
<div ng:controller="UserForm">
<label>Name:</label><br/>
<input type="text" name="form.name" ng:required/> <br/><br/>
<form name="myForm">
<label>Address:</label><br/>
<input type="text" name="form.address.line1" size="33" ng:required/> <br/>
<input type="text" name="form.address.city" size="12" ng:required/>,
<input type="text" name="form.address.state" size="2" ng:required ng:validate="regexp:state"/>
<input type="text" name="form.address.zip" size="5" ng:required
ng:validate="regexp:zip"/><br/><br/>
<label>Name:</label><br/>
<input type="text" ng:model="form.name" required/> <br/><br/>
<label>Contacts:</label>
[ <a href="" ng:click="form.contacts.$add()">add</a> ]
<div ng:repeat="contact in form.contacts">
<select name="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" name="contact.value" ng:required/>
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<button ng:click="cancel()" ng:disabled="{{master.$equals(form)}}">Cancel</button>
<button ng:click="save()" ng:disabled="{{$invalidWidgets.visible() ||
master.$equals(form)}}">Save</button>
<label>Address:</label> <br/>
<input type="text" ng:model="form.address.line1" size="33" required/> <br/>
<input type="text" ng:model="form.address.city" size="12" required/>,
<input type="text" ng:model="form.address.state" size="2"
ng:pattern="state" required/>
<input type="text" ng:model="form.address.zip" size="5"
ng:pattern="zip" required/><br/><br/>
<label>Contacts:</label>
[ <a href="" ng:click="addContact()">add</a> ]
<div ng:repeat="contact in form.contacts">
<select ng:model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" ng:model="contact.value" required/>
[ <a href="" ng:click="removeContact(contact)">X</a> ]
</div>
<button ng:click="cancel()" ng:disabled="{{isCancelDisabled()}}">Cancel</button>
<button ng:click="save()" ng:disabled="{{isSaveDisabled()}}">Save</button>
</form>
<hr/>
Debug View:
@@ -75,7 +96,7 @@ master.$equals(form)}}">Save</button>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function(){
it('should enable save button', function() {
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.name').enter('');
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
@@ -84,13 +105,13 @@ master.$equals(form)}}">Save</button>
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function(){
it('should enable cancel button', function() {
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.name').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[name="form.name"]').val()).toEqual('John Smith');
expect(element(':input[ng\\:model="form.name"]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
+4 -4
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Resources - Buzz
@description
@@ -15,11 +14,12 @@ to retrieve Buzz activity and comments.
<script>
BuzzController.$inject = ['$resource'];
function BuzzController($resource) {
this.userId = 'googlebuzz';
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt: 'json', callback: 'JSON_CALLBACK'},
{ get: {method: 'JSON', params: {visibility: '@self'}},
replies: {method: 'JSON', params: {visibility: '@self', comments: '@comments'}}
{ get: {method: 'JSONP', params: {visibility: '@self'}},
replies: {method: 'JSONP', params: {visibility: '@self', comments: '@comments'}}
});
}
BuzzController.prototype = {
@@ -32,7 +32,7 @@ to retrieve Buzz activity and comments.
};
</script>
<div ng:controller="BuzzController">
<input name="userId" value="googlebuzz"/>
<input ng:model="userId"/>
<button ng:click="fetch()">fetch</button>
<hr/>
<div class="buzz" ng:repeat="item in activities.data.items">
+7 -8
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Deep Linking
@description
@@ -38,7 +37,7 @@ The two partials are defined in the following URLs:
* <a href="./examples/welcome.html" ng:ext-link>./examples/welcome.html</a>
<doc:example>
<doc:source>
<doc:source jsfiddle="false">
<script>
AppCntl.$inject = ['$route']
function AppCntl($route) {
@@ -56,7 +55,7 @@ The two partials are defined in the following URLs:
function WelcomeCntl($route){}
WelcomeCntl.prototype = {
greet: function(){
greet: function() {
alert("Hello " + this.person.name);
}
};
@@ -67,11 +66,11 @@ The two partials are defined in the following URLs:
this.cancel();
}
SettingsCntl.prototype = {
cancel: function(){
cancel: function() {
this.form = angular.copy(this.person);
},
save: function(){
save: function() {
angular.copy(this.form, this.person);
this.$location.path('/welcome');
}
@@ -89,7 +88,7 @@ The two partials are defined in the following URLs:
</div>
</doc:source>
<doc:scenario>
it('should navigate to URL', function(){
it('should navigate to URL', function() {
element('a:contains(Welcome)').click();
expect(element('ng\\:view').text()).toMatch(/Hello anonymous/);
element('a:contains(Settings)').click();
@@ -106,9 +105,9 @@ The two partials are defined in the following URLs:
# Things to notice
* Routes are defined in the `AppCntl` class. The initialization of the controller causes the
initialization of the {@link api/angular.service.$route $route} service with the proper URL
initialization of the {@link api/angular.module.ng.$route $route} service with the proper URL
routes.
* The {@link api/angular.service.$route $route} service then watches the URL and instantiates the
* The {@link api/angular.module.ng.$route $route} service then watches the URL and instantiates the
appropriate controller when the URL changes.
* The {@link api/angular.widget.ng:view ng:view} widget loads the view when the URL changes. It
also
+42 -28
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: Form
@description
@@ -11,7 +10,7 @@ allow a user to enter data.
<doc:example>
<doc:source>
<script>
function FormController(){
function FormController() {
this.user = {
name: 'John Smith',
address:{line1: '123 Main St.', city:'Anytown', state:'AA', zip:'12345'},
@@ -19,31 +18,44 @@ allow a user to enter data.
};
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.addContact = function() {
this.user.contacts.push({type:'', value:''});
};
this.removeContact = function(contact) {
for ( var i = 0, ii = this.user.contacts.length; i < ii; i++) {
if (contact === this.user.contacts[i]) {
this.user.contacts.splice(i, 1);
}
}
};
}
</script>
<div ng:controller="FormController" class="example">
<label>Name:</label><br/>
<input type="text" name="user.name" ng:required/> <br/><br/>
<input type="text" ng:model="user.name" required/> <br/><br/>
<label>Address:</label><br/>
<input type="text" name="user.address.line1" size="33" ng:required/> <br/>
<input type="text" name="user.address.city" size="12" ng:required/>,
<input type="text" name="user.address.state" size="2" ng:required ng:validate="regexp:state"/>
<input type="text" name="user.address.zip" size="5" ng:required
ng:validate="regexp:zip"/><br/><br/>
<input type="text" ng:model="user.address.line1" size="33" required> <br/>
<input type="text" ng:model="user.address.city" size="12" required>,
<input type="text" ng:model="user.address.state" size="2"
ng:pattern="state" required>
<input type="text" ng:model="user.address.zip" size="5"
ng:pattern="zip" required><br/><br/>
<label>Phone:</label>
[ <a href="" ng:click="user.contacts.$add()">add</a> ]
[ <a href="" ng:click="addContact()">add</a> ]
<div ng:repeat="contact in user.contacts">
<select name="contact.type">
<select ng:model="contact.type">
<option>email</option>
<option>phone</option>
<option>pager</option>
<option>IM</option>
</select>
<input type="text" name="contact.value" ng:required/>
[ <a href="" ng:click="user.contacts.$remove(contact)">X</a> ]
<input type="text" ng:model="contact.value" required/>
[ <a href="" ng:click="removeContact(contact)">X</a> ]
</div>
<hr/>
Debug View:
@@ -52,35 +64,37 @@ ng:validate="regexp:zip"/><br/><br/>
</doc:source>
<doc:scenario>
it('should show debug', function(){
it('should show debug', function() {
expect(binding('user')).toMatch(/John Smith/);
});
it('should add contact', function(){
it('should add contact', function() {
using('.example').element('a:contains(add)').click();
using('.example div:last').input('contact.value').enter('you@example.org');
expect(binding('user')).toMatch(/\(234\) 555\-1212/);
expect(binding('user')).toMatch(/you@example.org/);
});
it('should remove contact', function(){
it('should remove contact', function() {
using('.example').element('a:contains(X)').click();
expect(binding('user')).not().toMatch(/\(234\) 555\-1212/);
});
it('should validate zip', function(){
expect(using('.example').element(':input[name="user.address.zip"]').prop('className'))
.not().toMatch(/ng-validation-error/);
it('should validate zip', function() {
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).not().toMatch(/ng-invalid/);
using('.example').input('user.address.zip').enter('abc');
expect(using('.example').element(':input[name="user.address.zip"]').prop('className'))
.toMatch(/ng-validation-error/);
expect(using('.example').
element(':input[ng\\:model="user.address.zip"]').
prop('className')).toMatch(/ng-invalid/);
});
it('should validate state', function(){
expect(using('.example').element(':input[name="user.address.state"]').prop('className'))
.not().toMatch(/ng-validation-error/);
it('should validate state', function() {
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.not().toMatch(/ng-invalid/);
using('.example').input('user.address.state').enter('XXX');
expect(using('.example').element(':input[name="user.address.state"]').prop('className'))
.toMatch(/ng-validation-error/);
expect(using('.example').element(':input[ng\\:model="user.address.state"]').prop('className'))
.toMatch(/ng-invalid/);
});
</doc:scenario>
</doc:example>
@@ -90,11 +104,11 @@ ng:validate="regexp:zip"/><br/><br/>
* The user data model is initialized {@link api/angular.directive.ng:controller controller} and is
available in
the {@link api/angular.scope scope} with the initial data.
the {@link api/angular.module.ng.$rootScope.Scope scope} with the initial data.
* For debugging purposes we have included a debug view of the model to better understand what
is going on.
* The {@link api/angular.widget.HTML input widgets} simply refer to the model and are auto bound.
* The inputs {@link api/angular.validator validate}. (Try leaving them blank or entering non digits
* The {@link api/angular.widget.input input widgets} simply refer to the model and are data-bound.
* The inputs {@link guide/dev_guide.forms validate}. (Try leaving them blank or entering non digits
in the zip field)
* In your application you can simply read from or write to the model and the form will be updated.
* By clicking the 'add' link you are adding new items into the `user.contacts` array which are then
+13 -7
View File
@@ -1,16 +1,22 @@
@workInProgress
@ngdoc overview
@name Cookbook: Hello World
@description
<doc:example>
<doc:source>
Your name: <input type="text" name="name" value="World"/>
<hr/>
Hello {{name}}!
<script>
function HelloCntl() {
this.name = 'World';
}
</script>
<div ng:controller="HelloCntl">
Your name: <input type="text" ng:model="name" value="World"/>
<hr/>
Hello {{name}}!
</div>
</doc:source>
<doc:scenario>
it('should change the binding when user enters text', function(){
it('should change the binding when user enters text', function() {
expect(binding('name')).toEqual('World');
input('name').enter('angular');
expect(binding('name')).toEqual('angular');
@@ -23,9 +29,9 @@
Take a look through the source and note:
* The script tag that {@link guide/dev_guide.bootstrap bootstraps} the angular environment.
* The text {@link api/angular.widget.HTML input widget} which is bound to the greeting name text.
* The text {@link api/angular.widget.input input widget} which is bound to the greeting name text.
* No need for listener registration and event firing on change events.
* The implicit presence of the `name` variable which is in the root {@link api/angular.scope scope}.
* The implicit presence of the `name` variable which is in the root {@link api/angular.module.ng.$rootScope.Scope scope}.
* The double curly brace `{{markup}}`, which binds the name variable to the greeting text.
* The concept of {@link guide/dev_guide.templates.databinding data binding}, which reflects any
changes to the
+1 -2
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook
@description
@@ -45,7 +44,7 @@ allowing you to send links to specific screens in your app.
# Services
{@link api/angular.service Services}: Services are long lived objects in your applications that are
{@link api/angular.module.ng Services}: Services are long lived objects in your applications that are
available across controllers. A collection of useful services are pre-bundled with angular but you
will likely add your own. Services are initialized using dependency injection, which resolves the
order of initialization. This safeguards you from the perils of global state (a common way to
+5 -6
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Cookbook: MVC
@description
@@ -36,7 +35,7 @@ no connection between the controller and the view.
this.setUrl();
}
},
reset: function(){
reset: function() {
this.board = [
['', '', ''],
['', '', ''],
@@ -46,7 +45,7 @@ no connection between the controller and the view.
this.winner = '';
this.setUrl();
},
grade: function(){
grade: function() {
var b = this.board;
this.winner =
row(0) || row(1) || row(2) ||
@@ -57,7 +56,7 @@ no connection between the controller and the view.
function diagonal(i) { return same(b[0][1-i], b[1][1], b[2][1+i]);}
function same(a, b, c) { return (a==b && b==c) ? a : '';};
},
setUrl: function(){
setUrl: function() {
var rows = [];
angular.forEach(this.board, function(row){
rows.push(row.join(','));
@@ -91,7 +90,7 @@ no connection between the controller and the view.
</div>
</doc:source>
<doc:scenario>
it('should play a game', function(){
it('should play a game', function() {
piece(1, 1);
expect(binding('nextMove')).toEqual('O');
piece(3, 1);
@@ -122,4 +121,4 @@ board variable.
* The view can call any controller function.
* In this example, the `setUrl()` and `readUrl()` functions copy the game state to/from the URL's
hash so the browser's back button will undo game steps. See deep-linking. This example calls {@link
api/angular.scope.$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
api/angular.module.ng.$rootScope.Scope#$watch $watch()} to set up a listener that invokes `readUrl()` when needed.
@@ -1,89 +1,37 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Initializing Angular: Automatic Initiialization
@name Developer Guide: Initializing Angular: Automatic Initialization
@description
Angular initializes automatically when you load the angular script into your page, specifying
angular's `ng:autobind` attribute with no arguments:
Angular initializes automatically when you load the angular script into your page that contains an element
with `ng:app` directive:
<script src="angular.js" ng:autobind>
<pre>
<!doctype html>
<html ng:app>
<head>
<script src="angular.js"></script>
</head>
<body>
I can add: {{ 1+2 }}.
</body>
</html>
</pre>
From a high-level view, this is what happens during angular's automatic initialization process:
1. The browser loads the page, and then runs the angular script.
1. The browser loads the page, and then runs the angular script. Angular waits for the
`DOMContentLoaded` (or 'Load') event to attempt to bootstrap.
The `ng:autobind` attribute tells angular to compile and manage the whole HTML document. The
compilation phase is initiated in the page's `onLoad()` handler. Angular doesn't begin processing
the page until after the page load is complete.
2. Angular finds the root of the HTML document and creates the global variable `angular` in the
global namespace. Everything that angular subsequently creates is bound to fields in this global
object.
3. Angular walks the DOM looking for angular widgets, directives, and markup (such as `ng:init` or
`ng:repeat`). As angular encounters these, it creates child scopes as necessary and attaches them
to the DOM, registers listeners on those scopes, associates any controller functions with their
data and their part of the view, and ultimately constructs a runnable application. The resulting
app features two-way data-binding and a nice separation between data, presentation, and business
logic.
4. For the duration of the application session (while the page is loaded), angular monitors the
state of the application, and updates the view and the data model whenever the state of either one
changes.
For details on how the compiler works, see {@link dev_guide.compiler Angular HTML Compiler}.
2. Angular looks for the `ng:app` directive. If found it then proceeds to compile the DOM element and its children.
Optionally the `ng:app` may specify a {@link api/angular.module module} to load before the compilation. For details on
how the compiler works, see {@link dev_guide.compiler Angular HTML Compiler}.
## Initialization Options
The reason why `ng:autobind` exists is because angular should not assume that the entire HTML
The reason why `ng:app` exists is because angular should not assume that the entire HTML
document should be processed just because the `angular.js` script is included. In order to compile
only a part of the document, specify the ID of the element you want to use for angular's root
element as the value of the `ng:autobind` attribute:
ng:autobind="angularContent"
## Auto-bootstrap with `#autobind`
In some rare cases you can't define the `ng:` prefix before the script tag's attribute (for
example, in some CMS systems). In those situations it is possible to auto-bootstrap angular by
appending `#autobind` to the `<script src=...>` URL, like in this snippet:
<pre>
<!doctype html>
<html>
<head>
<script type="text/javascript"
src="http://code.angularjs.org/angular.js#autobind"></script>
</head>
<body>
<div xmlns:ng="http://angularjs.org">
Hello {{'world'}}!
</div>
</body>
</html>
</pre>
As with `ng:autobind`, you can specify an element id that should be exclusively targeted for
compilation as the value of the `#autobind`, for example: `#autobind=angularContent`.
## Filename Restrictions for Auto-bootstrap
In order for us to find the auto-bootstrap from a script attribute or URL fragment, the value of
the `script` `src` attribute that loads the angular script must match one of these naming
conventions:
- `angular.js`
- `angular-min.js`
- `angular-x.x.x.js`
- `angular-x.x.x.min.js`
- `angular-x.x.x-xxxxxxxx.js` (dev snapshot)
- `angular-x.x.x-xxxxxxxx.min.js` (dev snapshot)
- `angular-bootstrap.js` (used for development of angular)
Optionally, any of the filename formats above can be prepended with a relative or absolute URL that
ends with `/`.
only a part of the document set the `ng:app` on the root element of this portion.
## Global Angular Object
@@ -98,4 +46,4 @@ APIs are bound to fields of this global object.
## Related API
{@link api/angular.compile Compiler API}
{@link api/angular.module.ng.$compile Compiler API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Initializing Angular: Manual Initialization
@description
@@ -8,7 +7,7 @@ angular, but advanced users who want more control over the initialization proces
the manual bootstrapping method instead.
The best way to get started with manual bootstrapping is to look at the what happens when you use
{@link api/angular.directive.ng:autobind ng:autobind}, by showing each step of the process
{@link api/angular.directive.ng:app ng:app}, by showing each step of the process
explicitly.
<pre>
@@ -18,7 +17,7 @@ explicitly.
<script src="http://code.angularjs.org/angular.js"></script>
<script>
angular.element(document).ready(function() {
angular.compile(document)();
angular.bootstrap(document);
});
</script>
</head>
@@ -32,7 +31,7 @@ This is the sequence that your code should follow if you bootstrap angular on yo
1. After the page is loaded, find the root of the HTML template, which is typically the root of
the document.
2. Run angular's {@link dev_guide.compiler Angular HTML compiler}, which converts a template into
2. Call {@link api/angular.bootstrap} to {@link dev_guide.compiler compile} the template into
an executable, bi-directionally bound application.
@@ -44,4 +43,4 @@ an executable, bi-directionally bound application.
## Related API
{@link api/angular.compile Compiler API}
{@link api/angular.module.ng.$compile Compiler API}
+4 -5
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Initializing Angular
@description
@@ -8,20 +7,20 @@ angular should process and manage the page. To initialize angular you do the fol
* Specify the angular namespace in the `<html>` page
* Choose which flavor of angular script to load (debug or production)
* Specify whether or not angular should process and manage the page automatically (`ng:autobind`)
* Specify whether or not angular should process and manage the page automatically (`ng:app`)
The simplest way to initialize angular is to load the angular script and tell angular to compile
and manage the whole page. You do this as follows:
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<html xmlns:ng="http://angularjs.org" ng:app>
<head>
...
</head>
<body>
...
<script src="angular.js" ng:autobind>
<script src="angular.js">
</body>
</pre>
@@ -32,7 +31,7 @@ and manage the whole page. You do this as follows:
You need to declare the angular namespace declaration in the following cases:
* For all types of browser if you are using XHTML.
* For all browsers if you are using XHTML.
* For Internet Explorer older than version 9 (because older versions of IE do not render widgets
properly for either HTML or XHTML).
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Directives: Creating Custom Angular Directives
@description
@@ -24,7 +23,7 @@ angular.directive('ng:bind', function(expression, compiledElement) {
The angular compiler exposes methods that you may need to use when writing your own widgets and
directives. For example, the `descend()` method lets you control whether the compiler ignores or
processes child elements of the element it is compiling. For information on this and other
compiler methods, see the {@link api/angular.compile Compiler API doc}.
compiler methods, see the {@link api/angular.module.ng.$compile Compiler API doc}.
## Related Docs
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding Angular Directives
@description
@@ -16,11 +15,11 @@ directives per element.
You add angular directives to a standard HTML tag as in the following example, in which we have
added the {@link api/angular.directive.ng:click ng:click} directive to a button tag:
<button name="button1" ng:click="foo()">Click This</button>
<button ng:click="foo()">Click This</button>
In the example above, `name` is the standard HTML attribute, and `ng:click` is the angular
directive. The `ng:click` directive lets you implement custom behavior in an associated controller
function.
The `ng:click` directive lets you specify click event handlers directly in the template. Unlike the
evil `onclick` attribute, the expression associated with the `ng:click` directive is always executed
in the context of the current angular scope.
In the next example, we add the {@link api/angular.directive.ng:bind ng:bind} directive to a
`<span>` tag:
@@ -31,8 +30,9 @@ The `ng:bind` directive tells angular to set up {@link dev_guide.templates.datab
binding} between the data model and the view for the specified expression. When the angular {@link
dev_guide.compiler compiler} encounters an `ng:bind` directive in a template, it passes the
attribute value to the `ng:bind` function, which in turn sets up the data binding. On any change to
the expression in the model, the view is updated to display the span text with the changed
expression value.
the model that would change the result of the expression, the view is updated and the text of the
span element will reflect the new value. In the example above, the model is represented by two
constants, so nothing will ever change - Sorry!
## Related Topics
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Comparing Directives and Attribute Widgets
@description
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Extending the Angular Compiler
@description
@@ -94,4 +93,4 @@ corresponding spans.
## Related API
* {@link api/angular.compile angular.compile()}
* {@link api/angular.module.ng.$compile $compile()}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding Angular Markup
@description
@@ -90,4 +89,4 @@ angular.attrMarkup('extraClass', function(attrValue, attrName, element){
## Related API
* {@link api/angular.compile Compiler API Reference}
* {@link api/angular.module.ng.$compile Compiler API Reference}
+1 -2
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler
@description
@@ -24,4 +23,4 @@ All compilation takes place in the web browser; no server is involved.
## Related API
* {@link api/angular.compile Angular Compiler API}
* {@link api/angular.module.ng.$compile Angular Compiler API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Testing a New DOM Element
@description
@@ -15,4 +14,4 @@
## Related API
* {@link api/angular.compile angular.compile()}
* {@link api/angular.module.ng.$compile $compile()}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding How the Compiler Works
@description
@@ -7,8 +6,7 @@ Every {@link api/angular.widget widget}, {@link api/angular.directive directive}
dev_guide.compiler.markup markup} is defined with a compile function, which the angular compiler
executes on each widget or directive it encounters. The compile function optionally returns a link
function. This compilation process happens automatically when the page is loaded when you specify
`ng:autobind` in the script tag from which you load the angular script file. (See {@link
dev_guide.bootstrap Initializing Angular}.)
`ng:app` on the root element of the application. (See {@link dev_guide.bootstrap Initializing Angular}.)
The compile and link functions are related as follows:
@@ -21,7 +19,7 @@ dataset]"`), the link function gets called to set up a listener on each element
Note that angular's built-in widgets, directives, and markup have predefined compile and link
functions that you don't need to modify. When you create your own widgets, directives, or markup,
you must write compile and link functions for them. Refer to the {@link api/angular.compile
you must write compile and link functions for them. Refer to the {@link api/angular.module.ng.$compile
Compiler API} for details.
When the angular compiler compiles a page, it proceeds through 3 phases: Compile, Create Root
@@ -54,7 +52,7 @@ Note that while the compile function is executed exactly once, the link function
multiple times, for example, once for each iteration in a repeater.
The angular compiler exposes methods that you will need to make use of when writing your own
widgets and directives. For information on these methods, see the {@link api/angular.compile
widgets and directives. For information on these methods, see the {@link api/angular.module.ng.$compile
Compiler API doc}.
@@ -66,4 +64,4 @@ Compiler API doc}.
## Related API
* {@link api/angular.compile angular.compile()}
* {@link api/angular.module.ng.$compile $compile()}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Widgets: Creating Custom Widgets
@description
@@ -61,7 +60,7 @@ angular.widget('@my:watch', function(expression, compileElement) {
angular.widget('my:time', function(compileElement){
compileElement.css('display', 'block');
return function(linkElement){
function update(){
function update() {
linkElement.text('Current time is: ' + new Date());
setTimeout(update, 1000);
}
@@ -81,7 +80,7 @@ angular.widget('my:time', function(compileElement){
The angular compiler exposes methods that you may need to use of when writing your own widgets and
directives. For example, the `descend()` method lets you control whether the compiler ignores or
processes child elements of the element it is compiling. For information on this and other
compiler methods, see the {@link api/angular.compile Compiler API doc}.
compiler methods, see the {@link api/angular.module.ng.$compile Compiler API doc}.
## Related Topics
@@ -93,4 +92,4 @@ compiler methods, see the {@link api/angular.compile Compiler API doc}.
## Related API
* {@link api/angular.compile Compiler API}
* {@link api/angular.module.ng.$compile Compiler API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular HTML Compiler: Understanding Angular Widgets
@description
@@ -33,4 +32,4 @@ dev_guide.compiler.widgets.creating_widgets Creating Custom Widgets}.
## Related API
* {@link api/angular.compile Compiler API}
* {@link api/angular.module.ng.$compile Compiler API}
+1 -2
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: About Dependency Injection (DI)
@description
@@ -29,5 +28,5 @@ book.
## Related API
* {@link api/angular.service Service API}
* {@link api/angular.module.ng Service API}
* {@link api/angular.injector Angular Injector API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: DI: Understanding DI in Angular
@description
@@ -8,15 +7,15 @@ While DI is widely used in statically typed languages such as Java or C++, it ha
used in JavaScript. Angular brings the benefits of DI into JavaScript apps.
In angular, DI is implemented as a subsystem that manages dependencies between services,
controllers, widgets, and filters. The most important of these are {@link api/angular.service
services}.
controllers, widgets, and filters.
Services are objects that handle common tasks in web applications. Angular provides several{@link
api/angular.service built-in services}, and you can create your own custom services.
Services are objects that handle common tasks in web applications. Angular provides several {@link
api/angular.module.ng built-in services}, and you can create your
{@link dev_guide.services.creating_services own custom services}.
The main job of angular's DI subsystem is to provide services to angular components that depend on
them. The way the DI subsystem provides services is as follows: all services are registered with
angular's {@link api/angular.service service API}, and all components that depend on services
angular's {@link api/angular.module.ng service API}, and all components that depend on services
define those dependencies as a property (`$inject`). With this information, the DI subsystem
manages the creation of service objects and the provision of those objects to the components that
need them, at the time they need them. The following illustration steps through the sequence of
@@ -26,34 +25,23 @@ events:
In the illustration above, the dependency injection sequence proceeds as follows:
1. Service factory functions are registered with angular's service factory repository.
2. `ng:autobind` triggers angular's bootstrap sequence, during which angular compiles the template,
creates the root scope, and creates the dependency injector.
3. The `ng:controller` directive implicitly creates a new child scope, augmented by the application
of the `PhoneListCtrl` controller function.
4. The Injector identifies the `$xhr` service as `PhoneListCtrl` controller's only dependency.
5. The Injector checks if the `$xhr` service has already been instantiated, and if not uses the
factory function from the service factory repository to construct it.
6. DI provides the instance of $xhr service to the PhoneListCtrl controller constructor
1. Module "phonecat" is created and all the service providers are registered with this module.
(the "ng" module is created by Angular behind the scenes as well)
2. `ng:app` triggers bootstrap sequence on given element, during which angular creates injector,
loads "phonecat" and "ng" modules and compiles the template.
3. The `ng:controller` directive implicitly creates a new child scope and instantiates
`PhoneListCtrl` controller.
4. Injector identifies the `$http` service as `PhoneListCtrl` controller's only dependency.
5. Injector checks its instances cache whether the `$http` service has already been instantiated.
If not uses the provider from the available modules to construct it.
6. Injector provides the instance of `$http` service to the `PhoneListCtrl` controller constructor.
## How Scope Relates to DI
The {@link api/angular.injector injector} is responsible for resolving the service dependencies in
the application. It gets created and configured with the creation of a root scope. The injector
caches instances of services, with the services cache bound to the root scope.
The root scope of the application is just a service that is available for injection to any part of
the application under the service name "$rootScope".
Different root scopes have different instances of the injector. While typical angular applications
will only have one root scope (and hence the services will act like application singletons), in
tests it is important to not share singletons across test invocations for isolation reasons. We
achieve the necessary isolation by having each test create its own separate root scope.
<pre>
// create a root scope
var rootScope = angular.scope();
// access the service locator
var myService = rootScope.$service('myService');
</pre>
## Inferring dependencies from the signature of the factory function or constructor
@@ -75,7 +63,9 @@ equivalent:
<pre>
// given a user defined service
angular.service('serviceA', ...);
angular.module('module1', [], function($provide) {
$provide.factory('serviceA', ...);
});
// inject '$window', 'serviceA', curry 'name';
function fnA($window, serviceA, name){};
@@ -103,4 +93,4 @@ code and insert the `$inject` into the source code so that it can be minified/ob
## Related API
* {@link api/angular.service Services API}
* {@link api/angular.module.ng Services API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: DI: Using DI in Controllers
@description
@@ -15,15 +14,15 @@ MyController.$inject = ['$route'];
</pre>
In this example, the `MyController` constructor function takes one argument, the {@link
api/angular.service.$route $route} service. Angular is then responsible for supplying the instance
api/angular.module.ng.$route $route} service. Angular is then responsible for supplying the instance
of `$route` to the controller when the constructor is instantiated. There are two ways to cause
controller instantiation by configuring routes with the `$route` service, or by referencing the
controller from the HTML template, as follows:
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org" ng:controller="MyController">
<script src="http://code.angularjs.org/angular.min.js" ng:autobind></script>
<html xmlns:ng="http://angularjs.org" ng:controller="MyController" ng:app>
<script src="http://code.angularjs.org/angular.min.js"></script>
<body>
...
</body>
@@ -0,0 +1,178 @@
@workInProgress
@ngdoc overview
@name Developer Guide: E2E Testing
@description
As applications grow in size and complexity, it becomes unrealistic to rely on manual testing to
verify the correctness of new features, catch bugs and notice regressions.
To solve this problem, we have built an Angular Scenario Runner which simulates user interactions
that will help you verify the health of your Angular application.
# Overview
You will write scenario tests in JavaScript, which describe how your application should behave,
given a certain interaction in a specific state. A scenario is comprised of one or more it blocks
(you can think of these as the requirements of your application), which in turn are made of
**commands** and **expectations**. Commands tell the Runner to do something with the application
(such as navigate to a page or click on a button), and expectations tell the Runner to assert
something about the state (such as the value of a field or the current URL). If any expectation
fails, the runner marks the `it` as "failed" and continues on to the next one. Scenarios may also
have **beforeEach** and **afterEach** blocks, which will be run before (or after) each `it` block,
regardless of whether they pass or fail.
<img src="img/guide/scenario_runner.png">
In addition to the above elements, scenarios may also contain helper functions to avoid duplicating
code in the `it` blocks.
Here is an example of a simple scenario:
<pre>
describe('Buzz Client', function() {
it('should filter results', function() {
input('user').enter('jacksparrow');
element(':button').click();
expect(repeater('ul li').count()).toEqual(10);
input('filterText').enter('Bees');
expect(repeater('ul li').count()).toEqual(1);
});
});
</pre>
This scenario describes the requirements of a Buzz Client, specifically, that it should be able to
filter the stream of the user. It starts by entering a value in the 'user' input field, clicking
the only button on the page, and then it verifies that there are 10 items listed. It then enters
'Bees' in the 'filterText' input field and verifies that the list is reduced to a single item.
The API section below lists the available commands and expectations for the Runner.
# API
Source: {@link https://github.com/angular/angular.js/blob/master/src/scenario/dsl.js}
## pause()
Pauses the execution of the tests until you call `resume()` in the console (or click the resume
link in the Runner UI).
## sleep(seconds)
Pauses the execution of the tests for the specified number of `seconds`.
## browser().navigateTo(url)
Loads the `url` into the test frame.
## browser().navigateTo(url, fn)
Loads the URL returned by `fn` into the testing frame. The given `url` is only used for the test
output. Use this when the destination URL is dynamic (that is, the destination is unknown when you
write the test).
## browser().reload()
Refreshes the currently loaded page in the test frame.
## browser().window().href()
Returns the window.location.href of the currently loaded page in the test frame.
## browser().window().path()
Returns the window.location.pathname of the currently loaded page in the test frame.
## browser().window().search()
Returns the window.location.search of the currently loaded page in the test frame.
## browser().window().hash()
Returns the window.location.hash (without `#`) of the currently loaded page in the test frame.
## browser().location().url()
Returns the {@link api/angular.module.ng.$location $location.url()} of the currently loaded page in
the test frame.
## browser().location().path()
Returns the {@link api/angular.module.ng.$location $location.path()} of the currently loaded page in
the test frame.
## browser().location().search()
Returns the {@link api/angular.module.ng.$location $location.search()} of the currently loaded page
in the test frame.
## browser().location().hash()
Returns the {@link api/angular.module.ng.$location $location.hash()} of the currently loaded page in
the test frame.
## expect(future).{matcher}
Asserts the value of the given `future` satisfies the `matcher`. All API statements return a
`future` object, which get a `value` assigned after they are executed. Matchers are defined using
`angular.scenario.matcher`, and they use the value of futures to run the expectation. For example:
`expect(browser().location().href()).toEqual('http://www.google.com')`
## expect(future).not().{matcher}
Asserts the value of the given `future` satisfies the negation of the `matcher`.
## using(selector, label)
Scopes the next DSL element selection.
## binding(name)
Returns the value of the first binding matching the given `name`.
## input(name).enter(value)
Enters the given `value` in the text field with the given `name`.
## input(name).check()
Checks/unchecks the checkbox with the given `name`.
## input(name).select(value)
Selects the given `value` in the radio button with the given `name`.
## input(name).val()
Returns the current value of an input field with the given `name`.
## repeater(selector, label).count()
Returns the number of rows in the repeater matching the given jQuery `selector`. The `label` is
used for test ouput.
## repeater(selector, label).row(index)
Returns an array with the bindings in the row at the given `index` in the repeater matching the
given jQuery `selector`. The `label` is used for test output.
## repeater(selector, label).column(binding)
Returns an array with the values in the column with the given `binding` in the repeater matching
the given jQuery `selector`. The `label` is used for test output.
## select(name).option(value)
Picks the option with the given `value` on the select with the given `name`.
## select(name).option(value1, value2...)
Picks the options with the given `values` on the multi select with the given `name`.
## element(selector, label).count()
Returns the number of elements that match the given jQuery `selector`. The `label` is used for test
output.
## element(selector, label).click()
Clicks on the element matching the given jQuery `selector`. The `label` is used for test output.
## element(selector, label).query(fn)
Executes the function `fn(selectedElements, done)`, where selectedElements are the elements that
match the given jQuery `selector` and `done` is a function that is called at the end of the `fn`
function. The `label` is used for test output.
## element(selector, label).{method}()
Returns the result of calling `method` on the element matching the given jQuery `selector`, where
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
`scrollTop`, `offset`. The `label` is used for test output.
## element(selector, label).{method}(value)
Executes the `method` passing in `value` on the element matching the given jQuery `selector`, where
`method` can be any of the following jQuery methods: `val`, `text`, `html`, `height`,
`innerHeight`, `outerHeight`, `width`, `innerWidth`, `outerWidth`, `position`, `scrollLeft`,
`scrollTop`, `offset`. The `label` is used for test output.
## element(selector, label).{method}(key)
Returns the result of calling `method` passing in `key` on the element matching the given jQuery
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
`label` is used for test output.
## element(selector, label).{method}(key, value)
Executes the `method` passing in `key` and `value` on the element matching the given jQuery
`selector`, where `method` can be any of the following jQuery methods: `attr`, `prop`, `css`. The
`label` is used for test output.
JavaScript is a dynamically typed language which comes with great power of expression, but it also
come 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
angular which makes testing your angular applications easy. So there is no excuse for not do it.
+44 -19
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Understanding Angular Expressions
@description
@@ -41,7 +40,7 @@ the `Scope:$eval()` method.
1+2={{1+2}}
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function(){
it('should calculate expression in binding', function() {
expect(binding('1+2')).toEqual('3');
});
</doc:scenario>
@@ -51,20 +50,37 @@ You can try evaluating different expressions here:
<doc:example>
<doc:source>
<div ng:init="exprs=[]" class="expressions">
<script>
function Cntl2() {
this.exprs = [];
this.expr = '3*10|currency';
this.addExp = function(expr) {
this.exprs.push(expr);
};
this.removeExp = function(contact) {
for ( var i = 0, ii = this.exprs.length; i < ii; i++) {
if (contact === this.exprs[i]) {
this.exprs.splice(i, 1);
}
}
};
}
</script>
<div ng:controller="Cntl2" class="expressions">
Expression:
<input type='text' name="expr" value="3*10|currency" size="80"/>
<button ng:click="exprs.$add(expr)">Evaluate</button>
<input type='text' ng:model="expr" size="80"/>
<button ng:click="addExp(expr)">Evaluate</button>
<ul>
<li ng:repeat="expr in exprs">
[ <a href="" ng:click="exprs.$remove(expr)">X</a> ]
[ <a href="" ng:click="removeExp(expr)">X</a> ]
<tt>{{expr}}</tt> => <span ng:bind="$parent.$eval(expr)"></span>
</li>
</ul>
</div>
</doc:source>
<doc:scenario>
it('should allow user expression testing', function(){
it('should allow user expression testing', function() {
element('.expressions :button').click();
var li = using('.expressions ul').repeater('li');
expect(li.count()).toBe(1);
@@ -84,13 +100,22 @@ the global state (a common source of subtle bugs).
<doc:example>
<doc:source>
<div class="example2" ng:init="$window = $service('$window')">
Name: <input name="name" type="text" value="World"/>
<button ng:click="($window.mockWindow || $window).alert('Hello ' + name)">Greet</button>
<script>
function Cntl1($window){
this.name = 'World';
this.greet = function() {
($window.mockWindow || $window).alert('Hello ' + this.name);
}
}
</script>
<div class="example2" ng:controller="Cntl1">
Name: <input ng:model="name" type="text"/>
<button ng:click="greet()">Greet</button>
</div>
</doc:source>
<doc:scenario>
it('should calculate expression in binding', function(){
it('should calculate expression in binding', function() {
var alertText;
this.addFutureAction('set mock', function($window, $document, done) {
$window.mockWindow = {
@@ -140,15 +165,15 @@ JavaScript method instead.
Built-in types have methods like `[].push()`, but the richness of these methods is limited.
Consider the example below, which allows you to do a simple search over a canned set of contacts.
The example would be much more complicated if we did not have the `Array:$filter()`. There is no
built-in method on `Array` called {@link api/angular.Array.filter $filter} and angular doesn't add
built-in method on `Array` called {@link api/angular.module.ng.$filter.filter $filter} and angular doesn't add
it to `Array.prototype` because that could collide with other JavaScript frameworks.
For this reason the scope expression evaluator augments the built-in types to make them act like
they have extra methods. The actual method for `$filter()` is `angular.Array.filter()`. You can
they have extra methods. The actual method for `$filter()` is `angular.module.ng.$filter.filter()`. You can
call it from JavaScript.
Extensions: You can further extend the expression vocabulary by adding new methods to
`angular.Array` or `angular.String`, etc.
`angular.module.ng.$filter` or `angular.String`, etc.
<doc:example>
<doc:source>
@@ -158,17 +183,17 @@ Extensions: You can further extend the expression vocabulary by adding new metho
{name:'Mike', phone:'555-4321'},
{name:'Adam', phone:'555-5678'},
{name:'Julie', phone:'555-8765'}]"></div>
Search: <input name="searchText"/>
Search: <input ng:model="searchText"/>
<table class="example3">
<tr><th>Name</th><th>Phone</th><tr>
<tr ng:repeat="friend in friends.$filter(searchText)">
<tr ng:repeat="friend in friends | filter:searchText">
<td>{{friend.name}}</td>
<td>{{friend.phone}}</td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should filter the list', function(){
it('should filter the list', function() {
var tr = using('table.example3').repeater('tr.ng-attr-widget');
expect(tr.count()).toBe(5);
input('searchText').enter('a');
@@ -187,7 +212,7 @@ of filters like this:
name | uppercase
The expression evaluator simply passes the value of name to angular.filter.uppercase.
The expression evaluator simply passes the value of name to angular.module.ng.$filter.uppercase.
Chain filters using this syntax:
@@ -218,4 +243,4 @@ so that angular developers and developers who use angular can develop in harmony
## Related API
* {@link api/angular.compile Angular Compiler API}
* {@link api/angular.module.ng.$compile Angular Compiler API}
+614
View File
@@ -0,0 +1,614 @@
@ngdoc overview
@name Developer Guide: Forms
@description
# Overview
Forms allow users to enter data into your application. Forms represent the bidirectional data
bindings in Angular.
Forms consist of all of the following:
- the individual widgets with which users interact
- the validation rules for widgets
- the form, a collection of widgets that contains aggregated validation information
# Form
A form groups a set of widgets together into a single logical data-set. A form is created using
the {@link api/angular.widget.form &lt;form&gt;} element that calls the
{@link api/angular.module.ng.$formFactory $formFactory} service. The form is responsible for managing
the widgets and for tracking validation information.
A form is:
- The collection which contains widgets or other forms.
- Responsible for marshaling data from the model into a widget. This is
triggered by {@link api/angular.module.ng.$rootScope.Scope#$watch $watch} of the model expression.
- Responsible for marshaling data from the widget into the model. This is
triggered by the widget emitting the `$viewChange` event.
- Responsible for updating the validation state of the widget, when the widget emits
`$valid` / `$invalid` event. The validation state is useful for controlling the validation
errors shown to the user in it consist of:
- `$valid` / `$invalid`: Complementary set of booleans which show if a widget is valid / invalid.
- `$error`: an object which has a property for each validation key emited by the widget.
The value of the key is always true. If widget is valid, then the `$error`
object has no properties. For example if the widget emits
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
updated to `$error.REQUIRED == true`.
- Responsible for aggregating widget validation information into the form.
- `$valid` / `$invalid`: Complementary set of booleans which show if all the child widgets
(or forms) are valid or if any are invalid.
- `$error`: an object which has a property for each validation key emited by the
child widget. The value of the key is an array of widgets which fired the invalid
event. If all child widgets are valid then, then the `$error` object has no
properties. For example if a child widget emits
`$invalid` event with `REQUIRED` key. The internal state of the `$error` would be
updated to `$error.REQUIRED == [ widgetWhichEmitedInvalid ]`.
# Widgets
In Angular, a widget is the term used for the UI with which the user input. Examples of
bult-in Angular widgets are {@link api/angular.widget.input input} and
{@link api/angular.widget.select select}. Widgets provide the rendering and the user
interaction logic. Widgets should be declared inside a form, if no form is provided an implicit
form {@link api/angular.module.ng.$formFactory $formFactory.rootForm} form is used.
Widgets are implemented as Angular controllers. A widget controller:
- implements methods:
- `$render` - Updates the DOM from the internal state as represented by `$viewValue`.
- `$parseView` - Translate `$viewValue` to `$modelValue`. (`$modelValue` will be assigned to
the model scope by the form)
- `$parseModel` - Translate `$modelValue` to `$viewValue`. (`$viewValue` will be assigned to
the DOM inside the `$render` method)
- responds to events:
- `$validate` - Emitted by the form when the form determines that the widget needs to validate
itself. There may be more then one listener on the `$validate` event. The widget responds
by emitting `$valid` / `$invalid` event of its own.
- emits events:
- `$viewChange` - Emitted when the user interacts with the widget and it is necessary to update
the model.
- `$valid` - Emitted when the widget determines that it is valid (usually as a response to
`$validate` event or inside `$parseView()` or `$parseModel()` method).
- `$invalid` - Emitted when the widget determines that it is invalid (usually as a response to
`$validate` event or inside `$parseView()` or `$parseModel()` method).
- `$destroy` - Emitted when the widget element is removed from the DOM.
# CSS
Angular-defined widgets and forms set `ng-valid` and `ng-invalid` classes on themselves to allow
the web-designer a way to style them. If you write your own widgets, then their `$render()`
methods must set the appropriate CSS classes to allow styling.
(See {@link dev_guide.templates.css-styling CSS})
# Example
The following example demonstrates:
- How an error is displayed when a required field is empty.
- Error highlighting.
- How form submission is disabled when the form is invalid.
- The internal state of the widget and form in the the 'Debug View' area.
<doc:example>
<doc:source>
<style>
.ng-invalid { border: solid 1px red; }
.ng-form {display: block;}
</style>
<script>
function UserFormCntl() {
this.state = /^\w\w$/;
this.zip = /^\d\d\d\d\d$/;
this.master = {
customer: 'John Smith',
address:{
line1: '123 Main St.',
city:'Anytown',
state:'AA',
zip:'12345'
}
};
this.cancel();
}
UserFormCntl.prototype = {
cancel: function() {
this.form = angular.copy(this.master);
},
save: function() {
this.master = this.form;
this.cancel();
},
isCancelDisabled: function() {
return angular.equals(this.master, this.form);
},
isSaveDisabled: function() {
return this.userForm.$invalid || angular.equals(this.master, this.form);
}
};
</script>
<div ng:controller="UserFormCntl">
<form name="userForm">
<label>Name:</label><br/>
<input type="text" name="customer" ng:model="form.customer" required/>
<span class="error" ng:show="userForm.customer.$error.REQUIRED">
Customer name is required!</span>
<br/><br/>
<ng:form name="addressForm">
<label>Address:</label> <br/>
<input type="text" name="line1" size="33" required
ng:model="form.address.line1"/> <br/>
<input type="text" name="city" size="12" required
ng:model="form.address.city"/>,
<input type="text" name="state" ng:pattern="state" size="2" required
ng:model="form.address.state"/>
<input type="text" name="zip" ng:pattern="zip" size="5" required
ng:model="form.address.zip"/><br/><br/>
<span class="error" ng:show="addressForm.$invalid">
Incomplete address:
<span class="error" ng:show="addressForm.state.$error.REQUIRED">
Missing state!</span>
<span class="error" ng:show="addressForm.state.$error.PATTERN">
Invalid state!</span>
<span class="error" ng:show="addressForm.zip.$error.REQUIRED">
Missing zip!</span>
<span class="error" ng:show="addressForm.zip.$error.PATTERN">
Invalid zip!</span>
</span>
</ng:form>
<button ng:click="cancel()"
ng:disabled="{{isCancelDisabled()}}">Cancel</button>
<button ng:click="save()"
ng:disabled="{{isSaveDisabled()}}">Save</button>
</form>
<hr/>
Debug View:
<pre>form={{form}}</pre>
<pre>master={{master}}</pre>
<pre>userForm={{userForm}}</pre>
<pre>addressForm={{addressForm}}</pre>
</div>
</doc:source>
<doc:scenario>
it('should enable save button', function() {
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.customer').enter('');
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
input('form.customer').enter('change');
expect(element(':button:contains(Save)').attr('disabled')).toBeFalsy();
element(':button:contains(Save)').click();
expect(element(':button:contains(Save)').attr('disabled')).toBeTruthy();
});
it('should enable cancel button', function() {
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
input('form.customer').enter('change');
expect(element(':button:contains(Cancel)').attr('disabled')).toBeFalsy();
element(':button:contains(Cancel)').click();
expect(element(':button:contains(Cancel)').attr('disabled')).toBeTruthy();
expect(element(':input[ng\\:model="form.customer"]').val()).toEqual('John Smith');
});
</doc:scenario>
</doc:example>
# Life-cycle
- The `<form>` element triggers creation of a new form {@link dev_guide.scopes scope} using the
{@link api/angular.module.ng.$formFactory $formfactory}. The new form scope is added to the
`<form>` element using the jQuery `.data()` method for later retrieval under the key `$form`.
The form also sets up these listeners:
- `$destroy` - This event is emitted by nested widget when it is removed from the view. It gives
the form a chance to clean up any validation references to the destroyed widget.
- `$valid` / `$invalid` - This event is emitted by the widget on validation state change.
- `<input>` element triggers the creation of the widget using the
{@link api/angular.module.ng.$formFactory $formfactory.$createWidget()} method. The `$createWidget()`
creates new widget instance by calling the current scope {@link api/angular.module.ng.$rootScope.Scope#$new .$new()} and
registers these listeners:
- `$watch` on the model scope.
- `$viewChange` event on the widget scope.
- `$validate` event on the widget scope.
- Element `change` event when the user enters data.
<img class="center" src="img/form_data_flow.png" border="1" />
- When the user interacts with the widget:
1. The DOM element fires the `change` event which the widget intercepts. Widget then emits
a `$viewChange` event which includes the new user-entered value. (Remember that the DOM events
are outside of the Angular environment so the widget must emit its event within the
{@link api/angular.module.ng.$rootScope.Scope#$apply $apply} method).
2. The form's `$viewChange` listener copies the user-entered value to the widget's `$viewValue`
property. Since the `$viewValue` is the raw value as entered by user, it may need to be
translated to a different format/type (for example, translating a string to a number).
If you need your widget to translate between the internal `$viewValue` and the external
`$modelValue` state, you must declare a `$parseView()` method. The `$parseView()` method
will copy `$viewValue` to `$modelValue` and perform any necessary translations.
3. The `$modelValue` is written into the application model.
4. The form then emits a `$validate` event, giving the widget's validators chance to validate the
input. There can be any number of validators registered. Each validator may in turn
emit a `$valid` / `$invalid` event with the validator's validation key. For example `REQUIRED`.
5. Form listens to `$valid`/`$invalid` events and updates both the form as well as the widget
scope with the validation state. The validation updates the `$valid` and `$invalid`, property
as well as `$error` object. The widget's `$error` object is updated with the validation key
such that `$error.REQUIRED == true` when the validation emits `$invalid` with `REQUIRED`
validation key. Similarly the form's `$error` object gets updated, but instead of boolean
`true` it contains an array of invalid widgets (widgets which fired `$invalid` event with
`REQUIRED` validation key).
- When the model is updated:
1. The model `$watch` listener assigns the model value to `$modelValue` on the widget.
2. The form then calls `$parseModel` method on widget if present. The method converts the
value to renderable format and assigns it to `$viewValue` (for example converting number to a
string.)
3. The form then emits a `$validate` which behaves as described above.
4. The form then calls `$render` method on the widget to update the DOM structure from the
`$viewValue`.
# Writing Your Own Widget
This example shows how to implement a custom HTML editor widget in Angular.
<doc:example>
<doc:source>
<script>
function EditorCntl() {
this.htmlContent = '<b>Hello</b> <i>World</i>!';
}
HTMLEditorWidget.$inject = ['$element', 'htmlFilter'];
function HTMLEditorWidget(element, htmlFilter) {
var self = this;
this.$parseModel = function() {
// need to protect for script injection
try {
this.$viewValue = htmlFilter(
this.$modelValue || '').get();
if (this.$error.HTML) {
// we were invalid, but now we are OK.
this.$emit('$valid', 'HTML');
}
} catch (e) {
// if HTML not parsable invalidate form.
this.$emit('$invalid', 'HTML');
}
}
this.$render = function() {
element.html(this.$viewValue);
}
element.bind('keyup', function() {
self.$apply(function() {
self.$emit('$viewChange', element.html());
});
});
}
angular.directive('ng:html-editor-model', function() {
return ['$formFactory', '$element', function ($formFactory, element) {
var exp = element.attr('ng:html-editor-model'),
form = $formFactory.forElement(element),
widget;
element.attr('contentEditable', true);
widget = form.$createWidget({
scope: this,
model: exp,
controller: HTMLEditorWidget,
controllerArgs: {$element: element}});
// if the element is destroyed, then we need to
// notify the form.
element.bind('$destroy', function() {
widget.$destroy();
});
}];
});
</script>
<form name='editorForm' ng:controller="EditorCntl">
<div ng:html-editor-model="htmlContent"></div>
<hr/>
HTML: <br/>
<textarea ng:model="htmlContent" cols="80"></textarea>
<hr/>
<pre>editorForm = {{editorForm}}</pre>
</form>
</doc:source>
<doc:scenario>
it('should enter invalid HTML', function() {
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-valid/);
input('htmlContent').enter('<');
expect(element('form[name=editorForm]').prop('className')).toMatch(/ng-invalid/);
});
</doc:scenario>
</doc:example>
# HTML Inputs
The most common widgets you will use will be in the form of the
standard HTML set. These widgets are bound using the `name` attribute
to an expression. In addition, they can have `required` attribute to further control their
validation.
<doc:example>
<doc:source>
<script>
function Ctrl() {
this.input1 = '';
this.input2 = '';
this.input3 = 'A';
this.input4 = false;
this.input5 = 'c';
this.input6 = [];
}
</script>
<table style="font-size:.9em;" ng:controller="Ctrl">
<tr>
<th>Name</th>
<th>Format</th>
<th>HTML</th>
<th>UI</th>
<th ng:non-bindable>{{input#}}</th>
</tr>
<tr>
<th>text</th>
<td>String</td>
<td><tt>&lt;input type="text" ng:model="input1"&gt;</tt></td>
<td><input type="text" ng:model="input1" size="4"></td>
<td><tt>{{input1|json}}</tt></td>
</tr>
<tr>
<th>textarea</th>
<td>String</td>
<td><tt>&lt;textarea ng:model="input2"&gt;&lt;/textarea&gt;</tt></td>
<td><textarea ng:model="input2" cols='6'></textarea></td>
<td><tt>{{input2|json}}</tt></td>
</tr>
<tr>
<th>radio</th>
<td>String</td>
<td><tt>
&lt;input type="radio" ng:model="input3" value="A"&gt;<br>
&lt;input type="radio" ng:model="input3" value="B"&gt;
</tt></td>
<td>
<input type="radio" ng:model="input3" value="A">
<input type="radio" ng:model="input3" value="B">
</td>
<td><tt>{{input3|json}}</tt></td>
</tr>
<tr>
<th>checkbox</th>
<td>Boolean</td>
<td><tt>&lt;input type="checkbox" ng:model="input4"&gt;</tt></td>
<td><input type="checkbox" ng:model="input4"></td>
<td><tt>{{input4|json}}</tt></td>
</tr>
<tr>
<th>pulldown</th>
<td>String</td>
<td><tt>
&lt;select ng:model="input5"&gt;<br>
&nbsp;&nbsp;&lt;option value="c"&gt;C&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="d"&gt;D&lt;/option&gt;<br>
&lt;/select&gt;<br>
</tt></td>
<td>
<select ng:model="input5">
<option value="c">C</option>
<option value="d">D</option>
</select>
</td>
<td><tt>{{input5|json}}</tt></td>
</tr>
<tr>
<th>multiselect</th>
<td>Array</td>
<td><tt>
&lt;select ng:model="input6" multiple size="4"&gt;<br>
&nbsp;&nbsp;&lt;option value="e"&gt;E&lt;/option&gt;<br>
&nbsp;&nbsp;&lt;option value="f"&gt;F&lt;/option&gt;<br>
&lt;/select&gt;<br>
</tt></td>
<td>
<select ng:model="input6" multiple size="4">
<option value="e">E</option>
<option value="f">F</option>
</select>
</td>
<td><tt>{{input6|json}}</tt></td>
</tr>
</table>
</doc:source>
<doc:scenario>
it('should exercise text', function() {
input('input1').enter('Carlos');
expect(binding('input1')).toEqual('"Carlos"');
});
it('should exercise textarea', function() {
input('input2').enter('Carlos');
expect(binding('input2')).toEqual('"Carlos"');
});
it('should exercise radio', function() {
expect(binding('input3')).toEqual('"A"');
input('input3').select('B');
expect(binding('input3')).toEqual('"B"');
input('input3').select('A');
expect(binding('input3')).toEqual('"A"');
});
it('should exercise checkbox', function() {
expect(binding('input4')).toEqual('false');
input('input4').check();
expect(binding('input4')).toEqual('true');
});
it('should exercise pulldown', function() {
expect(binding('input5')).toEqual('"c"');
select('input5').option('d');
expect(binding('input5')).toEqual('"d"');
});
it('should exercise multiselect', function() {
expect(binding('input6')).toEqual('[]');
select('input6').options('e');
expect(binding('input6')).toEqual('["e"]');
select('input6').options('e', 'f');
expect(binding('input6')).toEqual('["e","f"]');
});
</doc:scenario>
</doc:example>
#Testing
When unit-testing a controller it may be desirable to have a reference to form and to simulate
different form validation states.
This example demonstrates a login form, where the login button is enabled only when the form is
properly filled out.
<pre>
<div ng:controller="LoginController">
<form name="loginForm">
<input type="text" ng:model="username" required/>
<input type="password" ng:model="password" required/>
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
</form>
</div>
</pre>
In the unit tests we do not have access to the DOM, and therefore the `loginForm` reference does
not get set on the controller. This example shows how it can be unit-tested, by creating a mock
form.
<pre>
function LoginController() {
this.disableLogin = function() {
return this.loginForm.$invalid;
};
}
describe('LoginController', function() {
it('should disable login button when form is invalid', inject(function($rootScope) {
var loginController = $rootScope.$new(LoginController);
// In production the 'loginForm' form instance gets set from the view,
// but in unit-test we have to set it manually.
loginController.loginForm = scope.$service('$formFactory')();
expect(loginController.disableLogin()).toBe(false);
// Now simulate an invalid form
loginController.loginForm.$emit('$invalid', 'MyReason');
expect(loginController.disableLogin()).toBe(true);
// Now simulate a valid form
loginController.loginForm.$emit('$valid', 'MyReason');
expect(loginController.disableLogin()).toBe(false);
}));
});
</pre>
## Custom widgets
This example demonstrates a login form, where the password has custom validation rules.
<pre>
<div ng:controller="LoginController">
<form name="loginForm">
<input type="text" ng:model="username" required/>
<input type="@StrongPassword" ng:model="password" required/>
<button ng:disabled="{{!disableLogin()}}" ng:click="login()">Login</login>
</form>
</div>
</pre>
In the unit tests we do not have access to the DOM, and therefore the `loginForm` and custom
input type reference does not get set on the controller. This example shows how it can be
unit-tested, by creating a mock form and a mock custom input type.
<pre>
function LoginController(){
this.disableLogin = function() {
return this.loginForm.$invalid;
};
this.StrongPassword = function(element) {
var widget = this;
element.attr('type', 'password'); // act as password.
this.$on('$validate', function(){
widget.$emit(widget.$viewValue.length > 5 ? '$valid' : '$invalid', 'PASSWORD');
});
};
}
describe('LoginController', function() {
it('should disable login button when form is invalid', inject(function($rootScope) {
var loginController = $rootScope.$new(LoginController);
var input = angular.element('<input>');
// In production the 'loginForm' form instance gets set from the view,
// but in unit-test we have to set it manually.
loginController.loginForm = scope.$service('$formFactory')();
// now instantiate a custom input type
loginController.loginForm.$createWidget({
scope: loginController,
model: 'password',
alias: 'password',
controller: loginController.StrongPassword,
controllerArgs: [input]
});
// Verify that the custom password input type sets the input type to password
expect(input.attr('type')).toEqual('password');
expect(loginController.disableLogin()).toBe(false);
// Now simulate an invalid form
loginController.loginForm.password.$emit('$invalid', 'PASSWORD');
expect(loginController.disableLogin()).toBe(true);
// Now simulate a valid form
loginController.loginForm.password.$emit('$valid', 'PASSWORD');
expect(loginController.disableLogin()).toBe(false);
// Changing model state, should also influence the form validity
loginController.password = 'abc'; // too short so it should be invalid
scope.$digest();
expect(loginController.loginForm.password.$invalid).toBe(true);
// Changeing model state, should also influence the form validity
loginController.password = 'abcdef'; // should be valid
scope.$digest();
expect(loginController.loginForm.password.$valid).toBe(true);
}));
});
</pre>
+9 -9
View File
@@ -17,15 +17,15 @@ abstracted bits.
**What level of support for i18n/l10n is currently in Angular?**
Currently, Angular supports i18n/l10n for {@link
http://docs.angularjs.org/#!/api/angular.filter.date datetime}, {@link
http://docs.angularjs.org/#!/api/angular.filter.number number} and {@link
http://docs.angularjs.org/#!/api/angular.filter.currency currency} filters.
http://docs.angularjs.org/#!/api/angular.module.ng.$filter.date datetime}, {@link
http://docs.angularjs.org/#!/api/angular.module.ng.$filter.number number} and {@link
http://docs.angularjs.org/#!/api/angular.module.ng.$filter.currency currency} filters.
Additionally, Angular supports localizable pluralization support provided by the {@link
api/angular.widget.ng:pluralize ng:pluralize widget}.
All localizable Angular components depend on locale-specific rule sets managed by the {@link
api/angular.service.$locale $locale service}.
api/angular.module.ng.$locale $locale service}.
For readers who want to jump straight into examples, we have a few web pages that showcase how to
use Angular filters with various locale rule sets. You can find these examples either on {@link
@@ -67,10 +67,10 @@ You can also include the locale specific js file in the index.html page. For exa
requires German locale, you would serve index_de-ge.html which will look something like this:
<pre>
<html>
<html ng:app>
<head>
….
<script src="angular.js" ng:autobind></script>
<script src="angular.js"></script>
<script src="i18n/angular-locale_de-ge.js"></script>
….
</head>
@@ -90,8 +90,8 @@ because an extra script needs to be loaded.
**Currency symbol "gotcha"**
Angular's {@link http://docs.angularjs.org/#!/api/angular.filter.currency currency filter} allows
you to use the default currency symbol from the {@link api/angular.service.$locale locale service},
Angular's {@link http://docs.angularjs.org/#!/api/angular.module.ng.$filter.currency currency filter} allows
you to use the default currency symbol from the {@link api/angular.module.ng.$locale locale service},
or you can provide the filter with a custom currency symbol. If your app will be used only in one
locale, it is fine to rely on the default currency symbol. However, if you anticipate that viewers
in other locales might use your app, you should provide your own currency symbol to make sure the
@@ -104,7 +104,7 @@ browser will specify the locale as ja, and the balance of '¥1000.00' will be sh
will really upset your client.
In this case, you need to override the default currency symbol by providing the {@link
http://docs.angularjs.org/#!/api/angular.filter.currency currency filter} with a currency symbol as
http://docs.angularjs.org/#!/api/angular.module.ng.$filter.currency currency filter} with a currency symbol as
a parameter when you configure the filter, for example, {{ 1000 | currency:"USD$"}}. This way,
Angular will always show a balance of 'USD$1000' and disregard any locale changes.
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Introduction
@description
-1
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: About MVC in Angular
@description
@@ -1,11 +1,10 @@
@workInProgress
@ngdoc overview
@name Developer Guide: About MVC in Angular: Understanding the Controller Component
@description
In angular, a controller is a JavaScript function (type/class) that is used to augment instances of
In angular, a controller is a JavaScript function(type/class) that is used to augment instances of
angular {@link dev_guide.scopes Scope}, excluding the root scope. When you or angular create a new
child scope object via the {@link api/angular.scope.$new scope.$new} API , there is an
child scope object via the {@link api/angular.module.ng.$rootScope.Scope#$new scope.$new} API , there is an
option to pass in a controller as a method argument. This will tell angular to associate the
controller with the new scope and to augment its behavior.
@@ -68,7 +67,7 @@ Putting any presentation logic into controllers significantly affects testabilit
logic. Angular offers {@link dev_guide.templates.databinding} for automatic DOM manipulation. If
you have to perform your own manual DOM manipulation, encapsulate the presentation logic in {@link
dev_guide.compiler.widgets widgets} and {@link dev_guide.compiler.directives directives}.
- Input formatting — Use {@link dev_guide.templates.formatters angular formatters} instead.
- Input formatting — Use {@link dev_guide.forms angular form widgets} instead.
- Output filtering — Use {@link dev_guide.templates.filters angular filters} instead.
- Run stateless or stateful code shared across controllers — Use {@link dev_guide.services angular
services} instead.
@@ -78,9 +77,9 @@ instances).
# Associating Controllers with Angular Scope Objects
You can associate controllers with scope objects explicitly via the {@link api/angular.scope.$new
You can associate controllers with scope objects explicitly via the {@link api/angular.module.ng.$rootScope.Scope#$new
scope.$new} api or implicitly via the {@link api/angular.directive.ng:controller ng:controller
directive} or {@link api/angular.service.$route $route service}.
directive} or {@link api/angular.module.ng.$route $route service}.
## Controller Constructor and Methods Example
@@ -128,7 +127,7 @@ starts with capital letter and ends with "Ctrl" or "Controller".
controller augments.
- Assigning a property to `this` creates or updates the model.
- Controller methods can be created through direct assignment to scope (the `chiliSpicy` method) or
as prototype methods of the controller constructor function (the `jalapenoSpicy` method)
as prototype methods of the controller constructor function(the `jalapenoSpicy` method)
- Both controller methods are available in the template (for the `body` element and and its
children).
@@ -139,7 +138,7 @@ previous example.
<pre>
<body ng:controller="SpicyCtrl">
<input name="customSpice" value="wasabi">
<input ng:model="customSpice" value="wasabi">
<button ng:click="spicy('chili')">Chili</button>
<button ng:click="spicy(customSpice)">Custom spice</button>
<p>The food is {{spice}} spicy!</p>
@@ -161,7 +160,7 @@ input box) in the second button.
## Controller Inheritance Example
Controller inheritance in angular is based on {@link api/angular.scope Scope} inheritance. Let's
Controller inheritance in angular is based on {@link api/angular.module.ng.$rootScope.Scope Scope} inheritance. Let's
have a look at an example:
<pre>
@@ -227,7 +226,7 @@ Controller Test:
<pre>
describe('myController function', function() {
describe('myController', function(){
describe('myController', function() {
var ctrl;
beforeEach(function() {
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: About MVC in Angular: Understanding the Model Component
@description
@@ -41,7 +40,7 @@ when processing the following template constructs:
* Form input, select, textarea and other form elements:
<input name="query" value="fluffy cloud">
<input ng:model="query" value="fluffy cloud">
The code above creates a model called "query" on the current scope with the value set to "fluffy
cloud".
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: About MVC in Angular: Understanding the View Component
@description
+35 -31
View File
@@ -42,23 +42,31 @@ easier a web developer's life can if they're using angular:
<doc:example>
<doc:source>
<b>Invoice:</b>
<br />
<br />
<table>
<tr><td> </td><td> </td>
<tr><td>Quantity</td><td>Cost</td></tr>
<tr>
<td><input name="qty" value="1" ng:validate="integer:0" ng:required /></td>
<td><input name="cost" value="19.95" ng:validate="number" ng:required /></td>
</tr>
</table>
<hr />
<b>Total:</b> {{qty * cost | currency}}
<script>
function InvoiceCntl() {
this.qty = 1;
this.cost = 19.95;
}
</script>
<div ng:controller="InvoiceCntl">
<b>Invoice:</b>
<br />
<br />
<table>
<tr><td> </td><td> </td>
<tr><td>Quantity</td><td>Cost</td></tr>
<tr>
<td><input type="integer" min="0" ng:model="qty" required ></td>
<td><input type="number" ng:model="cost" required ></td>
</tr>
</table>
<hr />
<b>Total:</b> {{qty * cost | currency}}
</div>
</doc:source>
<!--
<doc:scenario>
it('should show of angular binding', function(){
it('should show of angular binding', function() {
expect(binding('qty * cost')).toEqual('$19.95');
input('qty').enter('2');
input('cost').enter('5.00');
@@ -71,36 +79,32 @@ easier a web developer's life can if they're using angular:
Try out the Live Preview above, and then let's walk through the example and describe what's going
on.
In the `<html>` tag, we add an attribute to let the browser know about the angular namespace:
In the `<html>` tag, we add an attribute to let the browser know about the angular namespace.
This ensures angular runs nicely in all major browsers. We also specify that it is an angular
application with the `ng:app` directive. The `ng:app' will cause the angular to {@link
dev_guide.bootstrap auto initialize} your application.
<html xmlns:ng="http://angularjs.org">
<html xmlns:ng="http://angularjs.org" ng:app>
This ensures angular runs nicely in all major browsers.
We load the angular using the `<script>` tag:
In the `<script>` tag we do two angular setup tasks:
`<script src="http://code.angularjs.org/0.9.15/angular-0.9.15.min.js"></script>`
1. We load `angular.js`.
2. The angular {@link api/angular.directive.ng:autobind ng:autobind} directive tells angular to
{@link dev_guide.compiler compile} and manage the whole HTML document.
`<script src="http://code.angularjs.org/0.9.15/angular-0.9.15.min.js"
ng:autobind></script>`
From the `name` attribute of the `<input>` tags, angular automatically sets up two-way data
From the `ng:model` attribute of the `<input>` tags, angular automatically sets up two-way data
binding, and we also demonstrate some easy input validation:
Quantity: <input name="qty" value="1" ng:validate="integer:0" ng:required/>
Cost: <input name="cost" value="199.95" ng:validate="number" ng:required/>
Quantity: <input type="integer" min="0" ng:model="qty" required >
Cost: <input type="number" ng:model="cost" required >
These input widgets look normal enough, but consider these points:
* When this page loaded, angular bound the names of the input widgets (`qty` and `cost`) to
variables of the same name. Think of those variables as the "Model" component of the
Model-View-Controller design pattern.
* Note the angular directives, {@link api/angular.widget.@ng:validate ng:validate} and {@link
api/angular.widget.@ng:required ng:required}. You may have noticed that when you enter invalid data
* Note the angular/HTML widget, {@link api/angular.widget.input input}.
You may have noticed that when you enter invalid data
or leave the the input fields blank, the borders turn red color, and the display value disappears.
These `ng:` directives make it easier to implement field validators than coding them in JavaScript,
These widgets make it easier to implement field validation than coding them in JavaScript,
no? Yes.
And finally, the mysterious `{{ double curly braces }}`:
@@ -11,8 +11,8 @@ pattern, a scope's properties comprise both the model and the controller methods
### Scope characteristics
- Scopes provide APIs ({@link api/angular.scope.$watch $watch}) to observe model mutations.
- Scopes provide APIs ({@link api/angular.scope.$apply $apply}) to propagate any model changes
- Scopes provide APIs ({@link api/angular.module.ng.$rootScope.Scope#$watch $watch}) to observe model mutations.
- Scopes provide APIs ({@link api/angular.module.ng.$rootScope.Scope#$apply $apply}) to propagate any model changes
through the system into the view from outside of the "Angular realm" (controllers, services,
Angular event handlers).
- Scopes can be nested to isolate application components while providing access to shared model
@@ -23,20 +23,18 @@ available as `this` within the given context. (Note: This api will change before
### Root scope
Every application has a root scope, which is the ancestor of all other scopes. The root scope is
responsible for creating the injector which is assigned to the {@link api/angular.scope.$service
$service} property, and initializing the services.
Every application has a root scope, which is the ancestor of all other scopes.
### What is scope used for?
{@link dev_guide.expressions Expressions} in the view are {@link api/angular.scope.$eval evaluated}
{@link dev_guide.expressions Expressions} in the view are {@link api/angular.module.ng.$rootScope.Scope#$eval evaluated}
against the current scope. When HTML DOM elements are attached to a scope, expressions in those
elements are evaluated against the attached scope.
There are two kinds of expressions:
- Binding expressions, which are observations of property changes. Property changes are reflected
in the view during the {@link api/angular.scope.$digest digest cycle}.
in the view during the {@link api/angular.module.ng.$rootScope.Scope#$digest digest cycle}.
- Action expressions, which are expressions with side effects. Typically, the side effects cause
execution of a method in a controller in response to a user action, such as clicking on a button.
@@ -59,41 +57,43 @@ A property write will always write to the current scope. This means that a write
property within the scope it writes to, as shown in the following example.
<pre>
var root = angular.scope();
var child = root.$new();
it('should inherit properties', inject(function($rootScope)) {
var root = $rootScope;
var child = root.$new();
root.name = 'angular';
expect(child.name).toEqual('angular');
expect(root.name).toEqual('angular');
root.name = 'angular';
expect(child.name).toEqual('angular');
expect(root.name).toEqual('angular');
child.name = 'super-heroic framework';
expect(child.name).toEqual('super-heroic framework');
expect(root.name).toEqual('angular');
child.name = 'super-heroic framework';
expect(child.name).toEqual('super-heroic framework');
expect(root.name).toEqual('angular');
});
</pre>
### Scope life cycle
1. **Creation**
* You can create the root scope via {@link api/angular.scope angular.scope()}.
* To create a child scopes, you should call {@link api/angular.scope.$new parentScope.$new()}.
* The root scope is created by the {@link api/angular.module.ng.$rootScope $rootScope} service.
* To create a child scopes, you should call {@link api/angular.module.ng.$rootScope.Scope#$new parentScope.$new()}.
2. **Watcher registration**
Watcher registration can happen at any time and on any scope (root or child) via {@link
api/angular.scope.$watch scope.$watch()} API.
api/angular.module.ng.$rootScope.Scope#$watch scope.$watch()} API.
3. **Model mutation**
For mutations to be properly observed, you should make them only within the execution of the
function passed into {@link api/angular.scope.$apply scope.$apply()} call. (Angular apis do this
function passed into {@link api/angular.module.ng.$rootScope.Scope#$apply scope.$apply()} call. (Angular apis do this
implicitly, so no extra `$apply` call is needed when doing synchronous work in controllers, or
asynchronous work with {@link api/angular.service.$xhr $xhr} or {@link api/angular.service.$defer
asynchronous work with {@link api/angular.module.ng.$http $http} or {@link api/angular.module.ng.$defer
$defer} services.
4. **Mutation observation**
At the end of each `$apply` call {@link api/angular.scope.$digest $digest} cycle is started on
At the end of each `$apply` call {@link api/angular.module.ng.$rootScope.Scope#$digest $digest} cycle is started on
the root scope, which then propagates throughout all child scopes.
During the `$digest` cycle, all `$watch-ers` expressions or functions are checked for model
@@ -102,7 +102,7 @@ mutation and if a mutation is detected, the `$watch-er` listener is called.
5. **Scope destruction**
When child scopes are no longer needed, it is the responsibility of the child scope creator to
destroy them via {@link api/angular.scope.$destroy scope.$destroy()} API. This will stop
destroy them via {@link api/angular.module.ng.$rootScope.Scope#$destroy scope.$destroy()} API. This will stop
propagation of `$digest` calls into the child scope and allow for memory used by the child scope
models to be reclaimed by the garbage collector.
@@ -116,29 +116,26 @@ scopes come into play throughout and get a sense of their interactions.
1. At application compile time, a root scope is created and is attached to the root `<HTML>` DOM
element.
1. The root scope creates an {@link api/angular.injector injector} which is assigned to the
{@link api/angular.scope.$service $service} property of the root scope.
2. Any eager {@link api/angular.scope.$service services} are initialized at this point.
2. During the compilation phase, the {@link dev_guide.compiler compiler} matches {@link
api/angular.directive directives} against the DOM template. The directives usually fall into one of
two categories:
- Observing {@link api/angular.directive directives}, such as double-curly expressions
`{{expression}}`, register listeners using the {@link api/angular.scope.$watch $watch()} method.
`{{expression}}`, register listeners using the {@link api/angular.module.ng.$rootScope.Scope#$watch $watch()} method.
This type of directive needs to be notified whenever the expression changes so that it can update
the view.
- Listener directives, such as {@link api/angular.directive.ng:click ng:click}, register a
listener with the DOM. When the DOM listener fires, the directive executes the associated
expression and updates the view using the {@link api/angular.scope.$apply $apply()} method.
expression and updates the view using the {@link api/angular.module.ng.$rootScope.Scope#$apply $apply()} method.
3. When an external event (such as a user action, timer or XHR) is received, the associated {@link
dev_guide.expressions expression} must be applied to the scope through the {@link
api/angular.scope.$apply $apply()} method so that all listeners are updated correctly.
api/angular.module.ng.$rootScope.Scope#$apply $apply()} method so that all listeners are updated correctly.
### Directives that create scopes
In most cases, {@link api/angular.directive directives} and scopes interact but do not create new
instances of scope. However, some directives, such as {@link api/angular.directive.ng:controller
ng:controller} and {@link api/angular.widget.@ng:repeat ng:repeat}, create new child scopes using
the {@link api/angular.scope.$new $new()} method and then attach the child scope to the
the {@link api/angular.module.ng.$rootScope.Scope#$new $new()} method and then attach the child scope to the
corresponding DOM element. You can retrieve a scope for any DOM element by using an
`angular.element(aDomElement).scope()` method call.)
@@ -148,13 +145,13 @@ Scopes and controllers interact with each other in the following situations:
- Controllers use scopes to expose controller methods to templates (see {@link
api/angular.directive.ng:controller ng:controller}).
- Controllers define methods (behavior) that can mutate the model (properties on the scope).
- Controllers may register {@link api/angular.scope.$watch watches} on the model. These watches
- Controllers may register {@link api/angular.module.ng.$rootScope.Scope#$watch watches} on the model. These watches
execute immediately after the controller behavior executes, but before the DOM gets updated.
See the {@link dev_guide.mvc.understanding_controller controller docs} for more information.
### Updating scope properties
You can update a scope by calling its {@link api/angular.scope.$apply $apply()} method with an
You can update a scope by calling its {@link api/angular.module.ng.$rootScope.Scope#$apply $apply()} method with an
expression or a function as the function argument. However it is typically not necessary to do this
explicitly. In most cases, angular intercepts all external events (such as user interactions, XHRs,
and timers) and wraps their callbacks into the `$apply()` method call on the scope object for you
@@ -177,8 +174,8 @@ doesn't need to worry about propagating the `$digest` call from the parent scope
This happens automatically.
## Scopes in unit-testing
You can create scopes, including the root scope, in tests using the {@link api/angular.scope
angular.scope()} API. This allows you to mimic the run-time environment and have full control over
You can create scopes, including the root scope, in tests by having the $rootScope injected into
your spec. This allows you to mimic the run-time environment and have full control over
the life cycle of the scope so that you can assert correct model transitions. Since these scopes
are created outside the normal compilation process, their life cycles must be managed by the test.
@@ -188,18 +185,20 @@ within the unit-tests.
<pre>
// example of a test
var scope = angular.scope();
scope.$watch('name', function(scope, name){
scope.greeting = 'Hello ' + name + '!';
});
it('should trigger a watcher', inject(function($rootScope) {
var scope = $rootScope;
scope.$watch('name', function(scope, name){
scope.greeting = 'Hello ' + name + '!';
});
scope.name = 'angular';
// The watch does not fire yet since we have to manually trigger the digest phase.
expect(scope.greeting).toEqual(undefined);
scope.name = 'angular';
// The watch does not fire yet since we have to manually trigger the digest phase.
expect(scope.greeting).toEqual(undefined);
// manually trigger digest phase from the test
scope.$digest();
expect(scope.greeting).toEqual('Hello Angular!');
// manually trigger digest phase from the test
scope.$digest();
expect(scope.greeting).toEqual('Hello Angular!');
}
</pre>
@@ -209,9 +208,14 @@ When you find it necessary to inject your own mocks in your tests, use a scope t
service instances, as shown in the following example.
<pre>
var myLocation = {};
var scope = angular.scope(angular.service, {$location: myLocation});
expect(scope.$service('$location')).toEqual(myLocation);
it('should allow override of providers', inject(
function($provide) {
$provide.value('$location', {mode:'I am a mock'});
},
function($location){
expect($location.mode).toBe('I am a mock');
}
)};
</pre>
## Related Topics
@@ -221,5 +225,5 @@ expect(scope.$service('$location')).toEqual(myLocation);
## Related API
* {@link api/angular.scope Angular Scope API}
* {@link api/angular.module.ng.$rootScope.Scope Angular Scope API}
+5 -5
View File
@@ -13,15 +13,15 @@ the contexts in which Angular creates data-bindings between the model and the vi
In addition to providing the context in which data is evaluated, Angular scope objects watch for
model changes. The scope objects also notify all components interested in any model changes (for
example, functions registered through {@link api/angular.scope.$watch $watch}, bindings created by
example, functions registered through {@link api/angular.module.ng.$rootScope.Scope#$watch $watch}, bindings created by
{@link api/angular.directive.ng:bind ng:bind}, or HTML input elements).
Angular scope objects:
* Link the model, controller and view template together.
* Provide the mechanism to watch for model changes ({@link api/angular.scope.$watch $watch}).
* Apply model changes to the system ({@link api/angular.scope.$apply $apply}).
* Provide the context in which expressions are evaluated ({@link api/angular.scope.$eval $eval}).
* Provide the mechanism to watch for model changes ({@link api/angular.module.ng.$rootScope.Scope#$watch $watch}).
* Apply model changes to the system ({@link api/angular.module.ng.$rootScope.Scope#$apply $apply}).
* Provide the context in which expressions are evaluated ({@link api/angular.module.ng.$rootScope.Scope#$eval $eval}).
## Related Topics
@@ -31,5 +31,5 @@ Angular scope objects:
## Related API
* {@link api/angular.scope Angular Scope API}
* {@link api/angular.module.ng.$rootScope.Scope Angular Scope API}
@@ -63,4 +63,4 @@ The following illustration shows the DOM and angular scopes for the example abov
## Related API
* {@link api/angular.scope Angular Scope API}
* {@link api/angular.module.ng.$rootScope.Scope Angular Scope API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Using $location
@description
@@ -55,7 +54,7 @@ changes to $location are reflected into the browser address bar.
</tr>
<tr>
<td class="head">seamless integration with html5 API</td>
<td class="head">seamless integration with HTML5 API</td>
<td>no</td>
<td>yes (with a fallback for legacy browsers)</td>
</tr>
@@ -89,26 +88,22 @@ setter methods that allow you to get or change the current URL in the browser.
## $location service configuration
To configure the `$location` service, you define the `$config` service which is an object with
configuration properties:
To configure the `$location` service, retrieve the
{@link api/angular.module.ng.$locationProvider $locationProvider} and set the parameters as follows:
- **html5Mode**: {boolean}<br />
`true` - see Html5 mode<br />
- **html5Mode(mode)**: {boolean}<br />
`true` - see HTML5 mode<br />
`false` - see Hashbang mode<br />
default: `false`
- **hashPrefix**: {string}<br />
- **hashPrefix(prefix)**: {string}<br />
prefix used for Hashbang URLs (used in Hashbang mode or in legacy browser in Html5 mode)<br />
default: `'!'`
### Example configuration
<pre>
angular.service('$config', function() {
return {
html5mode: true,
hashPrefix: '!'
};
});
$locationProvider.html5Mode(true).hashPrefix('!');
</pre>
## Getter and setter methods
@@ -127,16 +122,25 @@ All of the setter methods return the same `$location` object to allow chaining.
change multiple segments in one go, chain setters like this:
<pre>$location.path('/newValue').search({key: value});</pre>
All setter methods take an optional boolean flag parameter, which signifies whether current history
record should be replaced or if a new record should be created (default). To change the current URL
without creating a new browser history record you can call:
<pre>$location.path('/newVal', true);</pre>
There is a special `replace` method which can be used to tell the $location service that the next
time the $location service is synced with the browser, the last history record should be replaced
instead of creating a new one. This is useful when you want to implement redirection, which would
otherwise break the back button (navigating back would retrigger the redirection). To change the
current URL without creating a new browser history record you can call:
<pre>
$location.path('/someNewPath');
$location.replace();
// or you can chain these as: $location.path('/someNewPath').replace();
</pre>
Note that the setters don't update `window.location` immediately. Instead, `$location` service is
aware of the {@link api/angular.scope scope} life-cycle and coalesces multiple `$location`
mutations into one "commit" to the `window.location` object during the scope `$flush` phase. Since
any of the setters can take the replace flag, it's enough for one setter to use this flag in order
to make the entire "commit" a replace operation rather than addition to the browser history.
aware of the {@link api/angular.module.ng.$rootScope.Scope scope} life-cycle and coalesces multiple `$location`
mutations into one "commit" to the `window.location` object during the scope `$digest` phase. Since
multiple changes to the $location's state will be pushed to the browser as a single change, it's
enough to call the `replace()` method just once to make the entire "commit" a replace operation
rather than addition to the browser history. Once the browser is updated, the $location service
resets the flag set by `replace()` method and future mutations will create new history records,
unless `replace()` is called again.
### Setters and character encoding
You can pass special characters to `$location` service and it will encode them according to rules
@@ -151,11 +155,11 @@ encoded.
`/path?search=a&b=c#hash`. The segments are encoded as well.
# Hashbang and Html5 Modes
# Hashbang and HTML5 Modes
`$location` service has two configuration modes which control the format of the URL in the browser
address bar: **Hashbang mode** (the default) and the **Html5 mode** which is based on using the
Html5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
address bar: **Hashbang mode** (the default) and the **HTML5 mode** which is based on using the
HTML5 {@link http://www.w3.org/TR/html5/history.html History API}. Applications use the same API in
both modes and the `$location` service will work with appropriate URL segments and browser APIs to
facilitate the browser URL change and history management.
@@ -167,7 +171,7 @@ facilitate the browser URL change and history management.
<tr>
<td class="empty-corner-lt"></td>
<td>Hashbang mode</td>
<td>Html5 mode</td>
<td>HTML5 mode</td>
</tr>
</thead>
@@ -206,26 +210,27 @@ In this mode, `$location` uses Hashbang URLs in all browsers.
### Example
<pre>
angular.service('$config', function() {
return {
html5Mode: false,
hashPrefix: '!'
};
});
it('should show example', inject(
function($locationProvider) {
$locationProvider.html5mode = false;
$locationProvider.hashPrefix = '!';
},
function($location) {
// open http://host.com/base/index.html#!/a
$location.absUrl() == 'http://host.com/base/index.html#!/a'
$location.path() == '/a'
// open http://host.com/base/index.html#!/a
$location.absUrl() == 'http://host.com/base/index.html#!/a'
$location.path() == '/a'
$location.path('/foo')
$location.absUrl() == 'http://host.com/base/index.html#!/foo'
$location.path('/foo')
$location.absUrl() == 'http://host.com/base/index.html#!/foo'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://host.com/base/index.html#!/foo?a=b&c'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://host.com/base/index.html#!/foo?a=b&c'
$location.path('/new').search('x=y');
$location.absUrl() == 'http://host.com/base/index.html#!/new?x=y'
$location.path('/new').search('x=y');
$location.absUrl() == 'http://host.com/base/index.html#!/new?x=y'
}
));
</pre>
### Crawling your app
@@ -254,39 +259,40 @@ having to worry about whether the browser displaying your app supports the histo
### Example
<pre>
angular.service('$config', function() {
return {
html5Mode: true,
hashPrefix: '!'
};
});
it('should show example', inject(
function($locationProvider) {
$locationProvider.html5mode = true;
$locationProvider.hashPrefix = '!';
},
function($location) {
// in browser with HTML5 history support:
// open http://host.com/#!/a -> rewrite to http://host.com/a
// (replacing the http://host.com/#!/a history record)
$location.path() == '/a'
// in browser with html5 history support:
// open http://host.com/#!/a -> rewrite to http://host.com/a
// (replacing the http://host.com/#!/a history record)
$location.path() == '/a'
$location.path('/foo');
$location.absUrl() == 'http://host.com/foo'
$location.path('/foo');
$location.absUrl() == 'http://host.com/foo'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://host.com/foo?a=b&c'
$location.search() == {}
$location.search({a: 'b', c: true});
$location.absUrl() == 'http://host.com/foo?a=b&c'
$location.path('/new').search('x=y');
$location.url() == 'new?x=y'
$location.absUrl() == 'http://host.com/new?x=y'
$location.path('/new').search('x=y');
$location.url() == 'new?x=y'
$location.absUrl() == 'http://host.com/new?x=y'
// in browser without html5 history support:
// open http://host.com/new?x=y -> redirect to http://host.com/#!/new?x=y
// (again replacing the http://host.com/new?x=y history item)
$location.path() == '/new'
$location.search() == {x: 'y'}
// in browser without html5 history support:
// open http://host.com/new?x=y -> redirect to http://host.com/#!/new?x=y
// (again replacing the http://host.com/new?x=y history item)
$location.path() == '/new'
$location.search() == {x: 'y'}
$location.path('/foo/bar');
$location.path() == '/foo/bar'
$location.url() == '/foo/bar?x=y'
$location.absUrl() == 'http://host.com/#!/foo/bar?x=y'
$location.path('/foo/bar');
$location.path() == '/foo/bar'
$location.url() == '/foo/bar?x=y'
$location.absUrl() == 'http://host.com/#!/foo/bar?x=y'
}
));
</pre>
### Fallback for legacy browsers
@@ -343,7 +349,7 @@ takes care of all relative link issues. **Otherwise you have to specify &lt;base
### Sending links among different browsers
Because of rewriting capability in Html5 mode, your users will be able to open regular url links in
Because of rewriting capability in HTML5 mode, your users will be able to open regular url links in
legacy browsers and hashbang links in modern browser:
- Modern browser will rewrite hashbang URLs to regular URLs.
@@ -360,9 +366,10 @@ redirect to regular / hashbang url, as this conversion happens only during parsi
= on page reload.
In this examples we use `<base href="/base/index.html" />`
<doc:example>
<doc:source source="false">
<ul class="doc-example">
<li ng:non-bindable class="html5-hashbang-example">
<div ng:non-bindable class="html5-hashbang-example">
<div id="html5-mode" ng:controller="Html5Cntl">
<h3>Browser with History API</h3>
<ng:address-bar browser="html5"></ng:address-bar><br /><br />
@@ -372,9 +379,9 @@ In this examples we use `<base href="/base/index.html" />`
$location.path() = {{$location.path()}}<br />
$location.search() = {{$location.search()}}<br />
$location.hash() = {{$location.hash()}}<br />
<a href="/base/first?a=b">/base/first?a=b</a> | <a
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
ng:ext-link>external</a>
<a href="/base/first?a=b">/base/first?a=b</a> |
<a href="sec/ond?flag#hash">sec/ond?flag#hash</a> |
<a href="/base/another?search" ng:ext-link>external</a>
</div>
<div id="hashbang-mode" ng:controller="HashbangCntl">
@@ -386,12 +393,11 @@ ng:ext-link>external</a>
$location.path() = {{$location.path()}}<br />
$location.search() = {{$location.search()}}<br />
$location.hash() = {{$location.hash()}}<br />
<a href="/base/first?a=b">/base/first?a=b</a> | <a
href="sec/ond?flag#hash">sec/ond?flag#hash</a> | <a href="/base/another?search"
ng:ext-link>external</a>
<a href="/base/first?a=b">/base/first?a=b</a> |
<a href="sec/ond?flag#hash">sec/ond?flag#hash</a> |
<a href="/base/another?search" ng:ext-link>external</a>
</div>
</li>
</ul>
</div>
<script type="text/javascript">
function FakeBrowser(initUrl, baseHref) {
@@ -417,8 +423,7 @@ ng:ext-link>external</a>
var browsers = {
html5: new FakeBrowser('http://www.host.com/base/path?a=b#h', '/base/index.html'),
hashbang: new FakeBrowser('http://www.host.com/base/index.html#!/path?a=b#h',
'/base/index.html')
hashbang: new FakeBrowser('http://www.host.com/base/index.html#!/path?a=b#h', '/base/index.html')
};
function Html5Cntl($location) {
@@ -456,14 +461,14 @@ ng:ext-link>external</a>
function initEnv(name) {
var root = angular.element(document.getElementById(name + '-mode'));
var scope = angular.scope(null, {
$config: {html5Mode: true, hashPrefix: '!'},
$browser: browsers[name],
$document: root,
$sniffer: {history: name == 'html5'}
});
angular.compile(root)(scope).$apply();
angular.bootstrap(root, [function($locationProvider, $provide){
$locationProvider.html5Mode = true;
$locationProvider.hashPrefix = '!';
$provide.value('$browser', browsers[name]);
$provide.value('$document', root);
$provide.value('$sniffer', {history: name == 'html5'});
}]);
root.bind('click', function(e) {
e.stopPropagation();
});
@@ -473,6 +478,9 @@ ng:ext-link>external</a>
initEnv('hashbang');
</script>
</doc:source>
</doc:example>
# Caveats
@@ -480,11 +488,11 @@ ng:ext-link>external</a>
The `$location` service allows you to change only the URL; it does not allow you to reload the
page. When you need to change the URL and reload the page or navigate to a different page, please
use a lower level API, {@link api/angular.service.$window $window.location.href}.
use a lower level API, {@link api/angular.module.ng.$window $window.location.href}.
## Using $location outside of the scope life-cycle
`$location` knows about Angular's {@link api/angular.scope scope} life-cycle. When a URL changes in
`$location` knows about Angular's {@link api/angular.module.ng.$rootScope.Scope scope} life-cycle. When a URL changes in
the browser it updates the `$location` and calls `$apply` so that all $watchers / $observers are
notified.
When you change the `$location` inside the `$digest` phase everything is ok; `$location` will
@@ -504,29 +512,23 @@ hashPrefix.
# Testing with the $location service
When using `$location` service during testing, you are outside of the angular's {@link
api/angular.scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
api/angular.module.ng.$rootScope.Scope scope} life-cycle. This means it's your responsibility to call `scope.$apply()`.
<pre>
angular.service('$serviceUnderTest', function($location) {
// whatever it does...
};
describe('$serviceUnderTest', function() {
var scope, $location, $sut;
beforeEach(function() {
scope = angular.scope();
$location = scope.$service('$location');
$sut = scope.$service('$serviceUnderTest');
describe('serviceUnderTest', function() {
beforeEach(module(function($provide) {
$provide.factory('serviceUnderTest', function($location){
// whatever it does...
});
});
it('should...', function() {
it('should...', inject(function($location, $rootScope, serviceUnderTest) {
$location.path('/new/path');
scope.$apply();
$rootScope.$apply();
// test whatever the service should do...
});
}));
});
</pre>
@@ -612,7 +614,7 @@ https://github.com/angular/angular.js/issues/404 issue}). If you should require
you will need to specify an extra property that has two watchers. For example:
<pre>
<!-- html -->
<input type="text" name="locationPath" />
<input type="text" ng:model="locationPath" />
</pre>
<pre>
// js - controller
@@ -628,7 +630,7 @@ this.$watch('$location.path()', function(scope, path) {
# Related API
* {@link api/angular.service.$location $location API}
* {@link api/angular.module.ng.$location $location API}
@@ -1,59 +1,104 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Creating Angular Services
@name Developer Guide: Angular Services: Creating Services
@description
While angular offers several useful services, for any nontrivial application you'll find it useful
to write your own custom services. To do this you begin by registering a service factory function
that angular's DI will use to create the service object when it is needed.
The `angular.service` method accepts three parameters:
- `{string} name` - Name of the service.
- `{function()} factory` - Factory function (called just once by DI).
- `{Object} config` - Configuration object with the following properties:
- `$inject` - {Array.<string>} - Array of service ids this service depends on. These services
will be passed as arguments into the factory function in the same order specified in the `$inject`
array. Defaults to `[]`.
- `$eager` - {boolean} - If true, the service factory will be called and the service will be
instantiated when angular boots. If false, the service will be lazily instantiated when it is first
requested during instantiation of a dependant. Defaults to `false`.
The `this` of the factory function is bound to the root scope of the angular application.
with a module either via the {@link api/angular.module Module#factory api} or directly
via the {@link api/angular.module.AUTO.$provide $provide} api inside of module config function.
All angular services participate in {@link dev_guide.di dependency injection (DI)} by registering
themselves with angular's DI system (injector) under a `name` (id) as well as by declaring
themselves with Angular's DI system (injector) under a `name` (id) as well as by declaring
dependencies which need to be provided for the factory function of the registered service. The
ability to swap dependencies for mocks/stubs/dummies in tests allows for services to be highly
testable.
# Registering Services
To register a service, you must have a module that this service will be part of. Afterwards, you
can register the service with the module either via the {@link api/angular.Module Module api} or
by using the {@link api/angular.module.AUTO.$provide $provide} service in the module configuration
function.The following pseudo-code shows both approaches:
Using the angular.Module api:
<pre>
var myModule = angular.module('myModule', []);
myModule.factory('serviceId', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
</pre>
Using the $provide service:
<pre>
angular.module('myModule', [], function($provide) {
$provide.factory('serviceId', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
});
</pre>
Note that you are not registering a service instance, but rather a factory function that will
create this instance when called.
# Dependencies
Services can not only be depended upon, but also have its own dependencies. These can be specified
as arguments of the factory function. {@link dev_guide.di.understanding_di Read more} about the DI
in Angular and the use of array notation and $inject property to make DI annotation
minification-proof.
Following is an example of a very simple service. This service depends on the `$window` service
(which is passed as a parameter to the factory function) and is just a function. The service simply
stores all notifications; after the third one, the service displays all of the notifications by
window alert.
<pre>
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
angular.module('myModule', [], function($provide) {
$provide.factory('notify', ['$window', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}]);
});
</pre>
# Instantiating Angular Services
All services in Angular are instantiates services lazily, this means that a service will be created
only when it is needed for instantiation of a service or an application component that depends on it.
In other words, angular won't instantiate lazy services unless they are requested directly or
indirectly by the application.
# Services as singletons
Lastly, it is important to realize that all angular services are application singletons. This means
that there is only one instance of a given service per injector. Since angular is lethally allergic
to the global state, it is possible to create multiple injectors, each with its own instance of a
given service, but that is rarely needed, except in tests where this property is crucially
important.
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.module.ng Angular Service API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Injecting Services Into Controllers
@description
@@ -29,19 +28,21 @@ this.secondMethod = function() {
myController.$inject = ['$location', '$log'];
</pre>
<doc:example>
<doc:example module="MyServiceModule">
<doc:source>
<script type="text/javascript">
angular.service('notify', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}, {$inject: ['$window']});
angular.
module('MyServiceModule', []).
factory('notify', ['$window', function(win) {
var msgs = [];
return function(msg) {
msgs.push(msg);
if (msgs.length == 3) {
win.alert(msgs.join("\n"));
msgs = [];
}
};
}]);
function myController(notifyService) {
this.callNotify = function(msg) {
@@ -54,13 +55,13 @@ myController.$inject = ['notify'];
<div ng:controller="myController">
<p>Let's try this simple notify service, injected into the controller...</p>
<input ng:init="message='test'" type="text" name="message" />
<input ng:init="message='test'" type="text" ng:model="message" />
<button ng:click="callNotify(message);">NOTIFY</button>
</div>
</doc:source>
<doc:scenario>
it('should test service', function(){
expect(element(':input[name=message]').val()).toEqual('test');
it('should test service', function() {
expect(element(':input[ng\\:model="message"]').val()).toEqual('test');
});
</doc:scenario>
</doc:example>
@@ -70,10 +71,9 @@ it('should test service', function(){
{@link dev_guide.services.understanding_services Understanding Angular Services}
{@link dev_guide.services.creating_services Creating Angular Services}
{@link dev_guide.services.registering_services Registering Angular Services}
{@link dev_guide.services.managing_dependencies Managing Service Dependencies}
{@link dev_guide.services.testing_services Testing Angular Services}
## Related API
{@link api/angular.service Angular Service API}
{@link api/angular.module.ng Angular Service API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Managing Service Dependencies
@description
@@ -6,80 +5,110 @@
Angular allows services to declare other services as dependencies needed for construction of their
instances.
To declare dependencies, you specify them in the factory function signature and via the `$inject`
property, as an array of string identifiers. Optionally the `$inject` property declaration can be
To declare dependencies, you specify them in the factory function signature and annotate the
function with the inject annotations either using by setting the `$inject` property, as an array of
string identifiers or using the array notation. Optionally the `$inject` property declaration can be
dropped (see "Inferring `$inject`" but note that that is currently an experimental feature).
Using the array notation:
<pre>
function myModuleCfgFn($provide) {
$provide.factory('myService', ['dep1', 'dep2', function(dep1, dep2) {}]);
}
</pre>
Using the $inject property:
<pre>
function myModuleCfgFn($provide) {
var myServiceFactory = function(dep1, dep2) {};
myServiceFactory.$inject = ['dep1', 'dep2'];
$provide.factory('myService', myServiceFactory);
}
</pre>
Using DI inference (incompatible with minifiers):
<pre>
function myModuleCfgFn($provide) {
$provide.factory('myService', function(dep1, dep2) {});
}
</pre>
Here is an example of two services that depend on each other, as well as on other services that are
provided by angular's web framework:
provided by Angular's web framework:
<pre>
/**
* batchLog service allows for messages to be queued in memory and flushed
* to the console.log every 50 seconds.
*
* @param {*} message Message to be logged.
*/
angular.service('batchLog', function($defer, $log) {
var messageQueue = [];
* batchLog service allows for messages to be queued in memory and flushed
* to the console.log every 50 seconds.
*
* @param {*} message Message to be logged.
*/
function batchLogModule($provide){
$provide.factory('batchLog', ['$defer', '$log', function($defer, $log) {
var messageQueue = [];
function log() {
if (messageQueue.length) {
$log('batchLog messages: ', messageQueue);
messageQueue = [];
}
$defer(log, 50000);
function log() {
if (messageQueue.length) {
$log('batchLog messages: ', messageQueue);
messageQueue = [];
}
$defer(log, 50000);
}
// start periodic checking
log();
return function(message) {
messageQueue.push(message);
}
}]);
/**
* routeTemplateMonitor monitors each $route change and logs the current
* template via the batchLog service.
*/
$provide.factory('routeTemplateMonitor',
['$route', 'batchLog', '$rootScope',
function($route, batchLog, $rootScope) {
$rootScope.$on('$afterRouteChange', function() {
batchLog($route.current ? $route.current.template : null);
});
}]);
}
// start periodic checking
log();
return function(message) {
messageQueue.push(message);
}
}, {$inject: ['$defer', '$log']});
// note how we declared dependency on built-in $defer and $log services above
/**
* routeTemplateMonitor monitors each $route change and logs the current
* template via the batchLog service.
*/
angular.service('routeTemplateMonitor', function($route, batchLog) {
this.$on('$afterRouteChange', function() {
batchLog($route.current ? $route.current.template : null);
});
}, {$inject: ['$route', 'batchLog'], $eager: true});
// get the main service to kick of the application
angular.injector([batchLogModule]).get('routeTemplateMonitor');
</pre>
Things to notice in this example:
* The `batchLog` service depends on the built-in {@link api/angular.service.$defer $defer} and
{@link api/angular.service.$log $log} services, and allows messages to be logged into the
* The `batchLog` service depends on the built-in {@link api/angular.module.ng.$defer $defer} and
{@link api/angular.module.ng.$log $log} services, and allows messages to be logged into the
`console.log` in batches.
* The `routeTemplateMonitor` service depends on the built-in {@link api/angular.service.$route
* The `routeTemplateMonitor` service depends on the built-in {@link api/angular.module.ng.$route
$route} service as well as our custom `batchLog` service.
* The `routeTemplateMonitor` service is declared to be eager, so that it is started as soon as the
application starts.
* To underline the need for the eager instantiation of the `routeTemplateMonitor` service, nothing
else in the application depends on this service, and in this particular case the factory function
of this service doesn't return anything at all.
* Both of our services use the factory function signature as well as the `$inject` property to
declare their dependencies. It is important that the order of the string identifiers in the array
associated with the `$inject` property is the same as the order of argument names in the signature
of the factory function. Unless the dependencies are inferred from the function signature, it is
this array with IDs and their order that the injector uses to determine which services and in which
order to inject.
* Both of our services use the factory function signature and array notation for inject annotations
to declare their dependencies. It is important that the order of the string identifiers in the array
is the same as the order of argument names in the signature of the factory function. Unless the
dependencies are inferred from the function signature, it is this array with IDs and their order
that the injector uses to determine which services and in which order to inject.
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Services}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.module.ng Angular Service API}
* {@link api/angular.injector Angular Injector API}
+2 -4
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services
@description
@@ -13,11 +12,10 @@ most often used with {@link dev_guide.di dependency injection}, also a key featu
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Conrollers}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers}
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.module.ng Angular Service API}
@@ -1,70 +0,0 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Registering Angular Services
@description
To register a service, register a factory function that creates the service with angular's
Injector. The Injector is exposed as {@link api/angular.scope.$service scope.$service}. The
following pseudo-code shows a simple service registration:
<pre>
angular.service('service id', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
});
</pre>
Note that you are not registering a service instance, but rather a factory function that will
create this instance when called.
# Instantiating Angular Services
A service can be instantiated eagerly or lazily. By default angular instantiates services lazily,
which means that a service will be created only when it is needed for instantiation of a service or
an application component that depends on it. In other words, angular won't instantiate lazy
services unless they are requested directly or indirectly by the application.
Eager services on the other hand, are instantiated right after the injector itself is created,
which happens when the angular {@link dev_guide.bootstrap application initializes}.
To override the default, you can request that a service is eagerly instantiated as follows:
<pre>
angular.service('service id', function() {
var shinyNewServiceInstance;
//factory function body that constructs shinyNewServiceInstance
return shinyNewServiceInstance;
}, {$eager: true});
</pre>
While it is tempting to declare services as eager, only in few cases it is actually useful. If you
are unsure whether to make a service eager, it likely doesn't need to be. To be more specific, a
service should be declared as eager only if it fits one of these scenarios:
* Nothing in your application declares this service as its dependency, and this service affects the
state or configuration of the application (e.g. a service that configures `$route` or `$resource`
services)
* A guarantee is needed that the service will be instantiated at application boot time, usually
because the service passively observes the application and it is optional for other application
components to depend on it. An example of this scenario is a service that monitors and logs
application memory usage.
Lastly, it is important to realize that all angular services are applicaiton singletons. This means
that there is only one instance of a given service per injector. Since angular is lethally allergic
to the global state, it is possible to create multiple injectors, each with its own instance of a
given service, but that is rarely needed, except in tests where this property is crucially
important.
## Related Topics
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Controllers }
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Testing Angular Services
@description
@@ -12,7 +11,14 @@ var mock, notify;
beforeEach(function() {
mock = {alert: jasmine.createSpy()};
notify = angular.service('notify')(mock);
module(function($provide) {
$provide.value('$window', mock);
});
inject(function($injector) {
notify = $injector.get('notify');
});
});
it('should not alert first two notifications', function() {
@@ -48,12 +54,9 @@ it('should clear messages after alert', function() {
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.injecting_controllers Injecting Services Into Conrollers}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.module.ng Angular Service API}
@@ -1,10 +1,9 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Angular Services: Understanding Angular Services
@description
Angular services are singletons that carry out specific tasks common to web apps, such as the
{@link api/angular.service.$xhr $xhr service} that provides low level access to the browser's
{@link api/angular.module.ng.$http $http service} that provides low level access to the browser's
`XMLHttpRequest` object.
To use an angular service, you identify it as a dependency for the dependent (a controller, or
@@ -13,14 +12,14 @@ of the rest. The angular injector subsystem is in charge of service instantiatio
dependencies, and provision of dependencies to factory functions as requested.
Angular injects dependencies using "constructor" injection (the service is passed in via a factory
function). Because JavaScript is a dynamically typed language, angular's dependency injection
function). Because JavaScript is a dynamically typed language, Angular's dependency injection
subsystem cannot use static types to identify service dependencies. For this reason a dependent
must explicitly define its dependencies by using the `$inject` property. For example:
myController.$inject = ['$location'];
The angular web framework provides a set of services for common operations. Like other core angular
variables and identifiers, the built-in services always start with `$` (such as `$xhr` mentioned
variables and identifiers, the built-in services always start with `$` (such as `$http` mentioned
above). You can also create your own custom services.
@@ -28,11 +27,10 @@ above). You can also create your own custom services.
* {@link dev_guide.di About Angular Dependency Injection}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.testing_services Testing Angular Services}
## Related API
* {@link api/angular.service Angular Service API}
* {@link api/angular.module.ng Angular Service API}
* {@link api/angular.injector Injector API}
@@ -1,51 +1,34 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Working With CSS in Angular
@description
Angular includes built-in CSS classes, which in turn have predefined CSS styles.
Angular sets these CSS classes. It is up to your application to provide useful styling.
# Built-in CSS classes
# CSS classes used by angular
* `ng-exception`
* `ng-invalid`, `ng-valid`
- **Usage:** angular applies this class to an input widget element if that element's input does
notpass validation. (see {@link api/angular.widget.input input} widget).
**Usage:** angular applies this class to a DOM element if that element contains an Expression that
threw an exception when evaluated.
* `ng-pristine`, `ng-dirty`
- **Usage:** angular {@link api/angular.widget.input input} widget applies `ng-pristine` class
to a new input widget element which did not have user interaction. Once the user interacts with
the input widget the class is changed to `ng-dirty`.
**Styling:** The built-in styling of the ng-exception class displays an error message surrounded
by a solid red border, for example:
# Marking CSS classes
<div class="ng-exception">Error message</div>
* `ng-widget`, `ng-directive`
- **Usage:** angular sets these class on elements where {@link api/angular.widget widget} or
{@link api/angular.directive directive} has bound to.
You can try to evaluate malformed expressions in {@link dev_guide.expressions expressions} to see
the `ng-exception` class' styling.
* `ng-validation-error`
**Usage:** angular applies this class to an input widget element if that element's input does not
pass validation. Note that you set the validation criteria on the input widget element using the
Ng:validate or Ng:required directives.
**Styling:** The built-in styling of the ng-validation-error class turns the border of the input
box red and includes a hovering UI element that includes more details of the validation error. You
can see an example in {@link api/angular.widget.@ng:validate ng:validate example}.
## Overriding Styles for Angular CSS Classes
To override the styles for angular's built-in CSS classes, you can do any of the following:
* Download the source code, edit angular.css, and host the source on your own server.
* Create a local CSS file, overriding any styles that you'd like, and link to it from your HTML file
as you normally would:
<pre>
<link href="yourfile.css" rel="stylesheet" type="text/css">
</pre>
* Old browser support
- Pre v9, IE browsers could not select `ng:include` elements in CSS, because of the `:`
character. For this reason angular also sets `ng-include` class on any element which has `:`
character in the name by replacing `:` with `-`.
## Related Topics
* {@link dev_guide.templates Angular Templates}
* {@link dev_guide.templates.formatters Angular Formatters}
* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters}
* {@link dev_guide.forms Angular Forms}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Data Binding in Angular
@description
@@ -1,9 +1,8 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Filters: Creating Angular Filters
@description
Writing your own filter is very easy: just define a JavaScript function on the `angular.filter`
Writing your own filter is very easy: just define a JavaScript function on the `angular.module.ng.$filter`
object.
The framework passes in the input value as the first argument to your function. Any filter
arguments are passed in as additional function arguments.
@@ -17,38 +16,42 @@ filter to manipulate the DOM.
The following sample filter reverses a text string. In addition, it conditionally makes the
text upper-case and assigns color.
<doc:example>
<doc:example module="MyReverseModule">
<doc:source>
<script type="text/javascript">
angular.filter('reverse', function(input, uppercase, color) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
// conditional based on optional argument
if (uppercase) {
out = out.toUpperCase();
}
// DOM manipulation using $element
if (color) {
this.$element.css('color', color);
}
return out;
});
angular.module('MyReverseModule', []).
filter('reverse', function() {
return function(input, uppercase) {
var out = "";
for (var i = 0; i < input.length; i++) {
out = input.charAt(i) + out;
}
// conditional based on optional argument
if (uppercase) {
out = out.toUpperCase();
}
return out;
}
});
function Ctrl() {
this.greeting = 'hello';
}
</script>
<input name="text" type="text" value="hello" /><br>
No filter: {{text}}<br>
Reverse: {{text|reverse}}<br>
Reverse + uppercase: {{text|reverse:true}}<br>
Reverse + uppercase + blue: {{text|reverse:true:"blue"}}
<div ng:controller="Ctrl">
<input ng:model="greeting" type="greeting"><br>
No filter: {{greeting}}<br>
Reverse: {{greeting|reverse}}<br>
Reverse + uppercase: {{greeting|reverse:true}}<br>
</div>
</doc:source>
<doc:scenario>
it('should reverse text', function(){
expect(binding('text|reverse')).toEqual('olleh');
input('text').enter('ABC');
expect(binding('text|reverse')).toEqual('CBA');
});
it('should reverse greeting', function() {
expect(binding('greeting|reverse')).toEqual('olleh');
input('greeting').enter('ABC');
expect(binding('greeting|reverse')).toEqual('CBA');
});
</doc:scenario>
</doc:example>
@@ -60,4 +63,4 @@ expect(binding('text|reverse')).toEqual('CBA');
## Related API
* {@link api/angular.filter Angular Filter API}
* {@link api/angular.module.ng.$filter Angular Filter API}
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Understanding Angular Filters
@description
@@ -12,7 +11,8 @@ displaying it to the user. You can pass expressions through a chain of filters l
name | uppercase
The expression evaluator simply passes the value of name to `angular.filter.uppercase()`.
The expression evaluator simply passes the value of name to
{@link api/angular.module.ng.$filter.uppercase uppercase filter}.
In addition to formatting data, filters can also modify the DOM. This allows filters to handle
tasks such as conditionally applying CSS styles to filtered output.
@@ -25,4 +25,4 @@ tasks such as conditionally applying CSS styles to filtered output.
## Related API
* {@link api/angular.filter Angular Filter API}
* {@link api/angular.module.ng.$filter Angular Filter API}
@@ -1,9 +1,8 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Filters: Using Angular Filters
@description
Filters can be part of any {@link api/angular.scope} evaluation but are typically used to format
Filters can be part of any {@link api/angular.module.ng.$rootScope.Scope} evaluation but are typically used to format
expressions in bindings in your templates:
{{ expression | filter }}
@@ -38,4 +37,4 @@ argument that specifies how many digits to display to the right of the decimal p
## Related API
* {@link api/angular.filter Angular Filter API}
* {@link api/angular.module.ng.$filter Angular Filter API}
@@ -1,55 +0,0 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Angular Formatters: Creating Angular Formatters
@description
To create your own formatter, you can simply register a pair of JavaScript functions with
`angular.formatter`. One of your functions is used to parse text from the input widget into the
data storage format; the other function is used to format stored data into user-readable text.
The following example demonstrates a "reverse" formatter. Data is stored in uppercase and in
reverse, but it is displayed in lower case and non-reversed. When a user edits the data model via
the input widget, the input is automatically parsed into the internal data storage format, and when
the data changes in the model, it is automatically formatted to the user-readable form for display
in the view.
<pre>
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</pre>
<doc:example>
<doc:source>
<script type="text/javascript">
function reverse(text) {
var reversed = [];
for (var i = 0; i < text.length; i++) {
reversed.unshift(text.charAt(i));
}
return reversed.join('');
}
angular.formatter('reverse', {
parse: function(value){
return reverse(value||'').toUpperCase();
},
format: function(value){
return reverse(value||'').toLowerCase();
}
});
</script>
@@ -1,20 +0,0 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Angular Formatters
@description
In angular, formatters are responsible for translating user-readable text entered in an {@link
api/angular.widget.HTML input widget} to a JavaScript object in the data model that the application
can manipulate.
You can use formatters in a template, and also in JavaScript. Angular provides built-in
formatters, and of course you can create your own formatters.
## Related Topics
* {@link dev_guide.templates.formatters.using_formatters Using Angular Formatters}
* {@link dev_guide.templates.formatters.creating_formatters Creating Angular Formatters}
## Related API
* {@link api/angular.formatter Angular Formatter API}
@@ -1,9 +0,0 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Angular Formatters: Using Angular Formatters
@description
The following snippet shows how to use a formatter in a template. The formatter below is
`ng:format="reverse"`, added as an attribute to an `<input>` tag.
<pre>
+6 -10
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Understanding Angular Templates
@description
@@ -18,9 +17,7 @@ is {@link api/angular.widget.@ng:repeat ng:repeat}.
* {@link dev_guide.compiler.markup Markup} — Shorthand for a widget or a directive. The double
curly brace notation `{{ }}` to bind expressions to elements is built-in angular markup.
* {@link dev_guide.templates.filters Filter} — Formats your data for display to the user.
* {@link dev_guide.templates.validators Validator} — Lets you validate user input.
* {@link dev_guide.templates.formatters Formatter} — Lets you format the input object into a user
readable view.
* {@link dev_guide.forms Form widgets} — Lets you validate user input.
Note: In addition to declaring the elements above in templates, you can also access these elements
in JavaScript code.
@@ -30,15 +27,15 @@ angular {@link dev_guide.compiler.directives directives}, {@link dev_guide.compi
and {@link dev_guide.expressions expressions}:
<pre>
<html>
<html ng:app>
<!-- Body tag augmented with ng:controller directive -->
<body ng:controller="MyController">
<input name="foo" value="bar">
<input ng:model="foo" value="bar">
<!-- Button tag with ng:click directive, and
string expression 'buttonText'
wrapped in "{{ }}" markup -->
<button ng:click="changeFoo()">{{buttonText}}</button>
<script src="angular.js" ng:autobind>
<script src="angular.js">
</body>
</html>
</pre>
@@ -46,7 +43,7 @@ and {@link dev_guide.expressions expressions}:
In a simple single-page app, the template consists of HTML, CSS, and angular directives contained
in just one HTML file (usually `index.html`). In a more complex app, you can display multiple views
within one main page using "partials", which are segments of template located in separate HTML
files. You "include" the partials in the main page using the {@link api/angular.service.$route
files. You "include" the partials in the main page using the {@link api/angular.module.ng.$route
$route} service in conjunction with the {@link api/angular.widget.ng:view ng:view} directive. An
example of this technique is shown in the {@link tutorial/ angular tutorial}, in steps seven and
eight.
@@ -55,8 +52,7 @@ eight.
## Related Topics
* {@link dev_guide.templates.filters Angular Filters}
* {@link dev_guide.templates.formatters Angular Formatters}
* {@link dev_guide.templates.validators Angular Validators}
* {@link dev_guide.forms Angular Forms}
## Related API
@@ -1,82 +0,0 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Validators: Creating Angular Validators
@description
To create a custom validator, you simply add your validator code as a method onto the
`angular.validator` object and provide input(s) for the validator function. Each input provided is
treated as an argument to the validator function. Any additional inputs should be separated by
commas.
The following bit of pseudo-code shows how to set up a custom validator:
<pre>
angular.validator('your_validator', function(input [,additional params]) {
[your validation code];
if ( [validation succeeds] ) {
return false;
} else {
return true; // No error message specified
}
}
</pre>
Note that this validator returns "true" when the user's input is incorrect, as in "Yes, it's true,
there was a problem with that input". If you prefer to provide more information when a validator
detects a problem with input, you can specify an error message in the validator that angular will
display when the user hovers over the input widget.
To specify an error message, replace "`return true;`" with an error string, for example:
return "Must be a value between 1 and 5!";
Following is a sample UPS Tracking Number validator:
<doc:example>
<doc:source>
<script>
angular.validator('upsTrackingNo', function(input, format) {
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
return input.match(regexp)?"":"The format must match " + format;
});
</script>
<input type="text" name="trackNo" size="40"
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
value="1Z 123 456 78 9012 345 6"/>
</doc:source>
<doc:scenario>
it('should validate correct UPS tracking number', function() {
expect(element('input[name=trackNo]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate in correct UPS tracking number', function() {
input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
In this sample validator, we specify a regular expression against which to test the user's input.
Note that when the user's input matches `regexp`, the function returns "false" (""); otherwise it
returns the specified error message ("true").
Note: you can also access the current angular scope and DOM element objects in your validator
functions as follows:
* `this` === The current angular scope.
* `this.$element` === The DOM element that contains the binding. This allows the filter to
manipulate the DOM in addition to transforming the input.
## Related Topics
* {@link dev_guide.templates Angular Templates}
* {@link dev_guide.templates.filters Angular Filters}
* {@link dev_guide.templates.formatters Angular Formatters}
## Related API
* {@link api/angular.validator API Validator Reference}
@@ -1,131 +0,0 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Templates: Understanding Angular Validators
@description
Angular validators are attributes that test the validity of different types of user input. Angular
provides a set of built-in input validators:
* {@link api/angular.validator.phone phone number}
* {@link api/angular.validator.number number}
* {@link api/angular.validator.integer integer}
* {@link api/angular.validator.date date}
* {@link api/angular.validator.email email address}
* {@link api/angular.validator.json JSON}
* {@link api/angular.validator.regexp regular expressions}
* {@link api/angular.validator.url URLs}
* {@link api/angular.validator.asynchronous asynchronous}
You can also create your own custom validators.
# Using Angular Validators
You can use angular validators in HTML template bindings, and in JavaScript:
* Validators in HTML Template Bindings
<pre>
<input ng:validator="validator_type:parameters" [...]>
</pre>
* Validators in JavaScript
<pre>
angular.validator.[validator_type](parameters)
</pre>
The following example shows how to use the built-in angular integer validator:
<doc:example>
<doc:source>
Change me: <input type="text" name="number" ng:validate="integer" value="123">
</doc:source>
<doc:scenario>
it('should validate the default number string', function() {
expect(element('input[name=number]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate "foo"', function() {
input('number').enter('foo');
expect(element('input[name=number]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
# Creating an Angular Validator
To create a custom validator, you simply add your validator code as a method onto the
`angular.validator` object and provide input(s) for the validator function. Each input provided is
treated as an argument to the validator function. Any additional inputs should be separated by
commas.
The following bit of pseudo-code shows how to set up a custom validator:
<pre>
angular.validator('your_validator', function(input [,additional params]) {
[your validation code];
if ( [validation succeeds] ) {
return false;
} else {
return true; // No error message specified
}
}
</pre>
Note that this validator returns "true" when the user's input is incorrect, as in "Yes, it's true,
there was a problem with that input". If you prefer to provide more information when a validator
detects a problem with input, you can specify an error message in the validator that angular will
display when the user hovers over the input widget.
To specify an error message, replace "`return true;`" with an error string, for example:
return "Must be a value between 1 and 5!";
Following is a sample UPS Tracking Number validator:
<doc:example>
<doc:source>
<script>
angular.validator('upsTrackingNo', function(input, format) {
var regexp = new RegExp("^" + format.replace(/9/g, '\\d') + "$");
return input.match(regexp)?"":"The format must match " + format;
});
</script>
<input type="text" name="trackNo" size="40"
ng:validate="upsTrackingNo:'1Z 999 999 99 9999 999 9'"
value="1Z 123 456 78 9012 345 6"/>
</doc:source>
<doc:scenario>
it('should validate correct UPS tracking number', function() {
expect(element('input[name=trackNo]').attr('class')).
not().toMatch(/ng-validation-error/);
});
it('should not validate in correct UPS tracking number', function() {
input('trackNo').enter('foo');
expect(element('input[name=trackNo]').attr('class')).
toMatch(/ng-validation-error/);
});
</doc:scenario>
</doc:example>
In this sample validator, we specify a regular expression against which to test the user's input.
Note that when the user's input matches `regexp`, the function returns "false" (""); otherwise it
returns the specified error message ("true").
Note: you can also access the current angular scope and DOM element objects in your validator
functions as follows:
* `this` === The current angular scope.
* `this.$element` === The DOM element that contains the binding. This allows the filter to
manipulate the DOM in addition to transforming the input.
## Related Topics
* {@link dev_guide.templates Angular Templates}
## Related API
* {@link api/angular.validator Validator API}
+20 -19
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide: Unit Testing
@description
@@ -43,11 +42,11 @@ on a constructor permanently binds the call site to the type. For example lets s
trying to instantiate an `XHR` so that we can get some data from the server.
<pre>
function MyClass(){
this.doWork = function(){
function MyClass() {
this.doWork = function() {
var xhr = new XHR();
xhr.open(method, url, true);
xhr.onreadystatechange = function(){...}
xhr.onreadystatechange = function() {...}
xhr.send();
}
}
@@ -61,7 +60,7 @@ patching, that is a bad idea for many reasons, which is outside the scope of thi
The class above is hard to test since we have to resort to monkey patching:
<pre>
var oldXHR = XHR;
XHR = function MockXHR(){};
XHR = function MockXHR() {};
var myClass = new MyClass();
myClass.doWork();
// assert that MockXHR got called with the right arguments
@@ -73,8 +72,8 @@ XHR = oldXHR; // if you forget this bad things will happen
Another way to approach the problem is look for the service in a well known location.
<pre>
function MyClass(){
this.doWork = function(){
function MyClass() {
this.doWork = function() {
global.xhr({
method:'...',
url:'...',
@@ -94,7 +93,7 @@ State & Singletons}
The class above is hard to test since we have to change global state:
<pre>
var oldXHR = glabal.xhr;
glabal.xhr = function mockXHR(){};
glabal.xhr = function mockXHR() {};
var myClass = new MyClass();
myClass.doWork();
// assert that mockXHR got called with the right arguments
@@ -110,7 +109,7 @@ having the tests replace the services as needed.
<pre>
function MyClass() {
var serviceRegistry = ????;
this.doWork = function(){
this.doWork = function() {
var xhr = serviceRegistry.get('xhr');
xhr({
method:'...',
@@ -128,7 +127,7 @@ there is only one global variable to be reset).
The class above is hard to test since we have to change global state:
<pre>
var oldServiceLocator = glabal.serviceLocator;
glabal.serviceLocator.set('xhr', function mockXHR(){});
glabal.serviceLocator.set('xhr', function mockXHR() {});
var myClass = new MyClass();
myClass.doWork();
// assert that mockXHR got called with the right arguments
@@ -141,7 +140,7 @@ Lastly the dependency can be passed in.
<pre>
function MyClass(xhr) {
this.doWork = function(){
this.doWork = function() {
xhr({
method:'...',
url:'...',
@@ -174,13 +173,13 @@ for your application is mixed in with DOM manipulation, it will be hard to test
below:
<pre>
function PasswordController(){
function PasswordController() {
// get references to DOM elements
var msg = $('.ex1 span');
var input = $('.ex1 input');
var strength;
this.grade = function(){
this.grade = function() {
msg.removeClass(strength);
var pwd = input.val();
password.text(pwd);
@@ -219,9 +218,9 @@ In angular the controllers are strictly separated from the DOM manipulation logi
a much easier testability story as can be seen in this example:
<pre>
function PasswordCntrl(){
function PasswordCntrl() {
this.password = '';
this.grade = function(){
this.grade = function() {
var size = this.password.length;
if (size > 8) {
this.strength = 'strong';
@@ -248,16 +247,18 @@ that such a test tells a story, rather then asserting random bits which don't se
## Filters
{@link api/angular.filter Filters} are functions which transform the data into user readable
{@link api/angular.module.ng.$filter Filters} are functions which transform the data into user readable
format. They are important because they remove the formatting responsibility from the application
logic, further simplifying the application logic.
<pre>
angular.filter('length', function(text){
return (''+(text||'')).length;
myModule.filter('length', function() {
return function(text){
return (''+(text||'')).length;
}
});
var length = angular.filter('length');
var length = $filter('length');
expect(length(null)).toEqual(0);
expect(length('abc')).toEqual(3);
</pre>
+1 -4
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Developer Guide
@description
@@ -42,14 +41,12 @@ of the following documents before returning here to the Developer Guide:
## {@link dev_guide.templates Angular Templates}
* {@link dev_guide.templates.filters Understanding Angular Filters}
* {@link dev_guide.templates.formatters Understanding Angular Formatters}
* {@link dev_guide.templates.validators Understanding Angular Validators}
* {@link dev_guide.forms Understanding Angular Forms}
## {@link dev_guide.services Angular Services}
* {@link dev_guide.services.understanding_services Understanding Angular Services}
* {@link dev_guide.services.creating_services Creating Angular Services}
* {@link dev_guide.services.registering_services Registering Angular Services}
* {@link dev_guide.services.managing_dependencies Managing Service Dependencies}
* {@link dev_guide.services.testing_services Testing Angular Services}
+2 -3
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name Downloading
@description
@@ -21,10 +20,10 @@ example points to (non-minified) version 0.9.12:
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<html xmlns:ng="http://angularjs.org" ng:app>
<head>
<title>My Angular App</title>
<script src="http://code.angularjs.org/angular-0.9.12.js" ng:autobind></script>
<script src="http://code.angularjs.org/angular-0.9.12.js"></script>
</head>
<body>
</body>
+1 -2
View File
@@ -1,4 +1,3 @@
@workInProgress
@ngdoc overview
@name FAQ
@description
@@ -69,7 +68,7 @@ manipulate the DOM.
### What is testability like in angular?
Very testable. It has an integrated dependency injection framework. See
{@link api/angular.service service} for details.
{@link api/angular.module.ng service} for details.
### How can I learn more about angular?
+6 -7
View File
@@ -27,18 +27,17 @@ Now let's take a closer look at that code, and see what is going on behind
the scenes.
The first line of interest defines the `ng` namespace, which makes
AngularJS work across all browsers (especially important for IE):
AngularJS work across all browsers (especially important for IE). The
`ng:app` tags tells angular to process the entire HTML when it is loaded:
<pre>
<html xmlns:ng="http://angularjs.org">
<html xmlns:ng="http://angularjs.org" ng:app>
</pre>
The next line downloads the angular script, and instructs angular to process
the entire HTML page when it is loaded:
The next line downloads the angular script:
<pre>
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js"
ng:autobind></script>
<script type="text/javascript" src="http://code.angularjs.org/angular-?.?.?.min.js"></script>
</pre>
(For details on what happens when angular processes an HTML page,
@@ -67,7 +66,7 @@ This example demonstrates angular's two-way data binding:
<doc:example>
<doc:source>
Your name: <input type="text" name="yourname" value="World"/>
Your name: <input type="text" ng:model="yourname" value="World"/>
<hr/>
Hello {{yourname}}!
</doc:source>
-150
View File
@@ -1,150 +0,0 @@
@ngdoc overview
@name Tutorial
@description
A great way to get introduced to Angular 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
of Android devices, lets you filter the list to see only devices that interest you, and then view
details for any device.
<img src="img/tutorial/catalog_screen.png">
Work through the tutorial to see how Angular makes browsers smarter — without the use of extensions
or plug-ins. As you work through the tutorial, you will:
* See examples of how to use client-side data binding and dependency injection to build dynamic
views of data that change immediately in response to user actions.
* See how Angular creates listeners on your data without the need for DOM manipulation.
* Learn a better, easier way to test your web apps.
* Learn how to use Angular services to make common web tasks, such as getting data into your app,
easier.
And all of this works in any browser without modification to the browser!
When you finish the tutorial you will be able to:
* Create a dynamic application that works in any browser.
* Define the differences between Angular and common JavaScript frameworks.
* Understand how data binding works in AngularJS.
* Use the angular-seed project to quickly boot-strap your own projects.
* Create and run tests.
* Identify resources for learning more about AngularJS.
The tutorial guides you through the entire process of building a simple application, including
writing and running unit and end-to-end tests. Experiments at the end of each step provide
suggestions for you learn more about AngularJS and the application you are building.
You can go through the whole tutorial in a couple of hours or you may want to spend a pleasant day
really digging into it. If you're looking for a shorter introduction to AngularJS, check out the
{@link misc/started Getting Started} document.
# Working with the code
You can follow this tutorial and hack on the code in either the Mac/Linux or the Windows
environment. Options for working with the tutorial are to use the Git versioning system for source
code management or to use scripts that copy snapshots of project files into your workspace
(`sandbox`) directory. Select one of the tabs below and follow the instructions for setting up your
computer for your preferred option.
<doc:tutorial-instructions show="true">
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed by running the
following command in a terminal window:</p>
<pre><code>java -version</code></pre>
<p>You will need Java to run unit tests.</p></li>
<li><p>Download Git from the <a href="http://git-scm.com/download">Git</a> site.</p>
<p>You can build Git from source or use the pre-compiled package.</p></li>
<li><p>Clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
<p>This command creates the <code>angular-phonecat</code> directory in your current
directory.</p></li>
<li><p>Change your current directory to <code>angular-phonecat</code>:</p>
<pre><code>cd angular-phonecat</code></pre>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p></li>
<li><p>You will need an http server running on your system. Mac and Linux machines typically
have Apache pre-installed, but If you don't already have one installed, you can <a
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
node.js</a>. Use <code>node</code> to run <code>scripts/web-server.js</code>, a simple bundled
http server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="git-win" title="Git on Windows">
<ol>
<li><p>You will need Java to run unit tests, so run the following command to verify that you
have <a href="http://java.com/">Java</a> installed and that the <code>java</code> executable is on
your <code>PATH</code>.</p>
<pre><code>java -version</code></pre>
<p></p></li>
<li><p>Install msysGit from <a href="http://git-scm.com/download">the Git</a> site.</p></li>
<li><p>Open msysGit bash and clone the angular-phonecat repository located at <a
href="https://github.com/angular/angular-phonecat">Github</a> by running the following command:</p>
<pre><code>git clone git://github.com/angular/angular-phonecat.git</code></pre>
<p>This command creates the angular-phonecat directory in your current directory.</p></li>
<li><p>Change your current directory to angular-phonecat.</p>
<pre><code>cd angular-phonecat</code></pre>
<p>The tutorial instructions assume you are running all commands from the angular-phonecat
directory.</p>
<p>You should run all <code>git</code> commands from msysGit bash.</p>
<p>Other commands like <code>test-server.bat</code> or <code>test.bat</code> should be
executed from the Windows command line.</li>
<li><p>You need an http server running on your system. If you don't already have one
installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
<code>scripts\web-server.js</code>, a simple, bundled http server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
<ol>
<li><p>You need Java to run unit tests, so verify that you have <a
href="http://java.com/">Java</a> installed by running the following command in a terminal
window:</p>
<pre><code>java -version</code></pre>
<li><p>Download the <a href="http://code.angularjs.org/angular-phonecat/">zip archive</a>
containing all of the files and unzip them into the [tutorial-dir] directory</p>.</li>
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
<p>The tutorial instructions assume you are running all commands from your
<code>sandbox</code> directory.</p></li>
<li><p>You need an http server running on your system and Mac and Linux machines typically
have Apache pre-installed. If you don't have an http server installed, you can <a
href="https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager">install
node.js</a> and use it to run <code>scripts/web-server.js</code>, a simple bundled http
server.</p></li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
<ol>
<li><p>Verify that you have <a href="http://java.com/">Java</a> installed and that the
<code>java</code> executable is on your <code>PATH</code> by running the following command in the
Windows command line:</p>
<pre><code>java -version</code></pre>
<p>You need Java to run unit tests, so download the <a
href="http://code.angularjs.org/angular-phonecat/">zip archive</a> that contains all of the files
and unzip the files into the [tutorial-dir] directory</p></li>
<li><p>Change your current directory to [tutorial-dir]/sandbox, as follows:</p>
<pre><code>cd [tutorial-dir]/sandbox</code></pre>
<p>The tutorial instructions assume you are running all commands from this directory.</p></li>
<li><p>You need an http server running on your system, but if you don't already have one
already installed, you can install <a href="http://nodejs.org/">node.js</a>. Download the <a
href="http://node-js.prcn.co.cc/">pre-compiled binaries</a>, unzip them, and then add
<code>nodejs\bin</code> into your <code>PATH</code>. Use <code>node</code> to run
<code>scripts\web-server.js</code>, a simple bundled http server.</p></li>
</ol>
</doc:tutorial-instruction>
</doc:tutorial-instructions>
The last thing to do is to make sure your computer has a web browser and a good text editor
installed. Now, let's get going with {@link step_00 step 0}.
-216
View File
@@ -1,216 +0,0 @@
@ngdoc overview
@name Tutorial: 0 - angular-seed
@description
<ul doc:tutorial-nav="0"></ul>
You are now ready to build the Angular phonecat application. In this step, you will become familiar
with the most important source code files, learn how to start the development servers bundled with
angular-seed, and run the application in the browser.
<doc:tutorial-instructions show="true">
<doc:tutorial-instruction id="git-mac" title="Git on Mac/Linux">
<ol>
<li><p>In angular-phonecat directory, run this command:</p>
<pre><code>git checkout -f step-0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run
<code>./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="git-win" title="Git on Windows">
<ol>
<li><p>Open msysGit bash and run this command (in angular-phonecat directory):</p>
<pre><code>git checkout -f step-0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node
scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the <code>angular-phonecat</code>
directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-mac" title="Snapshots on Mac/Linux">
<ol>
<li><p>In the angular-phonecat directory, run this command:</p>
<pre><code>./goto_step.sh 0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run
<code>./scripts/web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the angular-phonecat
<code>sandbox</code> directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
<doc:tutorial-instruction id="ss-win" title="Snapshots on Windows">
<ol>
<li><p>Open windows command line and run this command (in the angular-phonecat directory):</p>
<pre><code>goto_step.bat 0</code></pre>
<p>This resets your workspace to step 0 of the tutorial app.</p>
<p>You must repeat this for every future step in the tutorial and change the number to
the number of the step you are on. This will cause any changes you made within
your working directory to be lost.</p></li>
<li>To see the app running in a browser, do one of the following:
<ul>
<li><b>For node.js users:</b>
<ol>
<li>In a <i>separate</i> terminal tab or window, run <code>node
scripts\web-server.js</code> to start the web server.</li>
<li>Open a browser window for the app and navigate to <a
href="http://localhost:8000/app/index.html">http://localhost:8000/app/index.html</a></li>
</ol>
</li>
<li><b>For other http servers:</b>
<ol>
<li>Configure the server to serve the files in the angular-phonecat
<code>sandbox</code> directory.</li>
<li>Navigate in your browser to
<code>http://localhost:[port-number]/[context-path]/app/index.html</code>.</li>
</ol>
</li>
</ul>
</li>
</ol>
</doc:tutorial-instruction>
</doc:tutorial-instructions>
You can now see the page in your browser. It's not very exciting, but that's OK.
The static 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 going forward.
__`app/index.html`:__
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org/">
<head>
<meta charset="utf-8">
<title>my angular app</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
Nothing here yet!
<script src="lib/angular/angular.js" ng:autobind></script>
</body>
</html>
</pre>
## What is the code doing?
* xmlns declaration
<html xmlns:ng="http://angularjs.org">
This `xmlns` declaration for the `ng` namespace must be specified in all Angular applications in
order to make Angular work with XHTML and IE versions older than 9 (regardless of whether you are
using XHTML or HTML).
* Angular script tag
<script src="lib/angular/angular.js" ng:autobind>
This single line of code is all that is needed to bootstrap an angular application.
The code downloads the `angular.js` script and registers a callback that will be executed by the
browser when the containing HTML page is fully downloaded. When the callback is executed, Angular
looks for the {@link api/angular.directive.ng:autobind ng:autobind} attribute. If Angular finds
`ng:autobind`, it creates a root scope for the application and associates it with the `<html>`
element of the template:
<img src="img/tutorial/tutorial_00_final.png">
As you will see shortly, everything in Angular is evaluated within a scope. We'll learn more
about this in the next steps.
## What are all these files in my working directory?
Most of the files in your working directory come from the {@link
https://github.com/angular/angular-seed angular-seed project} which is typically used to bootstrap
new Angular projects. The seed project includes the latest Angular libraries, test libraries,
scripts and a simple example app, all pre-configured for developing a typical web app.
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`
# Summary
Now let's go to {@link step_01 step 1} and add some content to the web app.
<ul doc:tutorial-nav="0"></ul>
-57
View File
@@ -1,57 +0,0 @@
@ngdoc overview
@name Tutorial: 1 - Static Template
@description
<ul doc:tutorial-nav="1"></ul>
In order to illustrate how angular enhances standard HTML, you will create a purely *static* HTML
page and then examine how we can turn this HTML code into a template that angular will use to
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.
<doc:tutorial-instructions step="1" show="true"></doc:tutorial-instructions>
The page now contains a list with information about two phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-0...step-1 GitHub}:
__`app/index.html`:__
<pre>
...
<ul>
<li>
<span>Nexus S</span>
<p>
Fast just got faster with Nexus S.
</p>
</li>
<li>
<span>Motorola XOOM™ with Wi-Fi</span>
<p>
The Next, Next Generation tablet.
</p>
</li>
</ul>
...
</pre>
# Experiments
* Try adding more static HTML to `index.html`. For example:
<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 angular to dynamically generate the same list.
<ul doc:tutorial-nav="1"></ul>
-202
View File
@@ -1,202 +0,0 @@
@ngdoc overview
@name Tutorial: 2 - Angular Templates
@description
<ul doc:tutorial-nav="2"></ul>
Now it's time to make the web page dynamic -- with Angular. We'll also add a test that verifies the
code for the controller we are going to add.
There are many ways to structure the code for an application. For Angular apps, we encourage the
use of {@link http://en.wikipedia.org/wiki/ModelViewController the Model-View-Controller (MVC)
design pattern} 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.
<doc:tutorial-instructions step="2"></doc:tutorial-instructions>
The app now contains a list with three phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-1...step-2 GitHub}:
## Template for the View
The __view__ component is constructed by Angular from this template:
__`app/index.html`:__
<pre>
...
<body ng:controller="PhoneListCtrl">
<ul>
<li ng:repeat="phone in phones">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
<script src="lib/angular/angular.js" ng:autobind></script>
<script src="js/controllers.js"></script>
</body>
</html>
</pre>
We replaced the hard-coded phone list with the {@link api/angular.widget.@ng:repeat ng:repeat
widget} and two {@link guide/dev_guide.expressions Angular expressions} enclosed in curly braces:
`{{phone.name}}` and `{{phone.snippet}}`:
* The `ng:repeat="phone in phones"` statement in the `<li>` tag is an Angular repeater. The
repeater tells Angular to create a `<li>` element for each phone in the list using the first `<li>`
tag as the template.
<img src="img/tutorial/tutorial_02_final.png">
* The curly braces around `phone.name` and `phone.snippet` are examples of {@link
guide/dev_guide.compiler.markup Angular markup}. The curly markup is shorthand for the Angular
directive {@link api/angular.directive.ng:bind ng:bind}. An `ng:bind` directive indicates a
template binding point to Angular. Binding points are locations in a template where Angular creates
data-binding between the view and the model.
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.
## Model and Controller
The data __model__ (a simple array of phones in object literal notation) is instantiated within
the __controller__ function (`PhoneListCtrl`):
__`app/js/controllers.js`:__
<pre>
function PhoneListCtrl() {
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."}];
}
</pre>
Although the controller is not yet doing very much controlling, it is playing 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:
* The name of our controller function (in the JavaScript file `controllers.js`) matches the {@link
api/angular.directive.ng:controller ng:controller} directive in the `<body>` tag (`PhoneListCtrl`).
* The data is instantiated within the *scope* of our controller function; our template binding
points are located within the block bounded by the `<body ng:controller="PhoneListCtrl">` tag.
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
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 api/angular.scope angular scope documentation}.
## Tests
The "Angular way" makes it easy to test code as it is being developed. Take a look at the following
unit test for your newly created controller:
__`test/unit/controllersSpec.js`:__
<pre>
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function() {
it('should create "phones" model with 3 phones', function() {
var ctrl = new PhoneListCtrl();
expect(ctrl.phones.length).toBe(3);
});
});
});
</pre>
The test verifies that we have three records in the phones array and the 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.
Angular developers prefer the syntax of Jasmine's Behavior-driven Development (BDD) framework when
writing tests. Although Angular does not require you to use Jasmine, we wrote all of the tests in
this tutorial in Jasmine. You can learn about Jasmine on the {@link
http://pivotal.github.com/jasmine/ Jasmine home page} and on the {@link
https://github.com/pivotal/jasmine/wiki Jasmine wiki}.
The angular-seed project is pre-configured to run all unit tests using {@link
http://code.google.com/p/js-test-driver/ JsTestDriver}. To run the test, do the following:
1. In a _separate_ terminal window or tab, go to the `angular-phonecat` directory and run
`./scripts/test-server.sh` to start the test web server.
2. Open a new browser tab or window and navigate to {@link http://localhost:9876}.
3. Choose "Capture this browser in strict mode".
At this point, you can leave this tab open and forget about it. JsTestDriver will use it to
execute the tests and report the results in the terminal.
4. Execute the test by running `./scripts/test.sh`
You should see the following or similar output:
Chrome: Runner reset.
.
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (2.00 ms)
Chrome 11.0.696.57 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (2.00 ms)
Yay! The test passed! Or not...
Note: If you see errors after you run the test, close the browser tab and go back to the terminal
and kill the script, then repeat the procedure above.
# Experiments
* Add another binding to `index.html`. For example:
<p>Total number of phones: {{phones.length}}</p>
* Create a new model property in the controller and bind to it from the template. For example:
this.hello = "Hello, World!"
Refresh your browser to make sure it says, "Hello, World!"
* Create a repeater 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>
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>
* Make the unit test fail by changing the `toBe(3)` statement to `toBe(4)`, and rerun the
`./scripts/test.sh` script.
# 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.
<ul doc:tutorial-nav="2"></ul>
-182
View File
@@ -1,182 +0,0 @@
@ngdoc overview
@name Tutorial: 3 - Filtering Repeaters
@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.
<doc:tutorial-instructions step="3"></doc:tutorial-instructions>
The app now has a search box. Notice that the phone list on the page changes depending on what a
user types into the search box.
The most important differences between Steps 2 and 3 are listed below. You can see the full diff on
{@link https://github.com/angular/angular-phonecat/compare/step-2...step-3
GitHub}:
## Controller
We made no changes to the controller.
## Template
__`app/index.html`:__
<pre>
...
Fulltext Search: <input name="query"/>
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query)">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</pre>
We added a standard HTML `<input>` tag and used angular's {@link api/angular.Array.filter $filter}
function to process the input for the `ng:repeater`.
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 src="img/tutorial/tutorial_03_final.png">
* Use of `$filter`. The {@link api/angular.Array.filter $filter} method uses the `query` value to
create a new array that contains only those records that match the `query`.
`ng:repeat` automatically updates the view in response to the changing number of phones returned
by the `$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`:__
<pre>
describe('PhoneCat App', function() {
describe('Phone list view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html');
});
it('should filter the phone list as user types into the search box', function() {
expect(repeater('.phones li').count()).toBe(3);
input('query').enter('nexus');
expect(repeater('.phones li').count()).toBe(1);
input('query').enter('motorola');
expect(repeater('.phones li').count()).toBe(2);
});
});
});
</pre>
Even though the syntax of this test looks very much like our controller unit test written with
Jasmine, the end-to-end test uses APIs of {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
Angular's end-to-end test runner}.
To run the end-to-end test, open one of the following in a new browser tab:
* node.js users: {@link http://localhost:8000/test/e2e/runner.html}
* users with other http servers:
`http://localhost:[port-number]/[context-path]/test/e2e/runner.html`
* casual reader: {@link http://angular.github.com/angular-phonecat/step-3/test/e2e/runner.html}
This test verifies that the search box and the repeater are correctly wired together. Notice how
easy it is to write end-to-end tests in Angular. Although this example is for a simple test, it
really is that easy to set up any functional, readable, end-to-end test.
# Experiments
* Display the current value of the `query` model by adding a `{{query}}` binding into the
`index.html` template, and see how it changes when you type in the input box.
* Let's see how we can get the current value of the `query` model to appear in the HTML page title.
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 body element:
<body ng:controller="PhoneListCtrl">
If you want to bind to the query model from the `<title>` element, you must __move__ the
`ng:controller` declaration to the HTML element because it is the common parent of both the body
and title elements:
<html ng:controller="PhoneListCtrl">
Be sure to *remove* the `ng:controller` declaration from the body element.
While using double curlies works fine in 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 api/angular.directive.ng:bind ng:bind} or {@link
api/angular.directive.ng:bind-template ng:bind-template} directives, which are invisible to the
user while the page is loading:
<title ng:bind-template="Google Phone Gallery: {{query}}">Google Phone Gallery</title>
* Add the following end-to-end test into the `describe` block within `test/e2e/scenarios.js`:
<pre>
it('should display the current filter value within an element with id "status"',
function() {
expect(element('#status').text()).toMatch(/Current filter: \s*$/);
input('query').enter('nexus');
expect(element('#status').text()).toMatch(/Current filter: nexus\s*$/);
//alternative version of the last assertion that tests just the value of the binding
using('#status').expect(binding('query')).toBe('nexus');
});
</pre>
Refresh the browser tab with the end-to-end test runner to see the test fail. To make the test
pass, edit the `index.html` template to add a `div` or `p` element with `id` `"status"` and content
with the `query` binding.
* Add a `pause()` statement into an end-to-end test and rerun it. You'll see the runner pause; this
gives you the opportunity to explore the state of your application while it is displayed in the
browser. The app is live! You can change the search query to prove it. Notice how useful this is
for troubleshooting end-to-end tests.
# 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.
<ul doc:tutorial-nav="3"></ul>
-198
View File
@@ -1,198 +0,0 @@
@ngdoc overview
@name Tutorial: 4 - Two-way Data Binding
@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.
<doc:tutorial-instructions step="4"></doc:tutorial-instructions>
You should see that in addition to the search box, the app displays a drop down menu that allows
users to control the order in which the phones are listed.
The most important differences between Steps 3 and 4 are listed below. You can see the full diff on
{@link https://github.com/angular/angular-phonecat/compare/step-3...step-4 GitHub}:
## Template
__`app/index.html`:__
<pre>
...
<ul class="controls">
<li>
Search: <input type="text" name="query"/>
</li>
<li>
Sort by:
<select name="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</li>
</ul>
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
{{phone.name}}
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</pre>
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.
<img src="img/tutorial/tutorial_04-06_final.png">
* We then chained the `$filter` method with {@link api/angular.Array.orderBy `$orderBy`} method to
further process the input into the repeater. `$orderBy` is a utility method similar to {@link
api/angular.Array.filter `$filter`}, but instead of filtering an array, it reorders it.
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` method.
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/controller.js`:__
<pre>
/* App Controllers */
function PhoneListCtrl() {
this.phones = [{"name": "Nexus S",
"snippet": "Fast just got faster with Nexus S.",
"age": 0},
{"name": "Motorola XOOM™ with Wi-Fi",
"snippet": "The Next, Next Generation tablet.",
"age": 1},
{"name": "MOTOROLA XOOM™",
"snippet": "The Next, Next Generation tablet.",
"age": 2}];
this.orderProp = 'age';
}
</pre>
* 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.
* We added a line to the controller that sets the default value of `orderProp` to `age`. If we had
not set the default value here, angular would have used the value of the first `<option>` element
(`'name'`) when it initialized the data model.
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.
## Test
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.
__`test/unit/controllerSpec.js`:__
<pre>
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function(){
var scope, $browser, ctrl;
beforeEach(function() {
ctrl = new PhoneListCtrl();
});
it('should create "phones" model with 3 phones', function() {
expect(ctrl.phones.length).toBe(3);
});
it('should set the default value of orderProp model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
});
</pre>
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.
To run the unit tests, once again execute the `./scripts/test.sh` script and you should see the
following output.
Chrome: Runner reset.
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
Let's turn our attention to the end-to-end test.
__`test/e2e/scenarios.js`:__
<pre>
...
it('should be possible to control phone order via the drop down select box',
function() {
// narrow the dataset to make the test assertions shorter
input('query').enter('tablet');
expect(repeater('.phones li', 'Phone List').column('a')).
toEqual(["Motorola XOOM\u2122 with Wi-Fi",
"MOTOROLA XOOM\u2122"]);
select('orderProp').option('alphabetical');
expect(repeater('.phones li', 'Phone List').column('a')).
toEqual(["MOTOROLA XOOM\u2122",
"Motorola XOOM\u2122 with Wi-Fi"]);
});
...
</pre>
The end-to-end test verifies that the ordering mechanism of the select box is working correctly.
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-4/test/e2e/runner.html
Angular's server}.
# Experiments
* In the `PhoneListCtrl` controller, remove the statement that sets the `orderProp` value and
you'll see that the ordering as well as the current selection in the dropdown menu will default to
"Alphabetical".
* Add an `{{orderProp}}` binding into the `index.html` template to display its current value as
text.
# 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.
<ul doc:tutorial-nav="4"></ul>
-216
View File
@@ -1,216 +0,0 @@
@ngdoc overview
@name Tutorial: 5 - XHRs & Dependency Injection
@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 api/angular.service services} called {@link
api/angular.service.$xhr $xhr}. We will use angular's {@link guide/dev_guide.di dependency
injection (DI)} to provide the service to the `PhoneListCtrl` controller.
<doc:tutorial-instructions step="5"></doc:tutorial-instructions>
You should now see a list of 20 phones.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-4...step-5
GitHub}:
## Data
The `app/phones/phone.json` file in your project is a dataset that contains a larger list of phones
stored in the JSON format.
Following is a sample of the file:
<pre>
[
{
"age": 13,
"id": "motorola-defy-with-motoblur",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
"snippet": "Are you ready for everything life throws your way?"
...
},
...
]
</pre>
## Controller
We'll use angular's {@link api/angular.service.$xhr $xhr} service in our controller to make an HTTP
request to your web server to fetch the data in the `app/phones/phones.json` file. `$xhr` is just
one of several built-in {@link api/angular.service angular services} that handle common operations
in web apps. Angular injects these services for you where you need them.
Services are managed by angular's {@link guide/dev_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).
__`app/js/controllers.js:`__
<pre>
function PhoneListCtrl($xhr) {
var self = this;
$xhr('GET', 'phones/phones.json', function(code, response) {
self.phones = response;
});
self.orderProp = 'age';
}
//PhoneListCtrl.$inject = ['$xhr'];
</pre>
`$xhr` makes an HTTP GET request to our web server, asking for `phone/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 `$xhr` service takes a callback as the last argument. This callback is used to process the
response. We assign the response to the scope controlled by the 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 services you need as arguments to
the controller's constructor function, as follows:
function PhoneListCtrl($xhr) {...}
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).
<img src="img/tutorial/xhr_service_final.png">
### '$' 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. Don't use a '$' prefix when naming your services and models, in order
to avoid any possible naming collisions.
### 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 {@link http://en.wikipedia.org/wiki/Minification_(programming)
minify} the JavaScript code for `PhoneListCtrl` controller, all of its function arguments would be
minified as well, and the dependency injector would not being able to identify services correctly.
To overcome issues caused by minification, just assign an array with service identifier strings
into the `$inject` property of the controller function, just like the last line in the snippet
(commented out) suggests:
PhoneListCtrl.$inject = ['$xhr'];
## 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 `$xhr` implementation. However, the recommended (and easier) way
is to create a controller in the test environment in the same way that angular does it in the
production code behind the scenes, as follows:
<pre>
describe('PhoneCat controllers', function() {
describe('PhoneListCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
$browser.xhr.expectGET('phones/phones.json')
.respond([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
ctrl = scope.$new(PhoneListCtrl);
});
});
</pre>
We created the controller in the test environment, as follows:
* We created a root scope object by calling `angular.scope()`
* We called `scope.$new(PhoneListCtrl)` to get angular to create the child scope associated with
the `PhoneListCtrl` controller
Because our code now uses the `$xhr` 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:
* Use the {@link api/angular.scope.$service `$service`} method to retrieve the `$browser` service,
a service that angular uses to represent various browser APIs. In tests, angular automatically uses
a mock version of this service that allows you to write tests without having to deal with these
native APIs and the global state associated with them.
* Use the `$browser.xhr.expectGET` method to train the `$browser` object to expect an incoming HTTP
request and tell it what to respond with. Note that the responses are not returned before we call
the `$browser.xhr.flush` method.
Now, we will make assertions to verify that the `phones` model doesn't exist on the scope, before
the response is received:
<pre>
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(ctrl.phones).toBeUndefined();
$browser.xhr.flush();
expect(ctrl.phones).toEqual([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
</pre>
* We flush the xhr queue in the browser by calling `$browser.xhr.flush()`. This causes the callback
we passed into the `$xhr` service to be executed with the trained response.
* 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:
<pre>
it('should set the default value of orderProp model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
});
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
..
Total 2 tests (Passed: 2; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 2 tests (Passed: 2; Fails: 0; Errors 0) (3.00 ms)
# Experiments
* At the bottom of `index.html`, add a `{{phones}}` binding to see the list of phones displayed in
json format.
* In the `PhoneListCtrl` controller, pre-process the xhr response by limiting the number of phones
to the first 5 in the list. Use the following code in the xhr callback:
self.phones = response.splice(0, 5);
# Summary
Now that you have learned how easy it is to use angular services (thanks to angular's
implementation of dependency injection), go to {@link step_06 step 6}, where you will add some
thumbnail images of phones and some links.
<ul doc:tutorial-nav="5"></ul>
-105
View File
@@ -1,105 +0,0 @@
@ngdoc overview
@name Tutorial: 6 - Templating Links & Images
@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.
<doc:tutorial-instructions step="6"></doc:tutorial-instructions>
You should now see links and images of the phones in the list.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-5...step-6
GitHub}:
## 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.
__`app/phones/phones.json`__ (sample snippet):
<pre>
[
{
...
"id": "motorola-defy-with-motoblur",
"imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
"name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
...
},
...
]
</pre>
## Template
__`app/index.html`:__
<pre>
...
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
<p>{{phone.snippet}}</p>
</li>
</ul>
...
</pre>
To dynamically generate links that will in the future lead to phone detail pages, we used the
now-familiar {@link guide/dev_guide.compiler.markup double-curly brace markup} 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
api/angular.directive.ng:src ng:src} directive. That directive prevents the browser from treating
the angular `{{ expression }}` markup literally, which it would have done if we had only specified
an attribute binding in a regular `src` attribute (`<img src="{{phone.imageUrl}}">`). Using
`ng:src` prevents the browser from making an http request to an invalid location.
## Test
__`test/e2e/scenarios.js`__:
<pre>
...
it('should render phone specific links', function() {
input('query').enter('nexus');
element('.phones li a').click();
expect(browser().location().hash()).toBe('/phones/nexus-s');
});
...
</pre>
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.
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-6/test/e2e/runner.html
angular's server}.
# 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/index.html/{{phone.imageUrl}}`).
# 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.
<ul doc:tutorial-nav="6"></ul>
-209
View File
@@ -1,209 +0,0 @@
@ngdoc overview
@name Tutorial: 7 - Routing & Multiple Views
@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.
<doc:tutorial-instructions step="7"></doc:tutorial-instructions>
Note that you are redirected to `app/index.html#/phones` and the same phone list appears in the
browser. When you click on a phone link the stub of a phone detail page is displayed.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-6...step-7
GitHub}:
## 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 api/angular.service.$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 {@link
http://en.wikipedia.org/wiki/Deep_linking deep linking}, which lets us utilize the browser's
history (back and forward navigation) and bookmarks.
## Controllers
__`app/js/controller.js`:__
<pre>
function PhoneCatCtrl($route) {
var self = this;
$route.when('/phones',
{template: 'partials/phone-list.html', controller: PhoneListCtrl});
$route.when('/phones/:phoneId',
{template: 'partials/phone-detail.html', controller: PhoneDetailCtrl});
$route.otherwise({redirectTo: '/phones'});
$route.onChange(function() {
self.params = $route.current.params;
});
$route.parent(this);
}
//PhoneCatCtrl.$inject = ['$route'];
...
</pre>
We created a new controller called `PhoneCatCtrl`. We declared its dependency on the `$route`
service and used this service to declare that our application consists of two different views:
* The phone list view will be shown when the URL hash fragment is `/phones`. To construct this
view, angular will use the `phone-list.html` template and the `PhoneListCtrl` controller.
* The phone details view will be shown when the URL hash fragment matches '/phone/:phoneId', where
`:phoneId` is a variable part of the URL. To construct the phone details view, angular will use the
`phone-detail.html` template and the `PhoneDetailCtrl` controller.
We reused the `PhoneListCtrl` controller that we constructed in previous steps and we added a new,
empty `PhoneDetailCtrl` controller to the `app/js/controllers.js` file for the phone details view.
The statement `$route.otherwise({redirectTo: '/phones'})` triggers a redirection to `/phones` when
the browser address doesn't match either of our routes.
Thanks to the `$route.parent(this);` statement and `ng:controller="PhoneCatCtrl"` declaration in
the `index.html` template, the `PhoneCatCtrl` controller has a special role in our app. It is the
"root" controller and the parent controller for the other two sub-controllers (`PhoneListCtrl` and
`PhoneDetailCtrl`). The sub-controllers inherit the model properties and behavior from the root
controller.
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 `$route.current.params` map.
The `params` alias created in the {@link api/angular.service.$route `$route.onChange`} callback
allows us to use the `phoneId` property of this map in the `phone-details.html` template.
## Template
The `$route` service is usually used in conjunction with the {@link api/angular.widget.ng:view
ng:view} widget. The role of the `ng:view` widget is to include the view template for the current
route into the layout template, which makes it a perfect fit for our `index.html` template.
__`app/index.html`:__
<pre>
...
<body ng:controller="PhoneCatCtrl">
<ng:view></ng:view>
<script src="lib/angular/angular.js" ng:autobind></script>
<script src="js/controllers.js"></script>
</body>
</html>
</pre>
Note that we removed most of the code in the `index.html` template and replaced it with a single
line containing the `ng:view` tag. The code that we removed was placed into the `phone-list.html`
template:
__`app/partials/phone-list.html`:__
<pre>
<ul class="predicates">
<li>
Search: <input type="text" name="query"/>
</li>
<li>
Sort by:
<select name="orderProp">
<option value="name">Alphabetical</option>
<option value="age">Newest</option>
</select>
</li>
</ul>
<ul class="phones">
<li ng:repeat="phone in phones.$filter(query).$orderBy(orderProp)">
<a href="#/phones/{{phone.id}}">{{phone.name}}</a>
<a href="#/phones/{{phone.id}}" class="thumb"><img ng:src="{{phone.imageUrl}}"></a>
<p>{{phone.snippet}}</p>
</li>
</ul>
</pre>
<img src="img/tutorial/tutorial_07_final.png">
We also added a placeholder template for the phone details view:
__`app/partials/phone-detail.html`:__
<pre>
TBD: detail view for {{params.phoneId}}
</pre>
Note how we are using `params` model defined in the `PhoneCatCtrl` controller.
## 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.
<pre>
...
it('should redirect index.html to index.html#/phones', function() {
browser().navigateTo('../../app/index.html');
expect(browser().location().hash()).toBe('/phones');
});
...
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display placeholder page with phoneId', function() {
expect(binding('params.phoneId')).toBe('nexus-s');
});
});
</pre>
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-7/test/e2e/runner.html
angular's server}.
# 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 `<ng:view>` element. If you add the
same binding into the `phone-list.html` template, the binding will work as expected.
* In `PhoneCatCtrl`, create a new model called "`hero`" with `this.hero = 'Zoro'`. In
`PhoneListCtrl` let's shadow it with `this.hero = 'Batman'`, and 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.
# 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.
<ul doc:tutorial-nav="7"></ul>
-188
View File
@@ -1,188 +0,0 @@
@ngdoc overview
@name Tutorial: 8 - More Templating
@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.
<doc:tutorial-instructions step="8"></doc:tutorial-instructions>
Now when you click on a phone on the list, the phone details page with phone-specific information
is displayed.
To implement the phone details view we will use {@link api/angular.service.$xhr $xhr} to fetch our
data, and we'll flesh out the `phone-details.html` view template.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-7...step-8
GitHub}:
## Data
In addition to `phones.json`, the `app/phones/` directory also contains one json file for each
phone:
__`app/phones/nexus-s.json`:__ (sample snippet)
<pre>
{
"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"
}
}
</pre>
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.
## Controller
We'll expand the `PhoneDetailCtrl` by using the `$xhr` service to fetch the json files. This works
the same way as the phone list controller.
__`app/js/controller.js`:__
<pre>
function PhoneDetailCtrl($xhr) {
var self = this;
$xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
self.phone = response;
});
}
//PhoneDetailCtrl.$inject = ['$xhr'];
</pre>
To construct the URL for the HTTP request, we use `params.phoneId` extracted from the current route
in the `PhoneCatCtrl` controller.
## 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 `ng:repeater`s to project phone data from
our model into the view.
__`app/partials/phone-details.html`:__
<pre>
<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}}"/>
</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>
...
</li>
<span>Additional Features</span>
<dd>{{phone.additionalFeatures}}</dd>
</li>
</ul>
</pre>
<img src="img/tutorial/tutorial_08-09_final.png">
## Test
We wrote a new unit test that is similar to the one we wrote for the `PhoneListCtrl` controller in
step 5.
__`test/unit/controllerSpec.js`:__
<pre>
...
it('should fetch phone detail', function() {
scope.params = {phoneId:'xyz'};
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
ctrl = scope.$new(PhoneDetailCtrl);
expect(ctrl.phone).toBeUndefined();
$browser.xhr.flush();
expect(ctrl.phone).toEqual({name:'phone xyz'});
});
...
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
...
Total 3 tests (Passed: 3; Fails: 0; Errors: 0) (5.00 ms)
Chrome 11.0.696.57 Mac OS: Run 3 tests (Passed: 3; Fails: 0; Errors 0) (5.00 ms)
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`:__
<pre>
...
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display nexus-s page', function() {
expect(binding('phone.name')).toBe('Nexus S');
});
});
...
</pre>
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html
angular's server}.
# Experiments
* Using the {@link
https://docs.google.com/document/d/11L8htLKrh6c92foV71ytYpiKkeKpM4_a5-9c3HywfIc/edit?hl=en&pli=1#
end-to-end test runner API}, write a test that verifies that we display 4 thumbnail images on the
Nexus S details page.
# 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.
<ul doc:tutorial-nav="8"></ul>
-121
View File
@@ -1,121 +0,0 @@
@ngdoc overview
@name Tutorial: 9 - Filters
@description
<ul doc:tutorial-nav="9"></ul>
In this step you will learn how to create your own custom display filter.
<doc:tutorial-instructions step="9"></doc:tutorial-instructions>
Navigate to one of the detail pages.
In the previous step, the details page displayed either "true" or "false" to indicate whether
certain phone features were present or not. We have used a custom filter to convert those text
strings into glyphs: ✓ for "true", and ✘ for "false". Let's see, what the filter code looks like.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-8...step-9
GitHub}:
## Custom Filter
In order to create a new filter, simply register your custom filter function with the {@link
api/angular.filter `angular.filter`} API.
__`app/js/filters.js`:__
<pre>
angular.filter('checkmark', function(input) {
return input ? '\u2713' : '\u2718';
});
</pre>
The name of our filter is "checkmark". The `input` evaluates to either `true` or `false`, and we
return one of two unicode characters we have chosen to represent true or false (`\u2713` and
`\u2718`).
## Template
Since the filter code lives in the `app/js/filters.js` file, we need to include this file in our
layout template.
__`app/index.html`:__
<pre>
...
<script src="js/controllers.js"></script>
<script src="js/filters.js"></script>
...
</pre>
The syntax for using filters in angular templates is as follows:
{{ expression | filter }}
Let's employ the filter in the phone details template:
__`app/partials/phone-detail.html`:__
<pre>
...
<dl>
<dt>Infrared</dt>
<dd>{{phone.connectivity.infrared | checkmark}}</dd>
<dt>GPS</dt>
<dd>{{phone.connectivity.gps | checkmark}}</dd>
</dl>
...
</pre>
## Test
Filters, like any other component, should be tested and these tests are very easy to write.
__`test/unit/filtersSpec.js`:__
<pre>
describe('checkmark filter', function() {
it('should convert boolean values to unicode checkmark or cross', function() {
expect(angular.filter.checkmark(true)).toBe('\u2713');
expect(angular.filter.checkmark(false)).toBe('\u2718');
});
})
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
....
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
# Experiments
* Let's experiment with some of the {@link api/angular.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" }}`
* We can also create a model with an input element, and combine it with a filtered binding. Add
the following to index.html:
<input name="userInput"> Uppercased: {{ userInput | uppercase }}
# 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.
<ul doc:tutorial-nav="9"></ul>
-140
View File
@@ -1,140 +0,0 @@
@ngdoc overview
@name Tutorial: 10 - Event Handlers
@description
<ul doc:tutorial-nav="10"></ul>
In this step, you will add a clickable phone image swapper to the phone details page.
<doc:tutorial-instructions step="10"></doc:tutorial-instructions>
The phone details view displays one large image of the current phone and several smaller thumbnail
images. It would be great if we could replace the large image with any of the thumbnails just by
clicking on the desired thumbnail image. Let's have a look at how we can do this with angular.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-9...step-10
GitHub}:
## Controller
__`app/js/controllers.js`:__
<pre>
...
function PhoneDetailCtrl($xhr) {
var self = this;
$xhr('GET', 'phones/' + self.params.phoneId + '.json', function(code, response) {
self.phone = response;
self.mainImageUrl = response.images[0];
});
self.setImage = function(imageUrl) {
self.mainImageUrl = imageUrl;
}
}
//PhoneDetailCtrl.$inject = ['$xhr'];
</pre>
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` controller method to change the value of `mainImageUrl`.
## Template
__`app/partials/phone-detail.html`:__
<pre>
<img ng:src="{{mainImageUrl}}" class="phone"/>
...
<ul class="phone-thumbs">
<li ng:repeat="img in phone.images">
<img ng:src="{{img}}" ng:click="setImage(img)">
</li>
</ul>
...
</pre>
We bound the `ng:src` attribute of the large image to the `mainImageUrl` property.
We also registered an {@link api/angular.directive.ng:click `ng:click`} handler with thumbnail
images. When a user clicks on one of the thumbnail images, the handler will use the `setImage`
controller method to change the value of the `mainImageUrl` property to the url of the thumbnail
image.
<img src="img/tutorial/tutorial_10-11_final.png">
## Test
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`:__
<pre>
...
describe('Phone detail view', function() {
beforeEach(function() {
browser().navigateTo('../../app/index.html#/phones/nexus-s');
});
it('should display the first phone image as the main phone image', function() {
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
it('should swap main image if a thumbnail image is clicked on', function() {
element('.phone-thumbs li:nth-child(3) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.2.jpg');
element('.phone-thumbs li:nth-child(1) img').click();
expect(element('img.phone').attr('src')).toBe('img/phones/nexus-s.0.jpg');
});
});
});
</pre>
You can now refresh the browser tab with the end-to-end test runner to see the tests run, or you
can see them running on {@link
http://angular.github.com/angular-phonecat/step-8/test/e2e/runner.html
angular's server}.
# Experiments
* Let's add a new controller method to `PhoneCatCtrl`:
this.hello = function(name) {
alert('Hello ' + (name || 'world') + '!');
}
and add:
<button ng:click="hello('Elmo')">Hello</button>
to the `index.html` template.
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.
# Summary
With the phone image swapper in place, we're ready for {@link step_11 step 11} (the last step!) to
learn an even better way to fetch data.
<ul doc:tutorial-nav="10"></ul>
-208
View File
@@ -1,208 +0,0 @@
@ngdoc overview
@name Tutorial: 11 - REST and Custom Services
@description
<ul doc:tutorial-nav="11"></ul>
In this step, you will improve the way our app fetches data.
<doc:tutorial-instructions step="11"></doc:tutorial-instructions>
The last improvement we will make to our app is to define a custom service that represents a {@link
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client. Using this client we
can make xhr requests for data in an easier way, without having to deal with the lower-level {@link
api/angular.service.$xhr $xhr} API, HTTP methods and URLs.
The most important changes are listed below. You can see the full diff on {@link
https://github.com/angular/angular-phonecat/compare/step-10...step-11
GitHub}:
## Template
The custom service is defined in `app/js/services.js` so we need to include this file in our layout
template:
__`app/index.html`.__
<pre>
...
<script src="js/services.js"></script>
...
</pre>
## Service
__`app/js/services.js`.__
<pre>
angular.service('Phone', function($resource) {
return $resource('phones/:phoneId.json', {}, {
query: {method: 'GET', params: {phoneId: 'phones'}, isArray: true}
});
});
</pre>
We used the {@link api/angular.service} API to register a custom service. We passed in the name of
the service - 'Phone' - and a factory function. The factory function is similar to a controller's
constructor in that both can declare dependencies via function arguments. The Phone service
declared a dependency on the `$resource` service.
The {@link api/angular.service.$resource `$resource`} service makes it easy to create a {@link
http://en.wikipedia.org/wiki/Representational_State_Transfer RESTful} client with just a few lines
of code. This client can then be used in our application, instead of the lower-level {@link
api/angular.service.$xhr $xhr} service.
## Controller
We simplified our sub-controllers (`PhoneListCtrl` and `PhoneDetailCtrl`) by factoring out the
lower-level {@link api/angular.service.$xhr $xhr} service, replacing it with a new service called
`Phone`. Angular's {@link api/angular.service.$resource `$resource`} service is easier to use than
{@link api/angular.service.$xhr $xhr} 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`.__
<pre>
...
function PhoneListCtrl(Phone) {
this.orderProp = 'age';
this.phones = Phone.query();
}
//PhoneListCtrl.$inject = ['Phone'];
function PhoneDetailCtrl(Phone) {
var self = this;
self.phone = Phone.get({phoneId: self.params.phoneId}, function(phone) {
self.mainImageUrl = phone.images[0];
});
...
}
//PhoneDetailCtrl.$inject = ['Phone'];
</pre>
Notice how in `PhoneListCtrl` we replaced:
$xhr('GET', 'phones/phones.json', function(code, response) {
self.phones = response;
});
with:
this.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
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 api/angular.service.$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` {@link
http://pivotal.github.com/jasmine/jsdoc/symbols/jasmine.Matchers.html Jasmine matcher}. When the
`toEqualData` matcher compares two objects, it takes only object properties into account and
ignores methods.
__`test/unit/controllersSpec.js`:__
<pre>
describe('PhoneCat controllers', function() {
beforeEach(function(){
this.addMatchers({
toEqualData: function(expected) {
return angular.equals(this.actual, expected);
}
});
});
describe('PhoneListCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
$browser.xhr.expectGET('phones/phones.json')
.respond([{name: 'Nexus S'}, {name: 'Motorola DROID'}]);
ctrl = scope.$new(PhoneListCtrl);
});
it('should create "phones" model with 2 phones fetched from xhr', function() {
expect(ctrl.phones).toEqual([]);
$browser.xhr.flush();
expect(ctrl.phones).toEqualData([{name: 'Nexus S'},
{name: 'Motorola DROID'}]);
});
it('should set the default value of orderProp model', function() {
expect(ctrl.orderProp).toBe('age');
});
});
describe('PhoneDetailCtrl', function() {
var scope, $browser, ctrl;
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
});
beforeEach(function() {
scope = angular.scope();
$browser = scope.$service('$browser');
});
it('should fetch phone detail', function() {
scope.params = {phoneId:'xyz'};
$browser.xhr.expectGET('phones/xyz.json').respond({name:'phone xyz'});
ctrl = scope.$new(PhoneDetailCtrl);
expect(ctrl.phone).toEqualData({});
$browser.xhr.flush();
expect(ctrl.phone).toEqualData({name:'phone xyz'});
});
});
});
</pre>
To run the unit tests, execute the `./scripts/test.sh` script and you should see the following
output.
Chrome: Runner reset.
....
Total 4 tests (Passed: 4; Fails: 0; Errors: 0) (3.00 ms)
Chrome 11.0.696.57 Mac OS: Run 4 tests (Passed: 4; Fails: 0; Errors 0) (3.00 ms)
# 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 were to go from here.
<ul doc:tutorial-nav="11"></ul>
-21
View File
@@ -1,21 +0,0 @@
@ngdoc overview
@name Tutorial: The End
@description
Our application is now complete. Feel free to experiment with the code further, and jump back to
previous steps using the `git checkout` or `goto_step.sh` commands.
For more details and examples of the angular concepts we touched on in this tutorial, see the
{@link guide/ Developer Guide}.
For several more examples of code, see the {@link cookbook/ Cookbook}.
When you are ready to start developing a project using angular, we recommend that you bootstrap
your development with the {@link https://github.com/angular/angular-seed angular seed} project.
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.
If you have questions or feedback or just want to say "hi", please post a message at {@link
https://groups.google.com/forum/#!forum/angular}.
+1
View File
@@ -0,0 +1 @@
Hello, $http!
+4 -4
View File
@@ -1,13 +1,13 @@
<label>Name:</label>
<input type="text" name="form.name" ng:required>
<input type="text" ng:model="form.name" required>
<div ng:repeat="contact in form.contacts">
<select name="contact.type">
<select ng:model="contact.type">
<option>url</option>
<option>email</option>
<option>phone</option>
</select>
<input type="text" name="contact.url">
<input type="text" ng:model="contact.url">
[ <a href="" ng:click="form.contacts.$remove(contact)">X</a> ]
</div>
<div>
@@ -15,4 +15,4 @@
</div>
<button ng:click="cancel()">Cancel</button>
<button ng:click="save()">Save</button>
<button ng:click="save()">Save</button>
Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

+8 -8
View File
@@ -1,15 +1,15 @@
var DOM = require('dom.js').DOM;
var DOM = require('../src/dom.js').DOM;
describe('dom', function(){
describe('dom', function() {
var dom;
beforeEach(function(){
beforeEach(function() {
dom = new DOM();
});
describe('h', function(){
describe('h', function() {
it('should render using function', function(){
it('should render using function', function() {
var cbThis;
var cdValue;
dom.h('heading', 'content', function(value){
@@ -20,11 +20,11 @@ describe('dom', function(){
expect(cbValue).toEqual('content');
});
it('should update heading numbers', function(){
dom.h('heading', function(){
it('should update heading numbers', function() {
dom.h('heading', function() {
this.html('<h1>sub-heading</h1>');
});
expect(dom.toString()).toContain('<h1>heading</h1>');
expect(dom.toString()).toContain('<h1 id="heading">heading</h1>');
expect(dom.toString()).toContain('<h2>sub-heading</h2>');
});
+94 -117
View File
@@ -1,11 +1,11 @@
var ngdoc = require('ngdoc.js');
var DOM = require('dom.js').DOM;
var ngdoc = require('../src/ngdoc.js');
var DOM = require('../src/dom.js').DOM;
describe('ngdoc', function(){
describe('ngdoc', function() {
var Doc = ngdoc.Doc;
var dom;
beforeEach(function(){
beforeEach(function() {
dom = new DOM();
this.addMatchers({
toContain: function(text) {
@@ -15,15 +15,15 @@ describe('ngdoc', function(){
});
});
describe('Doc', function(){
describe('metadata', function(){
describe('Doc', function() {
describe('metadata', function() {
it('should find keywords', function(){
it('should find keywords', function() {
expect(new Doc('\nHello: World! @ignore. $abc').keywords()).toEqual('$abc hello world');
expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the');
});
it('should have shortName', function(){
it('should have shortName', function() {
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
@@ -32,7 +32,7 @@ describe('ngdoc', function(){
expect(ngdoc.metadata([d3])[0].shortName).toEqual('more text');
});
it('should have depth information', function(){
it('should have depth information', function() {
var d1 = new Doc('@name a.b.c').parse();
var d2 = new Doc('@name a.b.ng:c').parse();
var d3 = new Doc('@name some text: more text').parse();
@@ -43,8 +43,8 @@ describe('ngdoc', function(){
});
describe('parse', function(){
it('should convert @names into properties', function(){
describe('parse', function() {
it('should convert @names into properties', function() {
var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n');
doc.parse();
expect(doc.name).toEqual('name');
@@ -52,8 +52,9 @@ describe('ngdoc', function(){
expect(doc.dep).toEqual('');
});
it('should parse parameters', function(){
it('should parse parameters', function() {
var doc = new Doc(
'@name a\n' +
'@param {*} a short\n' +
'@param {Type} b med\n' +
'@param {Class=} [c=2] long\nline');
@@ -65,8 +66,8 @@ describe('ngdoc', function(){
]);
});
it('should parse return', function(){
var doc = new Doc('@returns {Type} text *bold*.');
it('should parse return', function() {
var doc = new Doc('@name a\n@returns {Type} text *bold*.');
doc.parse();
expect(doc.returns).toEqual({
type: 'Type',
@@ -74,31 +75,39 @@ describe('ngdoc', function(){
});
});
it('should parse filename', function(){
it('should parse filename', function() {
var doc = new Doc('@name friendly name', 'docs/a.b.ngdoc', 1);
doc.parse(0);
expect(doc.id).toEqual('a.b');
expect(doc.name).toEqual('friendly name');
});
it('should escape <doc:source> element', function(){
var doc = new Doc('@description before <doc:example>' +
it('should escape <doc:source> element', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:source>\n<>\n</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source">\n&lt;&gt;\n</pre></doc:example><p>after</p>');
});
it('should preserve the jsfiddle attribute', function(){
var doc = new Doc('@description before <doc:example>' +
it('should preserve the source attribute', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:source source="false">lala</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source" source="false">lala</pre></doc:example><p>after</p>');
});
it('should preserve the jsfiddle attribute', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:source jsfiddle="foo">lala</doc:source></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
'<pre class="doc-source" jsfiddle="foo">lala</pre></doc:example><p>after</p>');
});
it('should escape <doc:scenario> element', function(){
var doc = new Doc('@description before <doc:example>' +
it('should escape <doc:scenario> element', function() {
var doc = new Doc('@name a\n@description before <doc:example>' +
'<doc:scenario>\n<>\n</doc:scenario></doc:example> after');
doc.parse();
expect(doc.description).toContain('<p>before </p><doc:example>' +
@@ -106,7 +115,7 @@ describe('ngdoc', function(){
});
it('should store all links', function() {
var doc = new Doc('@description {@link api/angular.link}');
var doc = new Doc('@name a\n@description {@link api/angular.link}');
doc.parse();
expect(doc.links).toContain('api/angular.link');
@@ -132,11 +141,11 @@ describe('ngdoc', function(){
});
});
describe('sorting', function(){
describe('sorting', function() {
function property(name) {
return function(obj) {return obj[name];};
}
function noop(){}
function noop() {}
function doc(type, name){
return {
id: name,
@@ -149,7 +158,7 @@ describe('ngdoc', function(){
var angular_x = doc('function', 'angular.x');
var angular_y = doc('property', 'angular.y');
it('should put angular.fn() in front of angular.widget, etc', function(){
it('should put angular.fn() in front of angular.widget, etc', function() {
expect(ngdoc.metadata([angular_widget, angular_y, angular_x]).map(property('id')))
.toEqual(['angular.x', 'angular.y', 'angular.widget' ]);
});
@@ -157,13 +166,13 @@ describe('ngdoc', function(){
});
});
describe('markdown', function(){
it('should replace angular in markdown', function(){
describe('markdown', function() {
it('should replace angular in markdown', function() {
expect(new Doc().markdown('<angular/>')).
toEqual('<p><tt>&lt;angular/&gt;</tt></p>');
});
it('should not replace anything in <pre>, but escape the html escape the content', function(){
it('should not replace anything in <pre>, but escape the html escape the content', function() {
expect(new Doc().markdown('bah x\n<pre>\n<b>angular</b>.k\n</pre>\n asdf x')).
toEqual(
'<p>bah x</p>' +
@@ -194,12 +203,12 @@ describe('ngdoc', function(){
it('should ignore nested doc widgets', function() {
expect(new Doc().markdown(
'before<doc:tutorial-instructions>\n' +
'<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">' +
'<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">' +
'\ngit bla bla\n</doc:tutorial-instruction>\n' +
'</doc:tutorial-instructions>')).toEqual(
'<p>before</p><doc:tutorial-instructions>\n' +
'<doc:tutorial-instruction id="git-mac" name="Git on Mac/Linux">\n' +
'<doc:tutorial-instruction id="git-mac" ng:model="Git on Mac/Linux">\n' +
'git bla bla\n' +
'</doc:tutorial-instruction>\n' +
'</doc:tutorial-instructions>');
@@ -234,30 +243,30 @@ describe('ngdoc', function(){
});
describe('trim', function(){
describe('trim', function() {
var trim = ngdoc.trim;
it('should remove leading/trailing space', function(){
it('should remove leading/trailing space', function() {
expect(trim(' \nabc\n ')).toEqual('abc');
});
it('should remove leading space on every line', function(){
it('should remove leading space on every line', function() {
expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3');
});
});
describe('merge', function(){
it('should merge child with parent', function(){
var parent = new Doc({id: 'angular.service.abc', name: 'angular.service.abc', section: 'api'});
var methodA = new Doc({name: 'methodA', methodOf: 'angular.service.abc'});
var methodB = new Doc({name: 'methodB', methodOf: 'angular.service.abc'});
var propA = new Doc({name: 'propA', propertyOf: 'angular.service.abc'});
var propB = new Doc({name: 'propB', propertyOf: 'angular.service.abc'});
var eventA = new Doc({name: 'eventA', eventOf: 'angular.service.abc'});
var eventB = new Doc({name: 'eventB', eventOf: 'angular.service.abc'});
describe('merge', function() {
it('should merge child with parent', function() {
var parent = new Doc({id: 'angular.module.ng.abc', name: 'angular.module.ng.abc', section: 'api'});
var methodA = new Doc({name: 'methodA', methodOf: 'angular.module.ng.abc'});
var methodB = new Doc({name: 'methodB', methodOf: 'angular.module.ng.abc'});
var propA = new Doc({name: 'propA', propertyOf: 'angular.module.ng.abc'});
var propB = new Doc({name: 'propB', propertyOf: 'angular.module.ng.abc'});
var eventA = new Doc({name: 'eventA', eventOf: 'angular.module.ng.abc'});
var eventB = new Doc({name: 'eventB', eventOf: 'angular.module.ng.abc'});
var docs = [methodB, methodA, eventB, eventA, propA, propB, parent]; // keep wrong order;
ngdoc.merge(docs);
expect(docs.length).toEqual(1);
expect(docs[0].id).toEqual('angular.service.abc');
expect(docs[0].id).toEqual('angular.module.ng.abc');
expect(docs[0].methods).toEqual([methodA, methodB]);
expect(docs[0].events).toEqual([eventA, eventB]);
expect(docs[0].properties).toEqual([propA, propB]);
@@ -294,10 +303,10 @@ describe('ngdoc', function(){
////////////////////////////////////////
describe('TAG', function(){
describe('@param', function(){
it('should parse with no default', function(){
var doc = new Doc('@param {(number|string)} number Number \n to format.');
describe('TAG', function() {
describe('@param', function() {
it('should parse with no default', function() {
var doc = new Doc('@name a\n@param {(number|string)} number Number \n to format.');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
@@ -307,8 +316,8 @@ describe('ngdoc', function(){
description : '<p>Number \nto format.</p>' }]);
});
it('should parse with default and optional', function(){
var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc');
it('should parse with default and optional', function() {
var doc = new Doc('@name a\n@param {(number|string)=} [fractionSize=2] desc');
doc.parse();
expect(doc.param).toEqual([{
type : '(number|string)',
@@ -321,14 +330,14 @@ describe('ngdoc', function(){
describe('@requires', function() {
it('should parse more @requires tag into array', function() {
var doc = new Doc('@requires $service for \n`A`\n@requires $another for `B`');
var doc = new Doc('@name a\n@requires $service for \n`A`\n@requires $another for `B`');
doc.ngdoc = 'service';
doc.parse();
expect(doc.requires).toEqual([
{name:'$service', text:'<p>for \n<code>A</code></p>'},
{name:'$another', text:'<p>for <code>B</code></p>'}]);
expect(doc.html()).toContain('<a href="api/angular.service.$service">$service</a>');
expect(doc.html()).toContain('<a href="api/angular.service.$another">$another</a>');
expect(doc.html()).toContain('<a href="api/angular.module.ng.$service">$service</a>');
expect(doc.html()).toContain('<a href="api/angular.module.ng.$another">$another</a>');
expect(doc.html()).toContain('<p>for \n<code>A</code></p>');
expect(doc.html()).toContain('<p>for <code>B</code></p>');
});
@@ -336,7 +345,7 @@ describe('ngdoc', function(){
describe('@property', function() {
it('should parse @property tags into array', function() {
var doc = new Doc("@property {type} name1 desc\n@property {type} name2 desc");
var doc = new Doc("@name a\n@property {type} name1 desc\n@property {type} name2 desc");
doc.parse();
expect(doc.properties.length).toEqual(2);
});
@@ -348,21 +357,21 @@ describe('ngdoc', function(){
});
it('should parse @property with type', function() {
var doc = new Doc("@property {string} name");
var doc = new Doc("@name a\n@property {string} name");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].type).toEqual('string');
});
it('should parse @property with optional description', function() {
var doc = new Doc("@property {string} name desc rip tion");
var doc = new Doc("@name a\n@property {string} name desc rip tion");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].description).toEqual('<p>desc rip tion</p>');
});
it('should parse @property with type and description both', function() {
var doc = new Doc("@property {bool} name desc rip tion");
var doc = new Doc("@name a\n@property {bool} name desc rip tion");
doc.parse();
expect(doc.properties[0].name).toEqual('name');
expect(doc.properties[0].type).toEqual('bool');
@@ -378,35 +387,35 @@ describe('ngdoc', function(){
});
it('should parse @returns with type and description', function() {
var doc = new Doc("@returns {string} descrip tion");
var doc = new Doc("@name a\n@returns {string} descrip tion");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip tion</p>'});
});
it('should transform description of @returns with markdown', function() {
var doc = new Doc("@returns {string} descrip *tion*");
var doc = new Doc("@name a\n@returns {string} descrip *tion*");
doc.parse();
expect(doc.returns).toEqual({type: 'string', description: '<p>descrip <em>tion</em></p>'});
});
it('should support multiline content', function() {
var doc = new Doc("@returns {string} description\n new line\n another line");
var doc = new Doc("@name a\n@returns {string} description\n new line\n another line");
doc.parse();
expect(doc.returns).
toEqual({type: 'string', description: '<p>description\nnew line\nanother line</p>'});
});
});
describe('@description', function(){
it('should support pre blocks', function(){
var doc = new Doc("@description <pre><b>abc</b></pre>");
describe('@description', function() {
it('should support pre blocks', function() {
var doc = new Doc("@name a\n@description <pre><b>abc</b></pre>");
doc.parse();
expect(doc.description).
toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">&lt;b&gt;abc&lt;/b&gt;</pre></div>');
});
it('should support multiple pre blocks', function() {
var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
var doc = new Doc("@name a\n@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>");
doc.parse();
expect(doc.description).
toBe('<p>foo </p>' +
@@ -418,7 +427,7 @@ describe('ngdoc', function(){
});
it('should support nested @link annotations with or without description', function() {
var doc = new Doc("@description " +
var doc = new Doc("@name a\n@description " +
'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' +
'dad{@link angular.foo}\n\n' +
'external{@link http://angularjs.org}\n\n' +
@@ -442,8 +451,8 @@ describe('ngdoc', function(){
toContain('<a href="./static.html">./static.html</a>');
});
it('should support line breaks in @link', function(){
var doc = new Doc("@description " +
it('should support line breaks in @link', function() {
var doc = new Doc("@name a\n@description " +
'{@link\napi/angular.foo\na\nb}');
doc.parse();
expect(doc.description).
@@ -452,15 +461,15 @@ describe('ngdoc', function(){
});
describe('@example', function(){
it('should not remove {{}}', function(){
var doc = new Doc('@example text {{ abc }}');
describe('@example', function() {
it('should not remove {{}}', function() {
var doc = new Doc('@name a\n@example text {{ abc }}');
doc.parse();
expect(doc.example).toEqual('<p>text {{ abc }}</p>');
});
it('should support doc:example', function(){
var doc = new Doc('@ngdoc overview\n@example \n' +
it('should support doc:example', function() {
var doc = new Doc('@name a\n@ngdoc overview\n@example \n' +
'<doc:example>\n' +
' <doc:source><escapeme></doc:source>\n' +
' <doc:scenario><scenario></doc:scenario>\n' +
@@ -474,15 +483,15 @@ describe('ngdoc', function(){
describe('@deprecated', function() {
it('should parse @deprecated', function() {
var doc = new Doc('@deprecated Replaced with foo.');
var doc = new Doc('@name a\n@deprecated Replaced with foo.');
doc.parse();
expect(doc.deprecated).toBe('Replaced with foo.');
});
});
describe('@this', function(){
describe('@this', function() {
it('should render @this', function() {
var doc = new Doc('@this I am self.');
var doc = new Doc('@name a\n@this I am self.');
doc.ngdoc = 'filter';
doc.parse();
expect(doc.html()).toContain('<h3>Method\'s <code>this</code></h3>\n' +
@@ -495,9 +504,9 @@ describe('ngdoc', function(){
});
});
describe('usage', function(){
describe('overview', function(){
it('should supress description heading', function(){
describe('usage', function() {
describe('overview', function() {
it('should supress description heading', function() {
var doc = new Doc('@ngdoc overview\n@name angular\n@description\n#heading\ntext');
doc.parse();
expect(doc.html()).toContain('text');
@@ -507,8 +516,8 @@ describe('ngdoc', function(){
});
describe('function', function(){
it('should format', function(){
describe('function', function() {
it('should format', function() {
var doc = new Doc({
ngdoc:'function',
name:'some.name',
@@ -520,15 +529,15 @@ describe('ngdoc', function(){
returns: {type: 'number', description: 'return desc'}
});
doc.html_usage_function(dom);
expect(dom).toContain('some.name([a][, b], c)'); //TODO(i) the comma position here is lame
expect(dom).toContain('name([a][, b], c)'); //TODO(i) the comma position here is lame
expect(dom).toContain('param desc');
expect(dom).toContain('(optional="xxx")');
expect(dom).toContain('return desc');
});
});
describe('filter', function(){
it('should format', function(){
describe('filter', function() {
it('should format', function() {
var doc = new Doc({
ngdoc:'formatter',
shortName:'myFilter',
@@ -539,44 +548,12 @@ describe('ngdoc', function(){
});
doc.html_usage_filter(dom);
expect(dom).toContain('myFilter_expression | myFilter:b');
expect(dom).toContain('angular.filter.myFilter(a, b)');
expect(dom).toContain('$filter(\'myFilter\')(a, b)');
});
});
describe('validator', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'validator',
shortName:'myValidator',
param: [
{name:'a'},
{name:'b'}
]
});
doc.html_usage_validator(dom);
expect(dom).toContain('ng:validate="myValidator:b"');
expect(dom).toContain('angular.validator.myValidator(a, b)');
});
});
describe('formatter', function(){
it('should format', function(){
var doc = new Doc({
ngdoc:'formatter',
shortName:'myFormatter',
param: [
{name:'a'},
]
});
doc.html_usage_formatter(dom);
expect(dom).toContain('ng:format="myFormatter:a"');
expect(dom).toContain('var userInputString = angular.formatter.myFormatter.format(modelValue, a);');
expect(dom).toContain('var modelValue = angular.formatter.myFormatter.parse(userInputString, a);');
});
});
describe('property', function(){
it('should format', function(){
describe('property', function() {
it('should format', function() {
var doc = new Doc({
ngdoc:'property',
name:'myProp',
+5 -5
View File
@@ -1,9 +1,9 @@
var SiteMap = require('SiteMap.js').SiteMap;
var Doc = require('ngdoc.js').Doc;
var SiteMap = require('../src/SiteMap.js').SiteMap;
var Doc = require('../src/ngdoc.js').Doc;
describe('sitemap', function(){
it('should render empty sitemap', function(){
describe('sitemap', function() {
it('should render empty sitemap', function() {
var map = new SiteMap([]);
expect(map.render()).toEqual([
'<?xml version="1.0" encoding="UTF-8"?>',
@@ -11,7 +11,7 @@ describe('sitemap', function(){
'</urlset>', ''].join('\n'));
});
it('should render ngdoc url', function(){
it('should render ngdoc url', function() {
var map = new SiteMap([new Doc({section: 'foo', id: 'a.b.c<>\'"&'})]);
expect(map.render()).toContain([
' <url>',
+2 -5
View File
@@ -1,16 +1,13 @@
if (global.jasmine) return;
require.paths.push(__dirname + "/../../lib");
require.paths.push(__dirname + '/../src');
var jasmine = require('jasmine-1.0.1');
var sys = require('util');
var jasmine = require('../../lib/jasmine-1.0.1');
for(var key in jasmine) {
global[key] = jasmine[key];
}
//Patch Jasmine for proper stack traces
jasmine.Spec.prototype.fail = function (e) {
jasmine.Spec.prototype.fail = function(e) {
var expectationResult = new jasmine.ExpectationResult({
passed: false,
message: e ? jasmine.util.formatException(e) : 'Exception'
+6 -6
View File
@@ -1,17 +1,17 @@
var writer = require('writer.js');
describe('writer', function(){
describe('toString', function(){
var writer = require('../src/writer.js');
describe('writer', function() {
describe('toString', function() {
var toString = writer.toString;
it('should merge string', function(){
it('should merge string', function() {
expect(toString('abc')).toEqual('abc');
});
it('should merge obj', function(){
it('should merge obj', function() {
expect(toString({a:1})).toEqual('{"a":1}');
});
it('should merge array', function(){
it('should merge array', function() {
expect(toString(['abc',{}])).toEqual('abc{}');
});
});
+1 -1
View File
@@ -7,7 +7,7 @@ exports.SiteMap = SiteMap;
* @returns {SiteMap}
*/
function SiteMap(docs){
this.render = function(){
this.render = function() {
var map = [];
map.push('<?xml version="1.0" encoding="UTF-8"?>');
map.push('<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">');
+19 -7
View File
@@ -12,7 +12,7 @@ function htmlEscape(text){
}
function DOM(){
function DOM() {
this.out = [];
this.headingDepth = 0;
}
@@ -69,22 +69,34 @@ DOM.prototype = {
},
code: function(text) {
this.tag('div', {'ng:non-bindable':''}, function(){
this.tag('div', {'ng:non-bindable':''}, function() {
this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
});
},
div: function(attr, text) {
this.tag('div', attr, text);
},
h: function(heading, content, fn){
if (content==undefined || (content instanceof Array && content.length == 0)) return;
this.headingDepth++;
this.tag('h' + this.headingDepth, heading);
var className = typeof heading == 'string'
? {'class': heading.toLowerCase().replace(/[^\d\w_]/mg, '-').replace(/-+/gm, '-')}
: null;
var className = null,
anchor = null;
if (typeof heading == 'string') {
var id = heading.
replace(/\(.*\)/mg, '').
replace(/[^\d\w\$]/mg, '.').
replace(/-+/gm, '-').
replace(/-*$/gm, '');
anchor = {'id': id};
className = {'class': id.toLowerCase().replace(/[._]/mg, '-')};
}
this.tag('h' + this.headingDepth, anchor, heading);
if (content instanceof Array) {
this.ul(content, className, fn);
} else if (fn) {
this.tag('div', className, function(){
this.tag('div', className, function() {
fn.call(this, content);
});
} else {
+12 -12
View File
@@ -1,13 +1,11 @@
require.paths.push(__dirname);
require.paths.push('lib');
var reader = require('reader.js'),
ngdoc = require('ngdoc.js'),
writer = require('writer.js'),
SiteMap = require('SiteMap.js').SiteMap,
appCache = require('appCache.js').appCache,
var reader = require('./reader.js'),
ngdoc = require('./ngdoc.js'),
writer = require('./writer.js'),
SiteMap = require('./SiteMap.js').SiteMap,
appCache = require('./appCache.js').appCache,
Q = require('qq');
process.on('uncaughtException', function (err) {
process.on('uncaughtException', function(err) {
console.error(err.stack || err);
});
@@ -22,7 +20,9 @@ writer.makeDir('build/docs/syntaxhighlighter').then(function() {
ngdoc.merge(docs);
var fileFutures = [];
docs.forEach(function(doc){
fileFutures.push(writer.output('partials/' + doc.section + '/' + doc.id + '.html', doc.html()));
// this hack is here because on OSX angular.module and angular.Module map to the same file.
var id = doc.id.replace('angular.Module', 'angular.IModule');
fileFutures.push(writer.output('partials/' + doc.section + '/' + id + '.html', doc.html()));
});
writeTheRest(fileFutures);
@@ -46,7 +46,7 @@ function writeTheRest(writesFuture) {
var manifest = 'manifest="/build/docs/appcache.manifest"';
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index.html',
writer.replace, {'doc:manifest': manifest}));
writer.replace, {'doc:manifest': ''})); //manifest //TODO(i): enable
writesFuture.push(writer.copy('docs/src/templates/index.html', 'build/docs/index-nocache.html',
writer.replace, {'doc:manifest': ''}));
@@ -93,6 +93,6 @@ function writeTheRest(writesFuture) {
}
function now(){ return new Date().getTime(); }
function now() { return new Date().getTime(); }
function noop(){};
function noop() {};
+173 -167
View File
@@ -2,9 +2,9 @@
* All parsing/transformation code goes here. All code here should be sync to ease testing.
*/
var Showdown = require('showdown').Showdown;
var DOM = require('dom.js').DOM;
var htmlEscape = require('dom.js').htmlEscape;
var Showdown = require('../../lib/showdown').Showdown;
var DOM = require('./dom.js').DOM;
var htmlEscape = require('./dom.js').htmlEscape;
var NEW_LINE = /\n\r?/;
exports.trim = trim;
@@ -13,6 +13,11 @@ exports.scenarios = scenarios;
exports.merge = merge;
exports.Doc = Doc;
var BOOLEAN_ATTR = {};
['multiple', 'selected', 'checked', 'disabled', 'readOnly', 'required'].forEach(function(value, key) {
BOOLEAN_ATTR[value] = true;
});
//////////////////////////////////////////////////////////
function Doc(text, file, line) {
if (typeof text == 'object') {
@@ -32,14 +37,14 @@ function Doc(text, file, line) {
this.events = this.events || [];
this.links = this.links || [];
}
Doc.METADATA_IGNORE = (function(){
Doc.METADATA_IGNORE = (function() {
var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
return words.toString().split(/[,\s\n\r]+/gm);
})();
Doc.prototype = {
keywords: function keywords(){
keywords: function keywords() {
var keywords = {};
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
var words = [];
@@ -78,12 +83,13 @@ Doc.prototype = {
return this.section + '/' + url;
},
markdown: function (text) {
markdown: function(text) {
if (!text) return text;
var self = this,
IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/,
IS_ANGULAR = /^(api\/)?angular\./,
IS_HASH = /^#/,
parts = trim(text).split(/(<pre>[\s\S]*?<\/pre>|<doc:(\S*).*?>[\s\S]*?<\/doc:\2>)/);
parts.forEach(function(text, i) {
@@ -112,9 +118,9 @@ Doc.prototype = {
'</pre></div>';
});
} else if (isDocWidget('example')) {
text = text.replace(/<doc:source(\s+jsfiddle="[^"]+")?>([\s\S]*)<\/doc:source>/mi,
function(_, jsfiddle, content){
return '<pre class="doc-source"' + (jsfiddle || '') +'>' +
text = text.replace(/<doc:source(\s+[^>]*)?>([\s\S]*)<\/doc:source>/mi,
function(_, attrs, content){
return '<pre class="doc-source"' + (attrs || '') +'>' +
htmlEscape(content) +
'</pre>';
});
@@ -129,15 +135,18 @@ Doc.prototype = {
function(_all, url, title){
var isFullUrl = url.match(IS_URL),
isAngular = url.match(IS_ANGULAR),
absUrl = isFullUrl ? url : self.convertUrlToAbsolute(url);
isHash = url.match(IS_HASH),
absUrl = isHash
? url
: (isFullUrl ? url : self.convertUrlToAbsolute(url));
if (!isFullUrl) self.links.push(absUrl);
return '<a href="' + absUrl + '">'
+ (isAngular ? '<code>' : '')
+ (title || url).replace(/\n/g, ' ')
+ (isAngular ? '</code>' : '')
+ '</a>';
return '<a href="' + absUrl + '">' +
(isAngular ? '<code>' : '') +
(title || url).replace(/^#/g, '').replace(/\n/g, ' ') +
(isAngular ? '</code>' : '') +
'</a>';
});
text = new Showdown.converter().makeHtml(text);
}
@@ -146,13 +155,13 @@ Doc.prototype = {
return parts.join('');
},
parse: function(){
parse: function() {
var atName;
var atText;
var match;
var self = this;
self.text.split(NEW_LINE).forEach(function(line){
if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
if ((match = line.match(/^\s*@(\w+)(\s+(.*))?/))) {
// we found @name ...
// if we have existing name
flush();
@@ -166,20 +175,20 @@ Doc.prototype = {
}
});
flush();
this.shortName = (this.name || '').split(/[\.#]/).pop();
this.id = this.id // if we have an id just use it
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // try to extract it from file name
|| this.name; // default to name
this.shortName = this.name.split(this.name.match(/#/) ? /#/ : /\./ ).pop();
this.id = this.id || // if we have an id just use it
(((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) || // try to extract it from file name
this.name; // default to name
this.description = this.markdown(this.description);
this.example = this.markdown(this.example);
this['this'] = this.markdown(this['this']);
return this;
function flush(){
function flush() {
if (atName) {
var text = trim(atText.join('\n'));
var text = trim(atText.join('\n')), match;
if (atName == 'param') {
var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
match = text.match(/^\{([^}=]+)(=)?\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 12 2 34 4 5 5 6 6 3 7 7
if (!match) {
throw new Error("Not a valid 'param' format: " + text);
@@ -193,7 +202,7 @@ Doc.prototype = {
};
self.param.push(param);
} else if (atName == 'returns') {
var match = text.match(/^{([^}=]+)}\s+(.*)/);
match = text.match(/^\{([^}=]+)\}\s+(.*)/);
if (!match) {
throw new Error("Not a valid 'returns' format: " + text);
}
@@ -202,24 +211,25 @@ Doc.prototype = {
description: self.markdown(text.replace(match[0], match[2]))
};
} else if(atName == 'requires') {
var match = text.match(/^([^\s]*)\s*([\S\s]*)/);
match = text.match(/^([^\s]*)\s*([\S\s]*)/);
self.requires.push({
name: match[1],
text: self.markdown(match[2])
});
} else if(atName == 'property') {
var match = text.match(/^{(\S+)}\s+(\S+)(\s+(.*))?/);
match = text.match(/^\{(\S+)\}\s+(\S+)(\s+(.*))?/);
if (!match) {
throw new Error("Not a valid 'property' format: " + text);
}
var property = {
var property = new Doc({
type: match[1],
name: match[2],
shortName: match[2],
description: self.markdown(text.replace(match[0], match[4]))
};
});
self.properties.push(property);
} else if(atName == 'eventType') {
var match = text.match(/^([^\s]*)\s+on\s+([\S\s]*)/);
match = text.match(/^([^\s]*)\s+on\s+([\S\s]*)/);
self.type = match[1];
self.target = match[2];
} else {
@@ -229,26 +239,24 @@ Doc.prototype = {
}
},
html: function(){
html: function() {
var dom = new DOM(),
self = this;
dom.h(this.name, function(){
notice('workInProgress', 'Work in Progress',
'This page is currently being revised. It might be incomplete or contain inaccuracies.');
dom.h(this.name, function() {
notice('deprecated', 'Deprecated API', self.deprecated);
if (self.ngdoc != 'overview') {
dom.h('Description', self.description, dom.html);
}
dom.h('Dependencies', self.requires, function(require){
dom.tag('code', function(){
dom.tag('a', {href: 'api/angular.service.' + require.name}, require.name);
dom.tag('code', function() {
dom.tag('a', {href: 'api/angular.module.ng.' + require.name}, require.name);
});
dom.html(require.text);
});
(self['html_usage_' + self.ngdoc] || function(){
(self['html_usage_' + self.ngdoc] || function() {
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
@@ -271,10 +279,10 @@ Doc.prototype = {
html_usage_parameters: function(dom) {
dom.h('Parameters', this.param, function(param){
dom.tag('code', function(){
dom.tag('code', function() {
dom.text(param.name);
if (param.optional) {
dom.tag('i', function(){
dom.tag('i', function() {
dom.text('(optional');
if(param['default']) {
dom.text('=' + param['default']);
@@ -284,6 +292,9 @@ Doc.prototype = {
}
dom.text(' {');
dom.text(param.type);
if (param.optional) {
dom.text('=');
}
dom.text('} ');
});
dom.html(param.description);
@@ -293,7 +304,7 @@ Doc.prototype = {
html_usage_returns: function(dom) {
var self = this;
if (self.returns) {
dom.h('Returns', function(){
dom.h('Returns', function() {
dom.tag('code', '{' + self.returns.type + '}');
dom.text(' ');
dom.html(self.returns.description);
@@ -314,9 +325,9 @@ Doc.prototype = {
html_usage_function: function(dom){
var self = this;
dom.h('Usage', function(){
dom.code(function(){
dom.text(self.name.split('service.').pop());
dom.h('Usage', function() {
dom.code(function() {
dom.text(self.name.split(/\./).pop());
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
@@ -326,12 +337,13 @@ Doc.prototype = {
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
this.method_properties_events(dom);
},
html_usage_property: function(dom){
var self = this;
dom.h('Usage', function(){
dom.code(function(){
dom.h('Usage', function() {
dom.code(function() {
dom.text(self.name);
});
@@ -341,8 +353,8 @@ Doc.prototype = {
html_usage_directive: function(dom){
var self = this;
dom.h('Usage', function(){
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
dom.h('Usage', function() {
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function() {
dom.text('<' + self.element + ' ');
dom.text(self.shortName);
if (self.param.length) {
@@ -357,9 +369,9 @@ Doc.prototype = {
html_usage_filter: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.tag('code', function(){
dom.h('Usage', function() {
dom.h('In HTML Template Binding', function() {
dom.tag('code', function() {
dom.text('{{ ');
dom.text(self.shortName);
dom.text('_expression | ');
@@ -369,11 +381,11 @@ Doc.prototype = {
});
});
dom.h('In JavaScript', function(){
dom.tag('code', function(){
dom.text('angular.filter.');
dom.h('In JavaScript', function() {
dom.tag('code', function() {
dom.text('$filter(\'');
dom.text(self.shortName);
dom.text('(');
dom.text('\')(');
self.parameters(dom, ', ');
dom.text(')');
});
@@ -385,77 +397,29 @@ Doc.prototype = {
});
},
html_usage_formatter: function(dom){
html_usage_inputType: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
if (self.inputType=='select')
dom.text('<select name="bindExpression"');
else
dom.text('<input type="text" name="bindExpression"');
dom.text(' ng:format="');
dom.text(self.shortName);
self.parameters(dom, ':', false, true);
dom.text('">');
dom.h('Usage', function() {
dom.code(function() {
dom.text('<input type="' + self.shortName + '"');
(self.param||[]).forEach(function(param){
dom.text('\n ');
dom.text(param.optional ? ' [' : ' ');
dom.text(param.name);
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."');
dom.text(param.optional ? ']' : '');
});
dom.text('>');
});
dom.h('In JavaScript', function(){
dom.code(function(){
dom.text('var userInputString = angular.formatter.');
dom.text(self.shortName);
dom.text('.format(modelValue');
self.parameters(dom, ', ', false, true);
dom.text(');');
dom.text('\n');
dom.text('var modelValue = angular.formatter.');
dom.text(self.shortName);
dom.text('.parse(userInputString');
self.parameters(dom, ', ', false, true);
dom.text(');');
});
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_validator: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
dom.text('<input type="text" ng:validate="');
dom.text(self.shortName);
self.parameters(dom, ':', true);
dom.text('"/>');
});
});
dom.h('In JavaScript', function(){
dom.code(function(){
dom.text('angular.validator.');
dom.text(self.shortName);
dom.text('(');
self.parameters(dom, ', ');
dom.text(')');
});
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_widget: function(dom){
var self = this;
dom.h('Usage', function(){
dom.h('In HTML Template Binding', function(){
dom.code(function(){
dom.h('Usage', function() {
dom.h('In HTML Template Binding', function() {
dom.code(function() {
if (self.shortName.match(/^@/)) {
dom.text('<');
dom.text(self.element);
@@ -473,11 +437,11 @@ Doc.prototype = {
dom.text('<');
dom.text(self.shortName);
(self.param||[]).forEach(function(param){
if (param.optional) {
dom.text(' [' + param.name + '="..."]');
} else {
dom.text(' ' + param.name + '="..."');
}
dom.text('\n ');
dom.text(param.optional ? ' [' : ' ');
dom.text(param.name);
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="..."');
dom.text(param.optional ? ']' : '');
});
dom.text('></');
dom.text(self.shortName);
@@ -495,12 +459,12 @@ Doc.prototype = {
dom.html(this.description);
},
html_usage_service: function(dom){
html_usage_interface: function(dom){
var self = this;
if (this.param.length) {
dom.h('Usage', function(){
dom.code(function(){
dom.h('Usage', function() {
dom.code(function() {
dom.text(self.name.split('.').pop());
dom.text('(');
self.parameters(dom, ', ');
@@ -512,39 +476,73 @@ Doc.prototype = {
self.html_usage_returns(dom);
});
}
this.method_properties_events(dom);
},
dom.h('Methods', this.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
dom.html(method.description);
method.html_usage_parameters(dom);
self.html_usage_this(dom);
method.html_usage_returns(dom);
html_usage_service: function(dom) {
this.html_usage_interface(dom)
},
dom.h('Example', method.example, dom.html);
});
});
dom.h('Properties', this.properties, function(property){
dom.h(property.name, function(){
dom.html(property.description);
dom.h('Example', property.example, dom.html);
});
});
dom.h('Events', this.events, function(event){
dom.h(event.shortName, event, function(){
dom.html(event.description);
dom.tag('div', {class:'inline'}, function(){
dom.h('Type:', event.type);
html_usage_object: function(dom) {
this.html_usage_interface(dom)
},
method_properties_events: function(dom) {
var self = this;
if (self.methods.length) {
dom.div({class:'member method'}, function(){
dom.h('Methods', self.methods, function(method){
var signature = (method.param || []).map(property('name'));
dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function() {
dom.html(method.description);
method.html_usage_parameters(dom);
self.html_usage_this(dom);
method.html_usage_returns(dom);
dom.h('Example', method.example, dom.html);
});
});
dom.tag('div', {class:'inline'}, function(){
dom.h('Target:', event.target);
});
event.html_usage_parameters(dom);
self.html_usage_this(dom);
dom.h('Example', event.example, dom.html);
});
});
}
if (self.properties.length) {
dom.div({class:'member property'}, function(){
dom.h('Properties', self.properties, function(property){
dom.h(property.shortName, function() {
dom.html(property.description);
if (!property.html_usage_returns) {
console.log(property);
}
property.html_usage_returns(dom);
dom.h('Example', property.example, dom.html);
});
});
});
}
if (self.events.length) {
dom.div({class:'member event'}, function(){
dom.h('Events', self.events, function(event){
dom.h(event.shortName, event, function() {
dom.html(event.description);
if (event.type == 'listen') {
dom.tag('div', {class:'inline'}, function() {
dom.h('Listen on:', event.target);
});
} else {
dom.tag('div', {class:'inline'}, function() {
dom.h('Type:', event.type);
});
dom.tag('div', {class:'inline'}, function() {
dom.h('Target:', event.target);
});
}
event.html_usage_parameters(dom);
self.html_usage_this(dom);
dom.h('Example', event.example, dom.html);
});
});
});
}
},
parameters: function(dom, separator, skipFirst, prefix) {
@@ -584,8 +582,8 @@ function scenarios(docs){
function appendSpecs(urlPrefix) {
docs.forEach(function(doc){
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
specs.push(' beforeEach(function(){');
specs.push(' describe("' + doc.section + '/' + doc.id + '", function() {');
specs.push(' beforeEach(function() {');
specs.push(' browser().navigateTo("' + urlPrefix + doc.section + '/' + doc.id + '");');
specs.push(' });');
specs.push(' ');
@@ -628,16 +626,18 @@ var KEYWORD_PRIORITY = {
'.index': 1,
'.guide': 2,
'.angular': 7,
'.angular.Array': 7,
'.angular.Object': 7,
'.angular.directive': 7,
'.angular.filter': 7,
'.angular.formatter': 7,
'.angular.scope': 7,
'.angular.service': 7,
'.angular.validator': 7,
'.angular.widget': 7,
'.angular.Module': 7,
'.angular.module': 8,
'.angular.mock': 9,
'.angular.module.ng.$filter': 7,
'.angular.module.ng.$filter': 7,
'.angular.module.ng.$rootScope.Scope': 7,
'.angular.module.ng': 7,
'.angular.mock': 8,
'.angular.directive': 6,
'.angular.inputType': 6,
'.angular.widget': 6,
'.angular.module.ngMock': 8,
'.dev_guide.overview': 1,
'.dev_guide.bootstrap': 2,
'.dev_guide.mvc': 3,
@@ -658,7 +658,7 @@ function keywordSort(a, b){
mangled.push(KEYWORD_PRIORITY[partialName] || 5);
mangled.push(name);
});
return doc.section + '/' + mangled.join('.');
return (doc.section + '/' + mangled.join('.')).toLowerCase();
}
var nameA = mangleName(a);
var nameB = mangleName(b);
@@ -724,7 +724,7 @@ function indent(text, spaceCount) {
function merge(docs){
var byFullId = {};
docs.forEach(function (doc) {
docs.forEach(function(doc) {
byFullId[doc.section + '/' + doc.id] = doc;
});
@@ -733,7 +733,13 @@ function merge(docs){
// check links - do they exist ?
doc.links.forEach(function(link) {
if (!byFullId[link]) console.log('WARNING: In ' + doc.section + '/' + doc.id + ', non existing link: "' + link + '"');
// convert #id to path#id
if (link[0] == '#') {
link = doc.section + '/' + doc.id.split('#').shift() + link;
}
if (!byFullId[link]) {
console.log('WARNING: In ' + doc.section + '/' + doc.id + ', non existing link: "' + link + '"');
}
});
// merge into parents
+3 -6
View File
@@ -5,8 +5,7 @@
exports.collect = collect;
require.paths.push(__dirname);
var ngdoc = require('ngdoc.js'),
var ngdoc = require('./ngdoc.js'),
Q = require('qq'),
qfs = require('q-fs');
@@ -23,7 +22,6 @@ function collect() {
files.forEach(function(file) {
var work;
if(/\.js$/.test(file)) {
console.log("reading " + file + ".......");
work = Q.when(qfs.read(file, 'b'), function(content) {
processJsFile(content, file).forEach (function(doc) {
allDocs.push(doc);
@@ -37,14 +35,13 @@ function collect() {
return done;
});
//collect all NG Docs in Content Folder
//collect all ng Docs in Content Folder
var path2 = 'docs/content';
var promiseB = Q.when(qfs.listTree(path2), function(files){
var done2;
files.forEach(function(file) {
var work2;
if (file.match(/\.ngdoc$/)) {
console.log("reading " + file + ".......");
work2 = Q.when(qfs.read(file, 'b'), function(content){
var section = '@section ' + file.split('/')[2] + '\n';
allDocs.push(new ngdoc.Doc(section + content.toString(),file, 1).parse());
@@ -96,4 +93,4 @@ function processJsFile(content, file) {
}
});
return docs;
}
}
+3
View File
@@ -10,6 +10,9 @@ RewriteEngine on
RewriteCond %{HTTP_COOKIE} ng-offline="NG_VERSION_FULL"
RewriteRule appcache.manifest appcache-offline.manifest
## Redirect to the latest manifest
RewriteCond %{HTTP_HOST} ^docs-next\.angularjs\.org$
RewriteRule appcache.manifest http://code.angularjs.org/next/docs/appcache.manifest [R=301]
## HTML5 URL Support ##
RewriteRule ^(guide|api|cookbook|misc|tutorial)(/.*)?$ index.html

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