Compare commits

...

969 Commits

Author SHA1 Message Date
ebcd14f8e9 docs(changelog): update change log to rc.5
Closes #10599
2016-08-09 10:34:07 -07:00
b65f66feff feat(i18n): switch to sha1 for message fingerprinting 2016-08-09 09:52:11 -07:00
dd68ae3ef1 feat(ExtractorMerger): ignore implicit tags in translatable sections 2016-08-09 09:52:11 -07:00
1b04d70626 feat(ExtractorMerger): allow nested implicit tags 2016-08-09 09:52:11 -07:00
01bca41168 fix: allow users to pass in absolute paths to ngc (#10572) 2016-08-09 09:36:37 -07:00
94e1ab33ce chore(release): update package.jsons 2016-08-09 09:33:17 -07:00
0b08dd8674 chore(animations): make sure host-prop animation deprecation is correctly emitted
Closes #10581
2016-08-08 23:39:22 -07:00
b2b47177cd add brandon suggestions 2016-08-09 03:30:25 +02:00
f08257ff4a docs(cheatsheet): update for rc5 2016-08-09 03:06:01 +02:00
d1f4222c83 docs(cheatsheet): add NgModules docs to the cheatsheet 2016-08-08 17:18:50 -07:00
d21331e902 fix(ngUpgrade): to work with @NgModule
We changed the bootstrap order:
1. create NgZone
2. bootstrap ng1 inside NgZone and upgrade ng1 components to ng2 components.
3. bootstrap ng2 with NgZone

Note: Previous footgun behavior was: bootstrap ng2 first to extract NgZone, so that ng1 bootstrap can happen in NgZone. This meant that if ng2 bootstrap eagerly compiled a component which contained ng1 components, then we did not have complete metadata.
2016-08-08 12:50:08 -07:00
37f138e83d test(ngUpgrade): convert the upgrade example to use NgModules
The example worked even before this change - verifying that the fallback/legacy mode works as well.
2016-08-08 12:20:22 -07:00
5dab0bad3c test(NgUpgrade): add support for loading plaground/upgrade example without bundles 2016-08-08 12:20:22 -07:00
85e70a4cde style(ngUpgrade): small cleanup 2016-08-08 12:20:21 -07:00
6b564ecda5 feat(ngUpgrade): add support for NgModules
BREAKING CHANGE: UpgradeAdapter.addProvider are now deprecated in favor of passing in an NgModule into the adapter's constructor

Before:

```
let upgradeAdapter = new UpgradeAdapter();
upgradeAdapter.addProviders([myProvidersArray);
```

After:

```
@NgModule({
  providers: myProvidersArray
})
class MyModule {}

let upgradeAdapter = new UpgradeAdapter(MyModule);
```
2016-08-08 12:17:35 -07:00
d4cceff0ef chore: remove module aware bootstrap API (#10543)
This API was introduced post RC4, but needs to be removed
before RC5 as we have decided against it.
2016-08-08 09:36:09 -07:00
a415613457 fix(router): update dts files 2016-08-08 09:15:59 -07:00
1a41bd1ca4 feature(router): add route.root returning the root of router state 2016-08-08 09:15:59 -07:00
5a99393355 fix(router): route.parent should work for secondary children 2016-08-08 09:15:59 -07:00
afcb3c0035 fix(router): support outlets in non-absolute positions 2016-08-08 09:15:59 -07:00
d2d36c61f3 fix(router): fix matrix params check to handle 'special' objects 2016-08-08 09:15:59 -07:00
0bd97ecda2 feat(http): add options method to Http (#10540)
Add options method to the Http object, which could be useful when using self-describing RESTful APIs.

This closes #10500, closes #7918
2016-08-08 09:15:13 -07:00
46bbcefb36 fix(linker): prevent pollution of empty embeddedView context (#10548)
Fixes #10045
2016-08-08 09:11:35 -07:00
74b57dfa7d refactor(i18n): remove circular dep 2016-08-05 13:39:54 -07:00
8c9c0986e9 refatcor(digestMessage): takes a i18n.Message 2016-08-05 13:39:54 -07:00
4028fcaa51 refactor(i18n): Drop html.Message and create i18n.Message right away 2016-08-05 13:39:54 -07:00
7a8ef1eae5 feat(i18n): merge translations 2016-08-05 13:39:54 -07:00
e811a5d97f refactor(i18n): misc updates 2016-08-05 13:39:54 -07:00
df44e3e425 fix(i18n extractor): array manipulation 2016-08-05 13:39:54 -07:00
cdb1a237e5 refactor(compiler): html_parser -> ml_parser 2016-08-05 13:39:54 -07:00
fcafdff10b feat(forms): allow both patching and strict setting of values (#10537) 2016-08-05 13:35:17 -07:00
c586656d43 fix(router): fix type definition 2016-08-05 13:12:21 -07:00
3a307c2794 fix(router): absolute redirects should work with lazy loading 2016-08-05 13:12:21 -07:00
4f17dbc721 fix(router): add segmentPath to the link DSL 2016-08-05 13:12:21 -07:00
99989f5d3f chore(facade): remove most facade/async functions 2016-08-05 12:26:28 -07:00
6baf3baedd chore(playground): clang-format 2016-08-05 12:26:28 -07:00
0d1f3c3b07 fix: support trailing slash in basePath (#10533) 2016-08-05 11:11:24 -07:00
83e2d3d1cb refactor(RegExpWrapper): remove the facade (#10512) 2016-08-05 09:50:49 -07:00
b4613ab2d2 fix(ExpressionParser): undefined is undefined (was null) 2016-08-05 09:45:13 -07:00
0ca05eee45 feat(ExpressionParser): add support for this 2016-08-05 09:45:13 -07:00
26c9e1dc70 refactor(NgFor spec): code cleanup 2016-08-05 09:45:13 -07:00
797cb5ae7b refactor(NgStyleSpec): simplify NgStyle tests (#10519) 2016-08-05 09:28:08 -07:00
63b82cd730 feat(router): Allow navigation without updating the URL (#9608) 2016-08-04 11:46:09 -07:00
2b704f0586 fix(linker/compiler): rename const to avoid duplicate declaration (#10457)
Currently in the `linker/compiler.ts` file, the **same identifier** is used in **two declarations**:
```typescript
export type CompilerOptions = { … }
…
export const CompilerOptions = new OpaqueToken('compilerOptions');
```
This breaks the API doc generation. I’m surprised that this was not flagged by the tsc.

The duplicate declaration was introduced in 46b212706b.
2016-08-04 11:31:58 -07:00
ce5ba80792 refactor(NgTemplateOutlet): simplify implementation (#10492) 2016-08-04 11:28:36 -07:00
8b18ef4ba2 feat(NgStyle): add support for the style.unit notation (#10496)
Closes #10326
2016-08-04 11:00:43 -07:00
cd18de7a21 refactor(compiler): use Object.keys instead of Object.getOwnPropertyNames (#10498) 2016-08-04 10:35:41 -07:00
fd19671c07 chore(benchpress): clang-format and lint 2016-08-04 10:08:37 -07:00
3fcd6fd93f chore(benchpress): make benchpress compile
Also adds compiling benchpress to the build scripts to verify that.
2016-08-04 10:08:37 -07:00
c8d53d71a3 fix(ngc): gather metadata for OpaqueToken
Fixes #10482
2016-08-04 10:05:25 -07:00
790362e243 fix: put all ngc files into a single directory (#10486)
Prior to this change `ngc` would place generated files which refer
to components in the node_modules into the node_module. This is an
issue. Now all of the files are forced into a single directory
as specified in `tsconfig.json` by the `genDir` option.

see: https://docs.google.com/document/d/1OgP1RIpZ-lWUc4113J3w13HTDcW-1-0o7TuGz0tGx0g
2016-08-03 21:34:03 -07:00
2eda7a5293 fix(router): provideRouter should use provideRoutes (#10488) 2016-08-03 19:35:12 -07:00
9925aa89dc fix(compiler): Report references to non-exported symbols.
Includes fixes to places now reported as errors.

Part of #8310
2016-08-03 15:42:38 -07:00
6195a45ae2 fix(forms): export AbstractFormGroupDirective
Because
- `Form` is **exported** -- see line 30/31 of
`modules/@angular/forms/src/forms.ts`: i.e., <br>`export {Form} from
'./directives/form_interface'`; and
- Methods of `Form`, which are public, have an
`AbstractFormGroupDirective` parameter;
e.g.,<br>`Form.getFormGroup(dir: AbstractFormGroupDirective):
FormGroup`.

Then it makes sense for `AbstractFormGroupDirective` to be
public/exported too. In any case, if it isn't exported then the **API
docs for `Form` don't get generated properly.**
2016-08-03 15:33:29 -07:00
422d380b3e feat(router): add queryParams and fragment to every activated route 2016-08-03 15:30:03 -07:00
550ab31bd0 feat(router): add parent, children, firstChild to ActivatedRoute 2016-08-03 15:30:03 -07:00
5fceb21549 refactor(router): remove dead code 2016-08-03 15:30:03 -07:00
29caa37943 feat(router): support sibling modules providing routes 2016-08-02 13:31:15 -07:00
8efbcc996a fix(compiler): allow to use pipes inside of *ngIf (#10452)
Fixes #9746
2016-08-02 12:19:34 -07:00
91c64d2b8d fix: missing export for validators 2016-08-02 12:12:15 -07:00
82e7ecd611 fix(compiler): StaticReflect now resolves re-exported symbols (#10453)
Fixes: #10451
2016-08-02 11:45:14 -07:00
3d53b33391 chore: update public api and integrate minor review comments 2016-08-02 11:23:26 -07:00
34624b2db2 refactor(core): deprecate old methods on ApplicationRef
BREAKING CHANGE:
- `ApplicationRef.run` is deprecated. Use `NgZone.run` directly
- `ApplicationRef.injector` is deprecated. Inject an `Injector` or
  use `NgModuleRef.injector` instead
- `ApplicationRef.zone` is deprecated. Inject `NgZone` instead.
2016-08-02 11:23:26 -07:00
1ab5eb0844 refactor(core): don’t use zone.run in ApplicationRef.bootstrap.
`ApplicationRef.bootstrap` is supposed to be run inside of `ngDoBootstrap` method
of the module that is bootstrapped, and that method already runs inside of the
zone.
2016-08-02 11:23:26 -07:00
630028350a refactor(core): Introduce AppInitStatus
This class allows any provider to know and wait for the initialization of the
application. This functionality previously was tied to `ApplicationRef`.

BREAKING CHANGE:
- `ApplicationRef.waitForAsyncInitializers` is deprecated. Use 
  `AppInitStatus.donePromise` / `AppInitStatus.done` instead.
2016-08-02 11:23:26 -07:00
7e4fd7d7da refactor(core): introduce @NgModule.bootstrap and ngDoBootstrap method
If a `@NgModule` has a `bootstrap` property, `PlatformRef.bootstrapModule` /
`PlatformRef.bootstrapModuleFactory` will automatically bootstrap the components
listed in there.
If such a property does not exist, `PlatformRef.bootstrapModule` /
`PlatformRef.bootstrapModuleFactory` will try to call the method `ngDoBootstrap(appRef: ApplicationRef)` on the module class.
Otherwise an error is reported.
2016-08-02 11:23:26 -07:00
af2e80e068 refactor(core): introduce APP_BOOTSTRAP_LISTENER multi provider
Using the `registerBootstrapListener` easily lead to race condition
and needed dependencies on `ApplicationRef`.

BREAKING CHANGE:
- `ApplicationRef.registerBootstrapListener` is deprecated. Provide a multi
  provider for the new token `APP_BOOTSTRAP_LISTENER` instead.
2016-08-02 11:23:26 -07:00
8e6091de6c refactor(core): use ngOnDestroy in providers
Note about the addition of `beforeEach(fakeAsync(inject(…))))` in some tests:
`ApplicationRef` is now using `ngOnDestroy` and there is eager,
including all of its dependencies which contain `NgZone`.
The additional `fakeAsync` in `beforeEach` ensures that `NgZone`
uses the fake async zone as parent, and not the root zone.

BREAKING CHANGE (via deprecations):
- `ApplicationRef.dispose` is deprecated. Destroy the module that was
   created during bootstrap instead by calling `NgModuleRef.destroy`.
- `AplicationRef.registerDisposeListener` is deprecated.
   Use the `ngOnDestroy` lifecycle hook for providers or
   `NgModuleRef.onDestroy` instead.
- `disposePlatform` is deprecated. Use `destroyPlatform` instead.
- `PlatformRef.dipose()` is deprecated. Use `PlatformRef.destroy()`
   instead.
- `PlatformRef.registerDisposeListener` is deprecated. Use
  `PlatformRef.onDestroy` instead.
- `PlaformRef.diposed` is deprecated. Use `PlatformRef.destroyed`
  instead.
2016-08-02 11:23:26 -07:00
ecdaded25f feat(core): introduce NgModuleRef.destroy and call ngOnDestroy on all providers 2016-08-02 11:23:26 -07:00
c161ed415d feat(core): support ngOnDestroy on providers of a directive.
Such providers also become eager as they will be instantiated
anyways on destruction.
2016-08-02 11:23:26 -07:00
ff3b71f7b3 refactor(core): collect lifecycle hooks for all injectable classes 2016-08-02 11:23:26 -07:00
16cc9b46aa fix(fake_async): share zone between beforeEach and it
This is needed for the case if a `beforeEach` instantiates
`NgZone`and the `it` uses
`TestComponentBuilder.createFakeAsync`.

Otherwise the `NgZone` will use the root zone as parent,
and `TestComponentBuilder.createFakeAsync` will always return
undefined as `tick` does not forward promises created
under the zone of `NgZone`.
2016-08-02 11:23:26 -07:00
3ce11ed58c docs(pipes): update I18nSelectPipe API documentation (#10449) 2016-08-02 10:59:07 -07:00
7db75fa361 test: reactivate some of the disabled tests in Edge (#10450) 2016-08-02 10:58:03 -07:00
d6d4568830 fix(forms): allow arrays as parents (#10440)
Closes #10432
2016-08-02 09:40:42 -07:00
a55d796c4b fix: Better error message in case of unknown property binding 2016-08-02 09:28:04 -07:00
73f02c7861 fix: throw useful error on missing platform module. 2016-08-02 09:28:04 -07:00
8c8754e573 chore: add browserNoRouter test running mode 2016-08-02 09:28:04 -07:00
c977a906b3 docs(cheatsheet): add moduleId (#10095) 2016-08-02 07:34:04 -07:00
e0eea6c2f4 feat(forms): add invalid prop to abstract controls (#10439) 2016-08-01 18:41:25 -07:00
3e377f520e fix(router): make an outlet to unregister itself when it is removed from the dom 2016-08-01 16:56:38 -07:00
8dc82a0080 fix(router): fix offline compilation by exporting provideLocationStrategy 2016-08-01 16:23:28 -07:00
4624a35845 docs(router): fixes api docs 2016-08-01 16:23:28 -07:00
8d4499959a feat(forms): add get method for easy access to child controls (#10428) 2016-08-01 14:22:50 -07:00
2dfc9c653b refactor: dart 2016-08-01 11:34:51 -07:00
106db0aba8 refactor: IS_DART === false 2016-08-01 11:34:51 -07:00
28c4852cd6 refactor: remove ts2dart annotations 2016-08-01 11:34:51 -07:00
13c8211065 fix: String.split(str, n) stops after n separator (#10408) 2016-08-01 11:33:35 -07:00
e73d0511cf fix(TemplateParser): report empty expression (#10391)
fixes #3754
2016-08-01 11:24:49 -07:00
e18626b7a2 fix(core): ensure ngFor only inserts/moves/removes elements when necessary (#10287)
Closes #9960
Closes #7239
Closes #9672
Closes #9454
Closes #10287
2016-08-01 11:09:52 -07:00
4df7b1cfbc chore: fixes some issues that happen with closure compiler. (#10392) 2016-08-01 09:47:49 -07:00
f9573ece41 ci(browser providers): update browsers in SL and BS (#10425) 2016-08-01 08:00:12 -07:00
93ade740e2 fix(router): configure DI correctly when using the old provideRouter function 2016-08-01 02:35:41 -07:00
a46437c57d refactor(core): fix bootstrapModule regarding zones and initializers (#10383)
This makes `bootstrapModuleFactory` wait for promises
returned by `APP_INITIALIZER`s, also making `bootstrapModuleFactory` async.
I.e. now `bootstrapModule` and `bootstrapModuleFactory` behave in the
same way.

This ensures that all code from module instantiation, to creating
`ApplicationRef`s as well as calling `APP_INITIALIZERS` is run
in the Angular zone.

This also moves the invocation of the initializers from the `ApplicationRef`
constructor into the `bootstrapModuleFactory` call, allowing initializers
to get a hold of `ApplicationRef` (see #9101).

Fixes #9101
Fixes #10363
Fixes #10205
2016-08-01 02:12:59 -07:00
633c7d1ebe refactor(core): cleanup unused NgZone factory
This factory was only used when using `coreBootstrap` / `coreLoadAndBoostrap`
which is not supported any more.
2016-08-01 02:12:59 -07:00
251953218c fix(http): URLSearchParams.clone propagate the QueryEncoder (#9900) 2016-07-30 19:01:20 -07:00
0d6cc17252 fix(UrlParser): stop setting default value 'true' (#10399) 2016-07-30 14:34:03 -07:00
c8989c900f chore(github): remove reference to Dart in the issue template (#10388) 2016-07-30 00:22:04 -07:00
6134320f16 refactor(http): rewrite for readable & efficient. 2016-07-29 21:30:28 -07:00
7f647822bd fix(http): headers should be case-insensitive.
fixes #9452

spec at https://tools.ietf.org/html/rfc2616
2016-07-29 21:30:15 -07:00
e34a04d2ad feat(xmb/xtb): support dtd 2016-07-29 14:18:34 -07:00
44093905e2 refactor(test): refine types 2016-07-29 13:53:13 -07:00
3e2900f74b refactor(i18n): move code around 2016-07-29 13:40:58 -07:00
11fd2eccec refactor: fix merge glitches 2016-07-29 13:40:58 -07:00
0eee1d5de3 feat(i18n): xtb serializer 2016-07-29 13:40:58 -07:00
1b77604ee2 refactor(compiler): re-arrange files 2016-07-29 13:40:58 -07:00
cc5cfe87c3 feat(i18n): xmb serializer 2016-07-29 13:40:58 -07:00
48f230a951 feat(I18nAst): introduce an intermediate AST 2016-07-29 13:40:58 -07:00
2be50bdbb0 refactor(i18n): message extractor
fixes #8802
2016-07-29 13:40:58 -07:00
f7258ea52a test(HtmlAst): add a serializer 2016-07-29 13:40:58 -07:00
28e8b2faab feat(ICU): extract ICU messages 2016-07-29 13:40:58 -07:00
3c3e9ddb10 feat(router): add a validation to make sure pathMatch is set correctly 2016-07-29 12:27:43 -07:00
5162fb6d52 feat(router): add isActive to router 2016-07-29 12:27:43 -07:00
2fdb39e60a feat(router): activateroute should expose its route config 2016-07-29 12:27:43 -07:00
43c71ae103 fix(platform-browser): IEMobile is badly detected when testing (#10382) 2016-07-29 10:05:12 -07:00
f0bd528d77 test(http): fix Blob creation for Android <= 4.3
Closes #10377
2016-07-29 18:20:09 +02:00
50a024b42f test(http): add Typed Array polyfill for IE9 2016-07-29 18:19:54 +02:00
b48f7bcb8d fix(forms): normalize written value in NumberValueAccessor
Closes #10379
2016-07-29 18:18:30 +02:00
763ca60f5b feat(compiler): Support default parameters in static reflector (#10370)
Closes: #10369
2016-07-29 09:10:45 -07:00
0eca7abdd8 chore: update public api
Closes #10355
2016-07-29 04:47:50 -07:00
d0a95e35af refactor(testing): introduce new testing api to support ng modules
BREAKING CHANGE:
- deprecations:
  * `withProviders`, use `TestBed.withModule` instead
  * `addProviders`, use `TestBed.configureTestingModule` instead
  * `TestComponentBuilder`, use `TestBed.configureTestModule` / `TestBed.override...` / `TestBed.createComponent` instead.

Closes #10354
2016-07-29 04:47:18 -07:00
acc6c8d0b7 feat(compiler): introduce MockDirectiveResolver.setDirective 2016-07-29 04:47:18 -07:00
3dbc66c1ac refactor(core): introduce interfaces for constructor arguments of decorators
For @Directive, @Component, @Pipe, @NgModule
2016-07-29 04:47:18 -07:00
4ad6bcce54 feat(compiler): add MockPipeResolver 2016-07-29 04:47:17 -07:00
0988cc82b0 refactor(core): remove ViewResolver and ViewResolverMock
The methods on `ViewResolverMock` have been merged into `DirectiveResolver`.

BREAKING CHANGE:
- ES5 users can no longer use the `View(…)` function to provide `ViewMetadata`.
  This mirrors the removal of the `@View` decorator a while ago.
2016-07-29 04:47:17 -07:00
20b03bad11 feat(compiler): Added support for conditional expressions. (#10366)
Expression evaluated by the static reflector can now supports
conditional expressions.

Closes: #10365
2016-07-28 17:32:29 -07:00
81d27daf0d fix(router): update dts files 2016-07-28 15:03:19 -07:00
bb8b82b3f5 fix(router): relax type defintion of Route to improve dev ergonomics 2016-07-28 15:03:19 -07:00
915a6666f8 fix(core): fix offline detection in ng_module_factory_loader 2016-07-28 15:03:19 -07:00
72da547d6a fix(router): updates router module to be offline-compilation friendly 2016-07-28 15:03:19 -07:00
7c76a75452 fix(forms): update dirty before emitting value change (#10362)
Closes #5328
2016-07-28 14:25:33 -07:00
a32c4ad2f0 fix(compiler): auto declare entryComponents recursively
Closes #10348
2016-07-28 12:03:34 -07:00
fb3608aa5d fix(compiler): report better error messages for host bindings
Closes #10346
2016-07-28 11:21:50 -07:00
0a46f37444 fix(metadata): fix typechecking with typescript@next 2016-07-28 11:17:53 -07:00
9b39e499ac fix(core): support components without a selector (#10331)
Components without a selector now get the selector `ng-component`.
Directives without a selector will throw an error message.

Closes #3464
Closes #10216
2016-07-28 10:39:10 -07:00
a67cc8229d chore: move injector to being non-internal but private (#10339)
* chore: move injector to being non-internal but private

* Add the new non-internal method to the public API.
2016-07-28 10:38:29 -07:00
b58e9ea775 feat(compiler): Added support for references to static fields. (#10334)
Closes: #10332
2016-07-27 19:26:59 -07:00
422effdd18 refactor(http): use ngModules for http (#10329) 2016-07-27 13:09:05 -07:00
3b690b68a6 fix(testing): Fix error message in test bed
The API name in the error message was wrong
2016-07-27 12:03:27 -07:00
43349dd373 fix(forms): improve ngModel error messages (#10314) 2016-07-27 10:59:40 -07:00
e44e8668ea fix(core): only warn and auto declare undeclared entryComponents.
This is needed to support existing applications.
After final these warnings will become errors.

Closes #10316
2016-07-27 10:56:12 -07:00
69e72c0786 chore: remove browserDynamicTestingPlatform as it is deprecated and was not part of rc.4
Closes #10319
2016-07-27 10:52:47 -07:00
553344739c fix(core): allow module providers to overwrite providers from ModuleWithProviders
Fixes #10313
Closes #10317
2016-07-27 10:51:58 -07:00
367f0fd142 fix(static_reflector): report methods with decorators in propMetadata as well
This was the behavior of our regular reflector as well, although the
method name does not imply this.

Fixes #10308
Closes #10318
2016-07-27 10:50:58 -07:00
58d9e7fc5a feat(facade): add support for all thenables (#10278)
All objects that have a then function will be considered Promises
2016-07-27 10:37:48 -07:00
9d9e9c6ff1 refactor(router): take advantage of the new way of configuring modules 2016-07-27 10:24:29 -07:00
ba88db5141 fix(ngClass): do not deconstruct classes on element removal (#10303)
Prior to this fix [ngClass] would remove all dynamic classes
when destroyed. It's essential that classes are persisted such
that remove-based animations will still be stylistically correct.
This patch fixes this issue.

Closes #10008
Closes #10303
2016-07-26 15:20:27 -07:00
62e7c0f464 feat(router): implement canLoad 2016-07-26 14:39:02 -07:00
fc83bbbe98 fix(compiler): treat custom elements as unknown elements by default
Closes #10300
2016-07-26 12:31:22 -07:00
482c019199 feat(security): only warn when actually sanitizing HTML. (#10272)
Previously, Angular would warn users when simply re-encoding text
outside of the ASCII range. While harmless, the log spam was annoying.

With this change, Angular specifically tracks whether anything was
stripped during sanitization, and only reports a warning if so.

Fixes #10206.
2016-07-26 11:39:09 -07:00
b449467940 feat(compiler): Allow calls to simple static methods (#10289)
Closes: #10266
2016-07-26 10:18:35 -07:00
0aba42ae5b fix(forms): throw error if wrong control container for reactive forms (#10286) 2016-07-26 10:08:46 -07:00
0d1bf8148b fix(animations): ensure animation detection doesn't rely on the body node
Closes #10230
Closes #10191
Closes #10273
2016-07-26 09:33:50 -07:00
b42411ba1f chore: update public api
- `precompile` -> `entryComponents`
- introduce `ModuleWithProviders`
- introduce `@NgModule.schema`
- update to bootstrap

Closes #10268
2016-07-26 07:45:40 -07:00
5a21f168d6 refactor(core): change bootstrap of modules and names of platforms
BREAKING CHANGES:
- `browserPlatform`/`browserDynamicPlatform`/... have been deprecated and renamed into `platformBrowser`/`platformBrowserDynamic`/....
- `bootstrapModule` and `bootstrapModuleFactory` have been moved to be members of `PlaformRef`.
  E.g. `platformBrowserDynamic().bootstrapModule(MyModule)`.
2016-07-26 07:28:55 -07:00
00b726f695 refactor(core): introduce NgModule.schemas
This allows Angular to error on unknown properties,
allowing applications that don’t use custom elements
to get better error reporting.

Part of #10043

BREAKING CHANGE:
- By default, Angular will error during parsing
  on unknown properties,
  even if they are on elements with a `-` in their name
  (aka custom elements). If you application is using
  custom elements, fill the new parameter `@NgModule.schemas`
  with the value `[CUSTOM_ELEMENTS_SCHEMA]`.

  E.g. for bootstrap:
  ```
  bootstrap(MyComponent, {schemas: [CUSTOM_ELEMENTS_SCHEMA]});
  ```
2016-07-26 07:04:36 -07:00
f02da4e91a feat(core): introduce ModuleWithProviders.
Modules can now provider helper functions that allow
to import a module together with an array of providers.

Part of #10043
2016-07-26 07:04:36 -07:00
d6b65db9a7 fix(static_reflector): resolve values of functions in the function context 2016-07-26 07:04:36 -07:00
6f4e49ed53 refactor(core): rename precompile into entryComponents.
Part of #10043

BREAKING CHANGE:
- `@Component.precompile` was renamed to `@Component.entryComponents`
  (old property still works but is deprecated)
- `ANALYZE_FOR_PRECOMPILE` was renamed to `ANALYZE_FOR_ENTRY_COMPONENTS` (no deprecations)
2016-07-26 07:04:36 -07:00
46b212706b refactor(core): change module semantics
This contains major changes to the compiler, bootstrap of the platforms
and test environment initialization.

Main part of #10043
Closes #10164

BREAKING CHANGE:
- Semantics and name of `@AppModule` (now `@NgModule`) changed quite a bit.
  This is actually not breaking as `@AppModules` were not part of rc.4.
  We will have detailed docs on `@NgModule` separately.
- `coreLoadAndBootstrap` and `coreBootstrap` can't be used any more (without migration support).
  Use `bootstrapModule` / `bootstrapModuleFactory` instead.
- All Components listed in routes have to be part of the `declarations` of an NgModule.
  Either directly on the bootstrap module / lazy loaded module, or in an NgModule imported by them.
2016-07-26 07:04:10 -07:00
ca16fc29a6 build: fix and document the symlinks scripts for Windows
Closes #10213
2016-07-26 09:52:11 +02:00
9edea0b139 fix(ng upgrade): do not compile ng2 components until after ng1 bootstrap (#10084)
Closes #9407 and angular/protractor#2944
2016-07-25 21:14:35 -07:00
d15a1d64e1 fix(router): make router provides work with cli and offline compilation 2016-07-25 18:04:22 -07:00
c87847974a chore(forms): separate out tests for reactive forms (#10283) 2016-07-25 15:57:51 -07:00
6f68330fa5 feat(router): rename UrlPathWithParams into UrlSegment
BREAKING CHANGE:

UrlPathWithParams => UrlSegment
UrlSegment => UrlSegmentGroup
2016-07-25 12:15:07 -07:00
2b63330a36 fix(router): handle when both primary and secondary are empty-path and primary has a child 2016-07-22 18:51:35 -07:00
06e4ca4bb3 fix(router): advance query params and fragment after advanced routes 2016-07-22 18:51:35 -07:00
43437c175a fix(router): handle url fragments when no url segments present 2016-07-22 18:51:35 -07:00
8d90a5a4cf fix(router): router link active should take all descendants into account 2016-07-22 18:51:35 -07:00
93a4ca652a refactor(router): renames PRIMARY_OUTLET into primary 2016-07-22 18:51:35 -07:00
41178367d1 feat(HtmlLexer): better hint on unclosed ICU message errors
fixes #10227
2016-07-22 17:27:15 -07:00
54f2edbb90 refactor(Lexer): code cleanup 2016-07-22 16:50:54 -07:00
b652a7fc9f chore: remove obsolete files (#10240) 2016-07-22 16:18:31 -07:00
e34eb4520f fix(testing): ComponentFixture - Avoid extra scheduleMicrotask (#10223)
Don't schedule microtask to check for pending macrotasks when no one is waiting for a whenStable().
2016-07-22 16:07:11 -07:00
190bcc89c1 refactor(EventEmitter): optional emits (#10058)
the Subject#next is optional
2016-07-22 10:19:57 -07:00
64fc4648b7 fix(testing): correctly import NgMatchers (#10077)
Some test cases incorrectly rely on the side effect of other test cases
importing `NgMatchers`. This commit fixes this by making `expect` in
`core/testing_internal` properly typed.
2016-07-22 09:20:51 -07:00
bdb59129d0 feat(http): add content-type override support for http request (#10211) 2016-07-22 08:37:32 -07:00
d455942389 fix(CurrencyPipe): use default Intl formatting options when none provided
fixes #10189
2016-07-22 08:21:31 -07:00
cdb3678fe3 chore(tools): remove unused files 2016-07-22 08:20:46 -07:00
e73ac1e992 chore(Dart): remove obsolete files, update the docs 2016-07-22 08:20:46 -07:00
51f3d22e4f feat(security): trust resource URLs as URLs. (#10220)
Resource URLs are strictly "more" trustworthy than plain URLs, so trusting them maintains the same level of security while avoiding to break people when we downgrade a resource URL context to a plain URL context.
2016-07-21 17:44:59 -07:00
00aa7a76b6 chore(tests): enable lint, make it green. (#10224) 2016-07-21 17:12:00 -07:00
27b87ef535 fix(router): throw when cannot parse a url 2016-07-21 16:14:55 -07:00
44709e0dca fix(router): handle urls with only secondary top-level segments 2016-07-21 16:14:44 -07:00
31a7709ece test: execute router tests on ./test.sh browser (#10053) 2016-07-21 16:12:40 -07:00
a441b5b8fe feat(security): categorize <track src> as a regular URL.
After security review, it turns out we were too paranoid about <track src>. Its content is not actually active or dangerous.

Fixes #10089.
2016-07-21 13:53:35 -07:00
76b8a49bfb feat(http): add support for blob as a response type (#10190) 2016-07-21 13:44:38 -07:00
db54a84d14 fix(router): routerLinkActive should only set classes after the router has successfully navigated 2016-07-20 17:51:21 -07:00
eb6ff65af7 refactor(router): makes an error message clearer 2016-07-20 14:47:51 -07:00
23ee29b6a2 fix(router): navigation should not preserve query params and fragment by default
BREAKING CHANGE

Previously both imperative (router.navigate) and declarative (routerLink) navigations
would preserve the current query params and fragment. This behavior turned out to
be confusing. This commit changes it.

Now, neither is preserved by default. To preserve them, you need to do the following:

router.naviage("newUrl", {preserveQueryParams: true, preserveFragment: true})

<a routerLink="newUrl" preserveQueryParams preserveFragment></a>
2016-07-20 14:30:04 -07:00
73a69895d8 cleanup(router): removes unnecessary files from tsconfig 2016-07-20 11:44:07 -07:00
2799e7a3ca refactor(router): rename RouterTestModule into RouterTestingModule 2016-07-20 11:39:31 -07:00
b43f95435b fix(testing): add an explicit doAsyncPrecompilation step (#10015)
This removes the magic from the `inject` test helper that would inspect
the current zone and would only work with our `async` test helper.
Now, `inject` is always synchronous, and if you are using a module
that requires async precompilation, you're required to call
`doAsyncPrecompilation` in your tests.

This is part of the breaking changes introduced with the swap
to each test having an AppModule.

Closes #9975
Closes #9593

BREAKING CHANGE:

`TestInjector` is now renamed to `TestBed`

Before:

```js
import {TestInjector, getTestInjector} from '@angular/core/testing';
```

After:

```js
import {TestBed, getTestBed} from '@angular/core/testing';
```
2016-07-20 10:51:21 -07:00
450f61d384 docs(router): fix provider token (#10177) 2016-07-20 07:27:09 -07:00
f3dd91e1d7 feat(NumberPipe): add string support (#10163)
fixes #10159
2016-07-19 11:27:06 -07:00
979946c062 fix(testing): Add platform directives to the shim that keeps setBaseTestProviders running (#10154)
Due to daa9da4047,
tests using the setBaseTestProviders stopped working with ambient directives such as
`ngIf`. Add them back in to keep the shim working.
2016-07-19 08:59:14 -07:00
51e661eb74 fix(router): export navigation extras 2016-07-18 17:42:04 -07:00
921a17960c fix(router): lazy loaded components should use loaded injector 2016-07-18 17:42:04 -07:00
7a4f6621ed fix(router): handle lastPathIndex of empty-path routes 2016-07-18 17:42:04 -07:00
83bc5c97ef fix(http): convert objects passed to requests into a string (#10124)
This remove a breaking change introduced with commit #e7a8e2757b06d572f614f53b648d2fd75df370d2 where json objects passed to  requests were not converted into string.

BREAKING CHANGE:

The behavior in this commit is the same as before PR 7260 : the objects sent with the request are converted to a string, therefore there is no need for the user to take care of the serialization.

Fixes #10073
2016-07-18 14:20:03 -07:00
3f08efa35d fix(KeyValueDiffer): check for changes
fixes #9115
2016-07-18 11:30:07 -07:00
0914dc35e8 refactor(Differ): cleanup 2016-07-18 11:30:07 -07:00
b6746cce9c refactor(decorators): cleanup 2016-07-18 10:38:59 -07:00
8cd97c2054 refactor(NgFor): cleanup 2016-07-18 10:38:59 -07:00
32d8cde9c6 docs(NgSwitch): fix typo ngSwitch to NgSwitch (#10143) 2016-07-18 10:24:55 -07:00
1803ed2512 fix(router): fix rollup config to properly set up rxjs 2016-07-15 16:27:54 -07:00
f08060b0b0 fix(router): back button does not work in IE11 and Safari 2016-07-15 14:59:59 -07:00
b77a4a40a4 fix(router): expose initalNavigation and dispose so they can be used with webworkers 2016-07-14 17:29:01 -07:00
e1109d52e1 docs(router): update the docs of LocationPathStrategy 2016-07-14 15:00:50 -07:00
0668ba50e8 fix(router): freeze params and queryParams to prevent common source of bugs 2016-07-14 13:33:05 -07:00
44ff005ce3 doc(developer): updates (#10075) 2016-07-14 08:51:05 -07:00
aa88438b54 feat(Router): add extra validation for when route was passed as Array (#9942) 2016-07-14 08:28:31 -07:00
85be729c70 fix(router): lazily-loaded modules should use loaded injectors instead of the root one 2016-07-13 18:25:30 -07:00
a5dc5705a3 feat(router): guards and data resolvers can now return promises 2016-07-13 18:25:30 -07:00
9e3d13f61f feat(router): add support for canActivateChild 2016-07-13 18:25:30 -07:00
961c9d48ae fix(compiler): Query expression lambdas should have dynamic type
Fixes: #9875
2016-07-13 15:41:30 -07:00
9229bbbc80 Revert "fix(compiler): Generates function expressions as returning any (#9980)"
This reverts commit eb5763c23f.
2016-07-13 15:41:30 -07:00
34feecf60e fix(forms): improve no value accessor error message (#10051) 2016-07-13 14:13:02 -07:00
4c762a6be3 chore(lint): enable lint check for duplicate vars 2016-07-13 12:33:39 -07:00
0426325ef7 fix(router): merge SystemJsAppModuleFactoryLoader and SystemJsAllModuleLoader 2016-07-13 11:16:46 -07:00
0b54e3cf0a fix(router): do not fire events on 'duplicate' location events 2016-07-13 11:16:46 -07:00
5cf58971f1 fix(router): update current state and url before activating components 2016-07-13 11:16:46 -07:00
6518ff88b2 fix(compiler): No longer writes 0 length files outside of genDir (#10023)
Fixes: #9984
2016-07-13 11:15:23 -07:00
42b0c1d8a2 refactor: misc cleanup (#10046) 2016-07-13 11:01:32 -07:00
4a965052f9 fix(platform-browser): remove testing_e2e target (#10029)
The testing_e2e util does not belong in platform-browser and was never
intended to be a public API. Move it out of that whole tree.

BREAKING CHANGE:

The following API was never intended to be public and is removed:

```js
import {verifyNoBrowserErrors} from '@angular/platform-browser/testing_e2e';
```

Consider using Protractor's console plugin: https://github.com/angular/protractor-console-plugin
2016-07-13 10:10:02 -07:00
5725c5925c docs(ExceptionHandler): Update code example in comment block to correct syntax error. (#10032) 2016-07-13 07:28:08 -07:00
a46291b67c docs(router) Added @Injectable decorators to Router API examples (#10033) 2016-07-13 07:27:32 -07:00
4ac76ca281 docs(router): fix syntax in code example of comment block (#10026) 2016-07-13 07:26:26 -07:00
e7a8e2757b refactor(http): share 'body' logic between Request and Response 2016-07-12 18:01:06 -07:00
1266460386 feat(http): add support for ArrayBuffer
Add the buffer option to store response in ArrayBuffer
Improve the interface to get back response independently of the buffer type
2016-07-12 18:01:06 -07:00
0ccb6e0dfc refactor: code cleanup 2016-07-12 16:57:39 -07:00
3050ae155c feat(ICU): enable ICU extraction even when when in is not used
BREAKING CHANGES:

"{" is used a a delimiter for ICU messages then it could not be used in text nodes.
"{" should be escaped as "{{ '{' }}"

Before:

    <span>some { valid } text</span>

After:

    <span>some { invalid } text<span> <!-- throw parse error -->
    <span>some {{ '{' }} valid } text</span>
2016-07-12 16:57:39 -07:00
402fd934d0 refactor: code cleanup 2016-07-12 15:26:03 -07:00
6c86e8d80a test(HtmlLexer): Add test for "{" and "{{" escaping 2016-07-12 15:26:03 -07:00
60e6f91a53 refactor(HmtlLexer): cleanup 2016-07-12 15:26:03 -07:00
e676fded21 refactor(Lexer): add types
relates to #9100
2016-07-12 15:26:03 -07:00
25e070dd65 fix(HtmlParser): correctly propagate the interpolation config across layers 2016-07-12 15:26:03 -07:00
da8eb9f8b8 feat(forms): add ability to reset forms (#9974)
Closes #4914
Closes #4933
2016-07-12 15:02:25 -07:00
806a25413c feat(animations): allow animation integration support into host params
Closes #9044
Closes #9933
2016-07-12 14:55:36 -07:00
5af1e891cd fix(animations): throw errors when duplicate component trigger names are registered 2016-07-12 14:55:31 -07:00
79eda30f0f refactor(animations): collect parser / lookup errors in the same place 2016-07-12 14:55:27 -07:00
6d02d2f107 fix(SyncAsyncResult): fix default async value (#10013) 2016-07-12 13:55:06 -07:00
ded518d47f feat(router): update routerLink DSL to handle aux routes 2016-07-12 11:44:55 -07:00
27436270fd fix(Compiler): Catch exceptions in the logging of binding update
fixes #9994
2016-07-12 11:21:32 -07:00
b4ea0b1601 test(datePipe): remove hardcoded timezone (#10007)
Closes #9964
2016-07-12 11:13:26 -07:00
7b31178546 fix(platform-browser-dynamic): Add @Injectable() annotation to XHRImpl.
Otherwise Closure compiled code will complain that the class is missing the annootation.
2016-07-11 23:27:35 -07:00
2ff83324af fix(core): Don't use ES6 spread operator when undefined is allowed.
Workaround a closure bug where it doesn't produce the right code ES6 operator when the array value can be undefined.
2016-07-11 23:14:25 -07:00
4ef86891a3 fix(compiler): Ignore references to declared modules and unneeded types (#9776)
Fixes: #9670
2016-07-11 17:26:35 -07:00
eb5763c23f fix(compiler): Generates function expressions as returning any (#9980)
Function expressions are used in an expression context so untyped
function expressions should have any as the result type.

Fixes: #9877
2016-07-11 17:19:38 -07:00
d1a3e3aff1 fix(DirectiveResolver): throw on duplicate Input & Output names 2016-07-11 16:38:06 -07:00
93d0a01d3d refactor: code cleanup 2016-07-11 16:22:23 -07:00
9af2d8b810 fix(testing): remove deprecated testing APIs (#9923)
See https://github.com/angular/angular/blob/master/CHANGELOG.md for
prior deprecation and how to update.
2016-07-11 16:04:32 -07:00
94dc632a6d fix(NgStyle): remove duplicate input declaration (#9978)
fixes #9977
2016-07-11 15:09:04 -07:00
29231877e6 fix(testing): reintroduce and deprecate setBaseTestProviders (#9905)
This change reverts the removal of setBaseTestProviders that was
introduced in 8d746e3f67.

Instead, setBaseTestProviders and the providers provided from
`@angular/platform-browser-dynamic/testing` and `@angular/server/testing`
will still work for the next release, but are deprecated.

See 8d746e3f67 for how to upgrade.
2016-07-11 14:01:11 -07:00
e68252a79b fix(forms): re-enable form provider functions for easier migration (#9972) 2016-07-11 13:23:38 -07:00
4ec2a30942 fix(compiler): Fixed ?. operator to short-circut execution (#9965)
Fixes: #9850
2016-07-11 12:58:56 -07:00
e6b24437a9 fix(compiler): Collector collects enum values. (#9967)
Fixes: #9928
2016-07-11 11:50:33 -07:00
a05f7b2d76 refactor(animations): move MockAnimationPlayer back into core (#9966)
Closes #9966
2016-07-11 10:55:47 -07:00
61e18434d3 refactor(router): not use reserved words as variable (#9941) 2016-07-11 10:53:29 -07:00
57473e72ec refactor: code cleanup (#9931) 2016-07-09 10:12:39 -07:00
c3bdd504d0 fix(animations): ensure all child elements are rendered before running animations
Closes #9402
Closes #9775
Closes #9887
2016-07-08 18:18:46 -07:00
daa9da4047 refactor(core): deprecate coreBootstrap, PLATFORM_PIPES/DIRECTIVES providers and ComponentResolver
BREAKING CHANGE (deprecations)

- Instead of `coreBootstrap`, create an `@AppModule` and use `bootstrapModule`.
- Instead of `coreLoadAndBootstarp`, create an `@AppModule` and use `bootstrapModuleFactory`.
- Instead of `bootstrapWorkerApp`, create an `@AppModule` that includes the `WorkerAppModule` and use `bootstrapModule` with the `workerAppPlatform()`.
- Instead of `bootstrapWorkerUi`, create an @AppModule that includes the `WorkerUiModule` and use `bootstrapModule` with the `workerUiPlatform()` instead.
- Instead of `serverBootstrap`, create an @AppModule and use `bootstrapModule` with the `serverDynamicPlatform()` instead.
- Instead of `PLATFORM_PIPES` and `PLATFORM_DIRECTIVES`, provide platform directives/pipes via an `@AppModule`.
- Instead of `ComponentResolver`:
  - use `ComponentFactoryResolver` together with `@AppModule.precompile`/`@Component.precompile` or `ANALYZE_FOR_PRECOMPILE` provider for dynamic component creation.
  - use `AppModuleFactoryLoader` for lazy loading.
- Instead of `SystemJsComponentResolver`, create an `@AppModule` and use `SystemJsAppModuleLoader`.
- Instead of `SystemJsCmpFactoryResolver`, create an `@AppModule` and use `SystemJsAppModuleFactoryLoader`

Closes #9726
2016-07-08 15:31:50 -07:00
245b0910ed feat(router): add activate and deactivate events to RouterOutlet 2016-07-08 15:01:52 -07:00
a77db44129 feat(router): empty-path routes should inherit matrix params 2016-07-08 14:48:59 -07:00
34b3c534e7 fix(router): disallow root segments with matrix params 2016-07-08 14:47:55 -07:00
fa47890032 refactor(core): clean up platform bootstrap and initTestEnvironment
- Introduces `CompilerFactory` which can be part of a `PlatformRef`.
- Introduces `WorkerAppModule`, `WorkerUiModule`, `ServerModule`
- Introduces `serverDynamicPlatform` for applications using runtime compilation
  on the server.
- Changes browser bootstrap for runtime and offline compilation (see below for an example).
  * introduces `bootstrapModule` and `bootstrapModuleFactory` in `@angular/core`
  * introduces new `browserDynamicPlatform` in `@angular/platform-browser-dynamic
- Changes `initTestEnvironment` (which used to be `setBaseTestProviders`) to not take a compiler factory any more (see below for an example).

BREAKING CHANGE:

## Migration from `setBaseTestProviders` to `initTestEnvironment`:

- For the browser platform:
  BEFORE:
  ```
  import {setBaseTestProviders} from ‘@angular/core/testing’;
  import {TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
      TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS} from ‘@angular/platform-browser-dynamic/testing’;

  setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
      TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);
  ```

  AFTER:
  ```
  import {initTestEnvironment} from ‘@angular/core/testing’;
  import {browserDynamicTestPlatform,
      BrowserDynamicTestModule} from ‘@angular/platform-browser-dynamic/testing’;

  initTestEnvironment(
      BrowserDynamicTestModule,
      browserDynamicTestPlatform());

  ```
- For the server platform:
  BEFORE:
  ```
  import {setBaseTestProviders} from ‘@angular/core/testing’;
  import {TEST_SERVER_PLATFORM_PROVIDERS,
      TEST_SERVER_APPLICATION_PROVIDERS} from ‘@angular/platform-server/testing/server’;

  setBaseTestProviders(TEST_SERVER_PLATFORM_PROVIDERS,
      TEST_SERVER_APPLICATION_PROVIDERS);
  ```

  AFTER:
  ```
  import {initTestEnvironment} from ‘@angular/core/testing’;
  import {serverTestPlatform,
      ServerTestModule} from ‘@angular/platform-browser-dynamic/testing’;

  initTestEnvironment(
      ServerTestModule,
      serverTestPlatform());

  ```

## Bootstrap changes
```
@AppModule({
  modules: [BrowserModule],
  precompile: [MainComponent],
  providers: […], // additional providers
  directives: […], // additional platform directives
  pipes: […] // additional platform pipes
})
class MyModule {
  constructor(appRef: ApplicationRef) {
    appRef.bootstrap(MainComponent);
  }
}

// offline compile
import {browserPlatform} from ‘@angular/platform-browser’;
import {bootstrapModuleFactory} from ‘@angular/core’;

bootstrapModuleFactory(MyModuleNgFactory, browserPlatform());

// runtime compile long form
import {browserDynamicPlatform} from ‘@angular/platform-browser-dynamic’;
import {bootstrapModule} from ‘@angular/core’;

bootstrapModule(MyModule, browserDynamicPlatform());
```

Closes #9922
Part of #9726
2016-07-08 13:41:38 -07:00
d84a43c828 fix(TemplateParser): add support for data-template attribute
fixes #9904
2016-07-08 13:07:01 -07:00
9a1babb30c refactor(TemplateParser): cleanup 2016-07-08 13:07:01 -07:00
30a332ee36 feat(forms): updateValue() for form groups and form arrays (#9901)
Closes #9553
2016-07-08 13:04:25 -07:00
426b002897 docs(developer): update building and testing instructions (#9903)
fixes #9683
2016-07-08 12:00:27 -07:00
2de8364de2 feat(ExpressionChangedAfterItHasBeenCheckedException): more meaningful error message
fixes #9882
2016-07-08 10:34:22 -07:00
eacc9e6541 refactor: misc cleanup 2016-07-08 10:34:22 -07:00
749dec7dfb doc(api): fix invalid doc links (#9873)
Errors were reported during API doc generation.
2016-07-07 23:02:35 -07:00
96a9e66616 feat(router): add the ANALYZE_FOR_PRECOMPILE provider to make dev ergonomics better 2016-07-07 18:07:10 -07:00
46e105f3ab fix(router): encode/decode params and path segments 2016-07-07 17:33:42 -07:00
f7a0e9ecb6 fix(router): fix RouterLinKActive to work with RouterLink 2016-07-07 17:33:42 -07:00
93025d1bc6 fix(forms): export form directive arrays for offline compile (#9893) 2016-07-07 17:29:20 -07:00
98d49d4ce3 refactor(core): make lockRunMode a noop and deprecate it.
BREAKING CHANGE:
- `lockRunMode` is deprecated and no more needed.

Closes #9878
2016-07-07 16:16:55 -07:00
1426f680f5 refactor(core): add a deprecation message for using PLATFORM_DIRECTIVES / PLATFORM_PIPES / CompilerConfig / XHR as regular providers in bootstrap.
We still support this via a hack, but should remove this soon.

This also fixes tests for module directives / pipes
as they used directives / pipes that were
already present in the underlying platform.
2016-07-07 16:16:55 -07:00
c7fc51a185 chore(public api): run build.sh before generating the api doc (#9889) 2016-07-07 15:39:53 -07:00
b7e69bc1a1 fix(NgPlural): expression inside cases (#9883)
fixes #9868
2016-07-07 14:47:06 -07:00
72544ba551 feat(router): add RouterTestModule 2016-07-07 14:28:01 -07:00
c43dd5a655 refactor(router): renamed RouterAppModule into RouterModule 2016-07-07 14:28:01 -07:00
7f4954bed6 fix(animations): change trigger binding syntax to function as a property binding []
Animation triggers can now be set via template bindings `[]`

BREAKING CHANGE:

animation trigger expressions within the template that are assigned as
an element attribute (e.g. `@prop`) are deprecated. Please use the
Angular2 property binding syntax (e.g. `[@prop]`) when assigning
properties.

```ts
// this is now deprecated
<div @trigger="expression"></div>

// do this instead
<div [@trigger]="expression"></div>
```
2016-07-07 14:13:06 -07:00
f1fc1dc669 revert: fix(animations): ensure all child elements are rendered before running animations
This reverts commit cbe85a0893.
2016-07-07 14:12:17 -07:00
cbe85a0893 fix(animations): ensure all child elements are rendered before running animations
Closes #9402
Closes #9775
2016-07-07 14:10:04 -07:00
7073cf74fe feat(core): allow to add precompiled tokens via a provider
Introduces the new `ANALYZE_FOR_PRECOMPILE` token. This token can be used to
create a virtual provider that will populate the `precompile` fields of
components and app modules based on its
`useValue`. All components that are referenced in the `useValue`
value (either directly or in a nested array or map) will be added
to the `precompile` property.

closes #9874
related to #9726
2016-07-07 12:16:48 -07:00
9d265b6f61 feat(forms): add modules for forms and deprecatedForms (#9859)
Closes #9732

BREAKING CHANGE:

We have removed the deprecated form directives from the built-in platform directive list, so apps are not required to package forms with their app. This also makes forms friendly to offline compilation.

Instead, we have exposed three modules:

OLD API:
- `DeprecatedFormsModule`

NEW API:
- `FormsModule`
- `ReactiveFormsModule`

If you provide one of these modules, the default forms directives and providers from that module will be available to you app-wide.  Note: You can provide both the `FormsModule` and the `ReactiveFormsModule` together if you like, but they are fully-functional separately.

**Before:**
```ts
import {disableDeprecatedForms, provideForms} from @angular/forms;

bootstrap(App, [
   disableDeprecatedForms(),
   provideForms()
]);
```

**After:**

```ts
import {DeprecatedFormsModule} from @angular/common;

bootstrap(App, {modules: [DeprecatedFormsModule] });
```

-OR-

```ts
import {FormsModule} from @angular/forms;

bootstrap(App, {modules: [FormsModule] });
```

-OR-

```ts
import {ReactiveFormsModule} from @angular/forms;

bootstrap(App, {modules: [ReactiveFormsModule] });
```

You can also choose not to provide any forms module and run your app without forms.

Or you can choose not to provide any forms module *and* provide form directives at will.  This will allow you to use the deprecatedForms API for some components and not others.

```
import {FORM_DIRECTIVES, FORM_PROVIDERS} from @angular/forms;

@Component({
   selector: some-comp,
   directives: [FORM_DIRECTIVES],
   providers: [FORM_PROVIDERS]
})
class SomeComp
```
2016-07-07 11:32:51 -07:00
776a83f9da doc(i18nPluralPipe): update API doc example (#9862) 2016-07-07 08:48:37 -07:00
f29457f3f0 fix(datePipe): short timezone not displayed, closes #9812 (#9816) 2016-07-07 08:47:30 -07:00
a005d1595e chore(compiler-cli): correct homepage URL in package.json (#9869) 2016-07-07 08:43:53 -07:00
8d746e3f67 feat(testing): add implicit test module
Every test now has an implicit module. It can be configured via `configureModule` (from @angular/core/testing)
to add providers, directives, pipes, ...

The compiler now has to be configured separately via `configureCompiler` (from @angular/core/testing)
to add providers or define whether to use jit.

BREAKING CHANGE:
- Application providers can no longer inject compiler internals (i.e. everything
  from `@angular/compiler). Inject `Compiler` instead. This reflects the
  changes to `bootstrap` for module support (3f55aa609f).
- Compiler providers can no longer be added via `addProviders` / `withProviders`.
  Use the new method `configureCompiler` instead.
- Platform directives / pipes need to be provided via
  `configureModule` and can no longer be provided via the
  `PLATFORM_PIPES` / `PLATFORM_DIRECTIVES` tokens.
- `setBaseTestProviders()` was renamed into `initTestEnvironment` and 
  now takes a `PlatformRef` and a factory for a
  `Compiler`.
- E.g. for the browser platform:
  
  BEFORE:
  ```
  import {setBaseTestProviders} from ‘@angular/core/testing’;
  import {TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
      TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS} from ‘@angular/platform-browser-dynamic/testing’;
  
  setBaseTestProviders(TEST_BROWSER_DYNAMIC_PLATFORM_PROVIDERS,
      TEST_BROWSER_DYNAMIC_APPLICATION_PROVIDERS);   
  ```

  AFTER:
  ```
  import {setBaseTestProviders} from ‘@angular/core/testing’;
  import {browserTestCompiler, browserDynamicTestPlatform,
      BrowserDynamicTestModule} from ‘@angular/platform-browser-dynamic/testing’;
  
  initTestEnvironment(
      browserTestCompiler,
      browserDynamicTestPlatform(),
      BrowserDynamicTestModule);

  ```
- E.g. for the server platform:
  
  BEFORE:
  ```
  import {setBaseTestProviders} from ‘@angular/core/testing’;
  import {TEST_SERVER_PLATFORM_PROVIDERS,
      TEST_SERVER_APPLICATION_PROVIDERS} from ‘@angular/platform-server/testing/server’;
  
  setBaseTestProviders(TEST_SERVER_PLATFORM_PROVIDERS,
      TEST_SERVER_APPLICATION_PROVIDERS);   
  ```

  AFTER:
  ```
  import {setBaseTestProviders} from ‘@angular/core/testing’;
  import {serverTestCompiler, serverTestPlatform,
      ServerTestModule} from ‘@angular/platform-browser-dynamic/testing’;
  
  initTestEnvironment(
      serverTestCompiler,
      serverTestPlatform(),
      ServerTestModule);

  ```

Related to #9726
Closes #9846
2016-07-06 18:04:19 -07:00
37e6da6dfb refactor(router): clean up naming 2016-07-06 16:19:52 -07:00
8aa2a0c1b2 feat(router): add RouterAppModule 2016-07-06 16:00:40 -07:00
6bfd514caf fix(router): remove a circular dep 2016-07-06 14:38:05 -07:00
ad3f18c0dd chore(build): update build script to increase node's heap size 2016-07-06 14:38:05 -07:00
39d04b4a15 chore(public_api): update public api 2016-07-06 14:38:05 -07:00
6fbe56dbf2 feat(router): update the example app to use lazily-loaded modules 2016-07-06 14:38:05 -07:00
8ebb8e44c8 feat(router): add support for lazily loaded modules 2016-07-06 14:38:05 -07:00
6fcf962fb5 feat(core): add AddModuleFactoryLoader 2016-07-06 14:38:05 -07:00
2708ce6a17 docs(api): fix links (#9852) 2016-07-06 14:34:27 -07:00
30bec78da3 fix(compiler): Missing metadata files should result in undefined (#9704)
RelectorHost threw an exception when metadata was requested for a
.d.ts file that didn't have a .metadata.json file.  Changed it to
return undefined.

Fixes #9678
2016-07-06 14:26:31 -07:00
9a04fcd061 feat(compiler): Expression span information and error correction (#9772)
Added error correction so the parser always returns an AST
Added span information to the expression parser
Refactored the test to account for the difference in error reporting
Added tests for error corretion
Modified tests to validate the span information
2016-07-06 14:06:47 -07:00
ae62f082fd docs(api): fix broken example urls (#9828) 2016-07-06 13:57:38 -07:00
9cc3b2ca9e fix(animations): ensure a null easing value is never used with web-animations
Closes #9780
Closes #9752
2016-07-06 11:25:54 -07:00
3fe1cb0253 refactor(core): ensure CSS parser uses ParseSourceSpan to track ast locations
This commit also fixes up any remaining TODO comments.

Closes #9778
2016-07-06 11:22:45 -07:00
0ed7773223 build(gulp): Fix paths for public-api tasks on Windows (#9794) 2016-07-05 20:52:35 -07:00
3f55aa609f feat(browser): use AppModules for bootstrap in the browser
This introduces the `BrowserModule` to be used for long form
bootstrap and offline compile bootstrap:

```
@AppModule({
  modules: [BrowserModule],
  precompile: [MainComponent],
  providers: […], // additional providers
  directives: […], // additional platform directives
  pipes: […] // additional platform pipes
})
class MyModule {
  constructor(appRef: ApplicationRef) {
    appRef.bootstrap(MainComponent);
  }
}

// offline compile
import {bootstrapModuleFactory} from ‘@angular/platform-browser’;
bootstrapModuleFactory(MyModuleNgFactory);

// runtime compile long form
import {bootstrapModule} from ‘@angular/platform-browser-dynamic’;
bootstrapModule(MyModule);
```

The short form, `bootstrap(...)`, can now creates a module on the fly,
given `directives`, `pipes, `providers`, `precompile` and `modules`
properties.

Related changes:
- make `SanitizationService`, `SecurityContext` public in `@angular/core` so that the offline compiler can resolve the token
- move `AnimationDriver` to `platform-browser` and make it
  public so that the offline compiler can resolve the token

BREAKING CHANGES:
- short form bootstrap does no longer allow
  to inject compiler internals (i.e. everything 
  from `@angular/compiler). Inject `Compiler` instead.
  To provide custom providers for the compiler,
  create a custom compiler via `browserCompiler({providers: [...]})`
  and pass that into the `bootstrap` method.
2016-07-02 20:35:09 -07:00
74b45dfbf8 Revert "refactor(core): ensure CSS parser uses ParseSourceSpan to track ast locations"
This reverts commit 5c9f871b21.
2016-07-01 21:21:56 -07:00
5c9f871b21 refactor(core): ensure CSS parser uses ParseSourceSpan to track ast locations
This commit also fixes up any remaining TODO comments.

Closes #9285
2016-07-01 17:18:14 -07:00
77dc6ef411 fix(forms): mark control containers as touched when child controls are touched (#9735) 2016-07-01 15:36:04 -07:00
5eca6e4e40 bug(datePipe): passing "hh" to the datepipe (#9774)
closes #9759
2016-07-01 15:34:57 -07:00
0c65d5cf2b fix(router): handle router outlets in ngIf 2016-06-30 22:14:42 -07:00
f65ebec3ed fix(router): update links when query params change 2016-06-30 22:14:42 -07:00
81bf3f66ca docs(router): rename global redirects into absolute redirects 2016-06-30 22:14:42 -07:00
3cbded6694 fix(forms): use change event for select multiple (#9713) 2016-06-30 20:24:39 -07:00
137fff9632 fix(router): remove private and internal annotations (#9753) 2016-06-30 19:39:13 -07:00
695c08b9dd test(forms): add test for multi-select and custom accessors (#9624) 2016-06-30 18:04:00 -07:00
119794249b doc(changelog): cleans up markdown for breaking changes (#9739) 2016-06-30 17:31:39 -07:00
afb72164e4 fix(docs): typo in comments (#9743)
correct a typo in comments
2016-06-30 15:18:41 -07:00
9fee5630fd chore: fix package on changelog (#9736) 2016-06-30 15:17:00 -07:00
01de58d650 chore(router): bump up version number 2016-06-30 14:58:59 -07:00
dabf214f17 fix(router): remove private and internal annotations (#9745) 2016-06-30 14:47:55 -07:00
fb2539e1d5 fix(router): remove the precompile warning 2016-06-30 14:33:04 -07:00
ad9f02a73e chore: enable cyclic dependency check
Closes #9742
2016-06-30 14:28:22 -07:00
2d73583253 chore(compiler): fix cyclic dependency 2016-06-30 14:28:22 -07:00
73f017bad9 fix(typescript): make router compile with typescript@next
fixes #9731
2016-06-30 11:51:52 -07:00
055282f156 chore(router): bump up version number 2016-06-30 11:45:31 -07:00
fe7de53b89 chore(router): update router change log 2016-06-30 11:45:31 -07:00
17e4cfc748 feat(core): introduce @AppModule
Main part for #9726
Closes #9730
2016-06-30 11:34:40 -07:00
1608d91728 docs(changelog): change log and package.json to rc4
Closes #9727
2016-06-30 10:16:32 -07:00
a3b90411aa fix(router): fix RouterLinkActive to handle the case when the link has extra paths 2016-06-30 09:26:57 -07:00
5781b96490 fix(router): redirect should not add unnecessary brackets 2016-06-30 09:26:57 -07:00
f208ee0d57 fix(router): reexport router directives 2016-06-30 09:26:57 -07:00
8aa388de6c doc(directive): fixes incorrect example code (#9635) 2016-06-29 22:16:43 -07:00
51d4c9dcbd fix(compiler): make code easier to type check
These changes are needed for the G3 sync as we use a different version/settings of Typescript than on Github.

closes #9701
2016-06-29 10:43:58 -07:00
e81dea695c fix(compiler): report not existing files as errors
Closes #9690
2016-06-29 07:35:34 -07:00
3fec27961e fix: support *directive on <template> (#9691)
fixes #7315
2016-06-28 21:53:41 -07:00
3784696b9e fix(router): make the contstructor of the router service public 2016-06-28 18:39:37 -07:00
8c45aebc18 fix(router): make router links work on non-a tags 2016-06-28 18:39:37 -07:00
810c722413 docs(security): point users to docs when sanitization fails. (#9680) 2016-06-28 18:13:46 -07:00
e2116c53f3 fix(upgrade): add peerDependency on platform-browser-dynamic (#9674)
Closes #9623
2016-06-28 17:27:28 -07:00
296a447e3c docs(router): add api docs 2016-06-28 14:49:29 -07:00
0961bd1eff feat(forms): use formControlName on radio buttons when name is absent (#9681) 2016-06-28 15:21:53 -06:00
9340e1b065 docs(security): security api doc update and fix stability marker for Type 2016-06-28 14:01:48 -07:00
ae4fa56ee9 fix(public API): update golden files
broken by #9606
2016-06-28 12:21:50 -07:00
2d9d7f1310 fix(security): allow empty CSS values. (#9675) 2016-06-28 11:45:02 -07:00
5ee84fe0f6 refactor: add types (#9606)
relates to #9100
2016-06-28 11:35:59 -07:00
1620426393 fix(http): don't encode values that are allowed in query (#9651)
This implements a new class, QueryEncoder, that provides
methods for encoding keys and values of query parameter.
The encoder encodes with encodeURIComponent, and then
decodes a whitelist of allowed characters back to their
unencoded form.

BREAKING CHANGE:

The changes to Http's URLSearchParams serialization now 
prevent encoding of these characters inside query parameters
which were previously converted to percent-encoded values:

@ : $ , ; + ; ? /

The default encoding behavior can be overridden by extending
QueryEncoder, as documented in the URLSearchParams service.

Fixes #9348
2016-06-28 11:31:35 -07:00
bf598d6b8b feat(compiler): support sync runtime compile
Adds new abstraction `Compiler` with methods
`compileComponentAsync` and `compileComponentSync`.
This is in preparation of deprecating `ComponentResolver`.

`compileComponentSync` is able to compile components
synchronously given all components either have an inline
template or they have been compiled before.

Also changes `TestComponentBuilder.createSync` to
take a `Type` and use the new `compileComponentSync` method.

Also supports overriding the component metadata even if
the component has already been compiled.

Also fixes #7084 in a better way.

BREAKING CHANGE:
`TestComponentBuilder.createSync` now takes a component type
and throws if not all templates are either inlined
are compiled before via `createAsync`.

Closes #9594
2016-06-28 10:26:16 -07:00
24eb8389d2 fix: public api surface fixes + stability markers
- ts-api-guardian will now error if a new public symbol is added with a stability marker (`@stable`, `@experimental`, `@deprecated`)
- DomEventsPlugin and KeyEventsPlugin were removed from public api surface - these classes is an implementation detail
- deprecated BROWSER_PROVIDERS was removed completely
- `@angular/compiler` was removed from the ts-api-guardian check since this package shouldn't contain anything that users need to directly import
- the rest of the api surface was conservatively marked as stable or experimental

BREAKING CHANGES: DomEventsPlugin and KeyEventsPlugin previously exported from core are no longer public - these classes are implementation detail.

Previously deprecated BROWSER_PROVIDERS was completely removed from platform-browser.

Closes #9236
Closes #9235
Ref #9234
2016-06-28 07:39:40 -07:00
fcfddbf79c feat(router): add pathMatch property to replace terminal 2016-06-27 20:21:30 -07:00
dc64e90ab9 feat(router): use componentFactoryResolver 2016-06-27 20:21:30 -07:00
e12b1277df feat(core): split ChangeDetectorStrategy into ChangeDetectionStrategy and ChangeDetectorStatus 2016-06-27 20:19:20 -07:00
797914e948 fix(forms): emit statusChange when child controls have async validator (#9652) 2016-06-27 21:01:24 -06:00
e0b0a594bb fix(animations): ensure void => * animations are triggered when an expression is omitted
Closes #9327
Closes #9381
2016-06-27 18:55:10 -07:00
ed0ade6f34 fix(forms): make radio button selection logic more flexible (#9646)
Closes #9558
2016-06-27 15:29:33 -06:00
5cc7b41f39 Revert "fix(Compiler): relax childIsRecursive check (#8705)"
This fix prevented waiting for child components even if the cycle was only introduced via the `directives` array, i.e. without actually having a cycle. This easily causes issues for applications that have one shared list of directives for all components.

This reverts commit 3d5bb23184.

Closes #9647
2016-06-27 14:27:03 -07:00
f2f1ec0117 feat(router): implement data and resolve 2016-06-27 14:25:56 -07:00
e913d9954d chore(typings): restrict Angular to es5+collections+promise 2016-06-27 13:58:59 -07:00
d20488752b fix(router): top-levels do not work in ngIf 2016-06-27 13:34:54 -07:00
855f3afb28 fix(router): canceled navigations should return a promise that is resolved with false 2016-06-27 13:34:54 -07:00
3f44377f2f fix(router): handle empty path with query params 2016-06-27 13:34:54 -07:00
90295e3252 fix(router): preserve fragment on initial load 2016-06-27 13:34:54 -07:00
a620f95891 build(npm): upgrade ts-api-guardian to v0.1.4
Closes #9642
2016-06-27 12:27:59 -07:00
db66509e66 test(security): tests for HTML5 elements, srcset.
Part of #9572.
2016-06-27 12:19:03 -07:00
6605eb30e9 feat(security): allow more HTML5 elements and attributes in sanitizers
Allow more elements and attributes from the HTML5 spec which were stripped by the htmlSanitizer.

fixes #9438

feat(security): allow audio data URLs in urlSanitizer

test(security) : add test for valid audio data URL

feat(security): allow and sanitize srcset attributes

test(security): test for srcset sanitization
2016-06-27 12:19:03 -07:00
3644eef860 feat(DomRenderer): Adding support for document fragments in SVG foreign objects (#9458) 2016-06-27 08:26:45 -07:00
fb2509675d doc(changelog): add backticks around html elements so they actually render (#9637) 2016-06-27 08:21:34 -07:00
eef9512ce6 fix(forms): async validator-directives process Observables correctly (#8186)
Closes #/8022
2016-06-26 16:52:50 -06:00
9f00a1b902 fix(forms): add select multiple accessor as built-in accessor 2016-06-26 16:24:27 -06:00
c369bc747d docs: update cheatsheet import lines (#9614) 2016-06-26 07:31:35 -07:00
c03e1f2f59 feat(forms): add support for formArrayName
Closes #9251
2016-06-25 13:30:53 -07:00
17dcbf66b9 feat(forms): expose ValidatorFn and AsyncValidatorFn
Closes #8834
2016-06-24 18:24:11 -07:00
40b907a657 refactor(testing): remove wrapping of Jasmine functions (#9564)
Instead, the async function now determines whether it should return a promise
or instead call a done function parameter. Importing Jasmine functions
from `@angular/core/testing` is no longer necessary and is now deprecated.

Additionally, beforeEachProviders is also deprecated, as it is specific
to the testing framework. Instead, use the new addProviders method directly.

Before:
```js
import {beforeEachProviders, it, describe, inject} from 'angular2/testing/core';

describe('my code', () => {
  beforeEachProviders(() => [MyService]);

  it('does stuff', inject([MyService], (service) => {
    // actual test
  });
});
```

After:
```js
import {addProviders, inject} from 'angular2/testing/core';

describe('my code', () => {
  beforeEach(() => {
    addProviders([MyService]);
  });

  it('does stuff', inject([MyService], (service) => {
    // actual test
  });
});
```
2016-06-24 17:48:35 -07:00
a33195dcf3 fix(core/testing compiler/testing): move TestComponentBuilder to core/testing (#9590)
TestComponentBuilder now lives in core/testing. compiler/testing contains a private
OverridingTestComponentBuilder implementation which handles the private behavior
we need to override templates. This is part of the effort to simplify the testing
imports and hide compiler APIs.

Closes #9585

BREAKING CHANGE:

`TestComponentBuilder` is now imported from `@angular/core/testing`. Imports
from `@angular/compiler/testing` are deprecated.

Before:

```
import {TestComponentBuilder, TestComponentRenderer, ComponentFixtureAutoDetect} from '@angular/compiler/testing';
```

After:
```
import {TestComponentBuilder, TestComponentRenderer, ComponentFixtureAutoDetect} from '@angular/core/testing';
```
2016-06-24 17:35:01 -07:00
c693c03f1d test(node): enable source maps in test.sh node (#9589) 2016-06-24 16:52:41 -07:00
de127109f9 feat(forms): make valueChanges and statusChanges available on abstract control directives 2016-06-24 14:37:19 -07:00
83208983b3 chore(router): bump up version number 2016-06-24 13:07:42 -07:00
327d04c9c6 chore(router): clang-format 2016-06-24 12:44:32 -07:00
54edce2bab fix(router): wildcard don't get notified on url changes 2016-06-24 12:44:32 -07:00
1a145ac500 fix(router): default exact to false in routerLinkActiveOptions 2016-06-24 12:44:32 -07:00
9f978cf49d test(router): add a test checking that you can use a slash in query params 2016-06-24 12:44:32 -07:00
41b781107b fix(router): doen't throw on canDeactive when route hasn't advanced 2016-06-24 12:44:32 -07:00
dcf75126bf fix(common/testing): remove internal MockLocationStrategy from common/testing (#9562)
BREAKING CHANGE:

MockLocationStrategy was intended to be internal only and is now removed
from the `@angular/common/testing` public api.

Use `SpyLocation` from `@angular/common/testing` for location testing.
2016-06-24 12:41:57 -07:00
1143b0389a fix(core/testing): move ComponentFixture to core (#9386)
BREAKING CHANGE:

`ComponentFixture` will be moving out of `@angular/compiler/testing` to `@angular/core/testing` in
this release. For now, it is deprecated from `@angular/compiler/testing`.
2016-06-24 12:41:49 -07:00
97a2119596 fix(forms): ngModel should emit valueChanges and statusChanges asynchronously 2016-06-24 12:37:46 -07:00
fbd2dd9ca2 fix(router): handle path:'' redirects and matches 2016-06-24 11:39:41 -07:00
f463e09b9f fix(ngc): work with typescript@next
This is required due to breaking change in TS, see
https://github.com/Microsoft/TypeScript/pull/8841#issuecomment-227300348
2016-06-24 10:27:31 -07:00
42a5b6cbda chore(testing): upgrade ts-api-guardian to 0.1.3 2016-06-23 18:19:32 -07:00
0ad1215a92 build(changelog): remove old changelog script and add gulp task 2016-06-23 17:35:57 -07:00
7733c97df3 build(npm): update conventional-changelog dependency
Fixes #5672
2016-06-23 17:35:57 -07:00
8a9e9c7bd3 fix(core/testing): clean up the core testing public API (#9466)
Previously, we were exporting internal mocks and helpers. Move these
to core/testing/testing_internal or remove them if they were
never used.

Remove deprecated items - injectAsync, clearPendingTimers.

BREAKING CHANGE:

Remove the following APIs from `@angular/core/testing`, which have been deprecated or were
never intended to be publicly exported:

```
injectAsync
clearPendingTimers
Log
MockAppliacationHref
MockNgZone
clearPendingTimers
getTypeOf
instantiateType
```

Instead of `injectAsync`, use `async(inject())`.

`clearPendingTimers` is no longer required.
2016-06-23 17:10:22 -07:00
3d8eb8cbca fix(platform-browser/testing): clean up public api for platform-browser/testing (#9519)
Mostly, removing things that were never intended to be exported publicy.

BREAKING CHANGE:

The following are no longer publicly exported APIs. They were intended as internal
utilities and you should use your own util:

```
browserDetection,
dispatchEvent,
el,
normalizeCSS,
stringifyElement,
expect (and custom matchers for Jasmine)
```
2016-06-23 16:42:25 -07:00
894747c34c fix(platform-browser/testing-e2e): clean up unused exports from e2e testing helpers (#9387) 2016-06-23 16:14:31 -07:00
8d5a312585 chore(api): clean up compiler/testing api (#9520)
Do not export MockXHR, which is a private helper.
2016-06-23 15:52:18 -07:00
8eb81b3741 ci: add updated ts-api-guardian check 2016-06-23 14:26:40 -07:00
22d8f73bc9 test: add public api golden files
Includes a few style fixes on "* as foo" imports.
2016-06-23 14:26:40 -07:00
249a6bdd98 test: upgrade ts-api-guardian to v0.1.2 2016-06-23 14:26:40 -07:00
3ad81b1beb test(security): simplify integration test. 2016-06-23 13:57:51 -07:00
5ab0534164 test(security): Ensure xlink:href is not bindable.
The DOM schema does not allow binding any properties to dangerous SVG
attributes/properties. This change adds a smoke test to verify that
behaviour, by testing that `xlink:href` (a sample dangerous property)
is not bindable.

Fixes #9510.
2016-06-23 13:57:51 -07:00
5150344213 fix(common): add license header to localization.ts 2016-06-23 13:27:43 -07:00
98cef76931 fix(security): no warning when sanitizing escaped html (#9392) (#9413) 2016-06-23 13:06:19 -07:00
6c5b653593 feat(core): add @Component.precompile and ComponentFactoryResolver
Part to #9467
Closes #9543
2016-06-23 12:10:04 -07:00
9ed8f2d26e fix(compiler): don't inject viewProviders into content child elements
E.g. in the following scenario,
`some-directive` should not be able to inject
any view provider that `my-comp-with-view-providers`
declares.

```
<my-comp-with-view-providers>
  <div some-directive></div>
</my-comp-with-view-providers>
```
2016-06-23 12:10:04 -07:00
33a2f86b28 chore: remove stale tsconfig.json
This tsconfig.json prevents fast round trip cycles in VsCode
as it relies on the package-dist folders to be filled.
2016-06-23 12:10:04 -07:00
fed1672a43 refactor(i18n): I18nPipe uses NgLocalization (#9313)
and some refactoring
2016-06-23 11:44:05 -07:00
54dbed4f48 fix(typings): don't test compiler-cli typings on TS 1.8 2016-06-23 10:57:03 -07:00
df759b8d4b fix(core): improve error message for broken bindings
Fixes #6820

Closes #9536
2016-06-23 19:28:56 +02:00
6edf0474cc feat(forms): add support for standalone ngModel dirs inside forms
Closes #9230
2016-06-23 10:16:47 -07:00
826f89f862 fix(ngc): correct dependencies for compiler-cli
Update compiler-cli dependencies to include minimist and also increment tsc-wrapped to 0.2.0.  There is signature mismatch between tsc-wrapped (v0.1.0) collector.js#getMetadata and compiler-cli reflector_host.js#getMetadataFor that caused an error anytime ngc was executed. The error received was as follows.

`TypeError: Cannot read property 'getSymbolsInScope' of undefined`

After forcing NPM to install @angular/tsc-wrapped@latest the error was resolved.

Fixes #9540
2016-06-23 10:16:04 -07:00
c43aec2182 fix(animations): make sure the easing value is passed into the web-animations player
Closes #9517
Closes #9523
2016-06-23 10:14:18 -07:00
ae75e3640a chore(lint): Added license headers to most TypeScript files
Relates to #9380
2016-06-23 09:47:54 -07:00
a5f2cc73f6 chore(lint): Add lint check for license headers
Added a tslint check to make sure all source files begin with a license
header (at the very beginning or after a `#!`).

Relates to #9380
2016-06-23 09:46:32 -07:00
e1e5c40ef7 fix(testing): remove the toThrowErrorWith matcher (jasmine has toThrowError)
BREAKING CHANGE:

Before:

    expect(...).toThrowErrorWith(msg);

After:

    expect(...).toThrowError(msg);
2016-06-23 08:58:52 -07:00
6420f75320 fix(testing): remove the toMatchPattern matcher (jasmine has toMatch)
BREAKING CHANGE:

Before:

    expect(...).toMatchPattern(pattern);

After:

    expect(...).toMatch(pattern);
2016-06-23 08:58:28 -07:00
5face35ae5 refactor: misc cleanup 2016-06-23 08:56:10 -07:00
398060d5ff fix(NgSwitch): display deprecation message only once 2016-06-23 08:56:10 -07:00
638fd744aa feat(forms): support updating of validators on exiting controls (#9516)
lint

fix

async

d

test

test
2016-06-23 08:18:07 -07:00
098b461b69 fix(core): report duplicate template bindings in templates
Fixes #7315

BREAKING CHANGES:

Previously multiple template bindings on one element
(ex. `<div *ngIf='..' *ngFor='...'>`) were allowed but most of the time
were leading to undesired result. It is possible that a small number
of applications will see template parse errors that shuld be fixed by
nesting elements or using `<template>` tags explicitly.

Closes #9462
2016-06-23 15:59:07 +02:00
9decc3d823 build: fix some issues on Windows platforms
Closes #9450
2016-06-23 10:46:01 +02:00
a5f2e205ef fix(http): add search param escaping for keys (#9166) 2016-06-22 18:23:15 -07:00
8899b83927 chore(typescript): Enabled noFallthroughCasesInSwitch
Turned on the noFallthroughCasesInSwitch flag in tsconfig and fixed
a few cases where there were fallthroughs.
2016-06-22 16:08:55 -07:00
f6a410a4a8 feat(QueryList): implement some() (#9464)
closes #9443
2016-06-22 13:13:31 -07:00
3d5bb23184 fix(Compiler): relax childIsRecursive check (#8705)
Fix how the compiler checks for recursive components by also considering
component descendants. Previously, it only checked if the current
component was evaluated previously. This failed in certain cases of
mutually recursive components, causing `createAsync` in tests to not
resolve.

closes [7084](https://github.com/angular/angular/issues/7084)
2016-06-22 07:02:11 -07:00
ef37d2ae0b example(router): add an example app for the new router 2016-06-21 23:19:26 -07:00
2eb234bc63 chore(router): enable bundling 2016-06-21 23:19:26 -07:00
758ee95880 fix(router): fix tsconfig to use es2015 modules 2016-06-21 23:19:26 -07:00
40e1112a8e chore(router): test karma config to rerun tests on change 2016-06-21 23:19:26 -07:00
397f5e2390 refactor(HtmlLexer): simplify the code 2016-06-21 18:03:22 -07:00
1a212259af refactor: cleanup lexers & parsers 2016-06-21 18:03:22 -07:00
f114dd300b fix(core): properly report missing providers and viewProviders (#9411)
Fixes #8237
2016-06-21 17:27:27 -07:00
5954a26bce docs: add latest changelog updates (#9415) 2016-06-21 17:16:22 -07:00
bdbbe5aa20 chore(release): 2.0.0-rc.3 2016-06-21 16:34:48 -07:00
15911367a2 refactor(router): removes a circualr dep 2016-06-21 12:17:30 -07:00
8dd3f59c81 chore(router): changes the router setup to align with other modules 2016-06-21 12:17:30 -07:00
c9d28492b7 chore(router): remove lint and format tasks from router 2016-06-21 12:17:30 -07:00
d1f93072a8 chore(router): clang-format 2016-06-21 12:17:30 -07:00
92d8bf9619 feat(router): add support for componentless routes 2016-06-21 12:17:30 -07:00
bd2281e32d fix(resolve): change resolve not to resolve root activate route 2016-06-21 12:17:30 -07:00
0c50bc6449 fix(router): url serializer should handle segments without primary children 2016-06-21 12:17:30 -07:00
f164715678 chore(README): fix a typo 2016-06-21 12:17:30 -07:00
2aa615b4ae chore(router): bump up version 2016-06-21 12:17:30 -07:00
42c89b1b9b docs(router): add a README to include a link to the guide 2016-06-21 12:17:30 -07:00
f6b75f56ad fix(router): typo in starts with slash validation error 2016-06-21 12:17:30 -07:00
280540e4a2 fix(router): change serialize not to require parenthesis in query string to be encoded 2016-06-21 12:17:30 -07:00
fea216db12 fix(router): fixes a type issue in a test 2016-06-21 12:17:30 -07:00
b260eb06f6 fix(router): change postinstall hook to devsetup to not require having 'typings' installed 2016-06-21 12:17:30 -07:00
1c937a10f9 chore(router): add changelog 2016-06-21 12:17:30 -07:00
ca23b4c55f feat(router): add route config validation 2016-06-21 12:17:30 -07:00
7e12208ca6 feat(router): do not support paths starting with / 2016-06-21 12:17:30 -07:00
2773281338 feat(router): drop index property
Use path: '/' instead of 'index: true'
2016-06-21 12:17:30 -07:00
f8e8d22e4e fix(router): stringify positional parameters when using routerLink 2016-06-21 12:17:30 -07:00
cf4a9236b9 chore(router): bump up version number 2016-06-21 12:17:30 -07:00
4450e7b246 cleanup(router): enable noImplicitAny and noImplicntReturns 2016-06-21 12:17:30 -07:00
cdbf67ee05 test(router): add a test checking that guards work for child routes 2016-06-21 12:17:30 -07:00
9a67f38728 fix(router): port fixes done on angular current router to the new one
The bugs were fixed on current angular router in the following commits:
angular/angular@b2a7fd05cb
angular/angular@fa2ce8100b
angular/angular@595bcdd1ac

Closes #12
2016-06-21 12:17:30 -07:00
25560ed048 feat(router): implement RouterLinkActive 2016-06-21 12:17:30 -07:00
2aa19fd078 feat(router): support navigating by url tree 2016-06-21 12:17:30 -07:00
cca9a58ded chore(router): bump up version number 2016-06-21 12:17:30 -07:00
d6a25325c7 chore(package): unpin version 2016-06-21 12:17:30 -07:00
a717da2d3e chore(router): bump up version number 2016-06-21 12:17:30 -07:00
b3e801ed9e feat(router): make it work with TypeScript 1.8 2016-06-21 12:17:30 -07:00
3683fb6886 refactor(router): minor refactoring 2016-06-21 12:17:30 -07:00
3bd0ce291e feat(router): mark the index property as deprecated
Use {path: '', component: A} instead of {index: true, component: A}

}#
2016-06-21 12:17:30 -07:00
523fc5536c fix(router): fix Params type to allow passing any value types when calling router.navigate 2016-06-21 12:17:30 -07:00
f5efccfb44 test(router): test update location when route does not change 2016-06-21 12:17:30 -07:00
b6ec22de6b test(router): test empty url with global redirect 2016-06-21 12:17:30 -07:00
15f27b5455 fix(providers): make providers static analysis friendly 2016-06-21 12:17:30 -07:00
127401598b feat(router): implement terminal 2016-06-21 12:17:30 -07:00
503b07f698 docs(router): add a README 2016-06-21 12:17:30 -07:00
f0a6329005 fix(router): fixes a typo 2016-06-21 12:17:30 -07:00
2982892acc cleanup(router): clang-format 2016-06-21 12:17:30 -07:00
9de56481f1 feat(router): add enableTracing option 2016-06-21 12:17:30 -07:00
777eb2f159 feat(router): emit an event when routes are recognized 2016-06-21 12:17:30 -07:00
05eebe0fed feat(router): provide meaningful toString impls 2016-06-21 12:17:30 -07:00
fdfbbd5bac chore(router): bump up version number 2016-06-21 12:17:30 -07:00
1f3f8ef6c8 fix(router): fix nested deactivation 2016-06-21 12:17:30 -07:00
820eeb49d1 chore(router): bump up version number 2016-06-21 12:17:30 -07:00
2d4be1c9eb fix(router): init is not triggered in certain scenarios 2016-06-21 12:17:30 -07:00
2fef30f619 fix(router): make stringify handle nulls 2016-06-21 12:17:30 -07:00
10113b63b5 chore(router): bump up version number 2016-06-21 12:17:30 -07:00
545caab433 fix(router): use bootstrap listener to trigger initial navigation 2016-06-21 12:17:30 -07:00
3f90659cc1 fix(router): supports index routes with path 2016-06-21 12:17:30 -07:00
131914ac94 fix(router): fix lazy loading issues 2016-06-21 12:17:30 -07:00
29a7c4538c Revert "fix(provider): fix a circular dependency & remove common providers"
This reverts commit 6375fdd4f2928d5ddeccaf11a8589a7668bc9049.
2016-06-21 12:17:30 -07:00
f195bb608c chore(router): update config before publishing to npm 2016-06-21 12:17:30 -07:00
66caabca0c feat(router): implement redirectTo 2016-06-21 12:17:30 -07:00
25c6a3715d fix(provider): fix a circular dependency & remove common providers 2016-06-21 12:17:30 -07:00
97cf0e40d5 fix(guards): Cancel in-flight guards if one returns false 2016-06-21 12:17:30 -07:00
6988a550ea cleanup(router): fix tslint errors 2016-06-21 12:17:30 -07:00
8a1cdc2dd5 chore: install typings with npm i 2016-06-21 12:17:30 -07:00
dadd5ddded chore: add lint and clang-format 2016-06-21 12:17:30 -07:00
56f8c95ee9 tests(router): add tests verifying that updating secondary segments using router link works 2016-06-21 12:17:30 -07:00
ed50e17e5b refactor(router): rename queryParameters into queryParams 2016-06-21 12:17:30 -07:00
33b518ad21 feat(router): update RouterLink to support query params and fragment 2016-06-21 12:17:30 -07:00
b0e7c14545 fix(router): add an app initializer to trigger initial navigation
Closes #10
2016-06-21 12:17:30 -07:00
5742d4720a fix(router): fix router to handle guards that return observable
Closes #19
2016-06-21 12:17:30 -07:00
9b356d9b86 fix(router): traverse route config in depth-first order
Closes #17
2016-06-21 12:17:30 -07:00
793ac3f6b4 Configure router with provided routes
Closes #9
2016-06-21 12:17:30 -07:00
9b094e42a3 chore(router): update the npm dist 2016-06-21 12:17:30 -07:00
6ce7a5a1ea docs(router): add docs 2016-06-21 12:17:30 -07:00
88920bfee1 feat(router): add support for basic events 2016-06-21 12:17:30 -07:00
2717bcc3af feat(router): implement cancelation 2016-06-21 12:17:30 -07:00
5d386dc426 chore(router): update build dir 2016-06-21 12:17:30 -07:00
f34af4f249 feat(router): add support for using classes as guard 2016-06-21 12:17:30 -07:00
f04b6978fb cleanup(router): add @internal to constructors where needed 2016-06-21 12:17:30 -07:00
ab958598d7 feat(router): implement CandDeactivate 2016-06-21 12:17:30 -07:00
1914847e72 cleanup(router): make strictNullChecks happy 2016-06-21 12:17:30 -07:00
d95f0fd83d fix(router): fix index routes 2016-06-21 12:17:30 -07:00
243612e36d refactor(router): rename candidate into snapshot 2016-06-21 12:17:30 -07:00
c5cca8e098 feat(router): add support for CanActivate guard 2016-06-21 12:17:30 -07:00
99f7404d8b refactor(router): remove rootNode function 2016-06-21 12:17:30 -07:00
9ff6b0828f feat(router): make activation sync 2016-06-21 12:17:30 -07:00
6f052d1daf feat(router): add a function to resolve components 2016-06-21 12:17:30 -07:00
63c194b71f feat(router): change recognize to return a router state candidate 2016-06-21 12:17:30 -07:00
46911117f1 feat(router): implement a function create router state out of a candidate 2016-06-21 12:17:30 -07:00
2de1030413 feat(router): add RouterStateCandidate 2016-06-21 12:17:30 -07:00
1f6ade894e cleanup(router): fix a typo 2016-06-21 12:17:30 -07:00
8407cfeac7 fix(router): fix router to take root component type instead of instance 2016-06-21 12:17:30 -07:00
91d64a2855 feat(router): export provideRouter via index 2016-06-21 12:17:30 -07:00
40a06af79b feat(router): add provideRouter to configure the router when bootstrapping an app 2016-06-21 12:17:30 -07:00
8aef86f4a0 feat(router): export all public api tokens via index 2016-06-21 12:17:30 -07:00
5bdc6ecec8 chore: adds the build dir to use with 'npm install' 2016-06-21 12:17:30 -07:00
c179b5033b feat(router): implement relative navigation 2016-06-21 12:17:30 -07:00
86f47273bc feat(router): changes router config not to use names 2016-06-21 12:17:30 -07:00
2e1bd46bb1 feat(router): add createUrlTree 2016-06-21 12:17:30 -07:00
a9e773b47b feat(router): serialize outlet names into the url 2016-06-21 12:17:30 -07:00
10d38cbb72 chore(router): change karma reporter 2016-06-21 12:17:30 -07:00
a5371bfb8a cleanup: cleanup tsconfig files 2016-06-21 12:17:30 -07:00
4b2740f270 refactor: move index.ts into src 2016-06-21 12:17:30 -07:00
5b371736b2 feat: add RouterLink 2016-06-21 12:17:30 -07:00
c9b4bcf689 refactor: move all utility functions into the utils dir 2016-06-21 12:17:30 -07:00
013f9a2bbc feat: add tree.siblings 2016-06-21 12:17:30 -07:00
5bf1c93ead docs: adds missing api docs 2016-06-21 12:17:30 -07:00
4f6ec01932 feat: implement a simple version of the router service 2016-06-21 12:17:30 -07:00
0f79e504c9 test: set up karma to support fakeAsync 2016-06-21 12:17:30 -07:00
1a4e911b8b cleanup: fix type errors when compiling with strictNullChecks enabled 2016-06-21 12:17:30 -07:00
1f98519380 feat: implement RouterOutletMap 2016-06-21 12:17:30 -07:00
aad7010952 feat: add RouterOutlet 2016-06-21 12:17:30 -07:00
1be9ea681b fix: fix source maps 2016-06-21 12:17:30 -07:00
f259a2204b feat: implement recognizer 2016-06-21 12:17:30 -07:00
4b1db0e61c feat: implement default url serializer 2016-06-21 12:17:30 -07:00
aee764d14d chore: update TS to enable non-nullable types 2016-06-21 12:17:30 -07:00
47585498af chore: updates typings to head 2016-06-21 12:17:30 -07:00
37c5320e33 feat: implement Tree 2016-06-21 12:17:30 -07:00
01111a1122 cleanup: removes a fake test 2016-06-21 12:17:30 -07:00
0b2bb1b6f5 chore(typings): use typings instead of tsd 2016-06-21 12:17:30 -07:00
f57df3cf8a chore: set up test and build infrastructure 2016-06-21 12:17:30 -07:00
c9c81e1fbc fix(XmbSerializer): add meaning attribute, escape attribute values 2016-06-21 11:52:11 -07:00
e38e04c1c2 refactor(MessageExtractor): pass the interpolationConfig around 2016-06-21 11:52:11 -07:00
99587ea4ed refactor(i18n): misc 2016-06-21 11:52:11 -07:00
58b18d7fe7 fix(partition): fix partition when <!-- i18n --> is the only child 2016-06-21 11:52:11 -07:00
04a50f5832 feat(MessageExtractor): do not expand ICU messages before extraction 2016-06-21 11:52:11 -07:00
e157a065b0 fix(compiler): codegen view query generic types 2016-06-21 11:51:54 -07:00
41ef4b3d4a chore(npm): add repository metadata 2016-06-21 11:21:04 -07:00
262650ab39 docs(test_injector): Fix documentation typo (#9403)
- Change 'teh' for 'the'
2016-06-21 11:19:08 -07:00
8c076d5a73 fix(upgrade): fix bundling issue and fix e2e test
the previous demo app was broken and is missing an e2e test.

I fixed the app, but was not able to get protractor to properly test
this app. Julie and I are looking into that. For now I manually verified
that the app works and that the original issue was fixed.

Closes #9244
2016-06-21 11:12:42 -07:00
c5c456120c refactor: delete containsRegexp() (there is escapeRegExp() in the lang facade)
BREAKING CHANGES:

`containsRegexp` is no more exported from `@angular/core/testing`. It should not have been part of the public API in the first place.
2016-06-21 09:15:21 -07:00
fdf6bc18dd fix(compiler): properly report unresolved dependencies
Fixes #9332

Closes #9341
2016-06-21 16:36:57 +02:00
297f0fd2c3 fix(core/testing): show full error
test(platform-browser): update fail capture

test(platform-browser-dynamic): update fail capture
2016-06-20 19:36:37 -07:00
86405345b7 doc: fix enableDebugTools import path (#9377) 2016-06-20 16:45:35 -07:00
12c49042ab fix(HTTP/XhrBackend): correctly set the status code on errors (#9355)
fixes #9329
fixes angular/http#54
2016-06-20 15:02:14 -07:00
ba46ca683b fix(animations): ensure starting styles are applied when a delay is present
Closes #9326
Closes #9328
2016-06-20 11:16:39 -07:00
ca42b49fa2 refactor: misc cleanup (#9369) 2016-06-20 10:55:29 -07:00
1b28cf71f5 feat(compiler): make interpolation symbols configurable (@Component config) (#9367)
closes #9158
2016-06-20 09:52:41 -07:00
6fd52dfb38 build(npm): update ts-api-guardian to v0.0.4 (#9366) 2016-06-20 09:32:27 -07:00
af2f5c3d7d cleanup(router): removes router 2016-06-20 08:47:54 -07:00
65be81baf8 doc(CheatSheet): update ngSwitch syntax in the cheatsheet (#9361) 2016-06-20 08:05:50 -07:00
7a3689f175 chore: formating 2016-06-19 22:27:29 +02:00
8675b8dc48 fix: cleanup public api of platform-server
BREAKING CHANGE: Parse5Adapter is no longer exported as public API, use serverBootstrap()

Parse5Adapter is an implementation detail not a public API

Closes #9237

Closes #9205
2016-06-19 09:03:01 -07:00
279e816ea7 chore: Remove unnecessary calls to Parse5DomAdapter
This reverts commit 80deac5cde.
2016-06-19 08:42:00 -07:00
2d60ff14ae bug(datePipe): date format pipe's 2-digit interpretation of minutes and seconds (#9338)
Closes #9333
2016-06-18 09:03:58 -07:00
dee1b774f2 docs(changelog): add note about forthcoming deprecation of Parse5DomAdapter (#9314) 2016-06-17 17:34:14 -07:00
c0f2a22a08 fix(perf): support prod mode again
After splitting the facades into multiple modules,
enabling prod mode for code had no effect for the compiler.

Also in a change between RC1 and RC2 we created the `CompilerConfig`
via a provider with `useValue` and not via a `useFactory`, which reads
the prod mode too early.

Closes #9318
Closes #8508
Closes #9318
2016-06-17 15:59:27 -07:00
5c8d3154d7 feat(datePipe): numeric string support 2016-06-17 15:58:06 -07:00
40f8a45b95 test(DatePipe): fixes 2016-06-17 15:48:26 -07:00
76a418760e fix(BrowserUtil): fix supportsIntlApi() 2016-06-17 15:34:12 -07:00
49bf3f5b3a fix(NumberPipe): fix broken RegExp
introduced in 7498050421 (#9308)
2016-06-17 15:33:25 -07:00
773c34900f fix(change_detection): ChangeDetectorRef reattach should restore original mode
After using ChangeDetectorRef detach, it should keep the ChangeDetector mode so that it is restored after calling reattach.

closes #7078
closes #7080
2016-06-17 15:00:41 -07:00
791153c93c fix(compiler): StaticReflector ignores unregistered decorators. (#9266)
Also modified static reflector to allow writing tests in using
the .ts and using the MetadataCollector.

Also made MetadataCollector be able to use SourceFiles that have
not been bound (that is, don't have the parent property set).

Fixes #9182
Fixes #9265
2016-06-17 13:11:00 -07:00
721f53f0d6 feat(I18N Expander): do not add extra <ul> & <li> around ICU messages (#9283)
fixes #9072
2016-06-17 11:38:24 -07:00
7498050421 refactor: misc (#9308) 2016-06-17 10:57:50 -07:00
5e3ccbcea9 refactor: add types (#9288) 2016-06-17 10:57:32 -07:00
8879aa1df4 feat(security): fail more detectably when using a safe value in an interpolation.
If a user ends up with a safe value in an interpolation context, that's probably
a bug. Returning `"SafeValue must use [property]= binding"` will make it easier
to detect and correct the situation. Detecting the situation and throwing an
error for it could cause performance issues, so we're not doing this at this
point (but might revisit later).

Part of #8511 and #9253.
2016-06-17 10:00:30 -07:00
44e0ad4987 refactor(forms): remove the facade local copy (#9276) 2016-06-17 08:53:17 -07:00
c449f325ba docs: remove and identify reverts in changelog 2016-06-17 08:45:40 -07:00
5fe60759f9 feat(QueryList): support index in callbacks
Closes #9278
2016-06-17 08:09:42 -07:00
6c389ed32f ci(local dev): fix test.sh 2016-06-16 15:46:08 -07:00
45549cda61 refactor(core): get rid of the bitwise operator facade helpers 2016-06-16 14:31:55 -07:00
a13052fc73 refactor(core): rename css_parser and css_lexer files
Closes #9273
2016-06-16 14:31:48 -07:00
4e7bb03e81 refactor(core): rename AST to Ast for all CSS parser code 2016-06-16 14:31:41 -07:00
935c39a7e2 feat(core): ensure CSS parser tracks start/end values and understands complex pseudo selectors 2016-06-16 14:31:34 -07:00
e0c1c13004 chore(.github): improve github issue and PR templates
the current templates accidentaly introduce tasks list which is confusing to both people
submitting the issue as well as triaging it.
2016-06-16 14:29:05 -07:00
c08ca22dba refactor(HtmlParser): clang format 2016-06-16 13:54:00 -07:00
a3f6e19881 docs: add missing breaking changes (#9267) 2016-06-16 13:49:29 -07:00
37b617dccf chore(tsickle): add @Annotation annotations
This lets users continue using runtime-sideeffect Decorators if they choose,
only down-leveling the marked ones to Annotations.

Also remove the "skipTemplateCodegen" option, which is no longer needed
since Angular compiles with tsc-wrapped rather than ngc. The former doesn't
include any codegen.
2016-06-16 12:29:46 -07:00
c60ef45bc8 fix(HtmlParser): add missing ; 2016-06-16 10:23:02 -07:00
2b1ac63e3a npm: bump the version number in package.json
this version string is currently not authoritative source see #9264 and #9233
2016-06-16 09:57:44 -07:00
6686bc62f6 feat(benchpress): add custom user metric to benchpress
This is a continuation of #7440 (@jeffbcross).

Closes #9229
2016-06-16 07:30:53 -07:00
1eaa193c51 feat(compiler-cli): add a debug option to control the output
fixes #9208
2016-06-15 18:13:57 -07:00
b620f4f456 feat(DomElementSchemaRegistry): add support for <ng-content> and <ng-container> 2016-06-15 18:13:57 -07:00
e484c62a8d fix(HtmlParser): do not add required parents to template root elements
fixes #5967
2016-06-15 18:13:57 -07:00
9ba400d7d5 fix(HtmlParser): consider <ng-container> when adding required parents 2016-06-15 18:13:57 -07:00
9cbd8f7afc test(ng-container): test nesting ng-containers 2016-06-15 18:13:57 -07:00
8e6e90e703 fix(forms): ngModel should export as ngModel 2016-06-15 17:46:45 -07:00
26676c4833 docs(webworker): add/correct experimental markers to web worker apis 2016-06-15 17:12:40 -07:00
39e0b4903c feat(radio): support radio button sharing a control 2016-06-15 15:27:34 -07:00
54c577cfe0 chore: update cheatsheet for new provider syntax (#9227) 2016-06-15 15:11:39 -07:00
27024915e4 docs: removed iframe from changelog (#9223) 2016-06-15 13:17:45 -07:00
80f3e7591e chore(docs): Update changelog for release (#9217) 2016-06-15 12:49:39 -07:00
933f45ef31 docs(webworkers): add experimental markers for all web worker public apis 2016-06-15 09:38:03 -07:00
4fc37aeabd fix(webworkers): rename all web worker apis with "render" to "ui"
"render" is gramatically incorrect and confusing to developers who used this api.

Since webworker apis were exposed only in master, renaming these before the rc2 release
is not a breaking change
2016-06-15 09:38:03 -07:00
2fd1e88199 fix(forms): suppress forms deprecation warning after first 2016-06-15 08:52:56 -07:00
17f317d31e fix: correct failing to push into builds repo on rerun 2016-06-14 21:28:37 -07:00
5941c92a31 fix: make ci fail when compiler integration test fails 2016-06-14 19:40:43 -07:00
d44d0852e5 Revert "fix: cleanup public api of platform-server"
This reverts commit ac84468f1c.
2016-06-14 19:40:43 -07:00
80deac5cde Revert "chore: Remove unnecessary calls to Parse5DomAdapter"
This reverts commit 387a90e546.
2016-06-14 19:40:43 -07:00
8a54c1a115 refactor(ViewBuilder): cleanup 2016-06-14 19:11:30 -07:00
0dbff55bc6 feat(Compiler): add support for <ng-container>
`<ng-container>` is a logical container that can be used to group nodes but is not rendered in the DOM tree as a node.

`<ng-container>` is rendered as an HTML comment.
2016-06-14 19:11:30 -07:00
22916bb5d1 feat(forms): add easy way to switch between forms modules (#9202) 2016-06-14 18:23:40 -07:00
fe01e2efb7 feat(I18nExtractor): Add file paths to error messages (#9177)
* feat(I18nExtractor): Add file paths to error messages

relates to #9071

* feat(i18n): allow i18n start comments without meaning

* refactor(i18n): cleanup

* test(HtmlParser): Add depth to expansion forms
2016-06-14 17:50:23 -07:00
7afee97d1b fix(platform-server): correctly import private DOMTestComponentRenderer 2016-06-14 17:26:55 -07:00
6fc267f22c fix: split dynamic bits in platform-browser into platform-browser-dynamic
Previously these symbols were exposed via platform-browser-dynamic, then we merged then into platform-browser
thinking that tools would know how to shake off the compiler and other dynamic bits not used with the offline
compilation flow. This turned out to be wrong as both webpack and rollup don't have good enough tree-shaking
capabilities to do this today. We think that in the future we'll be able to merge these two entry points into
one, but we need to give tooling some time before we can do it. In the meantime the reintroduction of the -dynamic
package point allows us to separate the compiler dependencies from the rest of the framework.

This change undoes the previous breaking change that removed the platform-browser-dynamic package.
2016-06-14 15:31:24 -07:00
ac84468f1c fix: cleanup public api of platform-server
BREAKING CHANGE: Parse5Adapter is no longer exported as public API, use serverBootstrap()

Parse5Adapter is an implementation detail not a public API
2016-06-14 13:21:28 -07:00
387a90e546 chore: Remove unnecessary calls to Parse5DomAdapter 2016-06-14 13:07:11 -07:00
6eeb9495d8 chore: have test.sh take platform argument 2016-06-14 13:07:11 -07:00
566b4ef481 ci(snapshots): publish tsc-wrapped snapshots 2016-06-13 18:30:38 -07:00
a191e9697c feat(forms): support setting control name in ngModelOptions 2016-06-13 16:57:10 -07:00
5504ca1e38 feat(compiler): Added support for limited function calls in metadata. (#9125)
The collector now collects the body of functions that return an
expression as a symbolic 'function'. The static reflector supports
expanding these functions statically to allow provider macros.

Also added support for the array spread operator in both the
collector and the static reflector.
2016-06-13 15:56:51 -07:00
5c0cfdee48 fix(forms): separate ngModelGroup from formGroupName 2016-06-13 13:41:39 -07:00
bc888bf3a1 refactor(compiler): Change arguments of CompilerConfig to named arguments
BREAKIKNG CHANGE:
`CompilerConfig` used to take positional arguments and now takes named arguments.

Closes #9172
2016-06-13 13:14:07 -07:00
1745366530 refactor(compiler): make PLATFORM_PIPES / PLATFORM_DIRECTIVES an option on CompilerConfig
This aligns the configuration of platform pipes / directives with offline compilation.

BREAKING CHANGE:
- `PLATFORM_PIPES` and `PLATFORM_DIRECTIVES` now are fields on `CompilerConfig`. 
  Instead of providing a binding to these tokens, provide a binding for `CompilerConfig` instead.
2016-06-13 13:13:45 -07:00
1fb0db4aeb chore(docs): Fixed pre-commit command for clang-format 2016-06-13 12:12:44 -07:00
61960c51a3 feat(forms): compose validator fns automatically if arrays 2016-06-13 11:41:32 -07:00
14a3ade662 chore(typescript): Changed double asterisks in #9100 to single asterisks
As in #9151, these comments are being read as JSDoc comments. This commit is smaller and only touches a few files that are causing errors.
2016-06-13 10:53:31 -07:00
de97687422 docs(i18n): fix typo (#9165) 2016-06-13 10:00:57 -07:00
a0251305ea doc(CompilerCli): Add a bootstrap example for a compiled application 2016-06-13 08:59:16 -07:00
2b8d12ddf0 chore(forms): rename ngControl to formControlName 2016-06-12 13:17:36 -07:00
1f6fd3c8fc refactor: add types (#9148) 2016-06-11 21:23:37 -07:00
55860e1621 fix(animations): ensure AUTO styles are cleared at the end of the state-change animation
Closes #9014
Closes #9015
2016-06-11 00:08:41 -07:00
4d51158b1a fix(animations): ensure the web-animations driver converts style props to camel-case
The web animations API now requires that all styles are converted to
camel case. Chrome has already made this breaking change and hyphenated
styles are not functional anymore.

Closes #9111
Closes #9112
2016-06-10 22:54:10 -07:00
7d9c1e1225 chore(forms): rename ngFormModel to formGroup 2016-06-10 19:10:17 -07:00
d53edfec47 chore(forms): rename ngFormControl to formControl 2016-06-10 17:28:19 -07:00
a6e5ddc5af feat(ComponentResolver): Add a SystemJS resolver for compiled apps (#9145) 2016-06-10 16:31:34 -07:00
b866f32832 chore(forms): rename Control, ControlGroup, and ControlArray classes 2016-06-10 12:00:18 -07:00
97833d48c1 chore(templateOutlet): fix linting 2016-06-10 11:32:09 -07:00
9c0031f7a5 fix: broken build due to bad noImplicitAny merge 2016-06-10 10:35:36 -07:00
164a091c71 feat(NgTemplateOutlet): add context to NgTemplateOutlet
Closes #9042
2016-06-10 10:25:44 -07:00
4ed6cf7519 feat(forms): allow ngModel to register with parent form 2016-06-10 10:24:01 -07:00
5267115481 feat(I18N): generate error on unknown cases
fixes #9094
2016-06-10 08:45:59 -07:00
43148d8233 feat(HtmlLexer): add support for alphabetic cases 2016-06-10 08:45:59 -07:00
537e99b4ea fix(http): respect custom Content-Type header in XHRConnection (#9131)
Fix a bug due to which setting a custom Content-Type header in the
Request led to multiple Content-Types being set on the sent
XMLHttpRequest: first the detected content type based on the request
body, followed by the custom set content type.

Fix #9130.
2016-06-10 08:18:49 -07:00
e1fcab777c fix(ngSwitch): use switchCase instead of switchWhen (#9076) 2016-06-09 22:52:30 -07:00
f39c9c9e75 style(lint): re-format modules/@angular 2016-06-09 17:00:15 -07:00
bbed364e7b chore(tsc-wrapped): update to newest tsickle 2016-06-09 16:45:16 -07:00
3aca5ff9e2 fix(compiler): properly report missing DI tokens (#9065)
Fixes #8245
2016-06-09 16:07:06 -07:00
9146bb0816 docs(DatePipe): Update date doc reference in date_pipe.ts (#9081) 2016-06-09 16:05:13 -07:00
0c9e8dbf60 docs(linker): document the injector argument as mandatory (#9098)
Fixes #8600
2016-06-09 16:03:42 -07:00
b34a04d53a chore(build): activate optional jobs in SL and BS (#8605) 2016-06-09 15:21:17 -07:00
0658eb4429 fix(compiler): Added unit test to ReflectorHost and fixed issues (#9052)
Refactored ReflectorHost to allow it to be tested.
Fixed an issue where the .d.ts was findable but it wasn't used by the project
  (This happens when the .metadata.json file references a module that was not
   needed, such as it doesn't declare any types, and the reference to it was
   elided by TypeScript when writing the .d.ts file).
Added tests for ReflectorHost
2016-06-09 14:51:53 -07:00
e178ee4ba0 fix(compiler): Added support for '* as m' style imports. (#9077)
Also includes fixes for broken test.
2016-06-09 13:23:29 -07:00
9f506cd330 chore(lint): remove unused lint checks
Now that we have --noImplicitAny we don't need these checks for explicit types in specific locations.

Also re-enable the check to disallow keywords as variable names.
2016-06-09 11:34:53 -07:00
729dc3b764 fix(security): support XSSI prefixes with and without commas.
Some implementations use an XSSI prefix with a trailing comma, some without.
This changes Angular to support both.
2016-06-09 11:32:07 -07:00
7ce0fc7d47 refactor: add types (#9116) 2016-06-09 11:04:15 -07:00
b60eecfc47 fix(build): update API spec to include the return value. 2016-06-09 10:11:02 -07:00
346304762e feat(security): document <iframe src> to be TRUSTED_URL.
Docs on the DomSanitizationService didn't match actual usage before.

Also fixes some minor docs and implementation issues.
2016-06-08 20:49:15 -07:00
e213939f28 chore(forms): fix implicit any 2016-06-08 17:08:59 -07:00
4c39eace52 feat(forms): add new forms folder 2016-06-08 16:41:08 -07:00
86fbd50c3d refactor(TypeScript): Add noImplicitAny
We automatically insert explicit 'any's where needed. These need to be
addressed as in #9100.

Fixes #4924
2016-06-08 16:20:50 -07:00
87d824e1b4 fix: add typescript test for our typings (#9096)
* Revert "fix(d.ts): enable angular2 compilation with TS flag --strictNullChecks (#8902)"

This reverts commit 7e352a27f7.

* test: add typescript test for our typings
2016-06-08 16:06:23 -07:00
50acb96130 fix(forms): update value and validity when controls are added
Closes #8826
2016-06-08 14:06:20 -07:00
29c2dcff61 fix(http): remove peerDep on @angular/common
it is not needed there because it will get transitively installed by @angular/platform-browser

we only need to declare this dependency in tsconfig.json because tsconfig.json's
do not support transitive dependencies in this way.
2016-06-08 12:20:42 -07:00
cea103a7ff test: add tree-shaking test
currently this doesn't throw or break the build, first we need to resolve all
of the existing issues.

to run execute: ./tools/tree-shaking-test/test.sh

then inspect dist/tree-shaking/test/**/*.bundle.js
2016-06-08 12:20:42 -07:00
d18694a1c3 Revert "WIP: test: add tree-shaking test (#8979)"
This reverts commit b746c64229.

Reason: bad merge via github ui
2016-06-08 12:20:17 -07:00
b746c64229 WIP: test: add tree-shaking test (#8979)
* test: add tree-shaking test

currently this doesn't throw or break the build, first we need to resolve all
of the existing issues.

to run execute: ./tools/tree-shaking-test/test.sh

then inspect dist/tree-shaking/test/**/*.bundle.js

* fix(http): remove peerDep on @angular/common

it is not needed there because it will get transitively installed by @angular/platform-browser

we only need to declare this dependency in tsconfig.json because tsconfig.json's
do not support transitive dependencies in this way.
2016-06-08 12:15:09 -07:00
d38aa5e25f chore(lint): sort imports in tools/ 2016-06-08 11:29:37 -07:00
efdc2d5118 chore(lint): upgrade clang-format 2016-06-08 11:29:37 -07:00
515a8e0765 fix(forms): rename old forms folder to forms-deprecated 2016-06-08 11:21:58 -07:00
45de65bd45 fix(router): don't mark the RouterOutletMap as internal
it's currently being reexported as public api which breaks anyone tryin to import
the router because the RouterOutletMap definition is missing
2016-06-08 11:18:35 -07:00
9d6b98794e chore: fix build break by using tsickle@0.1.2 2016-06-08 09:34:53 -07:00
7aa1790874 fix(travis): pin the version of tsickle for offline_compiler_test 2016-06-07 17:16:26 -07:00
5cd490eba2 test(public api): sort symbols case insensitive 2016-06-07 15:17:02 -07:00
ac1156739d feat(i18n): extract messages 2016-06-07 15:17:02 -07:00
7cefec77ef fix(ngUpgrade): prevent digest already in progress (#9054)
fix(ngUpgrade): prevent digest already in progress
2016-06-07 15:10:13 -07:00
36d25f2a07 feat(animations): support styling of the default animation state
It is now possible to set a fallback state that will apply its
styling when the destination state is not detected.

```ts
state("*", style({ ... }))
```

Closes #9013
2016-06-07 12:59:33 -07:00
c3d2459a4e fix(query): set fixed @ViewChild / @ContentChild right after the view is created
This is needed to have a true replacement of the previous
`DynamicComponentLoader.loadNextToLocation`, so that components
can be loaded into the view before change detection runs.

Closes #9040
2016-06-07 12:40:35 -07:00
8847580fd7 Revert "fix(compiler): add ability to parse : in * directives"
This reverts commit 53628e19ac.
as it breaks pipe arguments in `*ngFor`, ...

See #9062
Closes #9063
2016-06-07 12:39:17 -07:00
cf3548a02f fix(compiler): Improved error reporting of the static reflector.
StaticReflector provides more context on errors reported by the
collector.

The metadata collector now records the line and character of the node that
caused it to report the error.

Includes other minor fixes to error reporting and a wording change.

Fixes #8978
Closes #9011
2016-06-07 08:38:32 -07:00
c197e2bb42 Revert "fix(ngUpgrade): prevent digest already in progress (#9046)"
This reverts commit d1c989b8a5.

Breaks a Karma test.
2016-06-06 16:30:11 -07:00
d1c989b8a5 fix(ngUpgrade): prevent digest already in progress (#9046) 2016-06-06 13:34:27 -07:00
57c9a07fff chore: fix public api spec for beforeEachProviders
Closes #9043
2016-06-06 09:25:52 -07:00
a19c4e8f9a fix(upgrade): allow functions for template and templateUrl (#9022) 2016-06-04 19:53:51 -07:00
53083c0b52 refactor(testing): type beforeEachProviders (#9023)
these are valid otherwise
```typescript
beforeEachProviders(1)
beforeEachProviders('wat')
beforeEachProviders([
  Http
])
```
2016-06-04 19:52:51 -07:00
994d9212c1 docs(NgControlName): correct exports name (#9021) 2016-06-04 19:48:50 -07:00
52ddc96c9f refactor(compiler): remove obsolete @View-related code (#9019) 2016-06-04 19:46:55 -07:00
057abefe50 fix(compiler): report errors for queries without selectors (#9018)
Fixes #4489
2016-06-04 19:46:03 -07:00
f0e24b1a1e chore(karma): remove ref to legacy files (#9008) 2016-06-04 14:44:59 -07:00
a1e3004e62 docs(animations): provide API docs for the animation DSL
Closes #8970
2016-06-03 18:57:17 -07:00
e504d4eb05 fix(renderer): remove unecessary setElementStyles method
There is no need to expose this additional method inside of the Renderer
API. The functionality can be restored by looping and calling
`setElementStyle` instead.

Note that this change is changing code that was was introduced after
the last release therefore this fix is not a breaking change.

Closes #9000
Closes #9009
2016-06-03 15:20:34 -07:00
a6ad61d83e refactor: change provide(...) for {provide: ...}
- provide() is deprecated,
- {} syntax is required by the offline compiler
2016-06-03 15:03:49 -07:00
27a47e7841 refactor(imports): simplify paths 2016-06-03 14:46:04 -07:00
b00b9fe564 chore(README): update missing step in compiler-cli docs README 2016-06-03 14:36:16 -07:00
fa0718ba9a feat(animations): provide support for offline compilation 2016-06-03 14:36:11 -07:00
155b88213c feat(debug): collect styles and classes for the DebugElement 2016-06-03 14:36:06 -07:00
35ea02fb81 fix(compiler): Reflector generates imports for '..' relative modules.
Fixes #9003
Closes #9004
2016-06-03 12:54:30 -07:00
ddd2ac4f55 fix(core): fix type of DebugNode.properties (#8964)
Properties can have any value, not just strings.
2016-06-03 10:51:13 -07:00
6f281ab3c4 fix(HTMLParser): properly report errors for not properly closed tags (#8999)
Fixes #7849
2016-06-03 10:49:17 -07:00
fe8a7b0e82 fix(test-runner): make karma internal reporter compatible with 0.13.20 (#8977)
causes internal reporter to produce output messages again after upgrade to 0.13.20
2016-06-03 10:48:55 -07:00
76e6214b9b chore: remove angular_entry point (#8975)
this is obsolete and no longer needed. it wasn't part of the public api so it's ok to remove.
2016-06-03 10:47:55 -07:00
2d8f776e38 feat(build): Added a version stamp in .metadata.json files.
Also modified StaticReflector to handle multiple versions in a
single .metadata.json file.

Fixes #8974
Closes #8981
2016-06-03 10:40:49 -07:00
cf2d3cf920 style(pipes): cleanup unused imports
Remove unused imports from the pipes package. No impact on the code.
2016-06-03 09:58:57 -07:00
b160ada5d1 fix: QueryList documentation (#8976) 2016-06-02 16:24:26 -07:00
1090601e8b refactor(compiler): rename /compiler_cli to /compiler-cli 2016-06-02 13:50:32 -07:00
01dd7dde24 chore(tools): Remove use of TypeChecker from metadata collector.
The metadata collector was modified to look up references in the
import list instead of resolving the symbol using the TypeChecker
making the use of the TypeChecker vestigial. This change removes
all uses of the TypeChecker.

Modified the schema to be able to record global and local (non-module
specific references).

Added error messages to the schema and errors are recorded in
the metadata file allowing the static reflector to throw errors
if an unsupported construct is referenced by metadata.

Closes #8966
Fixes #8893
Fixes #8894
2016-06-02 13:39:15 -07:00
13c39a52c6 chore(compiler): Expose types needed by the language service
The language service needs access to the parser error ranges and
the static reflector.

Closes #8838
2016-06-02 13:36:22 -07:00
3b80ab51ba feat(SchemaRegistry): add Node.textContent
fixes #8413
2016-06-02 13:33:57 -07:00
1a386a58c8 Revert "feat(change_detection): make INTERPOLATE_REGEXP customizable (#7417)"
This reverts commit c3fafa0651.

The symbols should be configured at the component level and not be global to the compiler.
2016-06-01 17:53:45 -07:00
04220be8fd chore: remove old tests (#8954)
These tests are already captured in template_parser_spec.ts
2016-06-01 16:30:51 -07:00
48bf349c3c fix(upgrade): allow deeper nesting of ng2 components/directives (#8949)
* fix(upgrade): add test for upgrade adapter bug

* fix(upgrade): allow deeper nesting of ng2 components/directives

allow a nesting sequence of ng2 > ng1 > ng2 directives
2016-06-01 15:58:40 -07:00
21fc1bb655 fix(core): Keep core exports seperate from core/testing exports. (#8930) 2016-06-01 15:37:23 -07:00
d38d375fa6 test: enforce sorting of the public_api_spec
Closes #8950
2016-06-01 15:06:52 -07:00
602836800b feat(ChangeDetectorRef): make detectChanges() correct
Closes #8599
2016-06-01 12:50:47 -07:00
2953ea10a7 chore: fix public_api_spec after rebase 2016-06-01 10:57:45 -07:00
a738d0d54d chore: remove old public api tests
the tests under tools/public_api_guard have better coverage - we no longer need
to maintain two copies.
2016-06-01 10:43:57 -07:00
d781e69948 style(public_api_spec): reformat 2016-06-01 10:43:22 -07:00
c9b71fb5e2 test: add lots of missing stuff to the public_api_spec
- many entry points were previously missing (e.g. all testing entry points, http, etc)
- upgrade ts-api-guardian to 0.0.3 that adds support for more api surface
- add all info to the spec that was surfaced by ts-api-guardian@0.0.3
2016-06-01 10:43:22 -07:00
dd6cb233b5 build: add missing testing.ts entry points to tsconfigs 2016-06-01 10:43:22 -07:00
a3cf58b67a docs: add info about __moduleName into to DirectiveMetadata docs 2016-06-01 10:43:22 -07:00
70d944a59c refactor: correct api modifier flags 2016-06-01 10:43:22 -07:00
a5a422f8e7 refactor(NumberPipe): remove NumberPipe and replace it with private helper function
NumberPipe was just an implementation detail that we were accidentaly exposing as a public api.
2016-06-01 10:43:22 -07:00
e93b3d2360 fix(Location): make Location#platformStrategy:LocationStrategy property private
BREAKING CHANGE: Location#platformStrategy property was previously accidentaly exported as public

If any application requires access to the current location strategy, it should be accessed via DI instead
by injecting the LocationStrategy token.

The likelyhood of anyone actually depending on this property is very low.
2016-06-01 10:43:22 -07:00
7bc2d9a93a docs: add api stability indicators for @angular/common 2016-06-01 10:43:22 -07:00
1c929031a2 feat(router): export RouterLink and RouterOutlet (#8912)
Makes it possible to individually import RouterLink and RouterOutlet from the router module
2016-06-01 09:48:55 -07:00
f2809d1ed8 fix(PostMessageBus):Add the worker scope to please Closure 2016-06-01 09:24:05 -07:00
f4f6b8721a fix(core): Keep core exports seperate from core/testing exports. 2016-05-31 20:09:43 -07:00
7e352a27f7 fix(d.ts): enable angular2 compilation with TS flag --strictNullChecks (#8902)
Eliminate the following compiler error when using TS 1.9+ with strict null checking enabled:

node_modules/@angular/core/src/util/decorators.d.ts(9,5): error TS2411: Property 'extends' of type 'Type | undefined' is not assignable to string index type 'Type | Function | any[]'.

https://github.com/angular/angular/issues/8720
2016-05-31 18:25:48 -07:00
Tom
0c6b16c208 docs(async-pipe): include observable example (#8900)
Adds the missing example, usage case and a bit more description to the async-pipe.
2016-05-31 18:23:29 -07:00
10475b859d chore: remove deep imports to fix build. 2016-05-31 17:13:41 -07:00
4d793c4eb8 feat(security): Automatic XSRF handling.
Automatically recognize XSRF protection cookies, and set a corresponding XSRF
header. Allows applications to configure the cookie names, or if needed,
completely override the XSRF request configuration by binding their own
XSRFHandler implementation.

Part of #8511.
2016-05-31 16:12:33 -07:00
3ae29c08ac chore(build.sh): make tsc-wrapped installable 2016-05-31 10:43:21 -07:00
3331321f64 build(ngc): run integration test hermetically
This ensures we run in a clean directory, using our real distribution. It finds bugs like @internal
APIs needed to type-check in the offline compiler, as well as problems in package.json.

Also move tsc-wrapped under tools/@angular
2016-05-27 17:21:35 -07:00
c6064a30a1 chore(package.json): make the packages installable 2016-05-27 17:21:34 -07:00
040b101842 feat(security): complete DOM security schema.
This addresses several oversights in assigning security contexts to DOM schema
elements found by our security reviewers (thanks!).

This also adds some more precise unit tests for the interaction between
(Dom)ElementSchemaRegistry and the TemplateParser, and extracts the security
specific parts into dom_security_schema.ts.

Comparison of (potentially) dangerous property names is done case insensitive,
to avoid issues like formAction vs formaction.

Part of issue #8511.
2016-05-27 11:47:33 -07:00
a78a43c816 chore(readme): add CircleCI status badge 2016-05-27 11:40:30 -07:00
ec198b0dc6 docs(MockConnection) add mockError usage example (#8888) 2016-05-27 10:10:53 -07:00
b5d14c26d2 chore(lint): enable requireInternalWithUnderscore tslint check 2016-05-27 09:31:18 -07:00
2019050db2 chore(lint): enable duplicateModuleImport tslint check 2016-05-27 09:17:08 -07:00
5f999225ba chore(lint): upgrade tslint and its gulp plugin 2016-05-27 09:17:08 -07:00
307d105d2c fix(DomRegistry): fix svg support 2016-05-26 16:46:00 -07:00
0b6967fd74 chore(lint): fix missing semicolon to make Circle green (#8877) 2016-05-26 16:43:15 -07:00
ef0c32512c chore(lint): enable semicolon and variable-name tslint checks 2016-05-26 15:46:03 -07:00
9096481744 chore(lint): format tools dir 2016-05-26 15:46:03 -07:00
5936624d11 chore(lint): re-enable clang-format on tools/ 2016-05-26 15:46:03 -07:00
83723671af chore(lint): upgrade clang-format and gulp plugin 2016-05-26 15:46:03 -07:00
4d825dd9fd refactor(intl facade): remove outdated code 2016-05-26 14:19:26 -07:00
8e38291156 chore: enable the compiler_cli tests again.
Closes #8864
2016-05-26 13:33:26 -07:00
f93512bf27 feat(ViewEncapsulation): default ViewEncapsulation to configurable
BREAKING CHANGES:

DirectiveNormalizer takes new constructor arguments, `config:CompilerConfig`.

Closes #7883
2016-05-26 13:23:37 -07:00
c3fafa0651 feat(change_detection): make INTERPOLATE_REGEXP customizable (#7417)
BREAKING CHANGES:

`Parser` constructor required new parameter `config: CompilerConfig` as second argument.
2016-05-26 13:08:39 -07:00
9036f78b74 fix(compiler): throw an error if variable with the same name is already defined. (#7209)
* fix(compiler): throw an error if variable with the same name is already defined. Closes #6492

* fix(compiler): Clean up formatting for issue #6492

* fix(compiler): throw an error if reference with the same name is already defined.

Closes #6492
2016-05-26 13:04:17 -07:00
263122ea5f upgrade karma to v0.13.20 due to 'invalid characters in the headers on Node 5.6.0'(#1884) issue
Closes #7885
2016-05-26 12:12:35 -07:00
324f0147f6 feat(common/datePipe): change date formatter to use correct pattern closes #7008 (#8154)
- add regular expression to parse date parts
- add date part creator function
- replace tokens in pattern to parsed parts
2016-05-26 12:06:29 -07:00
Jon
be48ba1b06 fix(ngRouteShim): update anchors to function similar to angular 1.x (#8478)
* fix(ngRouteShim): update anchors to function similar to angular 1.x when setting the target

* fix(ngRouteShim): update anchor target check to use indexOf instead of contains on array
2016-05-26 12:03:23 -07:00
b2a7fd05cb fix(router): replace state when path is equal to current path (#8766)
Same as 2bf21e1747 but for new router.

This also fixes an issue where when application loads it clears forward history
because Router constructor calls navigateByUrl which was causing a push state to happen.
2016-05-26 12:02:24 -07:00
84f859d7b2 fix(Control): Support <select multiple> with Control class (#8069) 2016-05-26 12:01:49 -07:00
cbc8d0adf8 fix(upgrade): Ensure upgrade adapter works on angular.js 1.2 (#8647) 2016-05-26 11:58:47 -07:00
420e83a396 feat(forms): add the submitted flag to NgForm and NgFormModel directives
Closes #2960

Closes #7449
2016-05-26 10:48:13 -07:00
89f61087c7 feat(http): implement Response.prototype.toString() to make for a nicer error message
Added a toString method to the Response class displaying status / status and
the URL of the request.

Closes: https://github.com/angular/http/issues/89

Closes #7511
2016-05-26 10:38:29 -07:00
1dcb663917 readme: update badges
Closes #8853
2016-05-26 10:35:22 -07:00
b7b56785d1 fix(Renderer): update signatures to make RenderDebugInfo optional
The code does not force the user to provider `RenderDebugInfo`. The
current implementation lists this as a mandatory parameter. Update
the parameter to be optional.

Fixes #8466

Closes #8859
2016-05-26 10:32:03 -07:00
3a62023260 docs(LifecycleHooks): correct ngDoCheck description (#8807)
The current behavior is for ngDoCheck to supplement, not override, the default change detector. OnChanges will still be called when DoCheck is implemented (fixes #7307).
2016-05-26 10:27:42 -07:00
fa2ce8100b fix(router): openning links in new tab
Clicks on router-link should not prevent browser default action when
any mouse button other than left mouse button or ctrl/meta key is pressed.

router-link href should use location strategy external url.

Closes #5908
Closes #6806
Closes #7749
Closes #8806

Closes #8821
2016-05-26 10:26:57 -07:00
172a5663ef feat(platform-browser-dynamic): re-add a deprecated platform-browser-dynamic 2016-05-26 10:22:39 -07:00
f4b972815b fix(platform-browser): fix rollup config 2016-05-26 10:21:24 -07:00
0cb93a436d fix(testing): add discardPeriodicTasks to be used with fakeAsync (#8629)
Closes #8616
2016-05-26 10:19:30 -07:00
b2e804c961 fix(metadata): Allow spacing in multiple selectors (#7418) 2016-05-26 10:18:31 -07:00
85ce184197 refactor(compiler): remove obsolete code (#8837) 2016-05-26 10:15:47 -07:00
29c77df4be fix(playground): fix WebWorker single_thread example 2016-05-26 09:58:40 -07:00
c39e0463a3 removes tailing slashes from url in lines 229 231
Closes #8718
2016-05-26 09:58:14 -07:00
c27bc1956b docs(API): correct formatting of EmbeddedViewRef API doc example (#8584)
remove ... from EmbeddedViewRef API example doc comments
2016-05-26 09:53:18 -07:00
72707d80ab refactor(api): Correct public api variable names (#8552)
Correct public api variable names (ROUTER_DEPRETACED_TESTING -> ROUTER_DEPRECATED_TESTING)
2016-05-26 09:52:56 -07:00
f18356307b chore: consistent badge shape (#7053) 2016-05-26 09:52:06 -07:00
00475f25c8 fix(doc): Add missing comma in example (#8769) 2016-05-26 09:50:59 -07:00
bab6023eee fix(router): Added pushState fallback for IE 9 browser.
Closes #6506

Closes #7929
2016-05-26 09:49:07 -07:00
5e12a95789 test(security): test case for quoted URL values.
Test case that fixes #8701. This is already supported with the latest sanitizer
changes, but it's good to have an explicit test case.
2016-05-26 09:39:23 -07:00
e5904f4089 fix(facade): change EventEmitter to be sync by default (#8761) 2016-05-26 09:34:04 -07:00
cf1122cf9e chore(contributing): fix link to styleguide (#8781)
- Change to new link to Google's JavaScript style guide
2016-05-26 09:30:31 -07:00
6da79673c2 test(e2e): workaround flaky e2e tests 2016-05-25 17:52:30 -07:00
352ee53202 Revert "feat(AsyncPipe): allow onError argument"
This reverts commit 390046d7b3.
CI fails for IE on win8.
PR #7990
2016-05-25 17:23:20 -07:00
a20639558b npm: invoke webdriver-manager update after install 2016-05-25 16:51:30 -07:00
b9347eb01c build: remove dependency on tsd and use @types/* instead 2016-05-25 16:42:28 -07:00
4dbd8ed6b8 refactor: remove unnecessary annotations 2016-05-25 16:42:28 -07:00
cb980d3e43 fix(ci): incorrect import 2016-05-25 16:22:55 -07:00
f154e2c6cf fix(ci): extra API in public_api_spec 2016-05-25 16:14:03 -07:00
d0a64f9c86 fix: broken build 2016-05-25 16:03:11 -07:00
16ef21d086 fix(ngc): depend on correct tsc-wrapped package 2016-05-25 15:34:43 -07:00
39ecd01b86 chore: audit @angular/core API classification (#8808) 2016-05-25 15:00:05 -07:00
5e0f8cf3f0 feat(core): introduce support for animations
Closes #8734
2016-05-25 13:56:50 -07:00
6c6b316bd9 chore(ngc): fix release instructions 2016-05-25 13:25:26 -07:00
b49dac7be5 chore(git): update .gitignore 2016-05-25 10:03:42 -07:00
4c26397937 chore(ngc): refactor out tsc-wrapped
This allows angular's build to depend on some extensions, but not on code generation, and breaks a cycle in the angular build
We now merge ts-metadata-collector into tsc-wrapped and stop publishing the former.
2016-05-25 09:45:55 -07:00
e26e4f922e build(npm): trigger tsd install from npm postinstall 2016-05-25 08:24:59 -07:00
2ab1085dfb fix(core): remove @internal annotation from PLATFORM_CORE_PROVIDERS
This symbol is no longer reexported at the top level, so it's safe to not mark it as internal.

This fixes the offline compilation which got broken by this symbol not being present in the d.ts
files when the compiler tries to do a deep import.

Closes #8819
2016-05-25 08:22:07 -07:00
acc3a2de83 build: add comment for the tools watch option 2016-05-25 08:22:07 -07:00
12abdd8782 ci: disable the lint job since it's currently not doing anything
the tslint rules have not been executed on the right files when this the lint was reenabled
after package splitup making the linting useless. When reenabled lots of errors show up.
I'm disabling this for now until we come back and fix up the code issues.
2016-05-25 08:22:07 -07:00
2bcdec5aaf build: use connect web server instead of SimpleHTTPServer
Several flakes on CI have been tied to long running SimpleHTTPServer that was
put in place temporarily after the package splitup.
2016-05-25 08:22:07 -07:00
16dfe3c63f build: consolidate tsc to ease migration to @types/ based typings delivery
I actually tried to use @types/* directly but came across several issues which prevented me
from switching over:
- https://github.com/Microsoft/TypeScript/issues/8715
- https://github.com/Microsoft/TypeScript/issues/8723
2016-05-25 08:22:07 -07:00
b4a467e387 ci: disable debug output from env.sh 2016-05-25 08:22:07 -07:00
53628e19ac fix(compiler): add ability to parse : in * directives
- Add ability to parse bindings properly when `:` is present when using a directive with the `*` prefix

Closes #6038
2016-05-24 21:43:10 -07:00
62dd3ceb64 Revert "Don't update the location during initial router navigation"
This reverts commit d5066a9a0f.
2016-05-24 21:43:10 -07:00
d5066a9a0f Don't update the location during initial router navigation
Closes #6069
2016-05-24 21:34:59 -07:00
32b37432b0 docs: Remove text about Dart Editor bundle; clean up text
* Remove text about Dart Editor bundle.
* Fix links.
* Fix some grammar and Markdown formatting.

Closes #5928
2016-05-24 21:34:18 -07:00
5f3d02bc7c fix(Animation): Problem decimals using commas as decimal separation
Tests where failing due to `.` character being used as decimal separator in some regional settings (like spanish for example)

Closes #6335

Closes #6338
2016-05-24 21:23:46 -07:00
97e94dd6e0 docs(DEVELOPER): JDK is needed for protractor to run selenium
Closes #6343
2016-05-24 21:23:18 -07:00
846c031ec9 changelog: Update name of a lifecycle hook
Closes #6418
2016-05-24 21:22:57 -07:00
92340350d2 fix(http): Set response.ok
The ok property was not being set on response objects.
It's now set based on the status code.

Closes #6390
Closes #6503
2016-05-24 21:20:27 -07:00
d4827caa08 refactor(DomRootRenderer): allow registeredComponents access
Closes #6584
2016-05-24 21:17:11 -07:00
6ce13b68fa Typo in web_workers.md
Closes #6694
2016-05-24 21:16:11 -07:00
e82b700ad0 docs(directives): add a deprecation comment for properties, events
Closes #7059
2016-05-24 21:13:56 -07:00
60a2ba87d4 fix(#7099): support for comment finishing with multiple dashes
<!-- xxxx ------->

The issue came from a lack of support for backtracking on string
matching.
The way it is done, if the "end pattern" for consumeRawText starts with
twice the same character, you end up having problem when your string
being parsed has 3 times this char

Example
End string: xxyz
string to parse: aaaaaaxxxyz

calling consumeRawText(false, 'x', attemptParseStr('xyz')) would fail

Closes #7119
2016-05-24 21:01:41 -07:00
83c19a1fbc fix(pipes): handle undefined value in slice
Closes #7152
2016-05-24 20:58:14 -07:00
1513e201bb docs(ContentChild): add example with refs
Add missing example on how ContentChild works with refs
Closes #7160
Closes #7162
2016-05-24 20:57:25 -07:00
a38c9a1ef7 fix(Router) Fix detect active route in depth. 2016-05-24 20:51:51 -07:00
d5f5ce82ca Revert "fix(compiler): support string tokens with . inside."
This reverts commit 67c80fbb5e.
2016-05-24 20:51:51 -07:00
6dc88f5b61 fix(forms): radio buttons with different names should not share state
Closes #7051
2016-05-24 20:07:57 -07:00
7a2ce7ff21 fix(forms): update accessor value when native select value changes
Closes #8710
2016-05-24 20:03:49 -07:00
1ac38bd69a feat(renderer): add a setElementStyles method 2016-05-24 18:42:05 -07:00
982fad0c45 test(public API): Update the public API to the new world 2016-05-24 14:00:00 -07:00
390cefac72 refactor(testing/server): optimize imports 2016-05-24 14:00:00 -07:00
798bface7f fix(typings): remove rxjs workaround
Closes #7198
2016-05-24 12:45:46 -07:00
276fec6e50 build(serve): added cors support for the js.server task
Added and used the cors middleware:
- add the module as a dev depedency in the package.json file
- require the module in the jsserve.js file
- add the module in the middleware list

Closes #7273

Closes #7274
2016-05-24 12:00:40 -07:00
95af14b97c feat(http): added withCredentials support
Taken into account the withCredentials property within the request options:
- added corresponding property in the RequestOptions class
- added corresponding property in the Request class
- handle this property when merging options
- set the withCredentials property on the XHR object when specified

Added a test in the xhr_backend_spec.ts to check that the property is actually
set on the XHR object

Closes https://github.com/angular/http/issues/65

Closes #7281

Closes #7281
2016-05-24 11:53:43 -07:00
0f0a8ade7c feat(http): automatically set request Content-Type header based on body type
Implement the ability to provide objects as request body. The following use cases
are supported:
* raw objects: a JSON payload is created and the content type set to `application/json`
* text: the text is used as it is and no content type header is automatically added
* URLSearchParams: a form payload is created and the content type set to `application/x-www-form-urlencoded`
* FormData: the object is used as it is and no content type header is automatically added
* Blob: the object is used as it is and the content type set with the value of its `type` property if any
* ArrayBuffer: the object is used as it is and no content type header is automatically added

Closes https://github.com/angular/http/issues/69

Closes #7310
2016-05-24 11:42:37 -07:00
e0c83f669e fix(build): force a compatible baseURL for systemjs-builder
Closes #7167

Closes #7360
2016-05-24 11:16:33 -07:00
016f0d8e9e fix: Typo in async.ts
Closes #7382
2016-05-24 11:15:51 -07:00
3e5716ec16 feat(i18n): support implicit tags/attributes 2016-05-24 10:12:19 -07:00
75e6dfb9ab fix(browser): platform code cleanup 2016-05-23 17:57:28 -07:00
9634e8d14a fix(tests): Execute the security specs only once 2016-05-23 17:57:28 -07:00
f95a604b59 fix(bootstrap): swap coreBootstrap() and coreLoadAndBootstrap() arguments 2016-05-23 17:57:28 -07:00
3ff20cd7e3 fix(core): fix build 2016-05-23 17:56:48 -07:00
17f73cb7bc docs(http): fix MockBackend example
imports & syntax error fix

Closes #7383
2016-05-23 17:22:36 -07:00
fba0e2ff12 docs(browser.ts): update bootstrap injector override argument name
Closes #7387
2016-05-23 17:21:07 -07:00
b62415c962 refactor(chore): remove unused mapToObject and objectToMap methods from serializer
resolves #7402

Closes #7416
2016-05-23 17:20:25 -07:00
adc135e6c8 refactor(async_pipe): use subscription strategy interface
The strategies for Promise and Observable based subscriptions
have (nearly) the same method signatures. They should implement
a common interface.

Closes #7573
2016-05-23 17:02:56 -07:00
ceac045a7f fix(compiler): have CSS parser support nested parentheses inside functions
Closes #7580
2016-05-23 16:58:15 -07:00
4d6da7b1a7 chore: Update ISSUE_TEMPLATE.md
Closes #7591
2016-05-23 16:56:38 -07:00
01b9de7a15 Bump reflect-metadata to 0.1.3
Closes #7663
2016-05-23 16:40:24 -07:00
0a872ffd38 feat(core/linker): add SimpleChanges type to lifecycle_hooks to simplify OnChanges signature
Closes #8557
2016-05-23 16:11:52 -07:00
6f3a6a55a0 Update url_search_params.ts
Values need to be encoded on `toString()`
Closes #7686
2016-05-23 14:43:26 -07:00
0795dd307b refactor(chore): Replace all 'bindings' with 'providers'
BREAKING CHANGE

Deprecated `bindings:` and `viewBindings:` are replaced with
`providers:` and `viewProviders:`

Closes #7687
2016-05-23 13:31:08 -07:00
49fb7ef421 refactor(router): Remove deprecated 'as' from ‘RouteConfig’
BREAKING CHANGE
Remove deprecated 'as' from ‘RouteConfig’ in favour of ‘name’
2016-05-23 13:24:36 -07:00
cbeeff2bd6 fix(Router): do not kill event-emitter on navigation failure
Closes #7692
Closes #7532

Closes #7692
2016-05-23 13:21:09 -07:00
ce013a3dd9 feat(regex_url_paths): add regex_group_names to handle consistency with serializers
By adding `regex_named_groups` to regex route configurations we can consistently map
regex matching groups to component parameters.

This should fix #7554.

Closes #7694
2016-05-23 13:18:21 -07:00
1f7449ccf4 fix(changelog): add View decorator breaking change
Closes #7707
2016-05-23 13:14:25 -07:00
c43636f2bb fix(di): type error in InvalidProviderError
- when trying to instantiate an injector with an unknown provider
Closes #7729
2016-05-23 13:08:10 -07:00
9c2fe660a3 (docs) Fix pony greeting equivalence
Cheatsheet claims equivalence between two statements which are not equivalent
Closes #7761
2016-05-23 13:07:20 -07:00
abc266fa35 feat(common): DatePipe supports ISO string
Closes #7794
2016-05-23 12:30:02 -07:00
7d853dd9ad fix(router): ensuring MatchedUrl pass query params 2016-05-23 12:24:08 -07:00
3ac2821a1b Update CHANGELOG.md
remove duplicated content
Closes #7852
2016-05-23 10:27:38 -07:00
666dc75c15 chore(lint): disallow duplicate imports from a module
Closes #7859
2016-05-20 15:59:33 -07:00
e9332c66d2 chore(lint): re-enable linter and fix violations
fixes #7798
2016-05-20 15:49:18 -07:00
830aecd1a7 fix(router/parsing): change route rule error to say PascalCase instead of CamelCase
Closes #7874
2016-05-20 15:39:30 -07:00
ac6959c819 docs(DEVELOPER.md): add a clearer explanation about Windows symlinks issue. (#7886)
add a clearer explanation about Windows symlinks issue.
2016-05-20 15:34:59 -07:00
2bf21e1747 fix(Router): replace state when normalized path is equal to current normalized path
Make sure the same path is not added multiple times to the history.
It is replacing the state, instead of skipping it completely,
because the current path in the browser might not be normalized,
while the given one is normalized.

Closes #7829

Closes #7897
2016-05-20 15:30:15 -07:00
9105ab9596 docs(ng_control_group): update API docs syntax
Closes #7357
2016-05-20 13:19:08 -07:00
165357bfa3 refactor(forms): remove useless imports
Remove useless imports and change `bindings` to `providers` inside
`@Component` and `@Directive`.

Closes #7904
2016-05-20 13:13:27 -07:00
33c7f74cb9 style(platform/browser): fix type spacing
Closes #7980
2016-05-20 13:05:40 -07:00
cb84cbf545 feat(shadow_css): add encapsulation support for CSS @supports at-rule
Closes #7944
2016-05-20 13:02:24 -07:00
b2e0946696 fix(Request): Change Request.text's return type to string
Change the return typing for the .text method to `string` so typescript treats it
like a normal string.

Closes #8138
2016-05-20 12:57:31 -07:00
67c80fbb5e fix(compiler): support string tokens with . inside.
Closes #8178
2016-05-20 12:56:02 -07:00
c574e6808f build: update to rollup@0.26.3 and remove a hack from the build 2016-05-20 11:00:49 -07:00
9175a049d3 feat: add minified bundles
BREAKING CHANGE: bundles are now in the bundles/ subdirectory within each package
2016-05-20 11:00:49 -07:00
0035575c82 build: turn on tsc's stripInternal when producint public d.ts file
I also made some changes to fix imports and remove some stuff that caused
breakage once stripInternals was turned on.
2016-05-20 10:59:57 -07:00
7bfe8aa553 build: fix the build.sh script to work out of the box 2016-05-20 10:59:32 -07:00
1bff47f97d docs: fix syntax errors in HostBinding metadata example
add missing parentheses to getters, remove erroneous brackets in HostBindings

Closes #8269
2016-05-20 10:56:11 -07:00
e8e61de28d refactor(WebWorker): move XHR worker side 2016-05-20 10:48:55 -07:00
54f8308999 refactor(browser): merge static & dynamic platforms 2016-05-20 10:48:55 -07:00
6c99746f0b Update tools.ts
Closes #8296
2016-05-20 10:12:54 -07:00
4086b49046 feat(enableDebugTools): return ComponentRef
allows for 
```
bootstrap(App, [
  ...HTTP_PROVIDERS,
  ...ROUTER_PROVIDERS
])
.then(enableDebugTools)
```
without breaking the rule of always returning a value in a promise
2016-05-20 10:12:54 -07:00
0fad9c2786 docs(changelog): fix ViewQuery read usage example in beta.16 (#8330) 2016-05-20 10:06:46 -07:00
d75f928fca fix(docs): Fix a missing opening bracket (#8331) 2016-05-20 10:06:15 -07:00
68f9aaf214 chore: Update CHANGELOG.md (#8342) 2016-05-20 10:05:52 -07:00
efe4633b15 refactor(empty.ts): typo (#8398)
emppty -> empty
2016-05-20 10:02:47 -07:00
19e65382f7 fix(core): accurate dev mode message for dart (#8403) 2016-05-20 10:01:39 -07:00
5a9eda73b4 docs(changelog): line numbers to master can go out of sync (#8404) 2016-05-20 10:00:47 -07:00
c6f2b3e96b docs(router): fix import and replace RouteConfig (#8433) 2016-05-20 10:00:04 -07:00
55921be1af fix(router-deprecated): export RootRoute
Fix #8449

Closes #8450
2016-05-20 09:59:21 -07:00
9b1b5b393d fix(angular1_router): ngLink should not throw an error if routeParams are undefined (#8460)
If routeParams are undefined, no href attribute should be rendered. This matches the behaviour of ngHref.
2016-05-20 09:48:03 -07:00
3857c8226e docs(MockConnection) update mockRespond usage example (#8487)
Closes: #8486
2016-05-20 09:19:59 -07:00
595bcdd1ac fix(router): browser back and forward buttons not working correctly.
Closes #8524

Closes #8532
2016-05-19 22:42:08 -07:00
27c25bd0e8 chore(readme): update README: no longer beta (#8405) 2016-05-19 22:23:33 -07:00
aec95015f8 docs(http): update http docs to mention .map operator
Closes #5947
2016-05-19 22:15:26 -07:00
b2db6401cc chore(build): remove use of q.denodeify
This change also makes webpack properly reject
promise on build errors

Closes #6546
2016-05-19 22:01:52 -07:00
ebe14720eb chore(typing): enforce --noImplicitAny for tools directory.
Exposed a couple of bugs.

Closes #6645
2016-05-19 21:38:01 -07:00
7112d008d0 fix(IE): make shim work with instrumented code
Closes #6944
2016-05-19 20:50:01 -07:00
90af4763d8 fix(angular1_router): delay view update until activate promise gets fullfilled
If the $routerOnActivate hook returns a promise, the navigation
is commited, once the promise gets fullfilled but the view
is updated immediately.

This commit delays the view update so that both (view and url) are
updated at the same time.

Closes #7777
2016-05-19 20:42:06 -07:00
390046d7b3 feat(AsyncPipe): allow onError argument
Closes #7990
2016-05-19 16:49:28 -07:00
587c119c75 feat(NgZone): isStable
Closes #8108
2016-05-19 16:10:08 -07:00
3019140e7e feat(http): set the statusText property from the XMLHttpRequest instance
Closes #4162
2016-05-19 15:58:59 -07:00
7a80f0d1e1 change property and event to use camel case
Closes #6580
2016-05-19 15:21:54 -07:00
0894318f50 change property and event to use camel case:
* some-property -> someProperty
* some-event -> someEvent

Closes #6609
2016-05-19 15:20:34 -07:00
f9fc524a74 chore(contributing): added commit message samples (#7375)
Each time I am pointing someone to this contribution doc
to read and apply commit message style guide
each time type part of message formatting is missed
2016-05-19 15:19:01 -07:00
9019c6f937 Condition using component as well.
Closes #7755
2016-05-19 15:15:25 -07:00
166b73f4f3 Condition using urlPath, component is null on UnresolvedException of child AsyncRoute. 2016-05-19 15:15:25 -07:00
cb94111f18 style(Forms): remove unused imports
remove unused imports from the forms package
2016-05-19 14:50:33 -07:00
a88b887a05 docs(cheatsheet): Document SVG idiosyncrasies (#6055) 2016-05-19 10:20:08 -07:00
6e62217b78 fix(WebWorker): remove the platform-browser dependency on compiler 2016-05-18 16:23:09 -07:00
a01a54c180 adds 'repository' metadata to npm modules (#8649) 2016-05-17 23:17:15 -07:00
db8290632f fix(upgrade): fallback to root ng2 injector when element is compiled outside the document (#8684)
Currently downgraded ng2 elements fail inside a ui-router view because they are unable
to require an ng2 Injector via the require attribute of the DDO, because ui-router compiles
its templates before they are inserted in a ui-view. This adds a "fallback" behavior if
a parent injector cannot be found to go to the root ng2 Injector.
2016-05-17 15:55:53 -07:00
7bb5167239 chore(security): fix CHANGELOG formatting. (#8687)
Turns out the fenced code block needs to be in its own paragraph.
2016-05-17 15:00:28 -07:00
6cdc53c497 fix(UpgradeNg1ComponentAdapter): make bindings available on $scope in controller & link function (#8645)
Delays NG1 Directive controller instatiation where possible and pre-link function always
to the ngOnInit() lifecycle hook. This way bindings are always available on $scope in both
the controller and the link function.
2016-05-17 14:53:59 -07:00
15ae710d22 feat(security): allow url(...) style values.
Allows sanitized URLs for CSS properties. These can be abused for information
leakage, but only if the CSS rules are already set up to allow for it. That is,
an attacker cannot cause information leakage without controlling the style rules
present, or a very particular setup.

Fixes #8514.
2016-05-17 11:23:31 +02:00
dd50124254 feat(security): allow data: URLs for images and videos.
Allows known-to-be-safe media types in data URIs.

Part of #8511.
2016-05-17 10:57:14 +02:00
ff36b0384a fix(compiler_cli): normalize used directives
- e.g. needed for content projection.

Closes #8677
2016-05-16 13:07:13 -07:00
50c9bed630 feat(security): expose the safe value types.
This allows users to properly type their `SafeHtml`, `SafeStyle`, etc values.

Fixes #8568.
2016-05-15 11:47:06 +02:00
8b1b427195 feat(security): support transform CSS functions for sanitization.
Fixes part of #8514.
2016-05-14 13:25:45 +02:00
9a05ca95f6 fix(build): Release compiler_cli packages along with rest of @angular packages and use ANGULAR_VERSION for package version and peer dependencies. 2016-05-13 13:35:10 -07:00
05266241af build(npm): short-circuit npm install if node_modules are healthy
Closes #8627
2016-05-13 22:07:41 +02:00
4ddf5536b4 docs(DEVELOPER.md): state that git-clang-format must be in PATH
To use ```git clang-format``` your have to make sure that
```git-clang-format``` is in your path.

Closes #7778
2016-05-13 12:25:09 -07:00
f389b5a961 docs(changelog): add missing breaking changes for testing providers
Closes #8440
2016-05-13 12:21:33 -07:00
bac1a6eab3 fix(build): Fix an error in package publishing step where the script errors when a UMD bundle is not found for compiler-cli package. 2016-05-12 16:49:03 -07:00
ff400726ca fix(build): Declare the secure GITHUB_TOKEN_ANGULAR for package publishing from Travis 2016-05-12 15:08:28 -07:00
267d864976 fix(build): Fix broken e2e test Travis task by running the right variation of sed on Travis 2016-05-12 13:58:42 -07:00
97a1084c99 fix(build): Hook up publish-build-artifacts to Travis 2016-05-12 12:01:53 -07:00
61b339678d test(compiler): test schema generation only in Chrome
Closes #8581
2016-05-11 17:01:26 -07:00
d537a26297 chore(build): reenable optional jobs in SL and BS
Closes #8558
2016-05-11 17:00:43 -07:00
d414734aac fix(build): Change publish-build-artifacts.sh to work with new packaging system 2016-05-11 16:58:18 -07:00
817ddfa847 fix(compiler): allow --noImplicitAny 2016-05-11 16:56:12 -07:00
c1154b30c7 fix(compiler): allow decorators defined in the same file 2016-05-11 16:56:12 -07:00
0d71345b93 fix(codegen): codegen all files in the program, not just roots
fixes #8475
2016-05-11 16:56:12 -07:00
f235454dd6 ci: temporarily disable Edge because of SauceLabs issues
https://github.com/angular/angular/issues/8604

On Sauce we've been getting the following error:

11 05 2016 00:58:35.765:ERROR [launcher.sauce]: Heartbeat to microsoftedge 20.10240 (Windows 10) failed
  [title()] Error response status: 13, , UnknownError - An unknown server-side error occurred while processing the command. Selenium error: Unknown error (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 285 milliseconds
Build info: version: '2.52.0', revision: '4c2593c', time: '2016-02-11 19:06:42'
System info: host: 'WIN-SB3ER6JQ6ME', ip: '172.20.60.246', os.name: 'Windows 10', os.arch: 'x86', os.version: '10.0', java.version: '1.8.0_73'
Driver info: org.openqa.selenium.edge.EdgeDriver
Capabilities [{acceptSslCerts=true, browserVersion=25.10586.0.0, platformVersion=10, browserName=MicrosoftEdge, takesScreenshot=true, pageLoadStrategy=normal, takesElementScreenshot=true, platformName=windows, platform=ANY}]
Session ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXX478C1C1A
11 05 2016 00:58:35.766:ERROR [launcher]: microsoftedge 20.10240 (Windows 10) on SauceLabs failed 2 times (failure). Giving up.
2016-05-11 11:06:37 -07:00
6a80578d05 build: create the dist directory before building 2016-05-11 10:11:59 -07:00
d33cd43db1 docs(PULL_REQUEST_TEMPLATE.md): reorganize and improve the pull request template
Closes #7921
2016-05-10 10:55:35 -07:00
9e3df8eefe chore(tsickle): remove redundant jsdoc types
tsickle doesn't like them, and anyway they are bound to get out-of-sync with the inline TS types
2016-05-10 17:38:10 +02:00
cf73ad7c8f chore(security): document sanitization breaking change.
Sanitizing style and URL values breaks specific patterns, see #8491 for
an example. This documents and acknowledges the breaking change while we
work on improving CSS sanitization to allow more values through.
2016-05-10 17:36:36 +02:00
3e68b7eb1f feat(security): warn users when sanitizing in dev mode.
This should help developers to figure out what's going on when the sanitizer
strips some input.

Fixes #8522.
2016-05-09 16:46:31 +02:00
9fbafba993 chore(parsing): change internal usage of @ to : for namespaced values
Closes #8346
2016-05-09 16:20:32 +02:00
7a524e3deb feat(security): add tests for URL sanitization. 2016-05-09 16:00:24 +02:00
7b6c4d5acc feat(security): add tests for style sanitisation. 2016-05-09 16:00:24 +02:00
99c0d503d7 chore(build): run security tests in NodeJS, too. 2016-05-09 16:00:24 +02:00
f86edae9f3 feat(security): add an HTML sanitizer.
This is based on Angular 1's implementation, parsing an HTML document
into an inert DOM Document implementation, and then serializing only
specifically whitelisted elements.

It currently does not support SVG sanitization, all SVG elements are
rejected.

If available, the sanitizer uses the `<template>` HTML element as an
inert container.

Sanitization works client and server-side.

Reviewers: rjamet, tbosch , molnarg , koto

Differential Revision: https://reviews.angular.io/D108
2016-05-09 16:00:24 +02:00
df1b1f6957 feat(security): strip XSSI prefix from XHR responses. 2016-05-05 14:25:44 -07:00
9099160038 chore: fix comment indent. 2016-05-05 12:46:07 -07:00
119abe7bb9 chore: fail build if a command from tsc-watch fails.
This bug was introduced with eba6e7946d
to integrate the compiler_cli into the build properly.

Closes #8480
2016-05-04 20:30:10 -07:00
67ed2e2c0a feat(security): fill in missing security contexts.
Reviewers: koto, rjamet, molnarg

Differential Revision: https://reviews.angular.io/D109
2016-05-04 19:28:50 -07:00
6d36a7a45f chore: fix unit tests on node.js
Closes #8476
2016-05-04 18:00:29 -07:00
e2b1e1577d fix(core): don’t detach nested view containers when destroying a view
When a view is destroyed, we destroy all
views in view containers and should not detach them. However, previously, we also detached them which lead to problems during the iteration loop.

Closes #8458
Closes #8471

Introduced by 0c600cf6e3
2016-05-04 16:27:20 -07:00
b30ddfbfc5 chore(router): clang-format 2016-05-04 15:01:27 -07:00
abfb522f83 refactor(router): reuse existing segmentes when constructing new route trees 2016-05-04 14:51:04 -07:00
b8136cc26e fix(router): provide a top-level route segment for injection 2016-05-04 14:51:04 -07:00
d00b26d941 refactor(router): update link to reuse url segments when possible 2016-05-04 14:51:04 -07:00
12637a761c refactor(router): make names consistent 2016-05-04 14:50:00 -07:00
1a0aea67a0 feat(core): add a component resolver that can load components lazily using system.js 2016-05-04 14:50:00 -07:00
0f1465b899 feat(router): update router to support lazy loading 2016-05-04 14:50:00 -07:00
c0cfd3c6ed chore: remove ts-metadata-collector from shrinkwrap
We need to use the locally installed one.

Closes #8467
2016-05-04 12:29:47 -07:00
a81923b793 fix(compiler): emit correct types for literal arrays and maps. 2016-05-04 12:14:44 -07:00
7150ace7c7 fix(compiler): support lifecycle hooks in compiler_cli 2016-05-04 12:14:43 -07:00
bdce154282 chore: add test script for compiler_cli 2016-05-04 12:14:43 -07:00
5a84048f72 chore: adjust build for tools/metadata name change 2016-05-04 12:14:38 -07:00
188bda813e chore: rename tools/metadata into tools/ts-metadata-collector
Needed to that we can use the locally compiled one during
our tests.
2016-05-04 10:53:28 -07:00
29700aa188 feat(metadata): emit all methods
This is needed to detect lifecycle hooks.
2016-05-04 09:11:04 -07:00
3229bf1665 docs(changelog): add changelog for rc.1 2016-05-04 01:12:40 -06:00
52595f52f9 chore: make workaround_empty_observable_list_diff @internal 2016-05-04 01:12:40 -06:00
edec158dd8 fix(compiler_cli): allow to use builtin directives like NgIf, …
Related to #8448
Closes #8454
2016-05-03 20:49:17 -07:00
0297398f5e chore: clang-format master 2016-05-03 18:56:39 -07:00
9485f5a813 fix: platform-server should declare it's dependency on parse5 via package.json 2016-05-03 18:07:52 -06:00
8f8c017882 chore: remove angular2-template-compiler from package.json
Closes #8429
2016-05-03 16:25:55 -07:00
eba6e7946d refactor(compiler_cli): move it into modules/@angular and integrate properly into the build
This also does no more depend on a version
on npm for the compiler_cli.

Also runs the tests for tools/metadata
2016-05-03 15:57:49 -07:00
3cfe281790 fix(tsickle): put the tsickle support code at EOF 2016-05-03 15:15:22 -07:00
1767 changed files with 96899 additions and 87315 deletions

3
.gitattributes vendored
View File

@ -3,3 +3,6 @@
# JS files must always use LF for tools to work
*.js eol=lf
# Must keep Windows line ending to be parsed correctly
scripts/windows/packages.txt eol=crlf

View File

@ -1,11 +1,9 @@
**IMPORTANT**: This repository's issues are reserved for feature requests and bug reports. Do not submit support requests here, see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question.
**Steps to reproduce and a minimal demo of the problem**
_Use https://plnkr.co or similar -- try this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5_
_What steps should we try in your demo to see the problem?_
**I'm submitting a ...** (check one with "x")
```
[ ] bug report
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
```
**Current behavior**
@ -13,4 +11,23 @@ _What steps should we try in your demo to see the problem?_
**Expected/desired behavior**
**Other information**
**Reproduction of the problem**
If the current behavior is a bug or you can illustrate your feature request better with an example, please provide the steps to reproduce and if possible a minimal demo of the problem via https://plnkr.co or similar (you can use this template as a starting point: http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5).
**What is the expected behavior?**
**What is the motivation / use case for changing the behavior?**
**Please tell us about your environment:**
* **Angular version:** 2.0.0-rc.X
* **Browser:** [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
* **Language:** [all | TypeScript X.X | ES6/7 | ES5]

View File

@ -1,24 +1,36 @@
* **Please check if the PR fulfills these requirements**
**Please check if the PR fulfills these requirements**
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit-message-format
- [ ] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)
* **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...)
**What kind of change does this PR introduce?** (check one with "x")
```
[ ] Bugfix
[ ] Feature
[ ] Code style update (formatting, local variables)
[ ] Refactoring (no functional changes, no api changes)
[ ] Build related changes
[ ] CI related changes
[ ] Other... Please describe:
```
**What is the current behavior?** (You can also link to an open issue here)
* **What is the current behavior?** (You can also link to an open issue here)
**What is the new behavior?**
* **What is the new behavior (if this is a feature change)?**
**Does this PR introduce a breaking change?** (check one with "x")
```
[ ] Yes
[ ] No
```
If this PR contains a breaking change, please describe the impact and migration path for existing applications: ...
* **Does this PR introduce a breaking change?** (What changes might users need to make in their application due to this PR?)
* **Other information**:
**Other information**:

10
.gitignore vendored
View File

@ -21,16 +21,6 @@ tmp
*.js.deps
*.js.map
# Files created by the template compiler
**/*.ngfactory.ts
**/*.css.ts
**/*.css.shim.ts
# Or type definitions we mirror from github
# (NB: these lines are removed in publish-build-artifacts.sh)
**/typings/**/*.d.ts
**/typings/tsd.cached.json
# Include when developing application packages.
pubspec.lock
.c9

View File

@ -34,18 +34,22 @@ env:
# - LOGS_DIR=/tmp/angular-build/logs
# - ARCH=linux-x64
# GITHUB_TOKEN_ANGULAR
# This is needed for the e2e Travis matrix task to publish packages to github for continuous packages delivery.
- secure: "fq/U7VDMWO8O8SnAQkdbkoSe2X92PVqg4d044HmRYVmcf6YbO48+xeGJ8yOk0pCBwl3ISO4Q2ot0x546kxfiYBuHkZetlngZxZCtQiFT9kyId8ZKcYdXaIW9OVdw3Gh3tQyUwDucfkVhqcs52D6NZjyE2aWZ4/d1V4kWRO/LMgo="
matrix:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- CI_MODE=js
- CI_MODE=lint
- CI_MODE=e2e
- CI_MODE=saucelabs_required
- CI_MODE=browserstack_required
- CI_MODE=saucelabs_optional
- CI_MODE=browserstack_optional
#matrix:
# allow_failures:
# - env: "MODE=saucelabs_optional"
# - env: "MODE=browserstack_optional"
matrix:
allow_failures:
- env: "CI_MODE=saucelabs_optional"
- env: "CI_MODE=browserstack_optional"
install:

File diff suppressed because it is too large Load Diff

View File

@ -95,7 +95,7 @@ Before you submit your Pull Request (PR) consider the following guidelines:
* In GitHub, send a pull request to `angular:master`.
* If we suggest changes then:
* Make the required updates.
* Re-run the Angular 2 test suites for JS and Dart to ensure tests are still passing.
* Re-run the Angular 2 test suites to ensure tests are still passing.
* Rebase your branch and force push to your GitHub repository (this will update your Pull Request):
```shell
@ -166,6 +166,19 @@ The **header** is mandatory and the **scope** of the header is optional.
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
to read on GitHub as well as in various git tools.
Footer should contain a [closing reference to an issue](https://help.github.com/articles/closing-issues-via-commit-messages/) if any.
Samples: (even more [samples](https://github.com/angular/angular/commits/master))
```
docs(changelog): update change log to beta.5
```
```
fix(release): need to depend on latest rxjs and zone.js
The version in our package.json gets copied to the one we publish, and users need the latest of these.
```
### Revert
If the commit reverts a previous commit, it should begin with `revert: `, followed by the header of the reverted commit. In the body it should say: `This reverts commit <hash>.`, where the hash is the SHA of the commit being reverted.
@ -225,8 +238,8 @@ changes to be accepted, the CLA must be signed. It's a quick process, we promise
[github]: https://github.com/angular/angular
[gitter]: https://gitter.im/angular/angular
[individual-cla]: http://code.google.com/legal/individual-cla-v1.0.html
[js-style-guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
[jsfiddle]: http://jsfiddle.net/
[js-style-guide]: https://google.github.io/styleguide/javascriptguide.xml
[jsfiddle]: http://jsfiddle.net
[plunker]: http://plnkr.co/edit
[runnable]: http://runnable.com/
[runnable]: http://runnable.com
[stackoverflow]: http://stackoverflow.com/questions/tagged/angular

View File

@ -5,15 +5,9 @@ JS and Dart versions. It also explains the basic mechanics of using `git`, `node
* [Prerequisite Software](#prerequisite-software)
* [Getting the Sources](#getting-the-sources)
* [Environment Variable Setup](#environment-variable-setup)
* [Installing NPM Modules and Dart Packages](#installing-npm-modules-and-dart-packages)
* [Build commands](#build-commands)
* [Installing NPM Modules](#installing-npm-modules)
* [Building](#building)
* [Running Tests Locally](#running-tests-locally)
* [Code Style](#code-style)
* [Project Information](#project-information)
* [CI using Travis](#ci-using-travis)
* [Transforming Dart code](#transforming-dart-code)
* [Debugging](#debugging)
See the [contribution guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md)
if you'd like to contribute to Angular.
@ -32,17 +26,8 @@ following products on your development machine:
(version `>=3.5.3 <4.0`), which comes with Node. Depending on your system, you can install Node either from
source or as a pre-packaged bundle.
* *Optional*: [Dart](https://www.dartlang.org) (version ` >=1.13.2 <2.0.0`), specifically the Dart-SDK and
Dartium (a version of [Chromium](http://www.chromium.org) with native support for Dart through
the Dart VM). One of the **simplest** ways to get both is to install the **Dart Editor bundle**,
which includes the editor, SDK and Dartium. See the [Dart tools](https://www.dartlang.org/tools)
download [page for instructions](https://www.dartlang.org/tools/download.html).
You can also download both **stable** and **dev** channel versions from the [download
archive](https://www.dartlang.org/tools/download-archive). In that case, on Windows, Dart must be added
to the `Path` (e.g. `path-to-dart-sdk-folder\bin`) and a new `DARTIUM_BIN` environment variable must be
created, pointing to the executable (e.g. `path-to-dartium-folder\chrome.exe).`
* [Java Development Kit](http://www.oracle.com/technetwork/es/java/javase/downloads/index.html) which is used
to execute the selenium standalone server for e2e testing.
## Getting the Sources
@ -65,42 +50,7 @@ cd angular
# Add the main Angular repository as an upstream remote to your repository:
git remote add upstream https://github.com/angular/angular.git
```
## Environment Variable Setup
Define the environment variables listed below. These are mainly needed for the testing. The
notation shown here is for [`bash`](http://www.gnu.org/software/bash); adapt as appropriate for
your favorite shell.
Examples given below of possible values for initializing the environment variables assume **Mac OS
X** and that you have installed the Dart Editor in the directory named by
`DART_EDITOR_DIR=/Applications/dart`. This is only for illustrative purposes.
```shell
# DARTIUM_BIN: path to a Dartium browser executable; used by Karma to run Dart tests
export DARTIUM_BIN="$DART_EDITOR_DIR/chromium/Chromium.app/Contents/MacOS/Chromium"
```
Add the Dart SDK `bin` directory to your path and/or define `DART_SDK` (this is also detailed
[here](https://www.dartlang.org/tools/pub/installing.html)):
```shell
# DART_SDK: path to a Dart SDK directory
export DART_SDK="$DART_EDITOR_DIR/dart-sdk"
# Update PATH to include the Dart SDK bin directory
PATH+=":$DART_SDK/bin"
```
And specify where the pubs dependencies are downloaded. By default, this directory is located under .pub_cache
in your home directory (on Mac and Linux), or in AppData\Roaming\Pub\Cache (on Windows).
```shell
# PUB_CACHE: location of pub dependencies
export PUB_CACHE="/Users/<user>/.pub-cache"
```
## Installing NPM Modules and Dart Packages
## Installing NPM Modules
Next, install the JavaScript modules and Dart packages needed to build and test Angular:
@ -124,239 +74,67 @@ use in these instructions.
*Option 2*: defining a bash alias like `alias nbin='PATH=$(npm bin):$PATH'` as detailed in this
[Stackoverflow answer](http://stackoverflow.com/questions/9679932/how-to-use-package-installed-locally-in-node-modules/15157360#15157360) and used like this: e.g., `nbin gulp build`.
## Build commands
To build Angular and prepare tests, run:
## Windows only
In order to create the right symlinks, run **as administrator**:
```shell
$(npm bin)/gulp build
./scripts/windows/create-symlinks.sh
```
Notes:
* Results are put in the `dist` folder.
* This will also run `pub get` for the subfolders in `modules` and run `dartanalyzer` for
every file that matches `<module>/src/<module>.dart`, e.g. `di/src/di.dart`.
Before submitting a PR, do not forget to remove them:
```shell
./scripts/windows/remove-symlinks.sh
```
You can selectively build either the JS or Dart versions as follows:
## Building
* `$(npm bin)/gulp build.js`
* `$(npm bin)/gulp build.dart`
To clean out the `dist` folder, run:
To build Angular run:
```shell
$(npm bin)/gulp clean
./build.sh
```
* Results are put in the dist folder.
## Running Tests Locally
### Full test suite
* `npm test`: full test suite for both JS and Dart versions of Angular. These are the same tests
that run on Travis.
You can selectively run either the JS or Dart versions as follows:
* `$(npm bin)/gulp test.all.js`
* `$(npm bin)/gulp test.all.dart`
### Unit tests
You can run just the unit tests as follows:
* `$(npm bin)/gulp test.unit.js`: JS tests in a browser; runs in **watch mode** (i.e.
watches the test files for changes and re-runs tests when files are updated).
* `$(npm bin)/gulp test.unit.cjs`: JS tests in NodeJS; runs in **watch mode**.
* `$(npm bin)/gulp test.unit.dart`: Dart tests in Dartium; runs in **watch mode**.
If you prefer running tests in "single-run" mode rather than watch mode use:
* `$(npm bin)/gulp test.unit.js/ci`
* `$(npm bin)/gulp test.unit.cjs/ci`
* `$(npm bin)/gulp test.unit.dart/ci`
The task updates the dist folder with transpiled code whenever a source or test file changes, and
Karma is run against the new output.
**Note**: If you want to only run a single test you can alter the test you wish to run by changing
`it` to `iit` or `describe` to `ddescribe`. This will only run that individual test and make it
much easier to debug. `xit` and `xdescribe` can also be useful to exclude a test and a group of
tests respectively.
**Note**: **watch mode** needs symlinks to work, so if you're using windows, ensure you have the
rights to built them in your operating system.
### Unit tests with Sauce Labs or Browser Stack
First, in a terminal, create a tunnel with [Sauce Connect](https://docs.saucelabs.com/reference/sauce-connect/) or [Browser Stack Local](https://www.browserstack.com/local-testing#command-line), and valid credentials.
Then, in another terminal:
- Define the credentials as environment variables, e.g.:
```
export SAUCE_USERNAME='my_user'; export SAUCE_ACCESS_KEY='my_key';
export BROWSER_STACK_USERNAME='my_user'; export BROWSER_STACK_ACCESS_KEY='my_key';
```
- Then run `gulp test.unit.js.(sauce|browserstack) --browsers=option1,option2,..,optionN`
The options are any mix of browsers and aliases which are defined in the [browser-providers.conf.js](https://github.com/angular/angular/blob/master/browser-providers.conf.js) file.
They are case insensitive, and the `SL_` or `BS_` prefix must not be added for browsers.
Some examples of commands:
```
gulp test.unit.js.sauce --browsers=Safari8,ie11 //run in Sauce Labs with Safari 8 and IE11
gulp test.unit.js.browserstack --browsers=Safari,IE //run in Browser Stack with Safari 7, Safari 8, Safari 9, IE 9, IE 10 and IE 11
gulp test.unit.js.sauce --browsers=IOS,safari8,android5.1 //run in Sauce Labs with iOS 7, iOS 8, iOs 9, Safari 8 and Android 5.1
```
### E2E tests
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder).
2. `$(npm bin)/gulp serve.js.prod serve.dart` (runs a local webserver).
3. `$(npm bin)/protractor protractor-js.conf.js`: JS e2e tests.
4. `$(npm bin)/protractor protractor-dart2js.conf.js`: dart2js e2e tests.
Angular specific command line options when running protractor:
- `$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
### Performance tests
1. `$(npm bin)/gulp build.js.cjs` (builds benchpress and tests into `dist/js/cjs` folder)
2. `$(npm bin)/gulp serve.js.prod serve.dart` (runs a local webserver)
3. `$(npm bin)/protractor protractor-js.conf.js --benchmark`: JS performance tests
4. `$(npm bin)/protractor protractor-dart2js.conf.js --benchmark`: dart2js performance tests
Angular specific command line options when running protractor (e.g. force gc, ...):
`$(npm bin)/protractor protractor-{js|dart2js}-conf.js --ng-help`
## Code Style
### Formatting with <a name="clang-format">clang-format</a>
We use [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to automatically enforce code
style for our TypeScript code. This allows us to focus our code reviews more on the content, and
less on style nit-picking. It also lets us encode our style guide in the `.clang-format` file in the
repository, allowing many tools and editors to share our settings.
To check the formatting of your code, run
gulp check-format
Note that the continuous build on Travis runs `gulp enforce-format`. Unlike the `check-format` task,
this will actually fail the build if files aren't formatted according to the style guide.
Your life will be easier if you include the formatter in your standard workflow. Otherwise, you'll
likely forget to check the formatting, and waste time waiting for a build on Travis that fails due
to some whitespace difference.
* Use `$(npm bin)/clang-format -i [file name]` to format a file (or multiple).
* Use `gulp enforce-format` to check if your code is `clang-format` clean. This also gives
you a command line to format your code.
* `clang-format` also includes a git hook, run `git clang-format` to format all files you
touched.
* You can run this as a **git pre-commit hook** to automatically format your delta regions when you
commit a change. In the angular repo, run
```
$ echo -e '#!/bin/sh\nexec git clang-format' > .git/hooks/pre-commit
$ chmod u+x !$
```
* **WebStorm** can run clang-format on the current file.
1. Under Preferences, open Tools > External Tools.
1. Plus icon to Create Tool
1. Fill in the form:
- Name: clang-format
- Description: Format
- Synchronize files after execution: checked
- Open console: not checked
- Show in: Editor menu
- Program: `$ProjectFileDir$/node_modules/.bin/clang-format`
- Parameters: `-i -style=file $FilePath$`
- Working directory: `$ProjectFileDir$`
* `clang-format` integrations are also available for many popular editors (`vim`, `emacs`,
`Sublime Text`, etc.).
### Linting
We use [tslint](https://github.com/palantir/tslint) for linting. See linting rules in [gulpfile](gulpfile.js). To lint, run
To run tests:
```shell
$ gulp lint
$ ./test.sh node # Run all angular tests on node
$ ./test.sh browser # Run all angular tests in browser
$ ./test.sh browserNoRouter # Optionally run all angular tests without router in browser
$ ./test.sh tools # Run angular tooling (not framework) tests
```
## Generating the API documentation
You should execute the 3 test suites before submitting a PR to github.
The following gulp task will generate the API docs in the `dist/angular.io/partials/api/angular2`:
All the tests are executed on our Continuous Integration infrastructure and a PR could only be merged once the tests pass.
```shell
$(npm bin)/gulp docs/angular.io
- CircleCI fails if your code is not formatted properly,
- Travis CI fails if any of the test suite describe above fails.
## Update the public API tests
If you happen to modify the public API of Angular, API golden files must be updated using:
``` shell
$ gulp public-api:update
```
You can serve the generated documentation to check how it would render on [angular.io](https://angular.io/):
- check out the [angular.io repo](https://github.com/angular/angular.io) locally,
- install dependencies as described in the [angular.io README](https://github.com/angular/angular.io/blob/master/README.md),
- copy the generated documentation from your local angular repo at `angular/dist/angular.io/partials/api/angular2` to your local angular.io repo at `angular.io/public/docs/js/latest/api`,
- run `harp compile` at the root of the angular.io repo to check the generated documentation for errors,
- run `harp server` and open a browser at `http://localhost:9000/docs/js/latest/api/` to check the rendered documentation.
Note: The command `./test.sh tools` fails when the API doesn't match the golden files.
## Project Information
## Formatting your source code
### Folder structure
Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code
is not properly formatted, the CI will fail and the PR can not be merged.
* `modules/*`: modules that will be loaded in the browser
* `tools/*`: tools that are needed to build Angular
* `dist/*`: build files are placed here.
You can automatically format your code by running:
### File suffixes
``` shell
$ gulp format
```
* `*.ts`: TypeScript files that get transpiled to Dart and EcmaScript 5/6
* `*.dart`: Dart files that don't get transpiled
## CI using Travis
For instructions on setting up Continuous Integration using Travis, see the instructions given
[here](https://github.com/angular/angular.dart/blob/master/travis.md).
## Transforming Dart code
See the [wiki](//github.com/angular/angular/wiki/Angular-2-Dart-Transformer).
## Debugging
### Debug the transpiler
If you need to debug the transpiler:
- add a `debugger;` statement in the transpiler code,
- from the root folder, execute `node debug $(npm bin)/gulp build` to enter the node
debugger
- press "c" to execute the program until you reach the `debugger;` statement,
- you can then type "repl" to enter the REPL and inspect variables in the context.
See the [Node.js manual](http://nodejs.org/api/debugger.html) for more information.
Notes:
- You can also execute `node $(npm bin)/karma start karma-dart.conf.js` depending on which
code you want to debug (the former will process the "modules" folder while the later processes
the transpiler specs).
- You can also add `debugger;` statements in the specs (JavaScript). The execution will halt when
the developer tools are opened in the browser running Karma.
### Debug the tests
If you need to debug the tests:
- add a `debugger;` statement to the test you want to debug (or the source code),
- execute karma `$(npm bin)/gulp test.js`,
- press the top right "DEBUG" button,
- open the DevTools and press F5,
- the execution halts at the `debugger;` statement
**Note (WebStorm users)**:
1. Create a Karma run config from WebStorm.
2. Then in the "Run" menu, press "Debug 'karma-js.conf.js'", and WebStorm will stop in the generated
code on the `debugger;` statement.
3. You can then step into the code and add watches.
The `debugger;` statement is needed because WebStorm will stop in a transpiled file. Breakpoints in
the original source files are not supported at the moment.

View File

@ -1,8 +1,9 @@
[![Build Status](https://travis-ci.org/angular/angular.svg?branch=master)](https://travis-ci.org/angular/angular)
[![CircleCI](https://circleci.com/gh/angular/angular/tree/master.svg?style=shield)](https://circleci.com/gh/angular/angular/tree/master)
[![Join the chat at https://gitter.im/angular/angular](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/angular/angular?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/angular2.svg)](http://badge.fury.io/js/angular2)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/pr?style=flat)](http://issuestats.com/github/angular/angular)
[![Issue Stats](http://issuestats.com/github/angular/angular/badge/issue?style=flat)](http://issuestats.com/github/angular/angular)
[![npm version](https://badge.fury.io/js/%40angular%2Fcore.svg)](https://badge.fury.io/js/%40angular%2Fcore)
[![Downloads](http://img.shields.io/npm/dm/angular2.svg)](https://npmjs.org/package/angular2)
[![Sauce Test Status](https://saucelabs.com/browser-matrix/angular2-ci.svg)](https://saucelabs.com/u/angular2-ci)
@ -11,9 +12,11 @@ Angular
=========
Angular is a development platform for building mobile and desktop web applications. This is the
repository for [Angular 2][ng2], both the JavaScript (JS) and [Dart][dart] versions.
repository for [Angular 2][ng2] Typescript/JavaScript (JS).
Angular2 for [Dart][dart] can be found at [dart-lang/angular2][ng2dart].
Angular 2 is currently in **Beta**.
Angular 2 is currently in **Release Candidate**.
## Quickstart
@ -33,3 +36,4 @@ guidelines for [contributing][contributing] and then check out one of our issues
[ng2]: http://angular.io
[ngDart]: http://angulardart.org
[ngJS]: http://angularjs.org
[ng2dart]: https://github.com/dart-lang/angular2

View File

@ -1,376 +0,0 @@
# Developer Tools for Dart
Use these tools and techniques to increase your app's performance
and reliability.
* [Angular debugging tools](#angular-debugging-tools)
* [Code size](#code-size)
* [Performance](#performance)
## Angular debugging tools
Starting with alpha.38, Angular provides a set of debugging tools
that are accessible from any browser's developer console.
In Chrome, you can get to the dev console by pressing
Ctrl + Shift + J (on Mac: Cmd + Opt + J).
### Enabling the debugging tools
By default the debugging tools are disabled.
Enable the debugging tools as follows:
```dart
import 'package:angular2/platform/browser.dart';
main() async {
var appRef = await bootstrap(Application);
enableDebugTools(appRef);
}
```
<!-- Change function name to enableDebuggingTools? -->
### Using the debugging tools
In the browser, open the dev console. The top-level object is called `ng` and
contains more specific tools inside it.
For example, to run the change detection profiler on your app:
```javascript
// In the dev console:
ng.profiler.timeChangeDetection();
```
The [Change detection profiler](#change-detection-profiler) section
has more details.
<!-- Point to API docs when they're published, if they're useful.
They should be under
http://www.dartdocs.org/documentation/angular2/latest
and/or
https://angular.io/docs/js/latest/api/. -->
## Code size
Code must be downloaded, parsed, and executed. Too much code can lead to
slow application start-up time, especially on slow networks and low-end devices.
The tools and techniques in this section can help you to identify
unnecessarily large code and to reduce code size.
### Finding contributors to code size
Options for investigating code size include the `--dump-info` dart2js option,
ng2soyc, `reflector.trackUsage()`, and code coverage information
from the Dart VM.
#### Use --dump-info
The `--dump-info` option of `dart2js` outputs information about what happened
during compilation. You can specify `--dump-info` in `pubspec.yaml`:
```yaml
transformers:
...
- $dart2js:
commandLineOptions:
- --dump-info
```
The [Dump Info Visualizer](https://github.com/dart-lang/dump-info-visualizer)
can help you analyze the output.
For more information, see the
[dart2js_info API reference](http://dart-lang.github.io/dart2js_info/doc/api/).
#### Use ng2soyc.dart
[ng2soyc](https://github.com/angular/ng2soyc.dart) is a utility for analyzing
code size contributors in Angular 2 applications. It groups code size by
library and, assuming your library names follow
[standard naming conventions](https://www.dartlang.org/articles/style-guide/#do-prefix-library-names-with-the-package-name-and-a-dot-separated-path)
(package.library.sublibrary...), gives the code size breakdown at
each level. To reduce noise in the output of very large apps, ng2soyc provides
an option to hide libraries that are too small, so you can focus on the biggest
contributors.
#### Find unused reflection data
Your app might have types that are annotated with `@Component` or `@Injectable`
but never used.
To find these unused types, use `reflector.trackUsage()` and then,
after exercising your app, `reflector.listUnusedKeys()`.
For example:
```
import 'package:angular2/src/core/reflection/reflection.dart';
...
main() async {
reflector.trackUsage();
await bootstrap(AppComponent);
print('Unused keys: ${reflector.listUnusedKeys()}');
}
```
When you run that code (in Dartium or another browser),
you'll see a list of types that Angular _can_ inject but hasn't needed to.
Consider removing those types or their `@Component`/`@Injectable` annotation
to decrease your app's code size.
Three conditions must be true for `listUnusedKeys()` to return helpful data:
1. The angular2 transformer must run on the app.
2. If you're running a JavaScript version of the app,
the app must not be minified, so that the names are readable.
3. You must exercise your app in as many ways as possible
before calling `listUnusedKeys()`.
Otherwise, you might get false positives:
keys that haven't been used only because you didn't exercise
the relevant feature of the app.
To run the angular2 transformer, first specify it in `pubspec.yaml`:
```
name: hello_world
...
transformers:
- angular2:
entry_points: web/main.dart
```
Then use pub to run the transformer. If you use `pub serve`,
it provides both Dart and unminified (by default) JavaScript versions.
If you want to serve actual files, then use `pub build` in debug mode
to generate Dart and unminified JavaScript files:
`pub build --mode=debug`.
The `reflector.trackUsage()` method makes Angular track the reflection
information used by the app. Reflection information (`ReflectionInfo`) is a data
structure that stores information that Angular uses for locating DI factories
and for generating change detectors and other code related to a
given type.
#### Use code coverage to find dead code
When running in Dartium (or in the Dart VM, in general) you can request code
coverage information from the VM. You can either use
[observatory](https://www.dartlang.org/tools/observatory/) or download
the coverage file and use your own tools to inspect it. Lines of code that are
not covered are top candidates for dead code.
Keep in mind, however, that uncovered code is not sufficient evidence of dead
code, only necessary evidence. It is perfectly possible that you simply didn't
exercise your application in a way that triggers the execution of uncovered
code. A common example is error handling code. Just because your testing never
encountered an error does not mean the error won't happen in production. You
therefore don't have to rush and remove all the `catch` blocks.
### Reducing code size
To reduce code size, you can disable reflection,
enable minification, and manually remove dead code.
You can also try less safe options such as
telling dart2js to trust type annotations.
#### Disable reflection
`dart:mirrors` allows discovering program metadata at runtime. However, this
means that `dart2js` needs to retain that metadata and thus increase the size
of resulting JS output. In practice, however, it is possible to extract most
metadata necessary for your metaprogramming tasks statically using a
transformer and `package:analyzer`, and act on it before compiling to JS.
#### Enable minification
Minification shortens all your `longMethodNames` into 2- or 3-letter long
symbols. `dart2js` ensures that this kind of renaming is done safely, without
breaking the functionality of your programs. You can enable it in `pubspec.yaml`
under `$dart2js` transformer:
```yaml
transformers:
...
- $dart2js:
minify: true
```
#### Manually remove dead code
`dart2js` comes with dead code elimination out-of-the-box. However, it may not
always be able to tell if a piece of code could be used. Consider the following
example:
```dart
/// This function decides which serialization format to use
void setupSerializers() {
if (server.doYouSupportProtocolBuffers()) {
useProtobufSerializers();
} else {
useJsonSerializers();
}
}
```
In this example the application asks the server what kind of serialization
format it uses and dynamically chooses one or the other. `dart2js` can't
tell whether the server responds with yes or no, so it must retain both
kinds of serializers. However, if you know that your server supports
protocol buffers, you can remove that `if` block entirely and default to
protocol buffers.
Code coverage (see above) is a good way to find dead code in your app.
#### Unsafe options
Dart also provides more aggressive optimization options. However, you have to
be careful when using them and as of today the benefits aren't that clear. If
your type annotations are inaccurate you may end up with non-Darty runtime
behavior, including the classic "undefined is not a function" tautology, as
well as the "keep on truckin'" behavior, e.g. `null + 1 == 1` and
`{} + [] == 0`.
`--trust-type-annotations` tells `dart2js` to trust that your type annotations
are correct. So if you have a function `foo(Bar bar)` the compiler can omit the
check that `bar` is truly `Bar` when calling methods on it.
`--trust-primitives` tells `dart2js` that primitive types, such as numbers and
booleans are never `null` when performing arithmetic, and that your program
does not run into range error when operating on lists, letting the compiler
remove some of the error checking code.
Specify these options in `pubspec.yaml`.
Example:
```yaml
transformers:
...
- $dart2js:
commandLineOptions:
- --trust-type-annotations
- --trust-primitives
```
## Performance
### Change detection profiler
If your application is janky (it misses frames) or is slow according to other
metrics, you need to find out why. This tool helps by measuring the average
speed of _change detection_, a phase in Angular's
lifecycle that detects changes in values that are bound to the UI.
Janky UI updates can result from slowness either in _computing_ the changes or
in _applying_ those changes to the UI.
For your app to be performant, the process of _computing_ changes must be very
fast—preferably **under 3 milliseconds**.
Fast change computation leaves room for
the application logic, UI updates, and browser rendering pipeline
to fit within a 16 ms frame (assuming a target frame rate of 60 FPS).
The change detection profiler repeatedly performs change detection
without invoking any user actions, such as clicking buttons or entering
text in input fields. It then computes the average amount of time
(in milliseconds) to perform a single cycle of change detection and
prints that to the console. This number depends on the current state of the UI. You are likely to see different numbers
as you go from one screen in your application to another.
#### Running the profiler
Before running the profiler, enable the debugging tools
and put the app into the state you want to measure:
1. If you haven't already done so,
[enable the debugging tools](#enabling-the-debugging-tools).
2. Navigate the app to a screen whose performance you want to profile.
3. Make sure the screen is in a state that you want to measure.
For example, you might want to profile the screen several times,
with different amounts and kinds of data.
To run the profiler, enter the following in the dev console:
```javascript
ng.profiler.timeChangeDetection();
```
The results are visible in the console.
#### Recording CPU profiles
To record a profile, pass `{record: true}` to `timeChangeDetection()`:
```javascript
ng.profiler.timeChangeDetection({record: true});
```
Then open the **Profiles** tab. The recorded profile has the title
**Change Detection**. In Chrome, if you record the profile repeatedly, all the
profiles are nested under Change Detection.
#### Interpreting the numbers
In a properly designed application, repeated attempts to detect changes without
any user actions result in no changes to the UI. It is
also desirable to have the cost of a user action be proportional to the amount
of UI changes required. For example, popping up a menu with 5 items should be
vastly faster than rendering a table of 500 rows and 10 columns. Therefore,
change detection with no UI updates should be as fast as possible.
#### Investigating slow change detection
So you found a screen in your application on which the profiler reports a very
high number (i.e. >3ms). This is where a recorded CPU profile can help. Enable
recording while profiling:
```javascript
ng.profiler.timeChangeDetection({record: true});
```
Then look for hot spots using
[Chrome CPU profiler](https://developer.chrome.com/devtools/docs/cpu-profiling).
#### Reducing change detection cost
There are many reasons for slow change detection. To gain intuition about
possible causes it helps to understand how change detection works. Such a
discussion is outside the scope of this document,
but here are some key concepts.
<!-- TODO: link to change detection docs -->
By default, Angular uses a _dirty checking_ mechanism to find model changes.
This mechanism involves evaluating every bound expression that's active on the
UI. These usually include text interpolation via `{{expression}}` and property
bindings via `[prop]="expression"`. If any of the evaluated expressions are
costly to compute, they might contribute to slow change detection. A good way to
speed things up is to use plain class fields in your expressions and avoid any
kind of computation. For example:
```dart
@View(
template: '<button [enabled]="isEnabled">{{title}}</button>'
)
class FancyButton {
// GOOD: no computation, just returns the value
bool isEnabled;
// BAD: computes the final value upon request
String _title;
String get title => _title.trim().toUpperCase();
}
```
Most cases like these can be solved by precomputing the value and storing the
final value in a field.
Angular also supports a second type of change detection: the _push_ model. In
this model, Angular does not poll your component for changes. Instead, the
component tells Angular when it changes, and only then does Angular perform
the update. This model is suitable in situations when your data model uses
observable or immutable objects.
<!-- TODO: link to discussion of push model -->

View File

@ -14,7 +14,7 @@ Ctrl + Shift + j.
By default the debug tools are disabled. You can enable debug tools as follows:
```typescript
import {enableDebugTools} from 'angular2/platform/browser';
import {enableDebugTools} from '@angular/platform-browser';
bootstrap(Application).then((appRef) => {
enableDebugTools(appRef);

View File

@ -4,15 +4,16 @@
var CIconfiguration = {
'Chrome': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Firefox': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
// FirefoxBeta should be required:true
// https://github.com/angular/angular/issues/7560
'FirefoxBeta': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: false}},
// FirefoxBeta and ChromeBeta should be target:'BS' or target:'SL', and required:true
// Currently deactivated due to https://github.com/angular/angular/issues/7560
'ChromeBeta': { unitTest: {target: null, required: true}, e2e: {target: null, required: false}},
'FirefoxBeta': { unitTest: {target: null, required: false}, e2e: {target: null, required: false}},
'ChromeDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'FirefoxDev': { unitTest: {target: null, required: true}, e2e: {target: null, required: true}},
'IE9': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'IE10': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'IE11': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': { unitTest: {target: 'SL', required: true}, e2e: {target: null, required: true}},
'Edge': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'Android4.1': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.2': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
'Android4.3': { unitTest: {target: 'SL', required: false}, e2e: {target: null, required: true}},
@ -23,8 +24,7 @@ var CIconfiguration = {
'Safari9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'iOS7': { unitTest: {target: 'BS', required: true}, e2e: {target: null, required: true}},
'iOS8': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
// TODO(mlaval): iOS9 deactivated as not reliable, reactivate after https://github.com/angular/angular/issues/5408
'iOS9': { unitTest: {target: null, required: false}, e2e: {target: null, required: true}},
'iOS9': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}},
'WindowsPhone': { unitTest: {target: 'BS', required: false}, e2e: {target: null, required: true}}
};
@ -38,7 +38,7 @@ var customLaunchers = {
'SL_CHROME': {
base: 'SauceLabs',
browserName: 'chrome',
version: '50'
version: '52'
},
'SL_CHROMEBETA': {
base: 'SauceLabs',
@ -53,7 +53,7 @@ var customLaunchers = {
'SL_FIREFOX': {
base: 'SauceLabs',
browserName: 'firefox',
version: '45'
version: '46'
},
'SL_FIREFOXBETA': {
base: 'SauceLabs',
@ -69,13 +69,13 @@ var customLaunchers = {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.9',
version: '7'
version: '7.0'
},
'SL_SAFARI8': {
base: 'SauceLabs',
browserName: 'safari',
platform: 'OS X 10.10',
version: '8'
version: '8.0'
},
'SL_SAFARI9': {
base: 'SauceLabs',
@ -99,7 +99,7 @@ var customLaunchers = {
base: 'SauceLabs',
browserName: 'iphone',
platform: 'OS X 10.10',
version: '9.1'
version: '9.3'
},
'SL_IE9': {
base: 'SauceLabs',
@ -121,9 +121,9 @@ var customLaunchers = {
},
'SL_EDGE': {
base: 'SauceLabs',
browserName: 'microsoftedge',
browserName: 'MicrosoftEdge',
platform: 'Windows 10',
version: '20.10240'
version: '13.10586'
},
'SL_ANDROID4.1': {
base: 'SauceLabs',
@ -202,7 +202,7 @@ var customLaunchers = {
base: 'BrowserStack',
device: 'iPhone 6S',
os: 'ios',
os_version: '9.0'
os_version: '9.1'
},
'BS_IE9': {
base: 'BrowserStack',

117
build.sh
View File

@ -4,13 +4,18 @@ set -e -o pipefail
cd `dirname $0`
export NODE_PATH=${NODE_PATH}:$(pwd)/dist/all:$(pwd)/dist/tools
TSCONFIG=./modules/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/ng2tc -p ${TSCONFIG} ====="
rm -rf ./dist/all/
mkdir ./dist/all/
mkdir -p ./dist/all/
# prepare all files for e2e tests
TSCONFIG=./tools/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
$(npm bin)/tsc -p ${TSCONFIG}
cp ./tools/@angular/tsc-wrapped/package.json ./dist/tools/@angular/tsc-wrapped
echo "====== Copying files needed for e2e tests ====="
cp -r ./modules/playground ./dist/all/
cp -r ./modules/playground/favicon.ico ./dist/
#rsync -aP ./modules/playground/* ./dist/all/playground/
@ -22,13 +27,16 @@ ln -s ../../../../node_modules/zone.js/dist/long-stack-trace-zone.js .
ln -s ../../../../node_modules/systemjs/dist/system.src.js .
ln -s ../../../../node_modules/base64-js/lib/b64.js .
ln -s ../../../../node_modules/reflect-metadata/Reflect.js .
ln -s ../../../../node_modules/rxjs/bundles/Rx.js .
ln -s ../../../../node_modules/rxjs .
ln -s ../../../../node_modules/angular/angular.js .
cd -
# compile ts code
$(npm bin)/ng2tc -p ${TSCONFIG}
TSCONFIG=./modules/tsconfig.json
echo "====== (all)COMPILING: \$(npm bin)/tsc -p ${TSCONFIG} ====="
# compile ts code
TSC="node --max-old-space-size=3000 dist/tools/@angular/tsc-wrapped/src/main"
$TSC -p modules/tsconfig.json
rm -rf ./dist/packages-dist
@ -36,26 +44,28 @@ for PACKAGE in \
core \
compiler \
common \
forms \
platform-browser \
platform-browser-dynamic \
platform-server \
http \
router \
router-deprecated \
upgrade
upgrade \
compiler-cli
do
SRCDIR=./modules/@angular/${PACKAGE}
DESTDIR=./dist/packages-dist/${PACKAGE}
UMDES6PATH=${DESTDIR}/esm/${PACKAGE}.umd.js
UMDES5PATH=${DESTDIR}/${PACKAGE}.umd.js
UMD_ES6_PATH=${DESTDIR}/esm/${PACKAGE}.umd.js
UMD_ES5_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.js
UMD_ES5_MIN_PATH=${DESTDIR}/bundles/${PACKAGE}.umd.min.js
if [[ ${PACKAGE} == "router-deprecated" ]]; then
echo "====== COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json ====="
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es5.json
else
echo "====== COMPILING: \$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es5.json ====="
$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es5.json
echo "====== COMPILING: ${TSC} -p ${SRCDIR}/tsconfig-es5.json ====="
$TSC -p ${SRCDIR}/tsconfig-es5.json
fi
cp ${SRCDIR}/package.json ${DESTDIR}/
@ -63,50 +73,49 @@ do
echo "====== TSC 1.8 d.ts compat for ${DESTDIR} ====="
# safely strips 'readonly' specifier from d.ts files to make them compatible with tsc 1.8
if [[ ${TRAVIS} ]]; then
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
if [ "$(uname)" == "Darwin" ]; then
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\(^ *(static |private )*\)*readonly */\1/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
else
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -e 's/\(^ *(static |private )*\)*readonly */\1/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i '' -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -e 's/\(^ *(static |private )*\)*readonly */\1/g'
find ${DESTDIR} -type f -name '*.d.ts' -print0 | xargs -0 sed -i -E 's/^( +)abstract ([[:alnum:]]+\:)/\1\2/g'
fi
if [[ ${PACKAGE} != compiler-cli ]]; then
if [[ ${PACKAGE} == "router-deprecated" ]]; then
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json
else
echo "====== (esm)COMPILING: \$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es2015.json ====="
$(npm bin)/ng2tc -p ${SRCDIR}/tsconfig-es2015.json
if [[ ${PACKAGE} == "router-deprecated" ]]; then
echo "====== (esm)COMPILING: \$(npm bin)/tsc -p ${SRCDIR}/tsconfig-es2015.json ====="
$(npm bin)/tsc --emitDecoratorMetadata -p ${SRCDIR}/tsconfig-es2015.json
else
echo "====== (esm)COMPILING: $TSC -p ${SRCDIR}/tsconfig-es2015.json ====="
$TSC -p ${SRCDIR}/tsconfig-es2015.json
fi
echo "====== BUNDLING: ${SRCDIR} ====="
mkdir ${DESTDIR}/bundles
(
cd ${SRCDIR}
echo "..." # here just to have grep match something and not exit with 1
../../../node_modules/.bin/rollup -c rollup.config.js
) 2>&1 | grep -v "as external dependency"
$(npm bin)/tsc \
--out ${UMD_ES5_PATH} \
--target es5 \
--lib "es6,dom" \
--allowJs \
${UMD_ES6_PATH}
rm ${UMD_ES6_PATH}
cat ./modules/@angular/license-banner.txt > ${UMD_ES5_PATH}.tmp
cat ${UMD_ES5_PATH} >> ${UMD_ES5_PATH}.tmp
mv ${UMD_ES5_PATH}.tmp ${UMD_ES5_PATH}
$(npm bin)/uglifyjs -c --screw-ie8 -o ${UMD_ES5_MIN_PATH} ${UMD_ES5_PATH}
fi
echo "====== BUNDLING: ${SRCDIR} ====="
(
cd ${SRCDIR}
echo "..." # here just to have grep match something and not exit with 1
../../../node_modules/.bin/rollup -c rollup.config.js
) 2>&1 | grep -v "as external dependency"
# workaround for https://github.com/rollup/rollup/issues/626
if [[ ${TRAVIS} ]]; then
sed -i "s/ class exports\./ class /g" ${DESTDIR}/esm/${PACKAGE}.umd.js
else
sed -i '' "s/ class exports\./ class /g" ${DESTDIR}/esm/${PACKAGE}.umd.js
fi
$(npm bin)/tsc \
--out ${UMDES5PATH} \
--target es5 \
--allowJs \
${UMDES6PATH} \
modules/\@angular/manual_typings/globals.d.ts \
modules/\@angular/typings/es6-collections/es6-collections.d.ts \
modules/\@angular/typings/es6-promise/es6-promise.d.ts
rm ${UMDES6PATH}
cat ./modules/@angular/license-banner.txt > ${UMDES5PATH}.tmp
cat ${UMDES5PATH} >> ${UMDES5PATH}.tmp
mv ${UMDES5PATH}.tmp ${UMDES5PATH}
done
echo "====== COMPILING: \$(npm bin)/tsc -p benchpress/tsconfig.json ====="
$(npm bin)/tsc -p ./modules/benchpress/tsconfig.json

View File

@ -5,17 +5,7 @@ machine:
dependencies:
pre:
- npm install -g npm
override:
- npm install:
environment:
# Token for tsd to increase github rate limit
# See https://github.com/DefinitelyTyped/tsd#tsdrc
# This is not hidden using https://circleci.com/docs/fork-pr-builds#details
# because those are not visible for pull requests, and those should also be reliable.
# This SSO token belongs to github account angular-github-ratelimit-token which has no access
# (password is in Valentine)
TSD_GITHUB_TOKEN: ef474500309daea53d5991b3079159a29520a40b
test:
override:
- npm run build
- gulp lint

File diff suppressed because it is too large Load Diff

View File

@ -12,7 +12,6 @@ module.exports = function(config) {
// Sources and specs.
// Loaded through the System loader, in `test-main.js`.
{pattern: 'dist/all/@angular/**/*.js', included: false, watched: true},
{pattern: 'dist/all/angular2/**/*.js', included: false, watched: true},
'node_modules/es6-shim/es6-shim.js',
// include Angular v1 for upgrade module testing
@ -33,12 +32,14 @@ module.exports = function(config) {
'test-main.js',
{pattern: 'dist/all/empty.*', included: false, watched: false},
{pattern: 'modules/@angular/platform-browser/test/static_assets/**', included: false, watched: false},
{pattern: 'modules/@angular/platform-browser-dynamic/test/browser/static_assets/**', included: false, watched: false}
{pattern: 'modules/@angular/platform-browser/test/browser/static_assets/**', included: false, watched: false}
],
exclude: [
'dist/all/@angular/**/e2e_test/**',
'dist/all/@angular/examples/**',
'dist/all/@angular/router/**',
'dist/all/@angular/compiler-cli/**',
'dist/all/angular1_router.js',
'dist/all/@angular/platform-browser/testing/e2e_util.js'
],
@ -51,7 +52,6 @@ module.exports = function(config) {
'karma-sauce-launcher',
'karma-chrome-launcher',
'karma-sourcemap-loader',
'karma-dart',
internalAngularReporter
],
@ -67,7 +67,7 @@ module.exports = function(config) {
recordVideo: false,
recordScreenshots: false,
options: {
'selenium-version': '2.48.2',
'selenium-version': '2.53.0',
'command-timeout': 600,
'idle-timeout': 600,
'max-duration': 5400

View File

@ -1,6 +0,0 @@
Angular2
=========
The sources for this package are in the main [Angular2](https://github.com/angular/angular) repo. Please file issues and pull requests against that repo. This is the repository for the upcoming 2.0 version. If you're looking for the current official version of Angular you should go to [angular/angular.js](https://github.com/angular/angular.js)
License: Apache MIT 2.0

View File

@ -1 +0,0 @@
export 'index.dart';

View File

@ -1,5 +1,30 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgModule} from '@angular/core';
import {COMMON_DIRECTIVES} from './src/common_directives';
import {COMMON_PIPES} from './src/pipes';
export * from './src/pipes';
export * from './src/directives';
export * from './src/forms';
export * from './src/forms-deprecated';
export * from './src/common_directives';
export * from './src/location';
export {NgLocalization} from './src/localization';
// Note: This does not contain the location providers,
// as they need some platform specific implementations to work.
/**
* The module that includes all the basic Angular directives like {@link NgIf}, ${link NgFor}, ...
*
* @experimental
*/
@NgModule(
{declarations: [COMMON_DIRECTIVES, COMMON_PIPES], exports: [COMMON_DIRECTIVES, COMMON_PIPES]})
export class CommonModule {
}

View File

@ -1,6 +1,6 @@
{
"name": "@angular/common",
"version": "$$ANGULAR_VERSION$$",
"version": "0.0.0-PLACEHOLDER",
"description": "",
"main": "index.js",
"jsnext:main": "esm/index.js",
@ -8,6 +8,10 @@
"author": "angular",
"license": "MIT",
"peerDependencies": {
"@angular/core": "$$ANGULAR_VERSION$$"
"@angular/core": "0.0.0-PLACEHOLDER"
},
"repository": {
"type": "git",
"url": "https://github.com/angular/angular.git"
}
}

View File

@ -1,5 +1,13 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '@angular/core';
import {FORM_DIRECTIVES} from './forms';
import {CORE_DIRECTIVES} from './directives';
/**
@ -44,5 +52,7 @@ import {CORE_DIRECTIVES} from './directives';
* ...
* }
* ```
*
* @experimental Contains forms which are experimental.
*/
export const COMMON_DIRECTIVES: Type[][] = /*@ts2dart_const*/[CORE_DIRECTIVES, FORM_DIRECTIVES];
export const COMMON_DIRECTIVES: Type[][] = [CORE_DIRECTIVES];

View File

@ -1,14 +1,21 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* Common directives shipped with Angular.
*/
export {CORE_DIRECTIVES} from './directives/core_directives';
export {NgClass} from './directives/ng_class';
export {NgFor} from './directives/ng_for';
export {NgIf} from './directives/ng_if';
export {NgTemplateOutlet} from './directives/ng_template_outlet';
export {NgPlural, NgPluralCase} from './directives/ng_plural';
export {NgStyle} from './directives/ng_style';
export {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './directives/ng_switch';
export {NgPlural, NgPluralCase, NgLocalization} from './directives/ng_plural';
export * from './directives/observable_list_diff';
export {CORE_DIRECTIVES} from './directives/core_directives';
export {NgSwitch, NgSwitchCase, NgSwitchDefault} from './directives/ng_switch';
export {NgTemplateOutlet} from './directives/ng_template_outlet';

View File

@ -1,11 +1,20 @@
import {Type} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '../facade/lang';
import {NgClass} from './ng_class';
import {NgFor} from './ng_for';
import {NgIf} from './ng_if';
import {NgTemplateOutlet} from './ng_template_outlet';
import {NgStyle} from './ng_style';
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from './ng_switch';
import {NgPlural, NgPluralCase} from './ng_plural';
import {NgStyle} from './ng_style';
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from './ng_switch';
import {NgTemplateOutlet} from './ng_template_outlet';
/**
* A collection of Angular core directives that are likely to be used in each and every Angular
@ -46,16 +55,18 @@ import {NgPlural, NgPluralCase} from './ng_plural';
* ...
* }
* ```
*
* @stable
*/
export const CORE_DIRECTIVES: Type[] = /*@ts2dart_const*/[
export const CORE_DIRECTIVES: Type[] = [
NgClass,
NgFor,
NgIf,
NgTemplateOutlet,
NgStyle,
NgSwitch,
NgSwitchWhen,
NgSwitchCase,
NgSwitchDefault,
NgPlural,
NgPluralCase
NgPluralCase,
];

View File

@ -1,18 +1,17 @@
import {
DoCheck,
OnDestroy,
Directive,
ElementRef,
IterableDiffers,
KeyValueDiffers,
Renderer,
IterableDiffer,
KeyValueDiffer,
CollectionChangeRecord,
KeyValueChangeRecord
} from '@angular/core';
import {isPresent, isString, isArray} from '../../src/facade/lang';
import {StringMapWrapper, isListLikeIterable} from '../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CollectionChangeRecord, Directive, DoCheck, ElementRef, Input, IterableDiffer, IterableDiffers, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {StringMapWrapper, isListLikeIterable} from '../facade/collection';
import {isArray, isPresent, isString} from '../facade/lang';
/**
* The `NgClass` directive conditionally adds and removes CSS classes on an HTML element based on
@ -58,7 +57,7 @@ import {StringMapWrapper, isListLikeIterable} from '../../src/facade/collection'
* color: gray;
* border: medium solid gray;
* }
* `]
* `],
* directives: [NgClass]
* })
* class ToggleButton {
@ -72,17 +71,22 @@ import {StringMapWrapper, isListLikeIterable} from '../../src/facade/collection'
* }
* }
* ```
*
* @stable
*/
@Directive({selector: '[ngClass]', inputs: ['rawClass: ngClass', 'initialClasses: class']})
export class NgClass implements DoCheck, OnDestroy {
@Directive({selector: '[ngClass]'})
export class NgClass implements DoCheck {
private _iterableDiffer: IterableDiffer;
private _keyValueDiffer: KeyValueDiffer;
private _initialClasses: string[] = [];
private _rawClass: string[] | Set<string>;
private _rawClass: string[]|Set<string>;
constructor(private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {}
constructor(
private _iterableDiffers: IterableDiffers, private _keyValueDiffers: KeyValueDiffers,
private _ngEl: ElementRef, private _renderer: Renderer) {}
@Input('class')
set initialClasses(v: string) {
this._applyInitialClasses(true);
this._initialClasses = isPresent(v) && isString(v) ? v.split(' ') : [];
@ -90,14 +94,15 @@ export class NgClass implements DoCheck, OnDestroy {
this._applyClasses(this._rawClass, false);
}
set rawClass(v: string | string[] | Set<string>| {[key: string]: any}) {
@Input()
set ngClass(v: string|string[]|Set<string>|{[key: string]: any}) {
this._cleanupClasses(this._rawClass);
if (isString(v)) {
v = (<string>v).split(' ');
}
this._rawClass = <string[] | Set<string>>v;
this._rawClass = <string[]|Set<string>>v;
this._iterableDiffer = null;
this._keyValueDiffer = null;
if (isPresent(v)) {
@ -124,9 +129,7 @@ export class NgClass implements DoCheck, OnDestroy {
}
}
ngOnDestroy(): void { this._cleanupClasses(this._rawClass); }
private _cleanupClasses(rawClassVal: string[] | Set<string>| {[key: string]: any}): void {
private _cleanupClasses(rawClassVal: string[]|Set<string>|{[key: string]: any}): void {
this._applyClasses(rawClassVal, true);
this._applyInitialClasses(false);
}
@ -154,18 +157,18 @@ export class NgClass implements DoCheck, OnDestroy {
this._initialClasses.forEach(className => this._toggleClass(className, !isCleanup));
}
private _applyClasses(rawClassVal: string[] | Set<string>| {[key: string]: any},
isCleanup: boolean) {
private _applyClasses(
rawClassVal: string[]|Set<string>|{[key: string]: any}, isCleanup: boolean) {
if (isPresent(rawClassVal)) {
if (isArray(rawClassVal)) {
(<string[]>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
} else if (rawClassVal instanceof Set) {
(<Set<string>>rawClassVal).forEach(className => this._toggleClass(className, !isCleanup));
} else {
StringMapWrapper.forEach(<{[k: string]: any}>rawClassVal,
(expVal: any, className: string) => {
if (isPresent(expVal)) this._toggleClass(className, !isCleanup);
});
StringMapWrapper.forEach(
<{[k: string]: any}>rawClassVal, (expVal: any, className: string) => {
if (isPresent(expVal)) this._toggleClass(className, !isCleanup);
});
}
}
}

View File

@ -1,18 +1,15 @@
import {
DoCheck,
Directive,
ChangeDetectorRef,
IterableDiffer,
IterableDiffers,
ViewContainerRef,
TemplateRef,
EmbeddedViewRef,
TrackByFn,
DefaultIterableDiffer,
CollectionChangeRecord
} from '@angular/core';
import {isPresent, isBlank, getTypeNameForDebugging} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, CollectionChangeRecord, DefaultIterableDiffer, Directive, DoCheck, EmbeddedViewRef, Input, IterableDiffer, IterableDiffers, OnChanges, SimpleChanges, TemplateRef, TrackByFn, ViewContainerRef} from '@angular/core';
import {BaseException} from '../facade/exceptions';
import {getTypeNameForDebugging, isBlank, isPresent} from '../facade/lang';
export class NgForRow {
constructor(public $implicit: any, public index: number, public count: number) {}
@ -76,73 +73,79 @@ export class NgForRow {
*
* See a [live demo](http://plnkr.co/edit/KVuXxDp0qinGDyo307QW?p=preview) for a more detailed
* example.
*
* @stable
*/
@Directive({selector: '[ngFor][ngForOf]', inputs: ['ngForTrackBy', 'ngForOf', 'ngForTemplate']})
export class NgFor implements DoCheck {
/** @internal */
_ngForOf: any;
/** @internal */
_ngForTrackBy: TrackByFn;
@Directive({selector: '[ngFor][ngForOf]'})
export class NgFor implements DoCheck, OnChanges {
@Input() ngForOf: any;
@Input() ngForTrackBy: TrackByFn;
private _differ: IterableDiffer;
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>,
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {}
set ngForOf(value: any) {
this._ngForOf = value;
if (isBlank(this._differ) && isPresent(value)) {
try {
this._differ = this._iterableDiffers.find(value).create(this._cdr, this._ngForTrackBy);
} catch (e) {
throw new BaseException(
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
}
}
}
constructor(
private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<NgForRow>,
private _iterableDiffers: IterableDiffers, private _cdr: ChangeDetectorRef) {}
@Input()
set ngForTemplate(value: TemplateRef<NgForRow>) {
if (isPresent(value)) {
this._templateRef = value;
}
}
set ngForTrackBy(value: TrackByFn) { this._ngForTrackBy = value; }
ngOnChanges(changes: SimpleChanges): void {
if ('ngForOf' in changes) {
// React on ngForOf changes only once all inputs have been initialized
const value = changes['ngForOf'].currentValue;
if (isBlank(this._differ) && isPresent(value)) {
try {
this._differ = this._iterableDiffers.find(value).create(this._cdr, this.ngForTrackBy);
} catch (e) {
throw new BaseException(
`Cannot find a differ supporting object '${value}' of type '${getTypeNameForDebugging(value)}'. NgFor only supports binding to Iterables such as Arrays.`);
}
}
}
}
ngDoCheck() {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._ngForOf);
const changes = this._differ.diff(this.ngForOf);
if (isPresent(changes)) this._applyChanges(changes);
}
}
private _applyChanges(changes: DefaultIterableDiffer) {
// TODO(rado): check if change detection can produce a change record that is
// easier to consume than current.
var recordViewTuples: RecordViewTuple[] = [];
changes.forEachRemovedItem((removedRecord: CollectionChangeRecord) =>
recordViewTuples.push(new RecordViewTuple(removedRecord, null)));
const insertTuples: RecordViewTuple[] = [];
changes.forEachOperation(
(item: CollectionChangeRecord, adjustedPreviousIndex: number, currentIndex: number) => {
if (item.previousIndex == null) {
let view = this._viewContainer.createEmbeddedView(
this._templateRef, new NgForRow(null, null, null), currentIndex);
let tuple = new RecordViewTuple(item, view);
insertTuples.push(tuple);
} else if (currentIndex == null) {
this._viewContainer.remove(adjustedPreviousIndex);
} else {
let view = this._viewContainer.get(adjustedPreviousIndex);
this._viewContainer.move(view, currentIndex);
let tuple = new RecordViewTuple(item, <EmbeddedViewRef<NgForRow>>view);
insertTuples.push(tuple);
}
});
changes.forEachMovedItem((movedRecord: CollectionChangeRecord) =>
recordViewTuples.push(new RecordViewTuple(movedRecord, null)));
var insertTuples = this._bulkRemove(recordViewTuples);
changes.forEachAddedItem((addedRecord: CollectionChangeRecord) =>
insertTuples.push(new RecordViewTuple(addedRecord, null)));
this._bulkInsert(insertTuples);
for (var i = 0; i < insertTuples.length; i++) {
for (let i = 0; i < insertTuples.length; i++) {
this._perViewChange(insertTuples[i].view, insertTuples[i].record);
}
for (var i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
for (let i = 0, ilen = this._viewContainer.length; i < ilen; i++) {
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(i);
viewRef.context.index = i;
viewRef.context.count = ilen;
}
changes.forEachIdentityChange((record) => {
changes.forEachIdentityChange((record: any) => {
var viewRef = <EmbeddedViewRef<NgForRow>>this._viewContainer.get(record.currentIndex);
viewRef.context.$implicit = record.item;
});
@ -151,45 +154,8 @@ export class NgFor implements DoCheck {
private _perViewChange(view: EmbeddedViewRef<NgForRow>, record: CollectionChangeRecord) {
view.context.$implicit = record.item;
}
private _bulkRemove(tuples: RecordViewTuple[]): RecordViewTuple[] {
tuples.sort((a: RecordViewTuple, b: RecordViewTuple) =>
a.record.previousIndex - b.record.previousIndex);
var movedTuples: RecordViewTuple[] = [];
for (var i = tuples.length - 1; i >= 0; i--) {
var tuple = tuples[i];
// separate moved views from removed views.
if (isPresent(tuple.record.currentIndex)) {
tuple.view =
<EmbeddedViewRef<NgForRow>>this._viewContainer.detach(tuple.record.previousIndex);
movedTuples.push(tuple);
} else {
this._viewContainer.remove(tuple.record.previousIndex);
}
}
return movedTuples;
}
private _bulkInsert(tuples: RecordViewTuple[]): RecordViewTuple[] {
tuples.sort((a, b) => a.record.currentIndex - b.record.currentIndex);
for (var i = 0; i < tuples.length; i++) {
var tuple = tuples[i];
if (isPresent(tuple.view)) {
this._viewContainer.insert(tuple.view, tuple.record.currentIndex);
} else {
tuple.view = this._viewContainer.createEmbeddedView(
this._templateRef, new NgForRow(null, null, null), tuple.record.currentIndex);
}
}
return tuples;
}
}
class RecordViewTuple {
view: EmbeddedViewRef<NgForRow>;
record: any;
constructor(record: any, view: EmbeddedViewRef<NgForRow>) {
this.record = record;
this.view = view;
}
constructor(public record: any, public view: EmbeddedViewRef<NgForRow>) {}
}

View File

@ -1,5 +1,16 @@
import {Directive, ViewContainerRef, TemplateRef} from '@angular/core';
import {isBlank} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {isBlank} from '../facade/lang';
/**
* Removes or recreates a portion of the DOM tree based on an {expression}.
@ -22,15 +33,18 @@ import {isBlank} from '../../src/facade/lang';
* - `<div *ngIf="condition">...</div>`
* - `<div template="ngIf condition">...</div>`
* - `<template [ngIf]="condition"><div>...</div></template>`
*
* @stable
*/
@Directive({selector: '[ngIf]', inputs: ['ngIf']})
@Directive({selector: '[ngIf]'})
export class NgIf {
private _prevCondition: boolean = null;
constructor(private _viewContainer: ViewContainerRef, private _templateRef: TemplateRef<Object>) {
}
set ngIf(newCondition: any /* boolean */) {
@Input()
set ngIf(newCondition: any) {
if (newCondition && (isBlank(this._prevCondition) || !this._prevCondition)) {
this._prevCondition = true;
this._viewContainer.createEmbeddedView(this._templateRef);

View File

@ -1,21 +1,18 @@
import {
Directive,
ViewContainerRef,
TemplateRef,
ContentChildren,
QueryList,
Attribute,
AfterContentInit,
Input
} from '@angular/core';
import {isPresent, NumberWrapper} from '../../src/facade/lang';
import {Map} from '../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Attribute, Directive, Host, Input, OnInit, TemplateRef, ViewContainerRef} from '@angular/core';
import {isPresent} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization';
import {SwitchView} from './ng_switch';
const _CATEGORY_DEFAULT = 'other';
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
/**
* `ngPlural` is an i18n directive that displays DOM sub-trees that match the switch expression
@ -32,9 +29,6 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
* value matches aren't found and the value maps to its category using the `getPluralCategory`
* function provided.
*
* If no matching views are found for a switch expression, inner elements marked
* `[ngPluralCase]="other"` will be displayed.
*
* ```typescript
* class MyLocalization extends NgLocalization {
* getPluralCategory(value: any) {
@ -46,7 +40,7 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
*
* @Component({
* selector: 'app',
* providers: [provide(NgLocalization, {useClass: MyLocalization})]
* providers: [{provide: NgLocalization, useClass: MyLocalization}]
* })
* @View({
* template: `
@ -71,25 +65,13 @@ export abstract class NgLocalization { abstract getPluralCategory(value: any): s
* }
*
* ```
* @experimental
*/
@Directive({selector: '[ngPluralCase]'})
export class NgPluralCase {
/** @internal */
_view: SwitchView;
constructor(@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef) {
this._view = new SwitchView(viewContainer, template);
}
}
@Directive({selector: '[ngPlural]'})
export class NgPlural implements AfterContentInit {
export class NgPlural {
private _switchValue: number;
private _activeView: SwitchView;
private _caseViews = new Map<any, SwitchView>();
@ContentChildren(NgPluralCase) cases: QueryList<NgPluralCase> = null;
private _caseViews: {[k: string]: SwitchView} = {};
constructor(private _localization: NgLocalization) {}
@ -99,21 +81,15 @@ export class NgPlural implements AfterContentInit {
this._updateView();
}
ngAfterContentInit() {
this.cases.forEach((pluralCase: NgPluralCase): void => {
this._caseViews.set(this._formatValue(pluralCase), pluralCase._view);
});
this._updateView();
}
addCase(value: string, switchView: SwitchView): void { this._caseViews[value] = switchView; }
/** @internal */
_updateView(): void {
this._clearViews();
var view: SwitchView = this._caseViews.get(this._switchValue);
if (!isPresent(view)) view = this._getCategoryView(this._switchValue);
this._activateView(view);
var key =
getPluralCategory(this._switchValue, Object.keys(this._caseViews), this._localization);
this._activateView(this._caseViews[key]);
}
/** @internal */
@ -127,22 +103,16 @@ export class NgPlural implements AfterContentInit {
this._activeView = view;
this._activeView.create();
}
/** @internal */
_getCategoryView(value: number): SwitchView {
var category: string = this._localization.getPluralCategory(value);
var categoryView: SwitchView = this._caseViews.get(category);
return isPresent(categoryView) ? categoryView : this._caseViews.get(_CATEGORY_DEFAULT);
}
/** @internal */
_isValueView(pluralCase: NgPluralCase): boolean { return pluralCase.value[0] === "="; }
/** @internal */
_formatValue(pluralCase: NgPluralCase): any {
return this._isValueView(pluralCase) ? this._stripValue(pluralCase.value) : pluralCase.value;
}
/** @internal */
_stripValue(value: string): number { return NumberWrapper.parseInt(value.substring(1), 10); }
}
/**
* @experimental
*/
@Directive({selector: '[ngPluralCase]'})
export class NgPluralCase {
constructor(
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
ngPlural.addCase(value, new SwitchView(viewContainer, template));
}
}

View File

@ -1,13 +1,16 @@
import {KeyValueChangeRecord} from '@angular/core';
import {
DoCheck,
KeyValueDiffer,
KeyValueDiffers,
ElementRef,
Directive,
Renderer
} from '@angular/core';
import {isPresent, isBlank} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, DoCheck, ElementRef, Input, KeyValueChangeRecord, KeyValueDiffer, KeyValueDiffers, Renderer} from '@angular/core';
import {isBlank, isPresent} from '../facade/lang';
/**
* The `NgStyle` directive changes styles based on a result of expression evaluation.
@ -18,7 +21,8 @@ import {isPresent, isBlank} from '../../src/facade/lang';
*
* ### Syntax
*
* - `<div [ngStyle]="{'font-style': style}"></div>`
* - `<div [ngStyle]="{'font-style': styleExp}"></div>`
* - `<div [ngStyle]="{'max-width.px': widthExp}"></div>`
* - `<div [ngStyle]="styleExp"></div>` - here the `styleExp` must evaluate to an object
*
* ### Example ([live demo](http://plnkr.co/edit/YamGS6GkUh9GqWNQhCyM?p=preview)):
@ -59,27 +63,30 @@ import {isPresent, isBlank} from '../../src/facade/lang';
*
* In this example the `font-style`, `font-size` and `font-weight` styles will be updated
* based on the `style` property's value changes.
*
* @stable
*/
@Directive({selector: '[ngStyle]', inputs: ['rawStyle: ngStyle']})
@Directive({selector: '[ngStyle]'})
export class NgStyle implements DoCheck {
/** @internal */
_rawStyle: {[key: string]: string};
_ngStyle: {[key: string]: string};
/** @internal */
_differ: KeyValueDiffer;
constructor(private _differs: KeyValueDiffers, private _ngEl: ElementRef,
private _renderer: Renderer) {}
constructor(
private _differs: KeyValueDiffers, private _ngEl: ElementRef, private _renderer: Renderer) {}
set rawStyle(v: {[key: string]: string}) {
this._rawStyle = v;
@Input()
set ngStyle(v: {[key: string]: string}) {
this._ngStyle = v;
if (isBlank(this._differ) && isPresent(v)) {
this._differ = this._differs.find(this._rawStyle).create(null);
this._differ = this._differs.find(this._ngStyle).create(null);
}
}
ngDoCheck() {
if (isPresent(this._differ)) {
var changes = this._differ.diff(this._rawStyle);
var changes = this._differ.diff(this._ngStyle);
if (isPresent(changes)) {
this._applyChanges(changes);
}
@ -87,15 +94,19 @@ export class NgStyle implements DoCheck {
}
private _applyChanges(changes: any): void {
changes.forEachRemovedItem(
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
changes.forEachAddedItem(
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
changes.forEachChangedItem(
(record: KeyValueChangeRecord) => { this._setStyle(record.key, record.currentValue); });
changes.forEachRemovedItem(
(record: KeyValueChangeRecord) => { this._setStyle(record.key, null); });
}
private _setStyle(name: string, val: string): void {
this._renderer.setElementStyle(this._ngEl.nativeElement, name, val);
const nameParts = name.split('.');
const nameToSet = nameParts[0];
const valToSet = isPresent(val) && nameParts.length === 2 ? `${val}${nameParts[1]}` : val;
this._renderer.setElementStyle(this._ngEl.nativeElement, nameToSet, valToSet);
}
}

View File

@ -1,12 +1,24 @@
import {Directive, Host, ViewContainerRef, TemplateRef} from '@angular/core';
import {isPresent, isBlank, normalizeBlank} from '../../src/facade/lang';
import {ListWrapper, Map} from '../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const _WHEN_DEFAULT = /*@ts2dart_const*/ new Object();
import {Directive, Host, Input, TemplateRef, ViewContainerRef} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {isBlank, isPresent, normalizeBlank} from '../facade/lang';
const _CASE_DEFAULT = new Object();
// TODO: remove when fully deprecated
let _warned: boolean = false;
export class SwitchView {
constructor(private _viewContainerRef: ViewContainerRef,
private _templateRef: TemplateRef<Object>) {}
constructor(
private _viewContainerRef: ViewContainerRef, private _templateRef: TemplateRef<Object>) {}
create(): void { this._viewContainerRef.createEmbeddedView(this._templateRef); }
@ -16,17 +28,17 @@ export class SwitchView {
/**
* Adds or removes DOM sub-trees when their match expressions match the switch expression.
*
* Elements within `NgSwitch` but without `NgSwitchWhen` or `NgSwitchDefault` directives will be
* Elements within `NgSwitch` but without `NgSwitchCase` or `NgSwitchDefault` directives will be
* preserved at the location as specified in the template.
*
* `NgSwitch` simply inserts nested elements based on which match expression matches the value
* obtained from the evaluated switch expression. In other words, you define a container element
* (where you place the directive with a switch expression on the
* `[ngSwitch]="..."` attribute), define any inner elements inside of the directive and
* place a `[ngSwitchWhen]` attribute per element.
* place a `[ngSwitchCase]` attribute per element.
*
* The `ngSwitchWhen` property is used to inform `NgSwitch` which element to display when the
* expression is evaluated. If a matching expression is not found via a `ngSwitchWhen` property
* The `ngSwitchCase` property is used to inform `NgSwitch` which element to display when the
* expression is evaluated. If a matching expression is not found via a `ngSwitchCase` property
* then an element with the `ngSwitchDefault` attribute is displayed.
*
* ### Example ([live demo](http://plnkr.co/edit/DQMTII95CbuqWrl3lYAs?p=preview))
@ -39,24 +51,24 @@ export class SwitchView {
* <button (click)="inc()">Increment</button>
*
* <div [ngSwitch]="value">
* <p *ngSwitchWhen="'init'">increment to start</p>
* <p *ngSwitchWhen="0">0, increment again</p>
* <p *ngSwitchWhen="1">1, increment again</p>
* <p *ngSwitchWhen="2">2, stop incrementing</p>
* <p *ngSwitchCase="'init'">increment to start</p>
* <p *ngSwitchCase="0">0, increment again</p>
* <p *ngSwitchCase="1">1, increment again</p>
* <p *ngSwitchCase="2">2, stop incrementing</p>
* <p *ngSwitchDefault>&gt; 2, STOP!</p>
* </div>
*
* <!-- alternate syntax -->
*
* <p [ngSwitch]="value">
* <template ngSwitchWhen="init">increment to start</template>
* <template [ngSwitchWhen]="0">0, increment again</template>
* <template [ngSwitchWhen]="1">1, increment again</template>
* <template [ngSwitchWhen]="2">2, stop incrementing</template>
* <template ngSwitchCase="init">increment to start</template>
* <template [ngSwitchCase]="0">0, increment again</template>
* <template [ngSwitchCase]="1">1, increment again</template>
* <template [ngSwitchCase]="2">2, stop incrementing</template>
* <template ngSwitchDefault>&gt; 2, STOP!</template>
* </p>
* `,
* directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault]
* directives: [NgSwitch, NgSwitchCase, NgSwitchDefault]
* })
* export class App {
* value = 'init';
@ -68,14 +80,17 @@ export class SwitchView {
*
* bootstrap(App).catch(err => console.error(err));
* ```
*
* @experimental
*/
@Directive({selector: '[ngSwitch]', inputs: ['ngSwitch']})
@Directive({selector: '[ngSwitch]'})
export class NgSwitch {
private _switchValue: any;
private _useDefault: boolean = false;
private _valueViews = new Map<any, SwitchView[]>();
private _activeViews: SwitchView[] = [];
@Input()
set ngSwitch(value: any) {
// Empty the currently active ViewContainers
this._emptyAllActiveViews();
@ -85,7 +100,7 @@ export class NgSwitch {
var views = this._valueViews.get(value);
if (isBlank(views)) {
this._useDefault = true;
views = normalizeBlank(this._valueViews.get(_WHEN_DEFAULT));
views = normalizeBlank(this._valueViews.get(_CASE_DEFAULT));
}
this._activateViews(views);
@ -93,14 +108,14 @@ export class NgSwitch {
}
/** @internal */
_onWhenValueChanged(oldWhen: any, newWhen: any, view: SwitchView): void {
this._deregisterView(oldWhen, view);
this._registerView(newWhen, view);
_onCaseValueChanged(oldCase: any, newCase: any, view: SwitchView): void {
this._deregisterView(oldCase, view);
this._registerView(newCase, view);
if (oldWhen === this._switchValue) {
if (oldCase === this._switchValue) {
view.destroy();
ListWrapper.remove(this._activeViews, view);
} else if (newWhen === this._switchValue) {
} else if (newCase === this._switchValue) {
if (this._useDefault) {
this._useDefault = false;
this._emptyAllActiveViews();
@ -112,7 +127,7 @@ export class NgSwitch {
// Switch to default when there is no more active ViewContainers
if (this._activeViews.length === 0 && !this._useDefault) {
this._useDefault = true;
this._activateViews(this._valueViews.get(_WHEN_DEFAULT));
this._activateViews(this._valueViews.get(_CASE_DEFAULT));
}
}
@ -148,8 +163,8 @@ export class NgSwitch {
/** @internal */
_deregisterView(value: any, view: SwitchView): void {
// `_WHEN_DEFAULT` is used a marker for non-registered whens
if (value === _WHEN_DEFAULT) return;
// `_CASE_DEFAULT` is used a marker for non-registered cases
if (value === _CASE_DEFAULT) return;
var views = this._valueViews.get(value);
if (views.length == 1) {
this._valueViews.delete(value);
@ -160,30 +175,44 @@ export class NgSwitch {
}
/**
* Insert the sub-tree when the `ngSwitchWhen` expression evaluates to the same value as the
* Insert the sub-tree when the `ngSwitchCase` expression evaluates to the same value as the
* enclosing switch expression.
*
* If multiple match expression match the switch expression value, all of them are displayed.
*
* See {@link NgSwitch} for more details and example.
*
* @experimental
*/
@Directive({selector: '[ngSwitchWhen]', inputs: ['ngSwitchWhen']})
export class NgSwitchWhen {
// `_WHEN_DEFAULT` is used as a marker for a not yet initialized value
@Directive({selector: '[ngSwitchCase],[ngSwitchWhen]'})
export class NgSwitchCase {
// `_CASE_DEFAULT` is used as a marker for a not yet initialized value
/** @internal */
_value: any = _WHEN_DEFAULT;
_value: any = _CASE_DEFAULT;
/** @internal */
_view: SwitchView;
private _switch: NgSwitch;
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() ngSwitch: NgSwitch) {
constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() ngSwitch: NgSwitch) {
this._switch = ngSwitch;
this._view = new SwitchView(viewContainer, templateRef);
}
@Input()
set ngSwitchCase(value: any) {
this._switch._onCaseValueChanged(this._value, value, this._view);
this._value = value;
}
@Input()
set ngSwitchWhen(value: any) {
this._switch._onWhenValueChanged(this._value, value, this._view);
if (!_warned) {
_warned = true;
console.warn('*ngSwitchWhen is deprecated and will be removed. Use *ngSwitchCase instead');
}
this._switch._onCaseValueChanged(this._value, value, this._view);
this._value = value;
}
}
@ -193,11 +222,14 @@ export class NgSwitchWhen {
* value.
*
* See {@link NgSwitch} for more details and example.
*
* @experimental
*/
@Directive({selector: '[ngSwitchDefault]'})
export class NgSwitchDefault {
constructor(viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() sswitch: NgSwitch) {
sswitch._registerView(_WHEN_DEFAULT, new SwitchView(viewContainer, templateRef));
constructor(
viewContainer: ViewContainerRef, templateRef: TemplateRef<Object>,
@Host() sswitch: NgSwitch) {
sswitch._registerView(_CASE_DEFAULT, new SwitchView(viewContainer, templateRef));
}
}

View File

@ -1,26 +1,52 @@
import {Directive, Input, ViewContainerRef, ViewRef, TemplateRef} from '@angular/core';
import {isPresent} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, EmbeddedViewRef, Input, OnChanges, TemplateRef, ViewContainerRef} from '@angular/core';
/**
* Creates and inserts an embedded view based on a prepared `TemplateRef`.
* You can attach a context object to the `EmbeddedViewRef` by setting `[ngOutletContext]`.
* `[ngOutletContext]` should be an object, the object's keys will be the local template variables
* available within the `TemplateRef`.
*
* Note: using the key `$implicit` in the context object will set it's value as default.
*
* ### Syntax
* - `<template [ngTemplateOutlet]="templateRefExpression"></template>`
*
* ```
* <template [ngTemplateOutlet]="templateRefExpression"
* [ngOutletContext]="objectExpression">
* </template>
* ```
*
* @experimental
*/
@Directive({selector: '[ngTemplateOutlet]'})
export class NgTemplateOutlet {
private _insertedViewRef: ViewRef;
export class NgTemplateOutlet implements OnChanges {
private _viewRef: EmbeddedViewRef<any>;
private _context: Object;
private _templateRef: TemplateRef<any>;
constructor(private _viewContainerRef: ViewContainerRef) {}
@Input()
set ngTemplateOutlet(templateRef: TemplateRef<Object>) {
if (isPresent(this._insertedViewRef)) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._insertedViewRef));
set ngOutletContext(context: Object) { this._context = context; }
@Input()
set ngTemplateOutlet(templateRef: TemplateRef<Object>) { this._templateRef = templateRef; }
ngOnChanges() {
if (this._viewRef) {
this._viewContainerRef.remove(this._viewContainerRef.indexOf(this._viewRef));
}
if (isPresent(templateRef)) {
this._insertedViewRef = this._viewContainerRef.createEmbeddedView(templateRef);
if (this._templateRef) {
this._viewRef = this._viewContainerRef.createEmbeddedView(this._templateRef, this._context);
}
}
}

View File

@ -1,63 +0,0 @@
library angular2.directives.observable_list_iterable_diff;
import 'package:observe/observe.dart' show ObservableList;
import 'package:angular2/core.dart';
import 'package:angular2/src/core/change_detection/differs/default_iterable_differ.dart';
import 'dart:async';
class ObservableListDiff extends DefaultIterableDiffer {
ChangeDetectorRef _ref;
ObservableListDiff(this._ref);
bool _updated = true;
ObservableList _collection;
StreamSubscription _subscription;
onDestroy() {
if (this._subscription != null) {
this._subscription.cancel();
this._subscription = null;
this._collection = null;
}
}
DefaultIterableDiffer diff(ObservableList collection) {
if (collection is! ObservableList) {
throw "Cannot change the type of a collection";
}
// A new collection instance is passed in.
// - We need to set up a listener.
// - We need to diff collection.
if (!identical(_collection, collection)) {
_collection = collection;
if (_subscription != null) _subscription.cancel();
_subscription = collection.changes.listen((_) {
_updated = true;
_ref.markForCheck();
});
_updated = false;
return super.diff(collection);
// An update has been registered since the last change detection check.
// - We reset the flag.
// - We diff the collection.
} else if (_updated) {
_updated = false;
return super.diff(collection);
// No updates has been registered.
} else {
return null;
}
}
}
class ObservableListDiffFactory implements IterableDifferFactory {
const ObservableListDiffFactory();
bool supports(obj) => obj is ObservableList;
IterableDiffer create(ChangeDetectorRef cdRef, [Function trackByFn]) {
return new ObservableListDiff(cdRef);
}
}

View File

@ -1,10 +0,0 @@
// TS does not have Observables
// I need to be here to make TypeScript think this is a module.
import {} from '../../src/facade/lang';
/**
* This module exists in Dart, but not in Typescript. This exported symbol
* is only here to help Typescript think this is a module.
*/
export var workaround_empty_observable_list_diff: any;

View File

@ -0,0 +1,75 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* This module is used for handling user input, by defining and building a {@link ControlGroup} that
* consists of
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
* to read information
* from the form DOM elements.
*
* Forms providers are not included in default providers; you must import these providers
* explicitly.
*/
import {NgModule, Type} from '@angular/core';
import {FORM_DIRECTIVES} from './forms-deprecated/directives';
import {RadioControlRegistry} from './forms-deprecated/directives/radio_control_value_accessor';
import {FormBuilder} from './forms-deprecated/form_builder';
export {FORM_DIRECTIVES, RadioButtonState} from './forms-deprecated/directives';
export {AbstractControlDirective} from './forms-deprecated/directives/abstract_control_directive';
export {CheckboxControlValueAccessor} from './forms-deprecated/directives/checkbox_value_accessor';
export {ControlContainer} from './forms-deprecated/directives/control_container';
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './forms-deprecated/directives/control_value_accessor';
export {DefaultValueAccessor} from './forms-deprecated/directives/default_value_accessor';
export {Form} from './forms-deprecated/directives/form_interface';
export {NgControl} from './forms-deprecated/directives/ng_control';
export {NgControlGroup} from './forms-deprecated/directives/ng_control_group';
export {NgControlName} from './forms-deprecated/directives/ng_control_name';
export {NgControlStatus} from './forms-deprecated/directives/ng_control_status';
export {NgForm} from './forms-deprecated/directives/ng_form';
export {NgFormControl} from './forms-deprecated/directives/ng_form_control';
export {NgFormModel} from './forms-deprecated/directives/ng_form_model';
export {NgModel} from './forms-deprecated/directives/ng_model';
export {NgSelectOption, SelectControlValueAccessor} from './forms-deprecated/directives/select_control_value_accessor';
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator, Validator} from './forms-deprecated/directives/validators';
export {FormBuilder} from './forms-deprecated/form_builder';
export {AbstractControl, Control, ControlArray, ControlGroup} from './forms-deprecated/model';
export {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from './forms-deprecated/validators';
/**
* Shorthand set of providers used for building Angular forms.
*
* ### Example
*
* ```typescript
* bootstrap(MyApp, [FORM_PROVIDERS]);
* ```
*
* @experimental
*/
export const FORM_PROVIDERS: Type[] = [FormBuilder, RadioControlRegistry];
/**
* The ng module for the deprecated forms API.
* @deprecated
*/
@NgModule({
providers: [
FORM_PROVIDERS,
],
declarations: FORM_DIRECTIVES,
exports: FORM_DIRECTIVES
})
export class DeprecatedFormsModule {
}

View File

@ -1,52 +1,45 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Type} from '@angular/core';
import {NgControlName} from './directives/ng_control_name';
import {NgFormControl} from './directives/ng_form_control';
import {NgModel} from './directives/ng_model';
import {NgControlGroup} from './directives/ng_control_group';
import {NgFormModel} from './directives/ng_form_model';
import {NgForm} from './directives/ng_form';
import {DefaultValueAccessor} from './directives/default_value_accessor';
import {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
import {DefaultValueAccessor} from './directives/default_value_accessor';
import {NgControlGroup} from './directives/ng_control_group';
import {NgControlName} from './directives/ng_control_name';
import {NgControlStatus} from './directives/ng_control_status';
import {NgForm} from './directives/ng_form';
import {NgFormControl} from './directives/ng_form_control';
import {NgFormModel} from './directives/ng_form_model';
import {NgModel} from './directives/ng_model';
import {NumberValueAccessor} from './directives/number_value_accessor';
import {RadioControlValueAccessor} from './directives/radio_control_value_accessor';
import {NgControlStatus} from './directives/ng_control_status';
import {
SelectControlValueAccessor,
NgSelectOption
} from './directives/select_control_value_accessor';
import {
RequiredValidator,
MinLengthValidator,
MaxLengthValidator,
PatternValidator
} from './directives/validators';
import {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
import {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
import {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
export {NgControlName} from './directives/ng_control_name';
export {NgFormControl} from './directives/ng_form_control';
export {NgModel} from './directives/ng_model';
export {NgControlGroup} from './directives/ng_control_group';
export {NgFormModel} from './directives/ng_form_model';
export {NgForm} from './directives/ng_form';
export {DefaultValueAccessor} from './directives/default_value_accessor';
export {CheckboxControlValueAccessor} from './directives/checkbox_value_accessor';
export {
RadioControlValueAccessor,
RadioButtonState
} from './directives/radio_control_value_accessor';
export {NumberValueAccessor} from './directives/number_value_accessor';
export {NgControlStatus} from './directives/ng_control_status';
export {
SelectControlValueAccessor,
NgSelectOption
} from './directives/select_control_value_accessor';
export {
RequiredValidator,
MinLengthValidator,
MaxLengthValidator,
PatternValidator
} from './directives/validators';
export {NgControl} from './directives/ng_control';
export {ControlValueAccessor} from './directives/control_value_accessor';
export {DefaultValueAccessor} from './directives/default_value_accessor';
export {NgControl} from './directives/ng_control';
export {NgControlGroup} from './directives/ng_control_group';
export {NgControlName} from './directives/ng_control_name';
export {NgControlStatus} from './directives/ng_control_status';
export {NgForm} from './directives/ng_form';
export {NgFormControl} from './directives/ng_form_control';
export {NgFormModel} from './directives/ng_form_model';
export {NgModel} from './directives/ng_model';
export {NumberValueAccessor} from './directives/number_value_accessor';
export {RadioButtonState, RadioControlValueAccessor} from './directives/radio_control_value_accessor';
export {NgSelectOption, SelectControlValueAccessor} from './directives/select_control_value_accessor';
export {NgSelectMultipleOption, SelectMultipleControlValueAccessor} from './directives/select_multiple_control_value_accessor';
export {MaxLengthValidator, MinLengthValidator, PatternValidator, RequiredValidator} from './directives/validators';
/**
*
@ -63,8 +56,9 @@ export {ControlValueAccessor} from './directives/control_value_accessor';
* })
* class MyApp {}
* ```
* @experimental
*/
export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
export const FORM_DIRECTIVES: Type[] = [
NgControlName,
NgControlGroup,
@ -74,15 +68,17 @@ export const FORM_DIRECTIVES: Type[] = /*@ts2dart_const*/[
NgForm,
NgSelectOption,
NgSelectMultipleOption,
DefaultValueAccessor,
NumberValueAccessor,
CheckboxControlValueAccessor,
SelectControlValueAccessor,
SelectMultipleControlValueAccessor,
RadioControlValueAccessor,
NgControlStatus,
RequiredValidator,
MinLengthValidator,
MaxLengthValidator,
PatternValidator
PatternValidator,
];

View File

@ -1,11 +1,22 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {unimplemented} from '../../facade/exceptions';
import {isPresent} from '../../facade/lang';
import {AbstractControl} from '../model';
import {isPresent} from '../../../src/facade/lang';
import {unimplemented} from '../../../src/facade/exceptions';
/**
* Base class for control directives.
*
* Only used internally in the forms module.
*
* @experimental
*/
export abstract class AbstractControlDirective {
get control(): AbstractControl { return unimplemented(); }

View File

@ -1,7 +1,16 @@
import {Directive, Renderer, ElementRef, Self, forwardRef, Provider} from '@angular/core';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export const CHECKBOX_VALUE_ACCESSOR: any = /*@ts2dart_const*/ {
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const CHECKBOX_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckboxControlValueAccessor),
multi: true
@ -14,6 +23,8 @@ export const CHECKBOX_VALUE_ACCESSOR: any = /*@ts2dart_const*/ {
* ```
* <input type="checkbox" ngControl="rememberLogin">
* ```
*
* @experimental
*/
@Directive({
selector:

View File

@ -1,10 +1,21 @@
import {Form} from './form_interface';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbstractControlDirective} from './abstract_control_directive';
import {Form} from './form_interface';
/**
* A directive that contains multiple {@link NgControl}s.
*
* Only used by the forms module.
*
* @experimental
*/
export class ControlContainer extends AbstractControlDirective {
name: string;

View File

@ -1,3 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {OpaqueToken} from '@angular/core';
/**
@ -7,6 +15,8 @@ import {OpaqueToken} from '@angular/core';
* DOM element representing an input control.
*
* Please see {@link DefaultValueAccessor} for more information.
*
* @experimental
*/
export interface ControlValueAccessor {
/**
@ -29,6 +39,6 @@ export interface ControlValueAccessor {
* Used to provide a {@link ControlValueAccessor} for form controls.
*
* See {@link DefaultValueAccessor} for how to implement one.
* @experimental
*/
export const NG_VALUE_ACCESSOR: OpaqueToken =
/*@ts2dart_const*/ new OpaqueToken("NgValueAccessor");
export const NG_VALUE_ACCESSOR: OpaqueToken = new OpaqueToken('NgValueAccessor');

View File

@ -1,13 +1,22 @@
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
import {isBlank} from '../../../src/facade/lang';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
/* @ts2dart_Provider */ {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
import {isBlank} from '../../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const DEFAULT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DefaultValueAccessor),
multi: true
};
/**
* The default accessor for writing a value and listening to changes that is used by the
@ -17,6 +26,8 @@ export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
* ```
* <input type="text" ngControl="searchQuery">
* ```
*
* @experimental
*/
@Directive({
selector:
@ -25,7 +36,7 @@ export const DEFAULT_VALUE_ACCESSOR: any = /*@ts2dart_const*/
// https://github.com/angular/angular/issues/3011 is implemented
// selector: '[ngControl],[ngModel],[ngFormControl]',
host: {'(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
bindings: [DEFAULT_VALUE_ACCESSOR]
providers: [DEFAULT_VALUE_ACCESSOR]
})
export class DefaultValueAccessor implements ControlValueAccessor {
onChange = (_: any) => {};

View File

@ -1,11 +1,23 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Control, ControlGroup} from '../model';
import {NgControl} from './ng_control';
import {NgControlGroup} from './ng_control_group';
import {Control, ControlGroup} from '../model';
/**
* An interface that {@link NgFormModel} and {@link NgForm} implement.
*
* Only used by the forms module.
*
* @experimental
*/
export interface Form {
/**

View File

@ -0,0 +1,32 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {unimplemented} from '../../facade/exceptions';
import {AbstractControlDirective} from './abstract_control_directive';
import {ControlValueAccessor} from './control_value_accessor';
import {AsyncValidatorFn, ValidatorFn} from './validators';
/**
* A base class that all control directive extend.
* It binds a {@link Control} object to a DOM element.
*
* Used internally by Angular forms.
*
* @experimental
*/
export abstract class NgControl extends AbstractControlDirective {
name: string = null;
valueAccessor: ControlValueAccessor = null;
get validator(): ValidatorFn { return <ValidatorFn>unimplemented(); }
get asyncValidator(): AsyncValidatorFn { return <AsyncValidatorFn>unimplemented(); }
abstract viewToModelUpdate(newValue: any): void;
}

View File

@ -1,27 +1,25 @@
import {
OnInit,
OnDestroy,
Directive,
Optional,
Inject,
Host,
SkipSelf,
forwardRef,
Provider,
Self
} from '@angular/core';
import {ControlContainer} from './control_container';
import {controlPath, composeValidators, composeAsyncValidators} from './shared';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Host, Inject, OnDestroy, OnInit, Optional, Self, SkipSelf, forwardRef} from '@angular/core';
import {ControlGroup} from '../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
import {ControlContainer} from './control_container';
import {Form} from './form_interface';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {composeAsyncValidators, composeValidators, controlPath} from './shared';
import {AsyncValidatorFn, ValidatorFn} from './validators';
export const controlGroupProvider: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: ControlContainer,
useExisting: forwardRef(() => NgControlGroup)
};
export const controlGroupProvider: any = {
provide: ControlContainer,
useExisting: forwardRef(() => NgControlGroup)
};
/**
* Creates and binds a control group to a DOM element.
@ -33,12 +31,11 @@ export const controlGroupProvider: any =
* ```typescript
* @Component({
* selector: 'my-app',
* directives: [FORM_DIRECTIVES],
* template: `
* <div>
* <h2>Angular Control &amp; ControlGroup Example</h2>
* <form #f="ngForm">
* <div ngControlGroup="name" #cg-name="form">
* <div ngControlGroup="name" #cgName="ngForm">
* <h3>Enter your name:</h3>
* <p>First: <input ngControl="first" required></p>
* <p>Middle: <input ngControl="middle"></p>
@ -67,6 +64,8 @@ export const controlGroupProvider: any =
*
* This example declares a control group for a user's name. The value and validation state of
* this group can be accessed separately from the overall form.
*
* @experimental
*/
@Directive({
selector: '[ngControlGroup]',
@ -79,9 +78,10 @@ export class NgControlGroup extends ControlContainer implements OnInit,
/** @internal */
_parent: ControlContainer;
constructor(@Host() @SkipSelf() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
constructor(
@Host() @SkipSelf() parent: ControlContainer,
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
this._parent = parent;
}

View File

@ -1,39 +1,28 @@
import {
OnChanges,
OnDestroy,
SimpleChange,
Query,
Directive,
forwardRef,
Host,
SkipSelf,
Provider,
Inject,
Optional,
Self
} from '@angular/core';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
import {ControlContainer} from './control_container';
import {NgControl} from './ng_control';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {
controlPath,
composeValidators,
composeAsyncValidators,
isPropertyUpdated,
selectValueAccessor
} from './shared';
import {Directive, Host, Inject, OnChanges, OnDestroy, Optional, Self, SimpleChanges, SkipSelf, forwardRef} from '@angular/core';
import {EventEmitter} from '../../facade/async';
import {Control} from '../model';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {ValidatorFn, AsyncValidatorFn} from './validators';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
import {ControlContainer} from './control_container';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
import {composeAsyncValidators, composeValidators, controlPath, isPropertyUpdated, selectValueAccessor} from './shared';
import {AsyncValidatorFn, ValidatorFn} from './validators';
export const controlNameBinding: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: NgControl,
useExisting: forwardRef(() => NgControlName)
};
export const controlNameBinding: any = {
provide: NgControl,
useExisting: forwardRef(() => NgControlName)
};
/**
* Creates and binds a control with a specified name to a DOM element.
@ -52,7 +41,7 @@ export const controlNameBinding: any =
* directives: [FORM_DIRECTIVES],
* template: `
* <form #f="ngForm" (submit)='onLogIn(f.value)'>
* Login <input type='text' ngControl='login' #l="form">
* Login <input type='text' ngControl='login' #l="ngForm">
* <div *ngIf="!l.valid">Login is invalid</div>
*
* Password <input type='password' ngControl='password'>
@ -89,10 +78,12 @@ export const controlNameBinding: any =
* }
* }
* ```
*
* @experimental
*/
@Directive({
selector: '[ngControl]',
bindings: [controlNameBinding],
providers: [controlNameBinding],
inputs: ['name: ngControl', 'model: ngModel'],
outputs: ['update: ngModelChange'],
exportAs: 'ngForm'
@ -112,35 +103,37 @@ export class NgControlName extends NgControl implements OnChanges,
/* Array<Validator|Function> */ any[],
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) {
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
ngOnChanges(changes: {[key: string]: SimpleChange}) {
if (!this._added) {
this.formDirective.addControl(this);
this._added = true;
}
if (isPropertyUpdated(changes, this.viewModel)) {
this.viewModel = this.model;
this.formDirective.updateModel(this, this.model);
}
}
ngOnChanges(changes: SimpleChanges) {
if (!this._added) {
this.formDirective.addControl(this);
this._added = true;
}
if (isPropertyUpdated(changes, this.viewModel)) {
this.viewModel = this.model;
this.formDirective.updateModel(this, this.model);
}
}
ngOnDestroy(): void { this.formDirective.removeControl(this); }
ngOnDestroy(): void { this.formDirective.removeControl(this); }
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
ObservableWrapper.callEmit(this.update, newValue);
}
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);
}
get path(): string[] { return controlPath(this.name, this._parent); }
get path(): string[] { return controlPath(this.name, this._parent); }
get formDirective(): any { return this._parent.formDirective; }
get formDirective(): any { return this._parent.formDirective; }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
get asyncValidator(): AsyncValidatorFn {
return composeAsyncValidators(this._asyncValidators);
}
get control(): Control { return this.formDirective.getControl(this); }
get control(): Control { return this.formDirective.getControl(this); }
}

View File

@ -1,10 +1,23 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Self} from '@angular/core';
import {isPresent} from '../../facade/lang';
import {NgControl} from './ng_control';
import {isPresent} from '../../../src/facade/lang';
/**
* Directive automatically applied to Angular forms that sets CSS classes
* based on control status (valid/invalid/dirty/etc).
*
* @experimental
*/
@Directive({
selector: '[ngControl],[ngModel],[ngFormControl]',

View File

@ -1,22 +1,33 @@
import {Directive, forwardRef, Provider, Optional, Inject, Self} from '@angular/core';
import {
PromiseWrapper,
ObservableWrapper,
EventEmitter,
PromiseCompleter
} from '../../../src/facade/async';
import {ListWrapper} from '../../../src/facade/collection';
import {isPresent} from '../../../src/facade/lang';
import {NgControl} from './ng_control';
import {Form} from './form_interface';
import {NgControlGroup} from './ng_control_group';
import {ControlContainer} from './control_container';
import {AbstractControl, ControlGroup, Control} from '../model';
import {setUpControl, setUpControlGroup, composeValidators, composeAsyncValidators} from './shared';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export const formDirectiveProvider: any =
/*@ts2dart_const*/ {provide: ControlContainer, useExisting: forwardRef(() => NgForm)};
import {Directive, Inject, Optional, Self, forwardRef} from '@angular/core';
import {EventEmitter} from '../../facade/async';
import {ListWrapper} from '../../facade/collection';
import {isPresent} from '../../facade/lang';
import {AbstractControl, Control, ControlGroup} from '../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
import {ControlContainer} from './control_container';
import {Form} from './form_interface';
import {NgControl} from './ng_control';
import {NgControlGroup} from './ng_control_group';
import {composeAsyncValidators, composeValidators, setUpControl, setUpControlGroup} from './shared';
export const formDirectiveProvider: any = {
provide: ControlContainer,
useExisting: forwardRef(() => NgForm)
};
let _formWarningDisplayed: boolean = false;
const resolvedPromise = Promise.resolve(null);
/**
* If `NgForm` is bound in a component, `<form>` elements in that component will be
@ -24,7 +35,7 @@ export const formDirectiveProvider: any =
*
* ### Typical Use
*
* Include `FORM_DIRECTIVES` in the `directives` section of a {@link View} annotation
* Include `FORM_DIRECTIVES` in the `directives` section of a {@link Component} annotation
* to use `NgForm` and its associated controls.
*
* ### Structure
@ -76,10 +87,13 @@ export const formDirectiveProvider: any =
* }
* }
* ```
*
* @experimental
*/
@Directive({
selector: 'form:not([ngNoForm]):not([ngFormModel]),ngForm,[ngForm]',
bindings: [formDirectiveProvider],
providers: [formDirectiveProvider],
host: {
'(submit)': 'onSubmit()',
},
@ -87,16 +101,34 @@ export const formDirectiveProvider: any =
exportAs: 'ngForm'
})
export class NgForm extends ControlContainer implements Form {
private _submitted: boolean = false;
form: ControlGroup;
ngSubmit = new EventEmitter();
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
constructor(
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
super();
this.form = new ControlGroup({}, null, composeValidators(validators),
composeAsyncValidators(asyncValidators));
this._displayWarning();
this.form = new ControlGroup(
{}, null, composeValidators(validators), composeAsyncValidators(asyncValidators));
}
private _displayWarning() {
// TODO(kara): Update this when the new forms module becomes the default
if (!_formWarningDisplayed) {
_formWarningDisplayed = true;
console.warn(`
*It looks like you're using the old forms module. This will be opt-in in the next RC, and
will eventually be removed in favor of the new forms module. For more information, see:
https://docs.google.com/document/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/preview
`);
}
}
get submitted(): boolean { return this._submitted; }
get formDirective(): Form { return this; }
get control(): ControlGroup { return this.form; }
@ -106,11 +138,11 @@ export class NgForm extends ControlContainer implements Form {
get controls(): {[key: string]: AbstractControl} { return this.form.controls; }
addControl(dir: NgControl): void {
PromiseWrapper.scheduleMicrotask(() => {
resolvedPromise.then(() => {
var container = this._findContainer(dir.path);
var ctrl = new Control();
setUpControl(ctrl, dir);
container.addControl(dir.name, ctrl);
container.registerControl(dir.name, ctrl);
ctrl.updateValueAndValidity({emitEvent: false});
});
}
@ -118,31 +150,29 @@ export class NgForm extends ControlContainer implements Form {
getControl(dir: NgControl): Control { return <Control>this.form.find(dir.path); }
removeControl(dir: NgControl): void {
PromiseWrapper.scheduleMicrotask(() => {
resolvedPromise.then(() => {
var container = this._findContainer(dir.path);
if (isPresent(container)) {
container.removeControl(dir.name);
container.updateValueAndValidity({emitEvent: false});
}
});
}
addControlGroup(dir: NgControlGroup): void {
PromiseWrapper.scheduleMicrotask(() => {
resolvedPromise.then(() => {
var container = this._findContainer(dir.path);
var group = new ControlGroup({});
setUpControlGroup(group, dir);
container.addControl(dir.name, group);
container.registerControl(dir.name, group);
group.updateValueAndValidity({emitEvent: false});
});
}
removeControlGroup(dir: NgControlGroup): void {
PromiseWrapper.scheduleMicrotask(() => {
resolvedPromise.then(() => {
var container = this._findContainer(dir.path);
if (isPresent(container)) {
container.removeControl(dir.name);
container.updateValueAndValidity({emitEvent: false});
}
});
}
@ -152,14 +182,15 @@ export class NgForm extends ControlContainer implements Form {
}
updateModel(dir: NgControl, value: any): void {
PromiseWrapper.scheduleMicrotask(() => {
resolvedPromise.then(() => {
var ctrl = <Control>this.form.find(dir.path);
ctrl.updateValue(value);
});
}
onSubmit(): boolean {
ObservableWrapper.callEmit(this.ngSubmit, null);
this._submitted = true;
this.ngSubmit.emit(null);
return false;
}

View File

@ -1,36 +1,27 @@
import {
OnChanges,
SimpleChange,
Query,
Directive,
forwardRef,
Provider,
Inject,
Optional,
Self
} from '@angular/core';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StringMapWrapper} from '../../../src/facade/collection';
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
import {NgControl} from './ng_control';
import {EventEmitter} from '../../facade/async';
import {StringMapWrapper} from '../../facade/collection';
import {Control} from '../model';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {
setUpControl,
composeValidators,
composeAsyncValidators,
isPropertyUpdated,
selectValueAccessor
} from './shared';
import {ValidatorFn, AsyncValidatorFn} from './validators';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
export const formControlBinding: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: NgControl,
useExisting: forwardRef(() => NgFormControl)
};
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
import {AsyncValidatorFn, ValidatorFn} from './validators';
export const formControlBinding: any = {
provide: NgControl,
useExisting: forwardRef(() => NgFormControl)
};
/**
* Binds an existing {@link Control} to a DOM element.
@ -78,10 +69,12 @@ export const formControlBinding: any =
* login:string;
* }
* ```
*
* @experimental
*/
@Directive({
selector: '[ngFormControl]',
bindings: [formControlBinding],
providers: [formControlBinding],
inputs: ['form: ngFormControl', 'model: ngModel'],
outputs: ['update: ngModelChange'],
exportAs: 'ngForm'
@ -98,35 +91,37 @@ export class NgFormControl extends NgControl implements OnChanges {
/* Array<Validator|Function> */ any[],
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) {
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
ngOnChanges(changes: {[key: string]: SimpleChange}): void {
if (this._isControlChanged(changes)) {
setUpControl(this.form, this);
this.form.updateValueAndValidity({emitEvent: false});
}
if (isPropertyUpdated(changes, this.viewModel)) {
this.form.updateValue(this.model);
this.viewModel = this.model;
}
}
ngOnChanges(changes: SimpleChanges): void {
if (this._isControlChanged(changes)) {
setUpControl(this.form, this);
this.form.updateValueAndValidity({emitEvent: false});
}
if (isPropertyUpdated(changes, this.viewModel)) {
this.form.updateValue(this.model);
this.viewModel = this.model;
}
}
get path(): string[] { return []; }
get path(): string[] { return []; }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
get asyncValidator(): AsyncValidatorFn {
return composeAsyncValidators(this._asyncValidators);
}
get control(): Control { return this.form; }
get control(): Control { return this.form; }
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
ObservableWrapper.callEmit(this.update, newValue);
}
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);
}
private _isControlChanged(changes: {[key: string]: any}): boolean {
return StringMapWrapper.contains(changes, "form");
}
private _isControlChanged(changes: {[key: string]: any}): boolean {
return StringMapWrapper.contains(changes, 'form');
}
}

View File

@ -1,30 +1,32 @@
import {
SimpleChange,
OnChanges,
Directive,
forwardRef,
Provider,
Inject,
Optional,
Self
} from '@angular/core';
import {isBlank} from '../../../src/facade/lang';
import {ListWrapper, StringMapWrapper} from '../../../src/facade/collection';
import {BaseException} from '../../../src/facade/exceptions';
import {ObservableWrapper, EventEmitter} from '../../../src/facade/async';
import {NgControl} from './ng_control';
import {NgControlGroup} from './ng_control_group';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
import {EventEmitter} from '../../facade/async';
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
import {BaseException} from '../../facade/exceptions';
import {isBlank} from '../../facade/lang';
import {Control, ControlGroup} from '../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS, Validators} from '../validators';
import {ControlContainer} from './control_container';
import {Form} from './form_interface';
import {Control, ControlGroup} from '../model';
import {setUpControl, setUpControlGroup, composeValidators, composeAsyncValidators} from './shared';
import {Validators, NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {NgControl} from './ng_control';
import {NgControlGroup} from './ng_control_group';
import {composeAsyncValidators, composeValidators, setUpControl, setUpControlGroup} from './shared';
export const formDirectiveProvider: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: ControlContainer,
useExisting: forwardRef(() => NgFormModel)
};
export const formDirectiveProvider: any = {
provide: ControlContainer,
useExisting: forwardRef(() => NgFormModel)
};
let _formModelWarningDisplayed: boolean = false;
/**
* Binds an existing control group to a DOM element.
@ -97,10 +99,13 @@ export const formDirectiveProvider: any =
* }
* }
* ```
*
* @experimental
*/
@Directive({
selector: '[ngFormModel]',
bindings: [formDirectiveProvider],
providers: [formDirectiveProvider],
inputs: ['form: ngFormModel'],
host: {'(submit)': 'onSubmit()'},
outputs: ['ngSubmit'],
@ -108,18 +113,34 @@ export const formDirectiveProvider: any =
})
export class NgFormModel extends ControlContainer implements Form,
OnChanges {
private _submitted: boolean = false;
form: ControlGroup = null;
directives: NgControl[] = [];
ngSubmit = new EventEmitter();
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
constructor(
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[]) {
super();
this._displayWarning();
}
ngOnChanges(changes: {[key: string]: SimpleChange}): void {
private _displayWarning() {
// TODO(kara): Update this when the new forms module becomes the default
if (!_formModelWarningDisplayed) {
_formModelWarningDisplayed = true;
console.warn(`
*It looks like you're using the old forms module. This will be opt-in in the next RC, and
will eventually be removed in favor of the new forms module. For more information, see:
https://docs.google.com/document/d/1RIezQqE4aEhBRmArIAS1mRIZtWFf6JxN_7B4meyWK0Y/preview
`);
}
}
ngOnChanges(changes: SimpleChanges): void {
this._checkFormPresent();
if (StringMapWrapper.contains(changes, "form")) {
if (StringMapWrapper.contains(changes, 'form')) {
var sync = composeValidators(this._validators);
this.form.validator = Validators.compose([this.form.validator, sync]);
@ -132,6 +153,8 @@ export class NgFormModel extends ControlContainer implements Form,
this._updateDomValue();
}
get submitted(): boolean { return this._submitted; }
get formDirective(): Form { return this; }
get control(): ControlGroup { return this.form; }
@ -167,7 +190,8 @@ export class NgFormModel extends ControlContainer implements Form,
}
onSubmit(): boolean {
ObservableWrapper.callEmit(this.ngSubmit, null);
this._submitted = true;
this.ngSubmit.emit(null);
return false;
}

View File

@ -0,0 +1,99 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, Inject, OnChanges, Optional, Self, SimpleChanges, forwardRef} from '@angular/core';
import {EventEmitter} from '../../facade/async';
import {Control} from '../model';
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../validators';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from './shared';
import {AsyncValidatorFn, ValidatorFn} from './validators';
export const formControlBinding: any = {
provide: NgControl,
useExisting: forwardRef(() => NgModel)
};
/**
* Binds a domain model to a form control.
*
* ### Usage
*
* `ngModel` binds an existing domain model to a form control. For a
* two-way binding, use `[(ngModel)]` to ensure the model updates in
* both directions.
*
* ### Example ([live demo](http://plnkr.co/edit/R3UX5qDaUqFO2VYR0UzH?p=preview))
* ```typescript
* @Component({
* selector: "search-comp",
* directives: [FORM_DIRECTIVES],
* template: `<input type='text' [(ngModel)]="searchQuery">`
* })
* class SearchComp {
* searchQuery: string;
* }
* ```
*
* @experimental
*/
@Directive({
selector: '[ngModel]:not([ngControl]):not([ngFormControl])',
providers: [formControlBinding],
inputs: ['model: ngModel'],
outputs: ['update: ngModelChange'],
exportAs: 'ngForm'
})
export class NgModel extends NgControl implements OnChanges {
/** @internal */
_control = new Control();
/** @internal */
_added = false;
update = new EventEmitter();
model: any;
viewModel: any;
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) {
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
ngOnChanges(changes: SimpleChanges) {
if (!this._added) {
setUpControl(this._control, this);
this._control.updateValueAndValidity({emitEvent: false});
this._added = true;
}
if (isPropertyUpdated(changes, this.viewModel)) {
this._control.updateValue(this.model);
this.viewModel = this.model;
}
}
get control(): Control { return this._control; }
get path(): string[] { return []; }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get asyncValidator(): AsyncValidatorFn {
return composeAsyncValidators(this._asyncValidators);
}
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
this.update.emit(newValue);
}
}

View File

@ -1,5 +1,14 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbstractControl} from '../model';
import {Validator, ValidatorFn, AsyncValidatorFn} from './validators';
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
export function normalizeValidator(validator: ValidatorFn | Validator): ValidatorFn {
if ((<Validator>validator).validate !== undefined) {
@ -11,7 +20,7 @@ export function normalizeValidator(validator: ValidatorFn | Validator): Validato
export function normalizeAsyncValidator(validator: AsyncValidatorFn | Validator): AsyncValidatorFn {
if ((<Validator>validator).validate !== undefined) {
return (c: AbstractControl) => Promise.resolve((<Validator>validator).validate(c));
return (c: AbstractControl) => (<Validator>validator).validate(c);
} else {
return <AsyncValidatorFn>validator;
}

View File

@ -1,8 +1,18 @@
import {Directive, ElementRef, Renderer, Self, forwardRef, Provider} from '@angular/core';
import {NumberWrapper} from '../../../src/facade/lang';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export const NUMBER_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
import {Directive, ElementRef, Renderer, forwardRef} from '@angular/core';
import {NumberWrapper, isBlank} from '../../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const NUMBER_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => NumberValueAccessor),
multi: true
@ -25,7 +35,7 @@ export const NUMBER_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider
'(input)': 'onChange($event.target.value)',
'(blur)': 'onTouched()'
},
bindings: [NUMBER_VALUE_ACCESSOR]
providers: [NUMBER_VALUE_ACCESSOR]
})
export class NumberValueAccessor implements ControlValueAccessor {
onChange = (_: any) => {};
@ -34,7 +44,9 @@ export class NumberValueAccessor implements ControlValueAccessor {
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
writeValue(value: number): void {
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', value);
// The value needs to be normalized for IE9, otherwise it is set to 'null' when null
const normalizedValue = isBlank(value) ? '' : value;
this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
}
registerOnChange(fn: (_: number) => void): void {

View File

@ -1,21 +1,20 @@
import {
Directive,
ElementRef,
Renderer,
forwardRef,
Provider,
Input,
OnInit,
OnDestroy,
Injector,
Injectable
} from '@angular/core';
import {isPresent} from '../../../src/facade/lang';
import {ListWrapper} from '../../../src/facade/collection';
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, ElementRef, Injectable, Injector, Input, OnDestroy, OnInit, Renderer, forwardRef} from '@angular/core';
import {ListWrapper} from '../../facade/collection';
import {isPresent} from '../../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
export const RADIO_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
export const RADIO_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => RadioControlValueAccessor),
multi: true
@ -44,15 +43,23 @@ export class RadioControlRegistry {
select(accessor: RadioControlValueAccessor) {
this._accessors.forEach((c) => {
if (c[0].control.root === accessor._control.control.root && c[1] !== accessor) {
if (this._isSameGroup(c, accessor) && c[1] !== accessor) {
c[1].fireUncheck();
}
});
}
private _isSameGroup(
controlPair: [NgControl, RadioControlValueAccessor], accessor: RadioControlValueAccessor) {
return controlPair[0].control.root === accessor._control.control.root &&
controlPair[1].name === accessor.name;
}
}
/**
* The value provided by the forms API for radio buttons.
*
* @experimental
*/
export class RadioButtonState {
constructor(public checked: boolean, public value: string) {}
@ -95,8 +102,9 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
onChange = () => {};
onTouched = () => {};
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
private _registry: RadioControlRegistry, private _injector: Injector) {}
constructor(
private _renderer: Renderer, private _elementRef: ElementRef,
private _registry: RadioControlRegistry, private _injector: Injector) {}
ngOnInit(): void {
this._control = this._injector.get(NgControl);

View File

@ -1,26 +1,19 @@
import {
Directive,
Renderer,
forwardRef,
Provider,
ElementRef,
Input,
Host,
OnDestroy,
Optional
} from '@angular/core';
import {
StringWrapper,
isPrimitive,
isPresent,
isBlank,
looseIdentical
} from '../../../src/facade/lang';
import {MapWrapper} from '../../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor';
import {Directive, ElementRef, Host, Input, OnDestroy, Optional, Renderer, forwardRef} from '@angular/core';
export const SELECT_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
import {MapWrapper} from '../../facade/collection';
import {StringWrapper, isBlank, isPresent, isPrimitive, looseIdentical} from '../../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectControlValueAccessor),
multi: true
@ -28,12 +21,12 @@ export const SELECT_VALUE_ACCESSOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider
function _buildValueString(id: string, value: any): string {
if (isBlank(id)) return `${value}`;
if (!isPrimitive(value)) value = "Object";
if (!isPrimitive(value)) value = 'Object';
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
}
function _extractId(valueString: string): string {
return valueString.split(":")[0];
return valueString.split(':')[0];
}
/**
@ -44,9 +37,11 @@ function _extractId(valueString: string): string {
* https://bugzilla.mozilla.org/show_bug.cgi?id=1024350
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/4660045/
*
* @experimental
*/
@Directive({
selector: 'select[ngControl],select[ngFormControl],select[ngModel]',
selector:
'select:not([multiple])[ngControl],select:not([multiple])[ngFormControl],select:not([multiple])[ngModel]',
host: {'(change)': 'onChange($event.target.value)', '(blur)': 'onTouched()'},
providers: [SELECT_VALUE_ACCESSOR]
})
@ -69,7 +64,10 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
}
registerOnChange(fn: (value: any) => any): void {
this.onChange = (valueString: string) => { fn(this._getOptionValue(valueString)); };
this.onChange = (valueString: string) => {
this.value = valueString;
fn(this._getOptionValue(valueString));
};
}
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
@ -101,13 +99,16 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
* <option *ngFor="let c of cities" [value]="c"></option>
* </select>
* ```
*
* @experimental
*/
@Directive({selector: 'option'})
export class NgSelectOption implements OnDestroy {
id: string;
constructor(private _element: ElementRef, private _renderer: Renderer,
@Optional() @Host() private _select: SelectControlValueAccessor) {
constructor(
private _element: ElementRef, private _renderer: Renderer,
@Optional() @Host() private _select: SelectControlValueAccessor) {
if (isPresent(this._select)) this.id = this._select._registerOption();
}

View File

@ -0,0 +1,185 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Directive, ElementRef, Host, Input, OnDestroy, OpaqueToken, Optional, Renderer, Type, forwardRef} from '@angular/core';
import {MapWrapper} from '../../facade/collection';
import {StringWrapper, isBlank, isPresent, isPrimitive, isString, looseIdentical} from '../../facade/lang';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
export const SELECT_MULTIPLE_VALUE_ACCESSOR = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectMultipleControlValueAccessor),
multi: true
};
function _buildValueString(id: string, value: any): string {
if (isBlank(id)) return `${value}`;
if (isString(value)) value = `'${value}'`;
if (!isPrimitive(value)) value = 'Object';
return StringWrapper.slice(`${id}: ${value}`, 0, 50);
}
function _extractId(valueString: string): string {
return valueString.split(':')[0];
}
/** Mock interface for HTML Options */
interface HTMLOption {
value: string;
selected: boolean;
}
/** Mock interface for HTMLCollection */
abstract class HTMLCollection {
length: number;
abstract item(_: number): HTMLOption;
}
/**
* The accessor for writing a value and listening to changes on a select element.
*/
@Directive({
selector: 'select[multiple][ngControl],select[multiple][ngFormControl],select[multiple][ngModel]',
host: {'(change)': 'onChange($event.target)', '(blur)': 'onTouched()'},
providers: [SELECT_MULTIPLE_VALUE_ACCESSOR]
})
export class SelectMultipleControlValueAccessor implements ControlValueAccessor {
value: any;
/** @internal */
_optionMap: Map<string, NgSelectMultipleOption> = new Map<string, NgSelectMultipleOption>();
/** @internal */
_idCounter: number = 0;
onChange = (_: any) => {};
onTouched = () => {};
constructor() {}
writeValue(value: any): void {
this.value = value;
if (value == null) return;
let values: Array<any> = <Array<any>>value;
// convert values to ids
let ids = values.map((v) => this._getOptionId(v));
this._optionMap.forEach((opt, o) => { opt._setSelected(ids.indexOf(o.toString()) > -1); });
}
registerOnChange(fn: (value: any) => any): void {
this.onChange = (_: any) => {
let selected: Array<any> = [];
if (_.hasOwnProperty('selectedOptions')) {
let options: HTMLCollection = _.selectedOptions;
for (var i = 0; i < options.length; i++) {
let opt: any = options.item(i);
let val: any = this._getOptionValue(opt.value);
selected.push(val);
}
}
// Degrade on IE
else {
let options: HTMLCollection = <HTMLCollection>_.options;
for (var i = 0; i < options.length; i++) {
let opt: HTMLOption = options.item(i);
if (opt.selected) {
let val: any = this._getOptionValue(opt.value);
selected.push(val);
}
}
}
fn(selected);
};
}
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
/** @internal */
_registerOption(value: NgSelectMultipleOption): string {
let id: string = (this._idCounter++).toString();
this._optionMap.set(id, value);
return id;
}
/** @internal */
_getOptionId(value: any): string {
for (let id of MapWrapper.keys(this._optionMap)) {
if (looseIdentical(this._optionMap.get(id)._value, value)) return id;
}
return null;
}
/** @internal */
_getOptionValue(valueString: string): any {
let opt = this._optionMap.get(_extractId(valueString));
return isPresent(opt) ? opt._value : valueString;
}
}
/**
* Marks `<option>` as dynamic, so Angular can be notified when options change.
*
* ### Example
*
* ```
* <select multiple ngControl="city">
* <option *ngFor="let c of cities" [value]="c"></option>
* </select>
* ```
*/
@Directive({selector: 'option'})
export class NgSelectMultipleOption implements OnDestroy {
id: string;
/** @internal */
_value: any;
constructor(
private _element: ElementRef, private _renderer: Renderer,
@Optional() @Host() private _select: SelectMultipleControlValueAccessor) {
if (isPresent(this._select)) {
this.id = this._select._registerOption(this);
}
}
@Input('ngValue')
set ngValue(value: any) {
if (this._select == null) return;
this._value = value;
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
}
@Input('value')
set value(value: any) {
if (isPresent(this._select)) {
this._value = value;
this._setElementValue(_buildValueString(this.id, value));
this._select.writeValue(this._select.value);
} else {
this._setElementValue(value);
}
}
/** @internal */
_setElementValue(value: string): void {
this._renderer.setElementProperty(this._element.nativeElement, 'value', value);
}
/** @internal */
_setSelected(selected: boolean) {
this._renderer.setElementProperty(this._element.nativeElement, 'selected', selected);
}
ngOnDestroy() {
if (isPresent(this._select)) {
this._select._optionMap.delete(this.id);
this._select.writeValue(this._select.value);
}
}
}
export const SELECT_DIRECTIVES = [SelectMultipleControlValueAccessor, NgSelectMultipleOption];

View File

@ -1,21 +1,30 @@
import {ListWrapper, StringMapWrapper} from '../../../src/facade/collection';
import {isBlank, isPresent, looseIdentical, hasConstructor} from '../../../src/facade/lang';
import {BaseException} from '../../../src/facade/exceptions';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ControlContainer} from './control_container';
import {NgControl} from './ng_control';
import {AbstractControlDirective} from './abstract_control_directive';
import {NgControlGroup} from './ng_control_group';
import {ListWrapper, StringMapWrapper} from '../../facade/collection';
import {BaseException} from '../../facade/exceptions';
import {hasConstructor, isBlank, isPresent, looseIdentical} from '../../facade/lang';
import {Control, ControlGroup} from '../model';
import {Validators} from '../validators';
import {AbstractControlDirective} from './abstract_control_directive';
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
import {ControlContainer} from './control_container';
import {ControlValueAccessor} from './control_value_accessor';
import {DefaultValueAccessor} from './default_value_accessor';
import {NgControl} from './ng_control';
import {NgControlGroup} from './ng_control_group';
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
import {NumberValueAccessor} from './number_value_accessor';
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
import {SelectControlValueAccessor} from './select_control_value_accessor';
import {RadioControlValueAccessor} from './radio_control_value_accessor';
import {normalizeValidator, normalizeAsyncValidator} from './normalize_validator';
import {ValidatorFn, AsyncValidatorFn} from './validators';
import {SelectControlValueAccessor} from './select_control_value_accessor';
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
import {AsyncValidatorFn, ValidatorFn} from './validators';
export function controlPath(name: string, parent: ControlContainer): string[] {
@ -25,8 +34,8 @@ export function controlPath(name: string, parent: ControlContainer): string[] {
}
export function setUpControl(control: Control, dir: NgControl): void {
if (isBlank(control)) _throwError(dir, "Cannot find control");
if (isBlank(dir.valueAccessor)) _throwError(dir, "No value accessor for");
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
if (isBlank(dir.valueAccessor)) _throwError(dir, 'No value accessor for form control with');
control.validator = Validators.compose([control.validator, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
@ -47,37 +56,44 @@ export function setUpControl(control: Control, dir: NgControl): void {
}
export function setUpControlGroup(control: ControlGroup, dir: NgControlGroup) {
if (isBlank(control)) _throwError(dir, "Cannot find control");
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
control.validator = Validators.compose([control.validator, dir.validator]);
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
}
function _throwError(dir: AbstractControlDirective, message: string): void {
var path = dir.path.join(" -> ");
throw new BaseException(`${message} '${path}'`);
let messageEnd: string;
if (dir.path.length > 1) {
messageEnd = `path: '${dir.path.join(' -> ')}'`;
} else if (dir.path[0]) {
messageEnd = `name: '${dir.path}'`;
} else {
messageEnd = 'unspecified name';
}
throw new BaseException(`${message} ${messageEnd}`);
}
export function composeValidators(validators: /* Array<Validator|Function> */ any[]): ValidatorFn {
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
}
export function composeAsyncValidators(
validators: /* Array<Validator|Function> */ any[]): AsyncValidatorFn {
export function composeAsyncValidators(validators: /* Array<Validator|Function> */ any[]):
AsyncValidatorFn {
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
null;
}
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
if (!StringMapWrapper.contains(changes, "model")) return false;
var change = changes["model"];
if (!StringMapWrapper.contains(changes, 'model')) return false;
var change = changes['model'];
if (change.isFirstChange()) return true;
return !looseIdentical(viewModel, change.currentValue);
}
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
export function selectValueAccessor(dir: NgControl,
valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
export function selectValueAccessor(
dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
if (isBlank(valueAccessors)) return null;
var defaultAccessor: ControlValueAccessor;
@ -87,17 +103,18 @@ export function selectValueAccessor(dir: NgControl,
if (hasConstructor(v, DefaultValueAccessor)) {
defaultAccessor = v;
} else if (hasConstructor(v, CheckboxControlValueAccessor) ||
hasConstructor(v, NumberValueAccessor) ||
hasConstructor(v, SelectControlValueAccessor) ||
hasConstructor(v, RadioControlValueAccessor)) {
} else if (
hasConstructor(v, CheckboxControlValueAccessor) || hasConstructor(v, NumberValueAccessor) ||
hasConstructor(v, SelectControlValueAccessor) ||
hasConstructor(v, SelectMultipleControlValueAccessor) ||
hasConstructor(v, RadioControlValueAccessor)) {
if (isPresent(builtinAccessor))
_throwError(dir, "More than one built-in value accessor matches");
_throwError(dir, 'More than one built-in value accessor matches form control with');
builtinAccessor = v;
} else {
if (isPresent(customAccessor))
_throwError(dir, "More than one custom value accessor matches");
_throwError(dir, 'More than one custom value accessor matches form control with');
customAccessor = v;
}
});
@ -106,6 +123,6 @@ export function selectValueAccessor(dir: NgControl,
if (isPresent(builtinAccessor)) return builtinAccessor;
if (isPresent(defaultAccessor)) return defaultAccessor;
_throwError(dir, "No valid value accessor for");
_throwError(dir, 'No valid value accessor for form control with');
return null;
}

View File

@ -1,8 +1,16 @@
import {forwardRef, Attribute, Directive} from '@angular/core';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Attribute, Directive, forwardRef} from '@angular/core';
import {NumberWrapper} from '../../facade/lang';
import {Validators, NG_VALIDATORS} from '../validators';
import {AbstractControl} from '../model';
import * as modelModule from '../model';
import {NG_VALIDATORS, Validators} from '../validators';
@ -14,7 +22,7 @@ import * as modelModule from '../model';
* ```typescript
* @Directive({
* selector: '[custom-validator]',
* providers: [provide(NG_VALIDATORS, {useExisting: CustomValidatorDirective, multi: true})]
* providers: [{provide: NG_VALIDATORS, useExisting: CustomValidatorDirective, multi: true}]
* })
* class CustomValidatorDirective implements Validator {
* validate(c: Control): {[key: string]: any} {
@ -22,12 +30,14 @@ import * as modelModule from '../model';
* }
* }
* ```
*
* @experimental
*/
export interface Validator { validate(c: modelModule.AbstractControl): {[key: string]: any}; }
export interface Validator { validate(c: AbstractControl): {[key: string]: any}; }
const REQUIRED = /*@ts2dart_const*/ Validators.required;
export const REQUIRED = Validators.required;
export const REQUIRED_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
export const REQUIRED_VALIDATOR: any = {
provide: NG_VALIDATORS,
useValue: REQUIRED,
multi: true
@ -42,6 +52,8 @@ export const REQUIRED_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/
* ```
* <input ngControl="fullName" required>
* ```
*
* @experimental
*/
@Directive({
selector: '[required][ngControl],[required][ngFormControl],[required][ngModel]',
@ -62,7 +74,7 @@ export interface AsyncValidatorFn {
*
* {@example common/forms/ts/validators/validators.ts region='min'}
*/
export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
export const MIN_LENGTH_VALIDATOR: any = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MinLengthValidator),
multi: true
@ -71,6 +83,8 @@ export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
/**
* A directive which installs the {@link MinLengthValidator} for any `ngControl`,
* `ngFormControl`, or control with `ngModel` that also has a `minlength` attribute.
*
* @experimental
*/
@Directive({
selector: '[minlength][ngControl],[minlength][ngFormControl],[minlength][ngModel]',
@ -79,7 +93,7 @@ export const MIN_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
export class MinLengthValidator implements Validator {
private _validator: ValidatorFn;
constructor(@Attribute("minlength") minLength: string) {
constructor(@Attribute('minlength') minLength: string) {
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
}
@ -93,7 +107,7 @@ export class MinLengthValidator implements Validator {
*
* {@example common/forms/ts/validators/validators.ts region='max'}
*/
export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
export const MAX_LENGTH_VALIDATOR: any = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => MaxLengthValidator),
multi: true
@ -102,6 +116,8 @@ export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
/**
* A directive which installs the {@link MaxLengthValidator} for any `ngControl, `ngFormControl`,
* or control with `ngModel` that also has a `maxlength` attribute.
*
* @experimental
*/
@Directive({
selector: '[maxlength][ngControl],[maxlength][ngFormControl],[maxlength][ngModel]',
@ -110,7 +126,7 @@ export const MAX_LENGTH_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*
export class MaxLengthValidator implements Validator {
private _validator: ValidatorFn;
constructor(@Attribute("maxlength") maxLength: string) {
constructor(@Attribute('maxlength') maxLength: string) {
this._validator = Validators.maxLength(NumberWrapper.parseInt(maxLength, 10));
}
@ -118,6 +134,13 @@ export class MaxLengthValidator implements Validator {
}
export const PATTERN_VALIDATOR: any = {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => PatternValidator),
multi: true
};
/**
* A Directive that adds the `pattern` validator to any controls marked with the
* `pattern` attribute, via the {@link NG_VALIDATORS} binding. Uses attribute value
@ -129,12 +152,8 @@ export class MaxLengthValidator implements Validator {
* ```
* <input [ngControl]="fullName" pattern="[a-zA-Z ]*">
* ```
* @experimental
*/
export const PATTERN_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
provide: NG_VALIDATORS,
useExisting: forwardRef(() => PatternValidator),
multi: true
};
@Directive({
selector: '[pattern][ngControl],[pattern][ngFormControl],[pattern][ngModel]',
providers: [PATTERN_VALIDATOR]
@ -142,7 +161,7 @@ export const PATTERN_VALIDATOR: any = /*@ts2dart_const*/ /*@ts2dart_Provider*/ {
export class PatternValidator implements Validator {
private _validator: ValidatorFn;
constructor(@Attribute("pattern") pattern: string) {
constructor(@Attribute('pattern') pattern: string) {
this._validator = Validators.pattern(pattern);
}

View File

@ -1,8 +1,19 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
import {StringMapWrapper} from '../../src/facade/collection';
import {isPresent, isArray} from '../../src/facade/lang';
import * as modelModule from './model';
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
import {StringMapWrapper} from '../facade/collection';
import {isArray, isPresent} from '../facade/lang';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {AbstractControl, Control, ControlArray, ControlGroup} from './model';
/**
@ -13,7 +24,7 @@ import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
* ```typescript
* @Component({
* selector: 'my-app',
* viewBindings: [FORM_BINDINGS]
* viewProviders: [FORM_BINDINGS]
* template: `
* <form [ngFormModel]="loginForm">
* <p>Login <input ngControl="login"></p>
@ -45,6 +56,8 @@ import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
* }
* }
* ```
*
* @experimental
*/
@Injectable()
export class FormBuilder {
@ -54,38 +67,37 @@ export class FormBuilder {
*
* See the {@link ControlGroup} constructor for more details.
*/
group(controlsConfig: {[key: string]: any},
extra: {[key: string]: any} = null): modelModule.ControlGroup {
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): ControlGroup {
var controls = this._reduceControls(controlsConfig);
var optionals = <{[key: string]: boolean}>(
isPresent(extra) ? StringMapWrapper.get(extra, "optionals") : null);
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, "validator") : null;
isPresent(extra) ? StringMapWrapper.get(extra, 'optionals') : null);
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, 'validator') : null;
var asyncValidator: AsyncValidatorFn =
isPresent(extra) ? StringMapWrapper.get(extra, "asyncValidator") : null;
return new modelModule.ControlGroup(controls, optionals, validator, asyncValidator);
isPresent(extra) ? StringMapWrapper.get(extra, 'asyncValidator') : null;
return new ControlGroup(controls, optionals, validator, asyncValidator);
}
/**
* Construct a new {@link Control} with the given `value`,`validator`, and `asyncValidator`.
*/
control(value: Object, validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null): modelModule.Control {
return new modelModule.Control(value, validator, asyncValidator);
control(value: Object, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null):
Control {
return new Control(value, validator, asyncValidator);
}
/**
* Construct an array of {@link Control}s from the given `controlsConfig` array of
* configuration, with the given optional `validator` and `asyncValidator`.
*/
array(controlsConfig: any[], validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null): modelModule.ControlArray {
array(
controlsConfig: any[], validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null): ControlArray {
var controls = controlsConfig.map(c => this._createControl(c));
return new modelModule.ControlArray(controls, validator, asyncValidator);
return new ControlArray(controls, validator, asyncValidator);
}
/** @internal */
_reduceControls(controlsConfig: {[k: string]:
any}): {[key: string]: modelModule.AbstractControl} {
var controls: {[key: string]: modelModule.AbstractControl} = {};
_reduceControls(controlsConfig: {[k: string]: any}): {[key: string]: AbstractControl} {
var controls: {[key: string]: AbstractControl} = {};
StringMapWrapper.forEach(controlsConfig, (controlConfig: any, controlName: string) => {
controls[controlName] = this._createControl(controlConfig);
});
@ -93,10 +105,9 @@ export class FormBuilder {
}
/** @internal */
_createControl(controlConfig: any): modelModule.AbstractControl {
if (controlConfig instanceof modelModule.Control ||
controlConfig instanceof modelModule.ControlGroup ||
controlConfig instanceof modelModule.ControlArray) {
_createControl(controlConfig: any): AbstractControl {
if (controlConfig instanceof Control || controlConfig instanceof ControlGroup ||
controlConfig instanceof ControlArray) {
return controlConfig;
} else if (isArray(controlConfig)) {

View File

@ -1,56 +1,66 @@
import {isPresent, isBlank, normalizeBool} from '../../src/facade/lang';
import {Observable, EventEmitter, ObservableWrapper} from '../../src/facade/async';
import {PromiseWrapper} from '../../src/facade/promise';
import {StringMapWrapper, ListWrapper} from '../../src/facade/collection';
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {PromiseObservable} from 'rxjs/observable/PromiseObservable';
import {EventEmitter, Observable} from '../facade/async';
import {ListWrapper, StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent, isPromise, normalizeBool} from '../facade/lang';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
/**
* Indicates that a Control is valid, i.e. that no errors exist in the input value.
*/
export const VALID = "VALID";
export const VALID = 'VALID';
/**
* Indicates that a Control is invalid, i.e. that an error exists in the input value.
*/
export const INVALID = "INVALID";
export const INVALID = 'INVALID';
/**
* Indicates that a Control is pending, i.e. that async validation is occurring and
* errors are not yet available for the input value.
*/
export const PENDING = "PENDING";
export const PENDING = 'PENDING';
export function isControl(control: Object): boolean {
return control instanceof AbstractControl;
}
function _find(control: AbstractControl, path: Array<string | number>| string) {
function _find(control: AbstractControl, path: Array<string|number>| string) {
if (isBlank(path)) return null;
if (!(path instanceof Array)) {
path = (<string>path).split("/");
path = (<string>path).split('/');
}
if (path instanceof Array && ListWrapper.isEmpty(path)) return null;
return (<Array<string | number>>path)
.reduce((v, name) => {
if (v instanceof ControlGroup) {
return isPresent(v.controls[name]) ? v.controls[name] : null;
} else if (v instanceof ControlArray) {
var index = <number>name;
return isPresent(v.at(index)) ? v.at(index) : null;
} else {
return null;
}
}, control);
return (<Array<string|number>>path).reduce((v, name) => {
if (v instanceof ControlGroup) {
return isPresent(v.controls[name]) ? v.controls[name] : null;
} else if (v instanceof ControlArray) {
var index = <number>name;
return isPresent(v.at(index)) ? v.at(index) : null;
} else {
return null;
}
}, control);
}
function toObservable(r: any): Observable<any> {
return PromiseWrapper.isPromise(r) ? ObservableWrapper.fromPromise(r) : r;
return isPromise(r) ? PromiseObservable.create(r) : r;
}
/**
*
* @experimental
*/
export abstract class AbstractControl {
/** @internal */
@ -62,7 +72,7 @@ export abstract class AbstractControl {
private _errors: {[key: string]: any};
private _pristine: boolean = true;
private _touched: boolean = false;
private _parent: ControlGroup | ControlArray;
private _parent: ControlGroup|ControlArray;
private _asyncValidationSubscription: any;
constructor(public validator: ValidatorFn, public asyncValidator: AsyncValidatorFn) {}
@ -112,10 +122,10 @@ export abstract class AbstractControl {
}
}
setParent(parent: ControlGroup | ControlArray): void { this._parent = parent; }
setParent(parent: ControlGroup|ControlArray): void { this._parent = parent; }
updateValueAndValidity(
{onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
updateValueAndValidity({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}):
void {
onlySelf = normalizeBool(onlySelf);
emitEvent = isPresent(emitEvent) ? emitEvent : true;
@ -129,8 +139,8 @@ export abstract class AbstractControl {
}
if (emitEvent) {
ObservableWrapper.callEmit(this._valueChanges, this._value);
ObservableWrapper.callEmit(this._statusChanges, this._status);
this._valueChanges.emit(this._value);
this._statusChanges.emit(this._status);
}
if (isPresent(this._parent) && !onlySelf) {
@ -147,14 +157,14 @@ export abstract class AbstractControl {
this._status = PENDING;
this._cancelExistingSubscription();
var obs = toObservable(this.asyncValidator(this));
this._asyncValidationSubscription = ObservableWrapper.subscribe(
obs, (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent}));
this._asyncValidationSubscription = obs.subscribe(
{next: (res: {[key: string]: any}) => this.setErrors(res, {emitEvent: emitEvent})});
}
}
private _cancelExistingSubscription(): void {
if (isPresent(this._asyncValidationSubscription)) {
ObservableWrapper.dispose(this._asyncValidationSubscription);
this._asyncValidationSubscription.unsubscribe();
}
}
@ -188,7 +198,7 @@ export abstract class AbstractControl {
this._status = this._calculateStatus();
if (emitEvent) {
ObservableWrapper.callEmit(this._statusChanges, this._status);
this._statusChanges.emit(this._status);
}
if (isPresent(this._parent)) {
@ -196,7 +206,7 @@ export abstract class AbstractControl {
}
}
find(path: Array<string | number>| string): AbstractControl { return _find(this, path); }
find(path: Array<string|number>|string): AbstractControl { return _find(this, path); }
getError(errorCode: string, path: string[] = null): any {
var control = isPresent(path) && !ListWrapper.isEmpty(path) ? this.find(path) : this;
@ -266,13 +276,15 @@ export abstract class AbstractControl {
* validation function.
*
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
*
* @experimental
*/
export class Control extends AbstractControl {
/** @internal */
_onChange: Function;
constructor(value: any = null, validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null) {
constructor(
value: any = null, validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
super(validator, asyncValidator);
this._value = value;
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
@ -331,13 +343,15 @@ export class Control extends AbstractControl {
* controls, but is of variable length.
*
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
*
* @experimental
*/
export class ControlGroup extends AbstractControl {
private _optionals: {[key: string]: boolean};
constructor(public controls: {[key: string]: AbstractControl},
optionals: {[key: string]: boolean} = null, validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null) {
constructor(
public controls: {[key: string]: AbstractControl}, optionals: {[key: string]: boolean} = null,
validator: ValidatorFn = null, asyncValidator: AsyncValidatorFn = null) {
super(validator, asyncValidator);
this._optionals = isPresent(optionals) ? optionals : {};
this._initObservables();
@ -346,17 +360,28 @@ export class ControlGroup extends AbstractControl {
}
/**
* Add a control to this group.
* Register a control with the group's list of controls.
*/
addControl(name: string, control: AbstractControl): void {
registerControl(name: string, control: AbstractControl): void {
this.controls[name] = control;
control.setParent(this);
}
/**
* Add a control to this group.
*/
addControl(name: string, control: AbstractControl): void {
this.registerControl(name, control);
this.updateValueAndValidity();
}
/**
* Remove a control from this group.
*/
removeControl(name: string): void { StringMapWrapper.delete(this.controls, name); }
removeControl(name: string): void {
StringMapWrapper.delete(this.controls, name);
this.updateValueAndValidity();
}
/**
* Mark the named control as non-optional.
@ -448,10 +473,13 @@ export class ControlGroup extends AbstractControl {
* as broken change detection.
*
* ### Example ([live demo](http://plnkr.co/edit/23DESOpbNnBpBHZt1BR4?p=preview))
*
* @experimental
*/
export class ControlArray extends AbstractControl {
constructor(public controls: AbstractControl[], validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null) {
constructor(
public controls: AbstractControl[], validator: ValidatorFn = null,
asyncValidator: AsyncValidatorFn = null) {
super(validator, asyncValidator);
this._initObservables();
this._setParentForControls();

View File

@ -1,11 +1,20 @@
import {OpaqueToken} from '@angular/core';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {OpaqueToken} from '@angular/core';
import {toPromise} from 'rxjs/operator/toPromise';
import {StringMapWrapper} from '../facade/collection';
import {isBlank, isPresent, isPromise, isString} from '../facade/lang';
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {AbstractControl} from './model';
import {isBlank, isPresent, isString} from '../../src/facade/lang';
import {PromiseWrapper} from '../../src/facade/promise';
import {ObservableWrapper} from '../../src/facade/async';
import {StringMapWrapper} from '../../src/facade/collection';
import * as modelModule from './model';
import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
/**
* Providers for validators to be used for {@link Control}s in a form.
@ -15,8 +24,9 @@ import {ValidatorFn, AsyncValidatorFn} from './directives/validators';
* ### Example
*
* {@example core/forms/ts/ng_validators/ng_validators.ts region='ng_validators'}
* @experimental
*/
export const NG_VALIDATORS: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken("NgValidators");
export const NG_VALIDATORS: OpaqueToken = new OpaqueToken('NgValidators');
/**
* Providers for asynchronous validators to be used for {@link Control}s
@ -25,9 +35,10 @@ export const NG_VALIDATORS: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken("Ng
* Provide this using `multi: true` to add validators.
*
* See {@link NG_VALIDATORS} for more details.
*
* @experimental
*/
export const NG_ASYNC_VALIDATORS: OpaqueToken =
/*@ts2dart_const*/ new OpaqueToken("NgAsyncValidators");
export const NG_ASYNC_VALIDATORS: OpaqueToken = new OpaqueToken('NgAsyncValidators');
/**
* Provides a set of validators used by form controls.
@ -40,27 +51,29 @@ export const NG_ASYNC_VALIDATORS: OpaqueToken =
* ```typescript
* var loginControl = new Control("", Validators.required)
* ```
*
* @experimental
*/
export class Validators {
/**
* Validator that requires controls to have a non-empty value.
*/
static required(control: modelModule.AbstractControl): {[key: string]: boolean} {
return isBlank(control.value) || (isString(control.value) && control.value == "") ?
{"required": true} :
null;
static required(control: AbstractControl): {[key: string]: boolean} {
return isBlank(control.value) || (isString(control.value) && control.value == '') ?
{'required': true} :
null;
}
/**
* Validator that requires controls to have a value of a minimum length.
*/
static minLength(minLength: number): ValidatorFn {
return (control: modelModule.AbstractControl): {[key: string]: any} => {
return (control: AbstractControl): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
var v: string = control.value;
return v.length < minLength ?
{"minlength": {"requiredLength": minLength, "actualLength": v.length}} :
null;
{'minlength': {'requiredLength': minLength, 'actualLength': v.length}} :
null;
};
}
@ -68,12 +81,12 @@ export class Validators {
* Validator that requires controls to have a value of a maximum length.
*/
static maxLength(maxLength: number): ValidatorFn {
return (control: modelModule.AbstractControl): {[key: string]: any} => {
return (control: AbstractControl): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
var v: string = control.value;
return v.length > maxLength ?
{"maxlength": {"requiredLength": maxLength, "actualLength": v.length}} :
null;
{'maxlength': {'requiredLength': maxLength, 'actualLength': v.length}} :
null;
};
}
@ -81,19 +94,19 @@ export class Validators {
* Validator that requires a control to match a regex to its value.
*/
static pattern(pattern: string): ValidatorFn {
return (control: modelModule.AbstractControl): {[key: string]: any} => {
return (control: AbstractControl): {[key: string]: any} => {
if (isPresent(Validators.required(control))) return null;
let regex = new RegExp(`^${pattern}$`);
let v: string = control.value;
return regex.test(v) ? null :
{"pattern": {"requiredPattern": `^${pattern}$`, "actualValue": v}};
{'pattern': {'requiredPattern': `^${pattern}$`, 'actualValue': v}};
};
}
/**
* No-op validator.
*/
static nullValidator(c: modelModule.AbstractControl): {[key: string]: boolean} { return null; }
static nullValidator(c: AbstractControl): {[key: string]: boolean} { return null; }
/**
* Compose multiple validators into a single function that returns the union
@ -104,7 +117,7 @@ export class Validators {
var presentValidators = validators.filter(isPresent);
if (presentValidators.length == 0) return null;
return function(control: modelModule.AbstractControl) {
return function(control: AbstractControl) {
return _mergeErrors(_executeValidators(control, presentValidators));
};
}
@ -114,24 +127,22 @@ export class Validators {
var presentValidators = validators.filter(isPresent);
if (presentValidators.length == 0) return null;
return function(control: modelModule.AbstractControl) {
return function(control: AbstractControl) {
let promises = _executeAsyncValidators(control, presentValidators).map(_convertToPromise);
return PromiseWrapper.all(promises).then(_mergeErrors);
return Promise.all(promises).then(_mergeErrors);
};
}
}
function _convertToPromise(obj: any): any {
return PromiseWrapper.isPromise(obj) ? obj : ObservableWrapper.toPromise(obj);
function _convertToPromise(obj: any): Promise<any> {
return isPromise(obj) ? obj : toPromise.call(obj);
}
function _executeValidators(control: modelModule.AbstractControl,
validators: ValidatorFn[]): any[] {
function _executeValidators(control: AbstractControl, validators: ValidatorFn[]): any[] {
return validators.map(v => v(control));
}
function _executeAsyncValidators(control: modelModule.AbstractControl,
validators: AsyncValidatorFn[]): any[] {
function _executeAsyncValidators(control: AbstractControl, validators: AsyncValidatorFn[]): any[] {
return validators.map(v => v(control));
}

View File

@ -1,63 +0,0 @@
/**
* @module
* @description
* This module is used for handling user input, by defining and building a {@link ControlGroup} that
* consists of
* {@link Control} objects, and mapping them onto the DOM. {@link Control} objects can then be used
* to read information
* from the form DOM elements.
*
* Forms providers are not included in default providers; you must import these providers
* explicitly.
*/
export {AbstractControl, Control, ControlGroup, ControlArray} from './forms/model';
export {AbstractControlDirective} from './forms/directives/abstract_control_directive';
export {Form} from './forms/directives/form_interface';
export {ControlContainer} from './forms/directives/control_container';
export {NgControlName} from './forms/directives/ng_control_name';
export {NgFormControl} from './forms/directives/ng_form_control';
export {NgModel} from './forms/directives/ng_model';
export {NgControl} from './forms/directives/ng_control';
export {NgControlGroup} from './forms/directives/ng_control_group';
export {NgFormModel} from './forms/directives/ng_form_model';
export {NgForm} from './forms/directives/ng_form';
export {ControlValueAccessor, NG_VALUE_ACCESSOR} from './forms/directives/control_value_accessor';
export {DefaultValueAccessor} from './forms/directives/default_value_accessor';
export {NgControlStatus} from './forms/directives/ng_control_status';
export {CheckboxControlValueAccessor} from './forms/directives/checkbox_value_accessor';
export {
NgSelectOption,
SelectControlValueAccessor
} from './forms/directives/select_control_value_accessor';
export {FORM_DIRECTIVES, RadioButtonState} from './forms/directives';
export {NG_VALIDATORS, NG_ASYNC_VALIDATORS, Validators} from './forms/validators';
export {
RequiredValidator,
MinLengthValidator,
MaxLengthValidator,
PatternValidator,
Validator
} from './forms/directives/validators';
export {FormBuilder} from './forms/form_builder';
import {FormBuilder} from './forms/form_builder';
import {RadioControlRegistry} from './forms/directives/radio_control_value_accessor';
import {Type} from '@angular/core';
/**
* Shorthand set of providers used for building Angular forms.
*
* ### Example
*
* ```typescript
* bootstrap(MyApp, [FORM_PROVIDERS]);
* ```
*/
export const FORM_PROVIDERS: Type[] = /*@ts2dart_const*/[FormBuilder, RadioControlRegistry];
/**
* See {@link FORM_PROVIDERS} instead.
*
* @deprecated
*/
export const FORM_BINDINGS = /*@ts2dart_const*/ FORM_PROVIDERS;

View File

@ -1,101 +0,0 @@
import {
OnChanges,
SimpleChange,
Directive,
forwardRef,
Provider,
Inject,
Optional,
Self
} from '@angular/core';
import {EventEmitter, ObservableWrapper} from '../../../src/facade/async';
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
import {NgControl} from './ng_control';
import {Control} from '../model';
import {NG_VALIDATORS, NG_ASYNC_VALIDATORS} from '../validators';
import {
setUpControl,
isPropertyUpdated,
selectValueAccessor,
composeValidators,
composeAsyncValidators
} from './shared';
import {ValidatorFn, AsyncValidatorFn} from './validators';
export const formControlBinding: any =
/*@ts2dart_const*/ /* @ts2dart_Provider */ {
provide: NgControl,
useExisting: forwardRef(() => NgModel)
};
/**
* Binds a domain model to a form control.
*
* ### Usage
*
* `ngModel` binds an existing domain model to a form control. For a
* two-way binding, use `[(ngModel)]` to ensure the model updates in
* both directions.
*
* ### Example ([live demo](http://plnkr.co/edit/R3UX5qDaUqFO2VYR0UzH?p=preview))
* ```typescript
* @Component({
* selector: "search-comp",
* directives: [FORM_DIRECTIVES],
* template: `<input type='text' [(ngModel)]="searchQuery">`
* })
* class SearchComp {
* searchQuery: string;
* }
* ```
*/
@Directive({
selector: '[ngModel]:not([ngControl]):not([ngFormControl])',
bindings: [formControlBinding],
inputs: ['model: ngModel'],
outputs: ['update: ngModelChange'],
exportAs: 'ngForm'
})
export class NgModel extends NgControl implements OnChanges {
/** @internal */
_control = new Control();
/** @internal */
_added = false;
update = new EventEmitter();
model: any;
viewModel: any;
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators: any[],
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators: any[],
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
valueAccessors: ControlValueAccessor[]) {
super();
this.valueAccessor = selectValueAccessor(this, valueAccessors);
}
ngOnChanges(changes: {[key: string]: SimpleChange}) {
if (!this._added) {
setUpControl(this._control, this);
this._control.updateValueAndValidity({emitEvent: false});
this._added = true;
}
if (isPropertyUpdated(changes, this.viewModel)) {
this._control.updateValue(this.model);
this.viewModel = this.model;
}
}
get control(): Control { return this._control; }
get path(): string[] { return []; }
get validator(): ValidatorFn { return composeValidators(this._validators); }
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
viewToModelUpdate(newValue: any): void {
this.viewModel = newValue;
ObservableWrapper.callEmit(this.update, newValue);
}
}

View File

@ -1,20 +0,0 @@
library angular2.core.forms.normalize_validators;
import 'package:angular2/src/common/forms/directives/validators.dart' show Validator;
Function normalizeValidator(dynamic validator){
if (validator is Validator) {
return (c) => validator.validate(c);
} else {
return validator;
}
}
Function normalizeAsyncValidator(dynamic validator){
if (validator is Validator) {
return (c) => validator.validate(c);
} else {
return validator;
}
}

View File

@ -0,0 +1,26 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @experimental
*/
export abstract class NgLocalization { abstract getPluralCategory(value: any): string; }
/**
* Returns the plural category for a given value.
* - "=value" when the case exists,
* - the plural category otherwise
*
* @internal
*/
export function getPluralCategory(
value: number, cases: string[], ngLocalization: NgLocalization): string {
const nbCase = `=${value}`;
return cases.indexOf(nbCase) > -1 ? nbCase : ngLocalization.getPluralCategory(value);
}

View File

@ -1,3 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
export * from './location/platform_location';
export * from './location/location_strategy';
export * from './location/hash_location_strategy';

View File

@ -1,9 +1,19 @@
import {Injectable, Inject, Optional} from '@angular/core';
import {isPresent} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Inject, Injectable, Optional} from '@angular/core';
import {isPresent} from '../facade/lang';
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
import {Location} from './location';
import {UrlChangeListener, PlatformLocation} from './platform_location';
import {APP_BASE_HREF, LocationStrategy} from './location_strategy';
import {PlatformLocation, UrlChangeListener} from './platform_location';
/**
@ -42,15 +52,18 @@ import {UrlChangeListener, PlatformLocation} from './platform_location';
*
* bootstrap(AppCmp, [
* ROUTER_PROVIDERS,
* provide(LocationStrategy, {useClass: HashLocationStrategy})
* {provide: LocationStrategy, useClass: HashLocationStrategy}
* ]);
* ```
*
* @stable
*/
@Injectable()
export class HashLocationStrategy extends LocationStrategy {
private _baseHref: string = '';
constructor(private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
constructor(
private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
super();
if (isPresent(_baseHref)) {
this._baseHref = _baseHref;
@ -64,16 +77,13 @@ export class HashLocationStrategy extends LocationStrategy {
getBaseHref(): string { return this._baseHref; }
path(): string {
path(includeHash: boolean = false): string {
// the hash value is always prefixed with a `#`
// and if it is empty then it will stay empty
var path = this._platformLocation.hash;
if (!isPresent(path)) path = '#';
// Dart will complain if a call to substring is
// executed with a position value that extends the
// length of string.
return (path.length > 0 ? path.substring(1) : path);
return path.length > 0 ? path.substring(1) : path;
}
prepareExternalUrl(internal: string): string {

View File

@ -1,8 +1,16 @@
import {Injectable, Inject} from '@angular/core';
import {EventEmitter, ObservableWrapper} from '../../src/facade/async';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {EventEmitter, Injectable} from '@angular/core';
import {LocationStrategy} from './location_strategy';
/**
* `Location` is a service that applications can use to interact with a browser's URL.
* Depending on which {@link LocationStrategy} is used, `Location` will either persist
@ -42,6 +50,8 @@ import {LocationStrategy} from './location_strategy';
*
* bootstrap(AppCmp, [ROUTER_PROVIDERS]);
* ```
*
* @stable
*/
@Injectable()
export class Location {
@ -50,18 +60,32 @@ export class Location {
/** @internal */
_baseHref: string;
constructor(public platformStrategy: LocationStrategy) {
var browserBaseHref = this.platformStrategy.getBaseHref();
/** @internal */
_platformStrategy: LocationStrategy;
constructor(platformStrategy: LocationStrategy) {
this._platformStrategy = platformStrategy;
var browserBaseHref = this._platformStrategy.getBaseHref();
this._baseHref = Location.stripTrailingSlash(_stripIndexHtml(browserBaseHref));
this.platformStrategy.onPopState((ev) => {
ObservableWrapper.callEmit(this._subject, {'url': this.path(), 'pop': true, 'type': ev.type});
});
this._platformStrategy.onPopState(
(ev) => { this._subject.emit({'url': this.path(true), 'pop': true, 'type': ev.type}); });
}
/**
* Returns the normalized URL path.
*/
path(): string { return this.normalize(this.platformStrategy.path()); }
// TODO: vsavkin. Remove the boolean flag and always include hash once the deprecated router is
// removed.
path(includeHash: boolean = false): string {
return this.normalize(this._platformStrategy.path(includeHash));
}
/**
* Normalizes the given path and compares to the current normalized path.
*/
isCurrentPathEqualTo(path: string, query: string = ''): boolean {
return this.path() == this.normalize(path + Location.normalizeQueryParams(query));
}
/**
* Given a string representing a URL, returns the normalized URL path without leading or
@ -81,7 +105,7 @@ export class Location {
if (url.length > 0 && !url.startsWith('/')) {
url = '/' + url;
}
return this.platformStrategy.prepareExternalUrl(url);
return this._platformStrategy.prepareExternalUrl(url);
}
// TODO: rename this method to pushState
@ -90,7 +114,7 @@ export class Location {
* new item onto the platform's history.
*/
go(path: string, query: string = ''): void {
this.platformStrategy.pushState(null, '', path, query);
this._platformStrategy.pushState(null, '', path, query);
}
/**
@ -98,25 +122,26 @@ export class Location {
* the top item on the platform's history stack.
*/
replaceState(path: string, query: string = ''): void {
this.platformStrategy.replaceState(null, '', path, query);
this._platformStrategy.replaceState(null, '', path, query);
}
/**
* Navigates forward in the platform's history.
*/
forward(): void { this.platformStrategy.forward(); }
forward(): void { this._platformStrategy.forward(); }
/**
* Navigates back in the platform's history.
*/
back(): void { this.platformStrategy.back(); }
back(): void { this._platformStrategy.back(); }
/**
* Subscribe to the platform's `popState` events.
*/
subscribe(onNext: (value: any) => void, onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object {
return ObservableWrapper.subscribe(this._subject, onNext, onThrow, onReturn);
subscribe(
onNext: (value: any) => void, onThrow: (exception: any) => void = null,
onReturn: () => void = null): Object {
return this._subject.subscribe({next: onNext, error: onThrow, complete: onReturn});
}
/**

View File

@ -1,3 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {OpaqueToken} from '@angular/core';
import {UrlChangeListener} from './platform_location';
@ -16,9 +24,11 @@ import {UrlChangeListener} from './platform_location';
* `http://example.com/foo` as an equivalent URL.
*
* See these two classes for more.
*
* @stable
*/
export abstract class LocationStrategy {
abstract path(): string;
abstract path(includeHash?: boolean): string;
abstract prepareExternalUrl(internal: string): string;
abstract pushState(state: any, title: string, url: string, queryParams: string): void;
abstract replaceState(state: any, title: string, url: string, queryParams: string): void;
@ -54,8 +64,9 @@ export abstract class LocationStrategy {
*
* bootstrap(AppCmp, [
* ROUTER_PROVIDERS,
* provide(APP_BASE_HREF, {useValue: '/my/app'})
* {provide: APP_BASE_HREF, useValue: '/my/app'}
* ]);
* ```
* @stable
*/
export const APP_BASE_HREF: OpaqueToken = /*@ts2dart_const*/ new OpaqueToken('appBaseHref');
export const APP_BASE_HREF: OpaqueToken = new OpaqueToken('appBaseHref');

View File

@ -1,10 +1,20 @@
import {Injectable, Inject, Optional} from '@angular/core';
import {isBlank} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Inject, Injectable, Optional} from '@angular/core';
import {BaseException} from '../facade/exceptions';
import {isBlank} from '../facade/lang';
import {PlatformLocation, UrlChangeListener} from './platform_location';
import {LocationStrategy, APP_BASE_HREF} from './location_strategy';
import {Location} from './location';
import {APP_BASE_HREF, LocationStrategy} from './location_strategy';
import {PlatformLocation, UrlChangeListener} from './platform_location';
/**
* `PathLocationStrategy` is a {@link LocationStrategy} used to configure the
@ -15,51 +25,27 @@ import {Location} from './location';
* `PathLocationStrategy` is the default binding for {@link LocationStrategy}
* provided in {@link ROUTER_PROVIDERS}.
*
* If you're using `PathLocationStrategy`, you must provide a provider for
* {@link APP_BASE_HREF} to a string representing the URL prefix that should
* be preserved when generating and recognizing URLs.
* If you're using `PathLocationStrategy`, you must provide a {@link APP_BASE_HREF}
* or add a base element to the document. This URL prefix that will be preserved
* when generating and recognizing URLs.
*
* For instance, if you provide an `APP_BASE_HREF` of `'/my/app'` and call
* `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`.
*
* ### Example
* Similarly, if you add `<base href='/my/app'/>` to the document and call
* `location.go('/foo')`, the browser's URL will become
* `example.com/my/app/foo`.
*
* ```
* import {Component, provide} from '@angular/core';
* import {bootstrap} from '@angular/platform-browser/browser';
* import {
* Location,
* APP_BASE_HREF
* } from '@angular/common';
* import {
* ROUTER_DIRECTIVES,
* ROUTER_PROVIDERS,
* RouteConfig
* } from '@angular/router';
*
* @Component({directives: [ROUTER_DIRECTIVES]})
* @RouteConfig([
* {...},
* ])
* class AppCmp {
* constructor(location: Location) {
* location.go('/foo');
* }
* }
*
* bootstrap(AppCmp, [
* ROUTER_PROVIDERS, // includes binding to PathLocationStrategy
* provide(APP_BASE_HREF, {useValue: '/my/app'})
* ]);
* ```
* @stable
*/
@Injectable()
export class PathLocationStrategy extends LocationStrategy {
private _baseHref: string;
constructor(private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) href?: string) {
constructor(
private _platformLocation: PlatformLocation,
@Optional() @Inject(APP_BASE_HREF) href?: string) {
super();
if (isBlank(href)) {
@ -85,9 +71,11 @@ export class PathLocationStrategy extends LocationStrategy {
return Location.joinWithSlash(this._baseHref, internal);
}
path(): string {
return this._platformLocation.pathname +
Location.normalizeQueryParams(this._platformLocation.search);
path(includeHash: boolean = false): string {
const pathname = this._platformLocation.pathname +
Location.normalizeQueryParams(this._platformLocation.search);
const hash = this._platformLocation.hash;
return hash && includeHash ? `${pathname}${hash}` : pathname;
}
pushState(state: any, title: string, url: string, queryParams: string) {

View File

@ -1,3 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* This class should not be used directly by an application developer. Instead, use
* {@link Location}.
@ -21,15 +29,17 @@
* {@link Location} / {@link LocationStrategy} and DOM apis flow through the `PlatformLocation`
* class
* they are all platform independent.
*
* @stable
*/
export abstract class PlatformLocation {
abstract getBaseHrefFromDOM(): string;
abstract onPopState(fn: UrlChangeListener): void;
abstract onHashChange(fn: UrlChangeListener): void;
/* abstract */ get pathname(): string { return null; }
/* abstract */ get search(): string { return null; }
/* abstract */ get hash(): string { return null; }
get pathname(): string { return null; }
get search(): string { return null; }
get hash(): string { return null; }
abstract replaceState(state: any, title: string, url: string): void;
@ -42,7 +52,12 @@ export abstract class PlatformLocation {
/**
* A serializable version of the event from onPopState or onHashChange
*
* @experimental
*/
export interface UrlChangeEvent { type: string; }
/**
* @experimental
*/
export interface UrlChangeListener { (e: UrlChangeEvent): any; }

View File

@ -1,3 +1,11 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
@ -5,13 +13,13 @@
*/
export {AsyncPipe} from './pipes/async_pipe';
export {COMMON_PIPES} from './pipes/common_pipes';
export {DatePipe} from './pipes/date_pipe';
export {JsonPipe} from './pipes/json_pipe';
export {SlicePipe} from './pipes/slice_pipe';
export {LowerCasePipe} from './pipes/lowercase_pipe';
export {NumberPipe, DecimalPipe, PercentPipe, CurrencyPipe} from './pipes/number_pipe';
export {UpperCasePipe} from './pipes/uppercase_pipe';
export {ReplacePipe} from './pipes/replace_pipe';
export {I18nPluralPipe} from './pipes/i18n_plural_pipe';
export {I18nSelectPipe} from './pipes/i18n_select_pipe';
export {COMMON_PIPES} from './pipes/common_pipes';
export {JsonPipe} from './pipes/json_pipe';
export {LowerCasePipe} from './pipes/lowercase_pipe';
export {CurrencyPipe, DecimalPipe, PercentPipe} from './pipes/number_pipe';
export {ReplacePipe} from './pipes/replace_pipe';
export {SlicePipe} from './pipes/slice_pipe';
export {UpperCasePipe} from './pipes/uppercase_pipe';

View File

@ -1,23 +1,35 @@
import {Pipe, Injectable, ChangeDetectorRef, OnDestroy, WrappedValue} from '@angular/core';
import {isBlank, isPresent, isPromise} from '../../src/facade/lang';
import {ObservableWrapper, Observable, EventEmitter} from '../../src/facade/async';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ChangeDetectorRef, OnDestroy, Pipe, WrappedValue} from '@angular/core';
import {EventEmitter, Observable} from '../facade/async';
import {isBlank, isPresent, isPromise} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
class ObservableStrategy {
createSubscription(async: any, updateLatestValue: any): any {
return ObservableWrapper.subscribe(async, updateLatestValue, e => { throw e; });
}
dispose(subscription: any): void { ObservableWrapper.dispose(subscription); }
onDestroy(subscription: any): void { ObservableWrapper.dispose(subscription); }
interface SubscriptionStrategy {
createSubscription(async: any, updateLatestValue: any): any;
dispose(subscription: any): void;
onDestroy(subscription: any): void;
}
class PromiseStrategy {
class ObservableStrategy implements SubscriptionStrategy {
createSubscription(async: any, updateLatestValue: any): any {
return async.subscribe({next: updateLatestValue, error: (e: any) => { throw e; }});
}
dispose(subscription: any): void { subscription.unsubscribe(); }
onDestroy(subscription: any): void { subscription.unsubscribe(); }
}
class PromiseStrategy implements SubscriptionStrategy {
createSubscription(async: Promise<any>, updateLatestValue: (v: any) => any): any {
return async.then(updateLatestValue);
return async.then(updateLatestValue, e => { throw e; });
}
dispose(subscription: any): void {}
@ -30,25 +42,33 @@ var _observableStrategy = new ObservableStrategy();
var __unused: Promise<any>; // avoid unused import when Promise union types are erased
/**
* The `async` pipe subscribes to an Observable or Promise and returns the latest value it has
* The `async` pipe subscribes to an `Observable` or `Promise` and returns the latest value it has
* emitted.
* When a new value is emitted, the `async` pipe marks the component to be checked for changes.
* When the component gets destroyed, the `async` pipe unsubscribes automatically to avoid
* potential memory leaks.
*
* ### Example
* ## Usage
*
* object | async
*
* where `object` is of type `Observable` or of type `Promise`.
*
* ## Examples
*
* This example binds a `Promise` to the view. Clicking the `Resolve` button resolves the
* promise.
*
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipe'}
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipePromise'}
*
* It's also possible to use `async` with Observables. The example below binds the `time` Observable
* to the view. Every 500ms, the `time` Observable updates the view with the current time.
*
* ```typescript
* ```
* {@example core/pipes/ts/async_pipe/async_pipe_example.ts region='AsyncPipeObservable'}
*
* @stable
*/
@Pipe({name: 'async', pure: false})
@Injectable()
export class AsyncPipe implements OnDestroy {
/** @internal */
_latestValue: Object = null;
@ -58,10 +78,11 @@ export class AsyncPipe implements OnDestroy {
/** @internal */
_subscription: Object = null;
/** @internal */
_obj: Observable<any>| Promise<any>| EventEmitter<any> = null;
private _strategy: any = null;
_obj: Observable<any>|Promise<any>|EventEmitter<any> = null;
/** @internal */
public _ref: ChangeDetectorRef;
_ref: ChangeDetectorRef;
private _strategy: SubscriptionStrategy = null;
constructor(_ref: ChangeDetectorRef) { this._ref = _ref; }
ngOnDestroy(): void {
@ -70,7 +91,7 @@ export class AsyncPipe implements OnDestroy {
}
}
transform(obj: Observable<any>| Promise<any>| EventEmitter<any>): any {
transform(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
if (isBlank(this._obj)) {
if (isPresent(obj)) {
this._subscribe(obj);
@ -93,7 +114,7 @@ export class AsyncPipe implements OnDestroy {
}
/** @internal */
_subscribe(obj: Observable<any>| Promise<any>| EventEmitter<any>): void {
_subscribe(obj: Observable<any>|Promise<any>|EventEmitter<any>): void {
this._obj = obj;
this._strategy = this._selectStrategy(obj);
this._subscription = this._strategy.createSubscription(
@ -101,10 +122,10 @@ export class AsyncPipe implements OnDestroy {
}
/** @internal */
_selectStrategy(obj: Observable<any>| Promise<any>| EventEmitter<any>): any {
_selectStrategy(obj: Observable<any>|Promise<any>|EventEmitter<any>): any {
if (isPromise(obj)) {
return _promiseStrategy;
} else if (ObservableWrapper.isObservable(obj)) {
} else if ((<any>obj).subscribe) {
return _observableStrategy;
} else {
throw new InvalidPipeArgumentException(AsyncPipe, obj);

View File

@ -1,18 +1,27 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* This module provides a set of common Pipes.
*/
import {AsyncPipe} from './async_pipe';
import {UpperCasePipe} from './uppercase_pipe';
import {LowerCasePipe} from './lowercase_pipe';
import {JsonPipe} from './json_pipe';
import {SlicePipe} from './slice_pipe';
import {DatePipe} from './date_pipe';
import {DecimalPipe, PercentPipe, CurrencyPipe} from './number_pipe';
import {ReplacePipe} from './replace_pipe';
import {I18nPluralPipe} from './i18n_plural_pipe';
import {I18nSelectPipe} from './i18n_select_pipe';
import {JsonPipe} from './json_pipe';
import {LowerCasePipe} from './lowercase_pipe';
import {CurrencyPipe, DecimalPipe, PercentPipe} from './number_pipe';
import {ReplacePipe} from './replace_pipe';
import {SlicePipe} from './slice_pipe';
import {UpperCasePipe} from './uppercase_pipe';
/**
* A collection of Angular core pipes that are likely to be used in each and every
@ -20,8 +29,10 @@ import {I18nSelectPipe} from './i18n_select_pipe';
*
* This collection can be used to quickly enumerate all the built-in pipes in the `pipes`
* property of the `@Component` decorator.
*
* @experimental Contains i18n pipes which are experimental
*/
export const COMMON_PIPES = /*@ts2dart_const*/[
export const COMMON_PIPES = [
AsyncPipe,
UpperCasePipe,
LowerCasePipe,
@ -33,5 +44,5 @@ export const COMMON_PIPES = /*@ts2dart_const*/[
DatePipe,
ReplacePipe,
I18nPluralPipe,
I18nSelectPipe
I18nSelectPipe,
];

View File

@ -1,16 +1,17 @@
import {PipeTransform, Pipe, Injectable} from '@angular/core';
import {
isDate,
isNumber,
DateWrapper,
isBlank,
} from '../../src/facade/lang';
import {DateFormatter} from '../../src/facade/intl';
import {StringMapWrapper} from '../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {StringMapWrapper} from '../facade/collection';
import {DateFormatter} from '../facade/intl';
import {DateWrapper, NumberWrapper, isBlank, isDate, isString} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
// TODO: move to a global configurable location along with other i18n components.
var defaultLocale: string = 'en-US';
@ -29,8 +30,9 @@ var defaultLocale: string = 'en-US';
*
* expression | date[:format]
*
* where `expression` is a date object or a number (milliseconds since UTC epoch) and
* `format` indicates which date/time components to include:
* where `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
* (https://www.w3.org/TR/NOTE-datetime) and `format` indicates which date/time components to
* include:
*
* | Component | Symbol | Short Form | Long Form | Numeric | 2-digit |
* |-----------|:------:|--------------|-------------------|-----------|-----------|
@ -49,9 +51,6 @@ var defaultLocale: string = 'en-US';
*
* In javascript, only the components specified will be respected (not the ordering,
* punctuations, ...) and details of the formatting will be dependent on the locale.
* On the other hand in Dart version, you can also include quoted text as well as some extra
* date/time components such as quarter. For more information see:
* https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/intl/intl.DateFormat.
*
* `format` can also be one of the following predefined formats:
*
@ -79,9 +78,10 @@ var defaultLocale: string = 'en-US';
* ```
*
* {@example core/pipes/ts/date_pipe/date_pipe_example.ts region='DatePipe'}
*
* @experimental
*/
@Pipe({name: 'date', pure: true})
@Injectable()
export class DatePipe implements PipeTransform {
/** @internal */
static _ALIASES: {[key: string]: String} = {
@ -103,8 +103,10 @@ export class DatePipe implements PipeTransform {
throw new InvalidPipeArgumentException(DatePipe, value);
}
if (isNumber(value)) {
value = DateWrapper.fromMillis(value);
if (NumberWrapper.isNumeric(value)) {
value = DateWrapper.fromMillis(NumberWrapper.parseInt(value, 10));
} else if (isString(value)) {
value = DateWrapper.fromISOString(value);
}
if (StringMapWrapper.contains(DatePipe._ALIASES, pattern)) {
pattern = <string>StringMapWrapper.get(DatePipe._ALIASES, pattern);
@ -112,5 +114,13 @@ export class DatePipe implements PipeTransform {
return DateFormatter.format(value, defaultLocale, pattern);
}
supports(obj: any): boolean { return isDate(obj) || isNumber(obj); }
private supports(obj: any): boolean {
if (isDate(obj) || NumberWrapper.isNumeric(obj)) {
return true;
}
if (isString(obj) && isDate(DateWrapper.fromISOString(obj))) {
return true;
}
return false;
}
}

View File

@ -1,31 +1,52 @@
import {Injectable, PipeTransform, Pipe} from '@angular/core';
import {isStringMap, StringWrapper, isPresent, RegExpWrapper} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {StringWrapper, isBlank, isStringMap} from '../facade/lang';
import {NgLocalization, getPluralCategory} from '../localization';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
var interpolationExp: RegExp = RegExpWrapper.create('#');
const _INTERPOLATION_REGEXP: RegExp = /#/g;
/**
*
* Maps a value to a string that pluralizes the value properly.
*
* ## Usage
*
* expression | i18nPlural:mapping
*
* where `expression` is a number and `mapping` is an object that indicates the proper text for
* when the `expression` evaluates to 0, 1, or some other number. You can interpolate the actual
* value into the text using the `#` sign.
* where `expression` is a number and `mapping` is an object that mimics the ICU format,
* see http://userguide.icu-project.org/formatparse/messages
*
* ## Example
*
* ```
* <div>
* {{ messages.length | i18nPlural: messageMapping }}
* </div>
* class MyLocalization extends NgLocalization {
* getPluralCategory(value: any) {
* if(value > 1) {
* return 'other';
* }
* }
* }
*
* @Component({
* selector: 'app',
* template: `
* <div>
* {{ messages.length | i18nPlural: messageMapping }}
* </div>
* `,
* providers: [{provide: NgLocalization, useClass: MyLocalization}]
* })
*
* class MyApp {
* messages: any[];
* messageMapping: any = {
* messageMapping: {[k:string]: string} = {
* '=0': 'No messages.',
* '=1': 'One message.',
* 'other': '# messages.'
@ -34,21 +55,21 @@ var interpolationExp: RegExp = RegExpWrapper.create('#');
* }
* ```
*
* @experimental
*/
@Pipe({name: 'i18nPlural', pure: true})
@Injectable()
export class I18nPluralPipe implements PipeTransform {
constructor(private _localization: NgLocalization) {}
transform(value: number, pluralMap: {[count: string]: string}): string {
var key: string;
var valueStr: string;
if (isBlank(value)) return '';
if (!isStringMap(pluralMap)) {
throw new InvalidPipeArgumentException(I18nPluralPipe, pluralMap);
}
key = value === 0 || value === 1 ? `=${value}` : 'other';
valueStr = isPresent(value) ? value.toString() : '';
const key = getPluralCategory(value, Object.keys(pluralMap), this._localization);
return StringWrapper.replaceAll(pluralMap[key], interpolationExp, valueStr);
return StringWrapper.replaceAll(pluralMap[key], _INTERPOLATION_REGEXP, value.toString());
}
}

View File

@ -1,6 +1,13 @@
import {Injectable, PipeTransform, Pipe} from '@angular/core';
import {isStringMap} from '../../src/facade/lang';
import {StringMapWrapper} from '../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank, isStringMap} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
@ -24,22 +31,25 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
* class MyApp {
* gender: string = 'male';
* inviteMap: any = {
* 'male': 'Invite her.',
* 'female': 'Invite him.',
* 'male': 'Invite him.',
* 'female': 'Invite her.',
* 'other': 'Invite them.'
* }
* ...
* }
* ```
*
* @experimental
*/
@Pipe({name: 'i18nSelect', pure: true})
@Injectable()
export class I18nSelectPipe implements PipeTransform {
transform(value: string, mapping: {[key: string]: string}): string {
if (isBlank(value)) return '';
if (!isStringMap(mapping)) {
throw new InvalidPipeArgumentException(I18nSelectPipe, mapping);
}
return StringMapWrapper.contains(mapping, value) ? mapping[value] : mapping['other'];
return mapping.hasOwnProperty(value) ? mapping[value] : '';
}
}

View File

@ -1,5 +1,13 @@
import {Type, stringify} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {BaseException} from '../facade/exceptions';
import {Type, stringify} from '../facade/lang';
export class InvalidPipeArgumentException extends BaseException {
constructor(type: Type, value: Object) {

View File

@ -1,5 +1,15 @@
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
import {Json} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {Json} from '../facade/lang';
/**
@ -7,10 +17,10 @@ import {Json} from '../../src/facade/lang';
*
* ### Example
* {@example core/pipes/ts/json_pipe/json_pipe_example.ts region='JsonPipe'}
*
* @stable
*/
/* @ts2dart_const */
@Pipe({name: 'json', pure: false})
@Injectable()
export class JsonPipe implements PipeTransform {
transform(value: any): string { return Json.stringify(value); }
}

View File

@ -1,17 +1,26 @@
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
import {isString, isBlank} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank, isString} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
* Transforms text to lowercase.
*
* ### Example
*
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'}
*
* @experimental
*/
/* @ts2dart_const */
@Pipe({name: 'lowercase'})
@Injectable()
export class LowerCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;

View File

@ -1,62 +1,68 @@
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
isNumber,
isPresent,
isBlank,
NumberWrapper,
RegExpWrapper,
} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
import {NumberFormatter, NumberFormatStyle} from '../../src/facade/intl';
import {ListWrapper} from '../../src/facade/collection';
import {Pipe, PipeTransform} from '@angular/core';
import {NumberFormatStyle, NumberFormatter} from '../facade/intl';
import {NumberWrapper, Type, isBlank, isNumber, isPresent, isString} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
var defaultLocale: string = 'en-US';
var _re = RegExpWrapper.create('^(\\d+)?\\.((\\d+)(\\-(\\d+))?)?$');
const _NUMBER_FORMAT_REGEXP = /^(\d+)?\.((\d+)(\-(\d+))?)?$/;
/**
* Internal base class for numeric pipes.
*/
@Injectable()
export class NumberPipe {
/** @internal */
static _format(value: number, style: NumberFormatStyle, digits: string, currency: string = null,
currencyAsSymbol: boolean = false): string {
if (isBlank(value)) return null;
if (!isNumber(value)) {
throw new InvalidPipeArgumentException(NumberPipe, value);
}
var minInt = 1, minFraction = 0, maxFraction = 3;
if (isPresent(digits)) {
var parts = RegExpWrapper.firstMatch(_re, digits);
if (isBlank(parts)) {
throw new BaseException(`${digits} is not a valid digit info for number pipes`);
}
if (isPresent(parts[1])) { // min integer digits
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
}
if (isPresent(parts[3])) { // min fraction digits
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
}
if (isPresent(parts[5])) { // max fraction digits
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
}
}
return NumberFormatter.format(value, defaultLocale, style, {
minimumIntegerDigits: minInt,
minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction,
currency: currency,
currencyAsSymbol: currencyAsSymbol
});
function formatNumber(
pipe: Type, value: number | string, style: NumberFormatStyle, digits: string,
currency: string = null, currencyAsSymbol: boolean = false): string {
if (isBlank(value)) return null;
// Convert strings to numbers
value = isString(value) && NumberWrapper.isNumeric(value) ? +value : value;
if (!isNumber(value)) {
throw new InvalidPipeArgumentException(pipe, value);
}
let minInt: number;
let minFraction: number;
let maxFraction: number;
if (style !== NumberFormatStyle.Currency) {
// rely on Intl default for currency
minInt = 1;
minFraction = 0;
maxFraction = 3;
}
if (isPresent(digits)) {
var parts = digits.match(_NUMBER_FORMAT_REGEXP);
if (parts === null) {
throw new Error(`${digits} is not a valid digit info for number pipes`);
}
if (isPresent(parts[1])) { // min integer digits
minInt = NumberWrapper.parseIntAutoRadix(parts[1]);
}
if (isPresent(parts[3])) { // min fraction digits
minFraction = NumberWrapper.parseIntAutoRadix(parts[3]);
}
if (isPresent(parts[5])) { // max fraction digits
maxFraction = NumberWrapper.parseIntAutoRadix(parts[5]);
}
}
return NumberFormatter.format(value as number, defaultLocale, style, {
minimumIntegerDigits: minInt,
minimumFractionDigits: minFraction,
maximumFractionDigits: maxFraction,
currency: currency,
currencyAsSymbol: currencyAsSymbol
});
}
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/].
*
* Formats a number as local text. i.e. group sizing and separator and other locale-specific
* configurations are based on the active locale.
@ -79,18 +85,20 @@ export class NumberPipe {
* ### Example
*
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='NumberPipe'}
*
* @experimental
*/
@Pipe({name: 'number'})
@Injectable()
export class DecimalPipe extends NumberPipe implements PipeTransform {
export class DecimalPipe implements PipeTransform {
transform(value: any, digits: string = null): string {
return NumberPipe._format(value, NumberFormatStyle.Decimal, digits);
return formatNumber(DecimalPipe, value, NumberFormatStyle.Decimal, digits);
}
}
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/].
*
* Formats a number as local percent.
*
@ -103,18 +111,21 @@ export class DecimalPipe extends NumberPipe implements PipeTransform {
* ### Example
*
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='PercentPipe'}
*
* @experimental
*/
@Pipe({name: 'percent'})
@Injectable()
export class PercentPipe extends NumberPipe implements PipeTransform {
export class PercentPipe implements PipeTransform {
transform(value: any, digits: string = null): string {
return NumberPipe._format(value, NumberFormatStyle.Percent, digits);
return formatNumber(PercentPipe, value, NumberFormatStyle.Percent, digits);
}
}
/**
* WARNING: this pipe uses the Internationalization API.
* Therefore it is only reliable in Chrome and Opera browsers.
* Therefore it is only reliable in Chrome and Opera browsers. For other browsers please use an
* polyfill, for example: [https://github.com/andyearnshaw/Intl.js/].
*
*
* Formats a number as local currency.
*
@ -131,13 +142,15 @@ export class PercentPipe extends NumberPipe implements PipeTransform {
* ### Example
*
* {@example core/pipes/ts/number_pipe/number_pipe_example.ts region='CurrencyPipe'}
*
* @experimental
*/
@Pipe({name: 'currency'})
@Injectable()
export class CurrencyPipe extends NumberPipe implements PipeTransform {
transform(value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
digits: string = null): string {
return NumberPipe._format(value, NumberFormatStyle.Currency, digits, currencyCode,
symbolDisplay);
export class CurrencyPipe implements PipeTransform {
transform(
value: any, currencyCode: string = 'USD', symbolDisplay: boolean = false,
digits: string = null): string {
return formatNumber(
CurrencyPipe, value, NumberFormatStyle.Currency, digits, currencyCode, symbolDisplay);
}
}

View File

@ -1,14 +1,13 @@
import {Injectable, PipeTransform, Pipe} from '@angular/core';
import {
isBlank,
isString,
isNumber,
isFunction,
RegExpWrapper,
StringWrapper
} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {StringWrapper, isBlank, isFunction, isNumber, isString} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
@ -35,12 +34,15 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
* --Note--: The 'pattern' parameter will be converted to a RegExp instance. Make sure to escape the
* string properly if you are matching for regular expression special characters like parenthesis,
* brackets etc.
*
* @deprecated The current pipe has limited functionality. The pipe api is not meant to be able
* express complex yet generic value transformations. We recommend that these transformations happen
* in the component logic instead.
*/
@Pipe({name: 'replace'})
@Injectable()
export class ReplacePipe implements PipeTransform {
transform(value: any, pattern: string | RegExp, replacement: Function | string): any {
transform(value: any, pattern: string|RegExp, replacement: Function|string): any {
if (isBlank(value)) {
return value;
}
@ -57,14 +59,14 @@ export class ReplacePipe implements PipeTransform {
if (!this._supportedReplacement(replacement)) {
throw new InvalidPipeArgumentException(ReplacePipe, replacement);
}
// template fails with literal RegExp e.g /pattern/igm
// var rgx = pattern instanceof RegExp ? pattern : RegExpWrapper.create(pattern);
if (isFunction(replacement)) {
var rgxPattern = isString(pattern) ? RegExpWrapper.create(<string>pattern) : <RegExp>pattern;
const rgxPattern = isString(pattern) ? new RegExp(pattern, 'g') : pattern;
return StringWrapper.replaceAllMapped(input, rgxPattern, <Function>replacement);
return StringWrapper.replaceAllMapped(
input, rgxPattern, <(m: string[]) => string>replacement);
}
if (pattern instanceof RegExp) {
// use the replaceAll variant
return StringWrapper.replaceAll(input, pattern, <string>replacement);

View File

@ -1,9 +1,14 @@
import {Injectable, PipeTransform, WrappedValue, Pipe} from '@angular/core';
import {isBlank, isString, isArray, StringWrapper} from '../../src/facade/lang';
import {BaseException} from '../../src/facade/exceptions';
import {ListWrapper} from '../../src/facade/collection';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {ListWrapper} from '../facade/collection';
import {StringWrapper, isArray, isBlank, isString} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
@ -42,6 +47,8 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
* When operating on a [List], the returned list is always a copy even when all
* the elements are being returned.
*
* When operating on a blank value, returns it.
*
* ## List Example
*
* This `ngFor` example:
@ -56,16 +63,17 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
* ## String Examples
*
* {@example core/pipes/ts/slice_pipe/slice_pipe_example.ts region='SlicePipe_string'}
*
* @stable
*/
@Pipe({name: 'slice', pure: false})
@Injectable()
export class SlicePipe implements PipeTransform {
transform(value: any, start: number, end: number = null): any {
if (isBlank(value)) return value;
if (!this.supports(value)) {
throw new InvalidPipeArgumentException(SlicePipe, value);
}
if (isBlank(value)) return value;
if (isString(value)) {
return StringWrapper.slice(value, start, end);
}

View File

@ -1,5 +1,13 @@
import {PipeTransform, WrappedValue, Injectable, Pipe} from '@angular/core';
import {isString, isBlank} from '../../src/facade/lang';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Pipe, PipeTransform} from '@angular/core';
import {isBlank, isString} from '../facade/lang';
import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
/**
@ -8,9 +16,10 @@ import {InvalidPipeArgumentException} from './invalid_pipe_argument_exception';
* ### Example
*
* {@example core/pipes/ts/lowerupper_pipe/lowerupper_pipe_example.ts region='LowerUpperPipe'}
*
* @experimental
*/
@Pipe({name: 'uppercase'})
@Injectable()
export class UpperCasePipe implements PipeTransform {
transform(value: string): string {
if (isBlank(value)) return value;

View File

@ -1,20 +1,16 @@
import {
beforeEach,
beforeEachProviders,
ddescribe,
xdescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {ComponentFixture, TestComponentBuilder} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {ListWrapper, StringMapWrapper, SetWrapper} from '../../src/facade/collection';
import {Component, provide} from '@angular/core';
import {NgFor, NgClass} from '@angular/common';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgClass, NgFor} from '@angular/common';
import {Component} from '@angular/core';
import {ComponentFixture, TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, expect, iit, inject, it, xdescribe, xit} from '@angular/core/testing/testing_internal';
import {ListWrapper, StringMapWrapper} from '../../src/facade/collection';
function detectChangesAndCheck(fixture: ComponentFixture<any>, classes: string) {
fixture.detectChanges();
@ -25,502 +21,567 @@ export function main() {
describe('binding to CSS class list', () => {
it('should clean up when the directive is destroyed',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div *ngFor="let item of items" [ngClass]="item"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['0']];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [['1']];
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div *ngFor="let item of items" [ngClass]="item"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture: ComponentFixture<TestComponent>) => {
fixture.debugElement.componentInstance.items = [['0']];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [['1']];
detectChangesAndCheck(fixture, '1');
detectChangesAndCheck(fixture, '1');
async.done();
});
}));
async.done();
});
}));
describe('expressions evaluating to objects', () => {
it('should add classes specified in an object literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="{foo: true, bar: false}"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="{foo: true, bar: false}"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
it('should add classes specified in an object literal without change in class names',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngClass]="{'foo-bar': true, 'fooBar': true}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo-bar fooBar');
async.done();
});
}));
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo-bar fooBar');
async.done();
});
}));
it('should add and remove classes based on changes in object literal values',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="{foo: condition, bar: !condition}"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="{foo: condition, bar: !condition}"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.condition = false;
detectChangesAndCheck(fixture, 'bar');
fixture.debugElement.componentInstance.condition = false;
detectChangesAndCheck(fixture, 'bar');
async.done();
});
}));
async.done();
});
}));
it('should add and remove classes based on changes to the expression object',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'foo bar');
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'foo bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'baz', true);
detectChangesAndCheck(fixture, 'foo bar baz');
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'baz', true);
detectChangesAndCheck(fixture, 'foo bar baz');
StringMapWrapper.delete(fixture.debugElement.componentInstance.objExpr, 'bar');
detectChangesAndCheck(fixture, 'foo baz');
StringMapWrapper.delete(fixture.debugElement.componentInstance.objExpr, 'bar');
detectChangesAndCheck(fixture, 'foo baz');
async.done();
});
}));
async.done();
});
}));
it('should add and remove classes based on reference changes to the expression object',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.objExpr = {foo: true, bar: true};
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.objExpr = {foo: true, bar: true};
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.objExpr = {baz: true};
detectChangesAndCheck(fixture, 'baz');
fixture.debugElement.componentInstance.objExpr = {baz: true};
detectChangesAndCheck(fixture, 'baz');
async.done();
});
}));
async.done();
});
}));
it('should remove active classes when expression evaluates to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, '');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, '');
fixture.debugElement.componentInstance.objExpr = {'foo': false, 'bar': true};
detectChangesAndCheck(fixture, 'bar');
fixture.debugElement.componentInstance.objExpr = {'foo': false, 'bar': true};
detectChangesAndCheck(fixture, 'bar');
async.done();
});
}));
async.done();
});
}));
it('should allow multiple classes per expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.objExpr = {
'bar baz': true,
'bar1 baz1': true
};
detectChangesAndCheck(fixture, 'bar baz bar1 baz1');
fixture.debugElement.componentInstance.objExpr = {
'bar baz': true,
'bar1 baz1': true
};
detectChangesAndCheck(fixture, 'bar baz bar1 baz1');
fixture.debugElement.componentInstance.objExpr = {
'bar baz': false,
'bar1 baz1': true
};
detectChangesAndCheck(fixture, 'bar1 baz1');
fixture.debugElement.componentInstance.objExpr = {
'bar baz': false,
'bar1 baz1': true
};
detectChangesAndCheck(fixture, 'bar1 baz1');
async.done();
});
}));
async.done();
});
}));
it('should split by one or more spaces between classes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="objExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.objExpr = {'foo bar baz': true};
detectChangesAndCheck(fixture, 'foo bar baz');
fixture.debugElement.componentInstance.objExpr = {'foo bar baz': true};
detectChangesAndCheck(fixture, 'foo bar baz');
async.done();
});
}));
async.done();
});
}));
});
describe('expressions evaluating to lists', () => {
it('should add classes specified in a list literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngClass]="['foo', 'bar', 'foo-bar', 'fooBar']"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
async.done();
});
}));
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
async.done();
});
}));
it('should add and remove classes based on changes to the expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="arrExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var arrExpr: string[] = fixture.debugElement.componentInstance.arrExpr;
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var arrExpr: string[] = fixture.debugElement.componentInstance.arrExpr;
detectChangesAndCheck(fixture, 'foo');
arrExpr.push('bar');
detectChangesAndCheck(fixture, 'foo bar');
arrExpr.push('bar');
detectChangesAndCheck(fixture, 'foo bar');
arrExpr[1] = 'baz';
detectChangesAndCheck(fixture, 'foo baz');
arrExpr[1] = 'baz';
detectChangesAndCheck(fixture, 'foo baz');
ListWrapper.remove(fixture.debugElement.componentInstance.arrExpr, 'baz');
detectChangesAndCheck(fixture, 'foo');
ListWrapper.remove(fixture.debugElement.componentInstance.arrExpr, 'baz');
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
async.done();
});
}));
it('should add and remove classes when a reference changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="arrExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'bar');
fixture.debugElement.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'bar');
async.done();
});
}));
async.done();
});
}));
it('should take initial classes into account when a reference changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.arrExpr = ['bar'];
detectChangesAndCheck(fixture, 'foo bar');
async.done();
});
}));
async.done();
});
}));
it('should ignore empty or blank class names',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.arrExpr = ['', ' '];
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.arrExpr = ['', ' '];
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
async.done();
});
}));
it('should trim blanks from class names',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div class="foo" [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.arrExpr = [' bar '];
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.arrExpr = [' bar '];
detectChangesAndCheck(fixture, 'foo bar');
async.done();
});
}));
async.done();
});
}));
it('should allow multiple classes per item in arrays',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="arrExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="arrExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.arrExpr =
['foo bar baz', 'foo1 bar1 baz1'];
detectChangesAndCheck(fixture, 'foo bar baz foo1 bar1 baz1');
fixture.debugElement.componentInstance.arrExpr =
['foo bar baz', 'foo1 bar1 baz1'];
detectChangesAndCheck(fixture, 'foo bar baz foo1 bar1 baz1');
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz foobar'];
detectChangesAndCheck(fixture, 'foo bar baz foobar');
fixture.debugElement.componentInstance.arrExpr = ['foo bar baz foobar'];
detectChangesAndCheck(fixture, 'foo bar baz foobar');
async.done();
});
}));
async.done();
});
}));
});
describe('expressions evaluating to sets', () => {
it('should add and remove classes if the set instance changed',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="setExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="setExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var setExpr = new Set<string>();
setExpr.add('bar');
fixture.debugElement.componentInstance.setExpr = setExpr;
detectChangesAndCheck(fixture, 'bar');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var setExpr = new Set<string>();
setExpr.add('bar');
fixture.debugElement.componentInstance.setExpr = setExpr;
detectChangesAndCheck(fixture, 'bar');
setExpr = new Set<string>();
setExpr.add('baz');
fixture.debugElement.componentInstance.setExpr = setExpr;
detectChangesAndCheck(fixture, 'baz');
setExpr = new Set<string>();
setExpr.add('baz');
fixture.debugElement.componentInstance.setExpr = setExpr;
detectChangesAndCheck(fixture, 'baz');
async.done();
});
}));
async.done();
});
}));
});
describe('expressions evaluating to string', () => {
it('should add classes specified in a string literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="'foo bar foo-bar fooBar'"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngClass]="'foo bar foo-bar fooBar'"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
async.done();
});
}));
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo bar foo-bar fooBar');
async.done();
});
}));
it('should add and remove classes based on changes to the expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="strExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="strExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = 'foo bar';
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.strExpr = 'foo bar';
detectChangesAndCheck(fixture, 'foo bar');
fixture.debugElement.componentInstance.strExpr = 'baz';
detectChangesAndCheck(fixture, 'baz');
fixture.debugElement.componentInstance.strExpr = 'baz';
detectChangesAndCheck(fixture, 'baz');
async.done();
});
}));
async.done();
});
}));
it('should remove active classes when switching from string to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="strExpr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngClass]="strExpr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, '');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, '');
async.done();
});
}));
async.done();
});
}));
it('should take initial classes into account when switching from string to null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, 'foo');
fixture.debugElement.componentInstance.strExpr = null;
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
async.done();
});
}));
it('should ignore empty and blank strings',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div class="foo" [ngClass]="strExpr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.strExpr = '';
detectChangesAndCheck(fixture, 'foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.strExpr = '';
detectChangesAndCheck(fixture, 'foo');
async.done();
});
}));
async.done();
});
}));
});
describe('cooperation with other class-changing constructs', () => {
it('should co-operate with the class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div [ngClass]="objExpr" class="init foo"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div [ngClass]="objExpr" class="init foo"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, 'init bar');
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, 'init bar');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, 'init foo');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, 'init foo');
async.done();
});
}));
async.done();
});
}));
it('should co-operate with the interpolated class attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngClass]="objExpr" class="{{'init foo'}}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, `init bar`);
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, `init bar`);
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, `init foo`);
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, `init foo`);
async.done();
});
}));
async.done();
});
}));
it('should co-operate with the class attribute and binding to it',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngClass]="objExpr" class="init" [class]="'foo'"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, `init foo bar`);
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, `init bar`);
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, `init bar`);
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, `init foo`);
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, `init foo`);
async.done();
});
}));
async.done();
});
}));
it('should co-operate with the class attribute and class.name binding',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
'<div class="init foo" [ngClass]="objExpr" [class.baz]="condition"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'init foo baz');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'init foo baz');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo baz bar');
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo baz bar');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, 'init baz bar');
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'foo', false);
detectChangesAndCheck(fixture, 'init baz bar');
fixture.debugElement.componentInstance.condition = false;
detectChangesAndCheck(fixture, 'init bar');
fixture.debugElement.componentInstance.condition = false;
detectChangesAndCheck(fixture, 'init bar');
async.done();
});
}));
async.done();
});
}));
it('should co-operate with initial class and class attribute binding when binding changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div class="init" [ngClass]="objExpr" [class]="strExpr"></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'init foo');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
detectChangesAndCheck(fixture, 'init foo');
StringMapWrapper.set(fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
StringMapWrapper.set(
fixture.debugElement.componentInstance.objExpr, 'bar', true);
detectChangesAndCheck(fixture, 'init foo bar');
fixture.debugElement.componentInstance.strExpr = 'baz';
detectChangesAndCheck(fixture, 'init bar baz foo');
fixture.debugElement.componentInstance.strExpr = 'baz';
detectChangesAndCheck(fixture, 'init bar baz foo');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, 'init baz');
fixture.debugElement.componentInstance.objExpr = null;
detectChangesAndCheck(fixture, 'init baz');
async.done();
});
}));
async.done();
});
}));
});
})
});
}
@Component({selector: 'test-cmp', directives: [NgClass, NgFor], template: ''})

View File

@ -1,506 +1,566 @@
import {
beforeEach,
beforeEachProviders,
ddescribe,
describe,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgFor, NgIf} from '@angular/common';
import {Component, ContentChild, TemplateRef} from '@angular/core';
import {TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {By} from '@angular/platform-browser/src/dom/debug/by';
import {expect} from '@angular/platform-browser/testing/matchers';
import {ListWrapper} from '../../src/facade/collection';
import {IS_DART} from '../../src/facade/lang';
import {Component, TemplateRef, ContentChild} from '@angular/core';
import {NgFor} from '@angular/common';
import {NgIf} from '@angular/common';
import {By} from '@angular/platform-browser/src/dom/debug/by';
let thisArg: any;
export function main() {
describe('ngFor', () => {
var TEMPLATE =
const TEMPLATE =
'<div><copy-me template="ngFor let item of items">{{item.toString()}};</copy-me></div>';
it('should reflect initial elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
async.done();
});
}));
it('should reflect added elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
(<number[]>fixture.debugElement.componentInstance.items).push(3);
fixture.detectChanges();
(<number[]>fixture.debugElement.componentInstance.items).push(3);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
it('should reflect removed elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1);
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 1);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;');
async.done();
});
}));
expect(fixture.debugElement.nativeElement).toHaveText('1;');
async.done();
});
}));
it('should reflect moved elements',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
(<number[]>fixture.debugElement.componentInstance.items).push(1);
fixture.detectChanges();
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
(<number[]>fixture.debugElement.componentInstance.items).push(1);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
async.done();
});
}));
expect(fixture.debugElement.nativeElement).toHaveText('2;1;');
async.done();
});
}));
it('should reflect a mix of all changes (additions/removals/moves)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5];
fixture.detectChanges();
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = [6, 2, 7, 0, 4, 8];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;');
async.done();
});
}));
expect(fixture.debugElement.nativeElement).toHaveText('6;2;7;0;4;8;');
async.done();
});
}));
it('should iterate over an array of objects',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'<ul><li template="ngFor let item of items">{{item["name"]}};</li></ul>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
// INIT
fixture.debugElement.componentInstance.items =
[{'name': 'misko'}, {'name': 'shyam'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
// INIT
fixture.debugElement.componentInstance.items =
[{'name': 'misko'}, {'name': 'shyam'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;');
// GROW
(<any[]>fixture.debugElement.componentInstance.items).push({'name': 'adam'});
fixture.detectChanges();
// GROW
(<any[]>fixture.debugElement.componentInstance.items).push({'name': 'adam'});
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
expect(fixture.debugElement.nativeElement).toHaveText('misko;shyam;adam;');
// SHRINK
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
fixture.detectChanges();
// SHRINK
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 2);
ListWrapper.removeAt(fixture.debugElement.componentInstance.items, 0);
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
async.done();
});
}));
expect(fixture.debugElement.nativeElement).toHaveText('shyam;');
async.done();
});
}));
it('should gracefully handle nulls',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = '<ul><li template="ngFor let item of null">{{item}};</li></ul>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
it('should gracefully handle ref changing to null and back',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = null;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.items = null;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.items = [1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
fixture.debugElement.componentInstance.items = [1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;3;');
async.done();
});
}));
it('should throw on non-iterable ref and suggest using an array',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE).createAsync(TestComponent).then((fixture) => {
fixture.debugElement.componentInstance.items = 'whaaa';
expect(() => fixture.detectChanges())
.toThrowError(
/Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays/);
async.done();
});
}));
if (!IS_DART) {
it('should throw on non-iterable ref and suggest using an array',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = 'whaaa';
try {
fixture.detectChanges()
} catch (e) {
expect(e.message).toContain(
`Cannot find a differ supporting object 'whaaa' of type 'string'. NgFor only supports binding to Iterables such as Arrays.`);
async.done();
}
});
}));
}
}));
it('should throw on ref changing to string',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('1;2;');
fixture.debugElement.componentInstance.items = 'whaaa';
expect(() => fixture.detectChanges()).toThrowError();
async.done();
});
}));
fixture.debugElement.componentInstance.items = 'whaaa';
expect(() => fixture.detectChanges()).toThrowError();
async.done();
});
}));
it('should works with duplicates',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
var a = new Foo();
fixture.debugElement.componentInstance.items = [a, a];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, TEMPLATE)
.createAsync(TestComponent)
.then((fixture) => {
var a = new Foo();
fixture.debugElement.componentInstance.items = [a, a];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo;foo;');
async.done();
});
}));
it('should repeat over nested arrays',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<div template="ngFor let item of items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|' +
'</div>' +
'</div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = '<div>' +
'<div template="ngFor let item of items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div>|' +
'</div>' +
'</div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
fixture.detectChanges();
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;|c-1;|');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;|f-2;g-2;|');
async.done();
});
}));
async.done();
});
}));
it('should repeat over nested arrays with no intermediate element',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div><template ngFor let-item [ngForOf]="items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div></template></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = '<div><template ngFor let-item [ngForOf]="items">' +
'<div template="ngFor let subitem of item">' +
'{{subitem}}-{{item.length}};' +
'</div></template></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [['a', 'b'], ['c']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('a-2;b-2;c-1;');
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
async.done();
});
}));
fixture.debugElement.componentInstance.items = [['e'], ['f', 'g']];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('e-1;f-2;g-2;');
async.done();
});
}));
it('should repeat over nested ngIf that are the last node in the ngFor temlate',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`<div><template ngFor let-item [ngForOf]="items" let-i="index"><div>{{i}}|</div>` +
`<div *ngIf="i % 2 == 0">even|</div></template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var el = fixture.debugElement.nativeElement;
var items = [1];
fixture.debugElement.componentInstance.items = items;
fixture.detectChanges();
expect(el).toHaveText('0|even|');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
const el = fixture.debugElement.nativeElement;
const items = [1];
fixture.debugElement.componentInstance.items = items;
fixture.detectChanges();
expect(el).toHaveText('0|even|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|2|even|');
items.push(1);
fixture.detectChanges();
expect(el).toHaveText('0|even|1|2|even|');
async.done();
});
}));
async.done();
});
}));
it('should display indices correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let i=index">{{i.toString()}}</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'<div><copy-me template="ngFor: let item of items; let i=index">{{i.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
async.done();
});
}));
fixture.debugElement.componentInstance.items = [1, 2, 6, 7, 4, 3, 5, 8, 9, 0];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('0123456789');
async.done();
});
}));
it('should display first item correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'<div><copy-me template="ngFor: let item of items; let isFirst=first">{{isFirst.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsefalse');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
it('should display last item correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'<div><copy-me template="ngFor: let item of items; let isLast=last">{{isLast.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
it('should display even items correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'<div><copy-me template="ngFor: let item of items; let isEven=even">{{isEven.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('truefalse');
async.done();
});
}));
it('should display odd items correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div><copy-me template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
'<div><copy-me template="ngFor: let item of items; let isOdd=odd">{{isOdd.toString()}}</copy-me></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [0, 1, 2, 3];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetruefalsetrue');
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
fixture.debugElement.componentInstance.items = [2, 1];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('falsetrue');
async.done();
});
}));
it('should allow to use a custom template',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(
TestComponent,
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>')
.overrideTemplate(
ComponentUsingTestComponent,
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
var testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(
TestComponent,
'<ul><template ngFor [ngForOf]="items" [ngForTemplate]="contentTpl"></template></ul>')
.overrideTemplate(
ComponentUsingTestComponent,
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
async.done();
});
}));
it('should use a default template if a custom one is null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
[ngForTemplate]="contentTpl" let-i="index">{{i}}: {{item}};</template></ul>`)
.overrideTemplate(ComponentUsingTestComponent, '<test-cmp></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
var testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
.overrideTemplate(ComponentUsingTestComponent, '<test-cmp></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
async.done();
});
}));
it('should use a custom template when both default and a custom one are present',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
tcb.overrideTemplate(TestComponent, `<ul><template ngFor let-item [ngForOf]="items"
[ngForTemplate]="contentTpl" let-i="index">{{i}}=> {{item}};</template></ul>`)
.overrideTemplate(
ComponentUsingTestComponent,
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
var testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
.overrideTemplate(
ComponentUsingTestComponent,
'<test-cmp><li template="let item; let i=index">{{i}}: {{item}};</li></test-cmp>')
.createAsync(ComponentUsingTestComponent)
.then((fixture) => {
const testComponent = fixture.debugElement.children[0];
testComponent.componentInstance.items = ['a', 'b', 'c'];
fixture.detectChanges();
expect(testComponent.nativeElement).toHaveText('0: a;1: b;2: c;');
async.done();
});
}));
async.done();
});
}));
describe('track by', () => {
it('should set the context to the component instance',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByContext.bind(this)"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
thisArg = null;
fixture.detectChanges();
expect(thisArg).toBe(fixture.debugElement.componentInstance);
async.done();
});
}));
describe('track by', function() {
it('should not replace tracked items',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`<template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById" let-i="index">
<p>{{items[i]}}</p>
</template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var buildItemList =
() => {
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var buildItemList = () => {
fixture.debugElement.componentInstance.items = [{'id': 'a'}];
fixture.detectChanges();
return fixture.debugElement.queryAll(By.css('p'))[0];
}
};
var firstP = buildItemList();
var finalP = buildItemList();
expect(finalP.nativeElement).toBe(firstP.nativeElement);
async.done();
});
}));
var firstP = buildItemList();
var finalP = buildItemList();
expect(finalP.nativeElement).toBe(firstP.nativeElement);
async.done();
});
}));
it('should update implicit local variable on view',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blue');
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('red');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'blue'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blue');
fixture.debugElement.componentInstance.items = [{'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('red');
async.done();
});
}));
it('should move items around and keep them updated ',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items =
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow');
fixture.debugElement.componentInstance.items =
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackById">{{item['color']}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items =
[{'id': 'a', 'color': 'blue'}, {'id': 'b', 'color': 'yellow'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('blueyellow');
fixture.debugElement.componentInstance.items =
[{'id': 'b', 'color': 'orange'}, {'id': 'a', 'color': 'red'}];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('orangered');
async.done();
});
}));
it('should handle added and removed items properly when tracking by index',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'h'];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('efh');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template =
`<div><template ngFor let-item [ngForOf]="items" [ngForTrackBy]="trackByIndex">{{item}}</template></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.items = ['a', 'b', 'c', 'd'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'g', 'h'];
fixture.detectChanges();
fixture.debugElement.componentInstance.items = ['e', 'f', 'h'];
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('efh');
async.done();
});
}));
});
});
}
@ -516,6 +576,7 @@ class TestComponent {
constructor() { this.items = [1, 2]; }
trackById(index: number, item: any): string { return item['id']; }
trackByIndex(index: number, item: any): number { return index; }
trackByContext(): void { thisArg = this; }
}
@Component({selector: 'outer-cmp', directives: [TestComponent], template: ''})

View File

@ -1,244 +1,243 @@
import {
beforeEach,
ddescribe,
describe,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {Component} from '@angular/core';
import {NgIf} from '@angular/common';
import {IS_DART} from '../../src/facade/lang';
import {Component} from '@angular/core';
import {TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('ngIf directive', () => {
it('should work in a template attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
async.done();
});
}));
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
async.done();
});
}));
it('should work in a template element',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html =
'<div><template [ngIf]="booleanCondition"><copy-me>hello2</copy-me></template></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html =
'<div><template [ngIf]="booleanCondition"><copy-me>hello2</copy-me></template></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello2');
async.done();
});
}));
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello2');
async.done();
});
}));
it('should toggle node when condition changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html = '<div><copy-me template="ngIf booleanCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = true;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = true;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
async.done();
});
}));
it('should handle nested if correctly',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html =
'<div><template [ngIf]="booleanCondition"><copy-me *ngIf="nestedBooleanCondition">hello</copy-me></template></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html =
'<div><template [ngIf]="booleanCondition"><copy-me *ngIf="nestedBooleanCondition">hello</copy-me></template></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = true;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = true;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.nestedBooleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.nestedBooleanCondition = false;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.nestedBooleanCondition = true;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.nestedBooleanCondition = true;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.booleanCondition = false;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
async.done();
});
}));
it('should update several nodes with if',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html =
'<div>' +
'<copy-me template="ngIf numberCondition + 1 >= 2">helloNumber</copy-me>' +
'<copy-me template="ngIf stringCondition == \'foo\'">helloString</copy-me>' +
'<copy-me template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</copy-me>' +
'</div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html = '<div>' +
'<copy-me template="ngIf numberCondition + 1 >= 2">helloNumber</copy-me>' +
'<copy-me template="ngIf stringCondition == \'foo\'">helloString</copy-me>' +
'<copy-me template="ngIf functionCondition(stringCondition, numberCondition)">helloFunction</copy-me>' +
'</div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(3);
expect(getDOM().getText(fixture.debugElement.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction');
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(3);
expect(getDOM().getText(fixture.debugElement.nativeElement))
.toEqual('helloNumberhelloStringhelloFunction');
fixture.debugElement.componentInstance.numberCondition = 0;
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('helloString');
fixture.debugElement.componentInstance.numberCondition = 0;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('helloString');
fixture.debugElement.componentInstance.numberCondition = 1;
fixture.debugElement.componentInstance.stringCondition = "bar";
fixture.detectChanges();
expect(
getDOM().querySelectorAll(fixture.debugElement.nativeElement, 'copy-me').length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('helloNumber');
async.done();
});
}));
fixture.debugElement.componentInstance.numberCondition = 1;
fixture.debugElement.componentInstance.stringCondition = 'bar';
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('helloNumber');
async.done();
});
}));
it('should not add the element twice if the condition goes from true to true (JS)',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
if (!IS_DART) {
it('should not add the element twice if the condition goes from true to true (JS)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(1);
expect(fixture.debugElement.nativeElement).toHaveText('hello');
async.done();
});
}));
async.done();
});
}));
it('should not recreate the element if the condition goes from true to true (JS)',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
it('should not recreate the element if the condition goes from true to true (JS)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
getDOM().addClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
'foo');
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
getDOM().addClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'), "foo");
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
"foo"))
.toBe(true);
async.done();
});
}));
}
if (IS_DART) {
it('should not create the element if the condition is not a boolean (DART)',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var html = '<div><copy-me template="ngIf numberCondition">hello</copy-me></div>';
tcb.overrideTemplate(TestComponent, html)
.createAsync(TestComponent)
.then((fixture) => {
expect(() => fixture.detectChanges()).toThrowError();
expect(getDOM()
.querySelectorAll(fixture.debugElement.nativeElement, 'copy-me')
.length)
.toEqual(0);
expect(fixture.debugElement.nativeElement).toHaveText('');
async.done();
});
}));
}
fixture.debugElement.componentInstance.numberCondition = 2;
fixture.detectChanges();
expect(getDOM().hasClass(
getDOM().querySelector(fixture.debugElement.nativeElement, 'copy-me'),
'foo'))
.toBe(true);
async.done();
});
}));
});
}
@ -253,7 +252,7 @@ class TestComponent {
this.booleanCondition = true;
this.nestedBooleanCondition = true;
this.numberCondition = 1;
this.stringCondition = "foo";
this.functionCondition = function(s, n) { return s == "foo" && n == 1; };
this.stringCondition = 'foo';
this.functionCondition = function(s: any, n: any): boolean { return s == 'foo' && n == 1; };
}
}

View File

@ -1,135 +1,182 @@
import {
beforeEachProviders,
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Injectable, provide} from '@angular/core';
import {NgPlural, NgPluralCase, NgLocalization} from '@angular/common';
import {NgLocalization, NgPlural, NgPluralCase} from '@angular/common';
import {Component, Injectable} from '@angular/core';
import {TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('switch', () => {
beforeEachProviders(() => [provide(NgLocalization, {useClass: TestLocalizationMap})]);
beforeEachProviders(() => [{provide: NgLocalization, useClass: TestLocalization}]);
it('should display the template according to the exact value',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>you have no messages.</li></template>' +
'<template ngPluralCase="=1"><li>you have one message.</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 0;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 0;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
fixture.debugElement.componentInstance.switchValue = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
fixture.debugElement.componentInstance.switchValue = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
async.done();
});
}));
async.done();
});
}));
// https://github.com/angular/angular/issues/9868
// https://github.com/angular/angular/issues/9882
it('should not throw when ngPluralCase contains expressions',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="=0"><li>{{ switchValue }}</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 0;
expect(() => fixture.detectChanges()).not.toThrow();
async.done();
});
}));
it('should be applicable to <ng-container> elements',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ng-container [ngPlural]="switchValue">' +
'<template ngPluralCase="=0">you have no messages.</template>' +
'<template ngPluralCase="=1">you have one message.</template>' +
'</ng-container></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 0;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have no messages.');
fixture.debugElement.componentInstance.switchValue = 1;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have one message.');
async.done();
});
}));
it('should display the template according to the category',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="many"><li>you have many messages.</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('you have a few messages.');
fixture.debugElement.componentInstance.switchValue = 8;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.');
fixture.debugElement.componentInstance.switchValue = 8;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have many messages.');
async.done();
});
}));
async.done();
});
}));
it('should default to other when no matches are found',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="other"><li>default message.</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 100;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('default message.');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 100;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('default message.');
async.done();
});
}));
async.done();
});
}));
it('should prioritize value matches over category matches',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
'<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngPlural]="switchValue">' +
'<template ngPluralCase="few"><li>you have a few messages.</li></template>' +
'<template ngPluralCase="=2">you have two messages.</template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.switchValue = 2;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have two messages.');
fixture.debugElement.componentInstance.switchValue = 3;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('you have a few messages.');
fixture.debugElement.componentInstance.switchValue = 3;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('you have a few messages.');
async.done();
});
}));
async.done();
});
}));
});
}
@Injectable()
export class TestLocalizationMap extends NgLocalization {
class TestLocalization extends NgLocalization {
getPluralCategory(value: number): string {
if (value > 1 && value < 4) {
return 'few';
} else if (value >= 4 && value < 10) {
return 'many';
} else {
return 'other';
}
if (value >= 4 && value < 10) {
return 'many';
}
return 'other';
}
}
@Component({selector: 'test-cmp', directives: [NgPlural, NgPluralCase], template: ''})
class TestComponent {
switchValue: number;
constructor() { this.switchValue = null; }
switchValue: number = null;
}

View File

@ -1,155 +1,211 @@
import {
beforeEach,
beforeEachProviders,
ddescribe,
xdescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {StringMapWrapper} from '../../src/facade/collection';
import {AsyncTestCompleter, beforeEach, beforeEachProviders, ddescribe, xdescribe, describe, expect, iit, inject, it, xit,} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/core/testing';
import {Component} from '@angular/core';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {NgStyle} from '@angular/common/src/directives/ng_style';
function expectNativeEl(fixture: ComponentFixture<any>) {
return <any>expect(fixture.debugElement.children[0].nativeElement);
}
export function main() {
describe('binding to CSS styles', () => {
it('should add styles specified in an object literal',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngStyle]="{'max-width': '40px'}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
async.done();
});
}));
async.done();
});
}));
it('should add and change styles specified in an object expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngStyle]="expr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var expr: Map<string, any>;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
var expr: Map<string, any>;
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
expr = fixture.debugElement.componentInstance.expr;
expr['max-width'] = '30%';
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('30%');
expr = fixture.debugElement.componentInstance.expr;
(expr as any)['max-width'] = '30%';
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '30%'});
async.done();
});
}));
async.done();
});
}));
it('should add and remove styles specified using style.unit notation',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngStyle]="{'max-width.px': expr}"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = '40';
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
fixture.debugElement.componentInstance.expr = null;
fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
async.done();
});
}));
it('should update styles using style.unit notation when unit changes',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width.px': '40'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
fixture.debugElement.componentInstance.expr = {'max-width.em': '40'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40em'});
async.done();
});
}));
// keyValueDiffer is sensitive to key order #9115
it('should change styles specified in an object expression',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
const template = `<div [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {
// height, width order is important here
height: '10px',
width: '10px'
};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'height': '10px', 'width': '10px'});
fixture.debugElement.componentInstance.expr = {
// width, height order is important here
width: '5px',
height: '5px',
};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'height': '5px', 'width': '5px'});
async.done();
});
}));
it('should remove styles when deleting a key in an object expression',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [ngStyle]="expr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle({'max-width': '40px'});
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('');
delete fixture.debugElement.componentInstance.expr['max-width'];
fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
async.done();
});
}));
async.done();
});
}));
it('should co-operate with the style attribute',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div style="font-size: 12px" [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle(
{'max-width': '40px', 'font-size': '12px'});
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
delete fixture.debugElement.componentInstance.expr['max-width'];
fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'});
async.done();
});
}));
async.done();
});
}));
it('should co-operate with the style.[styleName]="expr" special-case in the compiler',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<div [style.font-size.px]="12" [ngStyle]="expr"></div>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('40px');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.expr = {'max-width': '40px'};
fixture.detectChanges();
expectNativeEl(fixture).toHaveCssStyle(
{'max-width': '40px', 'font-size': '12px'});
StringMapWrapper.delete(fixture.debugElement.componentInstance.expr, 'max-width');
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'font-size'))
.toEqual('12px');
delete fixture.debugElement.componentInstance.expr['max-width'];
fixture.detectChanges();
expectNativeEl(fixture).not.toHaveCssStyle('max-width');
expectNativeEl(fixture).toHaveCssStyle({'font-size': '12px'});
fixture.detectChanges();
expect(
getDOM().getStyle(fixture.debugElement.children[0].nativeElement, 'max-width'))
.toEqual('');
async.done();
});
}));
})
async.done();
});
}));
});
}
@Component({selector: 'test-cmp', directives: [NgStyle], template: ''})
class TestComponent {
expr;
expr: any;
}

View File

@ -1,150 +1,185 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {Component} from '@angular/core';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgSwitch, NgSwitchWhen, NgSwitchDefault} from '@angular/common';
import {NgSwitch, NgSwitchCase, NgSwitchDefault} from '@angular/common';
import {Component} from '@angular/core';
import {TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('switch', () => {
describe('switch value changes', () => {
it('should switch amongst when values',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchWhen="a"><li>when a</li></template>' +
'<template ngSwitchWhen="b"><li>when b</li></template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a</li></template>' +
'<template ngSwitchCase="b"><li>when b</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b');
async.done();
});
}));
async.done();
});
}));
// TODO(robwormald): deprecate and remove
it('should switch amongst when values using switchWhen',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchWhen="a"><li>when a</li></template>' +
'<template ngSwitchWhen="b"><li>when b</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b');
async.done();
});
}));
it('should switch amongst when values with fallback to default',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<li template="ngSwitchWhen \'a\'">when a</li>' +
'<li template="ngSwitchDefault">when default</li>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<li template="ngSwitchCase \'a\'">when a</li>' +
'<li template="ngSwitchDefault">when default</li>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default');
async.done();
});
}));
async.done();
});
}));
it('should support multiple whens with the same value',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchWhen="a"><li>when a1;</li></template>' +
'<template ngSwitchWhen="b"><li>when b1;</li></template>' +
'<template ngSwitchWhen="a"><li>when a2;</li></template>' +
'<template ngSwitchWhen="b"><li>when b2;</li></template>' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template ngSwitchCase="a"><li>when a1;</li></template>' +
'<template ngSwitchCase="b"><li>when b1;</li></template>' +
'<template ngSwitchCase="a"><li>when a2;</li></template>' +
'<template ngSwitchCase="b"><li>when b2;</li></template>' +
'<template ngSwitchDefault><li>when default1;</li></template>' +
'<template ngSwitchDefault><li>when default2;</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('when default1;when default2;');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement)
.toHaveText('when default1;when default2;');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a1;when a2;');
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when a1;when a2;');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b1;when b2;');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when b1;when b2;');
async.done();
});
}));
async.done();
});
}));
});
describe('when values changes', () => {
it('should switch amongst when values',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template [ngSwitchWhen]="when1"><li>when 1;</li></template>' +
'<template [ngSwitchWhen]="when2"><li>when 2;</li></template>' +
'<template ngSwitchDefault><li>when default;</li></template>' +
'</ul></div>';
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>' +
'<ul [ngSwitch]="switchValue">' +
'<template [ngSwitchCase]="when1"><li>when 1;</li></template>' +
'<template [ngSwitchCase]="when2"><li>when 2;</li></template>' +
'<template ngSwitchDefault><li>when default;</li></template>' +
'</ul></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.when1 = 'a';
fixture.debugElement.componentInstance.when2 = 'b';
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.debugElement.componentInstance.when1 = 'a';
fixture.debugElement.componentInstance.when2 = 'b';
fixture.debugElement.componentInstance.switchValue = 'a';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 2;');
fixture.debugElement.componentInstance.switchValue = 'b';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 2;');
fixture.debugElement.componentInstance.switchValue = 'c';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
fixture.debugElement.componentInstance.switchValue = 'c';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
fixture.debugElement.componentInstance.when1 = 'c';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.when1 = 'c';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when 1;');
fixture.debugElement.componentInstance.when1 = 'd';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
fixture.debugElement.componentInstance.when1 = 'd';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('when default;');
async.done();
});
}));
async.done();
});
}));
});
});
}
@Component(
{selector: 'test-cmp', directives: [NgSwitch, NgSwitchWhen, NgSwitchDefault], template: ''})
{selector: 'test-cmp', directives: [NgSwitch, NgSwitchCase, NgSwitchDefault], template: ''})
class TestComponent {
switchValue: any;
when1: any;

View File

@ -1,100 +1,207 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {Component, Directive, TemplateRef, ContentChildren, QueryList} from '@angular/core';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgTemplateOutlet} from '@angular/common';
import {Component, ContentChildren, Directive, QueryList, TemplateRef} from '@angular/core';
import {TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('insert', () => {
it('should do nothing if templateRef is null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<template [ngTemplateOutlet]="null"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = `<template [ngTemplateOutlet]="null"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
async.done();
});
}));
async.done();
});
}));
it('should insert content specified by TemplateRef',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
var refs = fixture.debugElement.children[0].references['refs'];
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
async.done();
});
}));
async.done();
});
}));
it('should clear content if TemplateRef becomes null',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
fixture.componentInstance.currentTplRef = null;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
async.done();
});
}));
async.done();
});
}));
it('should swap content if TemplateRef changes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = `<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template><template>bar</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
fixture.componentInstance.currentTplRef = refs.tplRefs.last;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
fixture.componentInstance.currentTplRef = refs.tplRefs.last;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('bar');
async.done();
});
}));
async.done();
});
}));
it('should display template if context is null',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template>foo</template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="null"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('');
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.nativeElement).toHaveText('foo');
async.done();
});
}));
it('should reflect initial context and changes',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template let-foo="foo"><span>{{foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('bar');
fixture.componentInstance.context.foo = 'alter-bar';
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('alter-bar');
async.done();
});
}));
it('should reflect user defined $implicit property in the context',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template let-ctx><span>{{ctx.foo}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.componentInstance.context = {
$implicit: fixture.componentInstance.context
};
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('bar');
async.done();
});
}));
it('should reflect context re-binding',
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template =
`<tpl-refs #refs="tplRefs"><template let-shawshank="shawshank"><span>{{shawshank}}</span></template></tpl-refs><template [ngTemplateOutlet]="currentTplRef" [ngOutletContext]="context"></template>`;
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var refs = fixture.debugElement.children[0].references['refs'];
fixture.componentInstance.currentTplRef = refs.tplRefs.first;
fixture.componentInstance.context = {shawshank: 'brooks'};
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('brooks');
fixture.componentInstance.context = {shawshank: 'was here'};
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('was here');
async.done();
});
}));
});
}
@ -107,4 +214,5 @@ class CaptureTplRefs {
@Component({selector: 'test-cmp', directives: [NgTemplateOutlet, CaptureTplRefs], template: ''})
class TestComponent {
currentTplRef: TemplateRef<any>;
context: any = {foo: 'bar'};
}

View File

@ -1,62 +1,67 @@
import {
beforeEach,
ddescribe,
describe,
expect,
iit,
inject,
it,
xit,
} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder, ComponentFixture} from '@angular/compiler/testing';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Component, Directive} from '@angular/core';
import {ElementRef} from '@angular/core/src/linker/element_ref';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {TestComponentBuilder} from '@angular/core/testing';
import {AsyncTestCompleter, beforeEach, ddescribe, describe, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
import {expect} from '@angular/platform-browser/testing/matchers';
export function main() {
describe('non-bindable', () => {
it('should not interpolate children',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo{{text}}');
async.done();
});
}));
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div>{{text}}<span ngNonBindable>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
expect(fixture.debugElement.nativeElement).toHaveText('foo{{text}}');
async.done();
});
}));
it('should ignore directives on child nodes',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div ngNonBindable><span id=child test-dec>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
// We must use getDOM().querySelector instead of fixture.query here
// since the elements inside are not compiled.
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeFalsy();
async.done();
});
}));
// We must use getDOM().querySelector instead of fixture.query here
// since the elements inside are not compiled.
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeFalsy();
async.done();
});
}));
it('should trigger directives on the same node',
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder, async) => {
var template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeTruthy();
async.done();
});
}));
})
inject(
[TestComponentBuilder, AsyncTestCompleter],
(tcb: TestComponentBuilder, async: AsyncTestCompleter) => {
var template = '<div><span id=child ngNonBindable test-dec>{{text}}</span></div>';
tcb.overrideTemplate(TestComponent, template)
.createAsync(TestComponent)
.then((fixture) => {
fixture.detectChanges();
var span = getDOM().querySelector(fixture.debugElement.nativeElement, '#child');
expect(getDOM().hasClass(span, 'compiled')).toBeTruthy();
async.done();
});
}));
});
}
@Directive({selector: '[test-dec]'})

View File

@ -1,102 +0,0 @@
library angular2.test.directives.observable_list_iterable_diff_spec;
import 'package:angular2/testing_internal.dart';
import 'package:observe/observe.dart' show ObservableList;
import 'package:angular2/core.dart' show ChangeDetectorRef;
import 'package:angular2/common.dart' show ObservableListDiffFactory;
@proxy
class SpyChangeDetectorRef extends SpyObject implements ChangeDetectorRef {}
main() {
describe('ObservableListDiff', () {
var factory, changeDetectorRef;
beforeEach(() {
factory = const ObservableListDiffFactory();
changeDetectorRef = new SpyChangeDetectorRef();
});
describe("supports", () {
it("should be true for ObservableList", () {
expect(factory.supports(new ObservableList())).toBe(true);
});
it("should be false otherwise", () {
expect(factory.supports([1, 2, 3])).toBe(false);
});
});
it("should return itself when called the first time", () {
final d = factory.create(changeDetectorRef);
final c = new ObservableList.from([1, 2]);
expect(d.diff(c)).toBe(d);
});
it("should return itself when no changes between the calls", () {
final d = factory.create(changeDetectorRef);
final c = new ObservableList.from([1, 2]);
d.diff(c);
expect(d.diff(c)).toBe(null);
});
it("should return the wrapped value once a change has been triggered",
fakeAsync(() {
final d = factory.create(changeDetectorRef);
final c = new ObservableList.from([1, 2]);
d.diff(c);
c.add(3);
// same value, because we have not detected the change yet
expect(d.diff(c)).toBe(null);
// now we detect the change
flushMicrotasks();
expect(d.diff(c)).toBe(d);
}));
it("should request a change detection check upon receiving a change",
fakeAsync(() {
final d = factory.create(changeDetectorRef);
final c = new ObservableList.from([1, 2]);
d.diff(c);
c.add(3);
flushMicrotasks();
expect(changeDetectorRef.spy("markForCheck")).toHaveBeenCalledOnce();
}));
it("should return the wrapped value after changing a collection", () {
final d = factory.create(changeDetectorRef);
final c1 = new ObservableList.from([1, 2]);
final c2 = new ObservableList.from([3, 4]);
expect(d.diff(c1)).toBe(d);
expect(d.diff(c2)).toBe(d);
});
it("should not unbsubscribe from the stream of chagnes after changing a collection",
() {
final d = factory.create(changeDetectorRef);
final c1 = new ObservableList.from([1, 2]);
expect(d.diff(c1)).toBe(d);
final c2 = new ObservableList.from([3, 4]);
expect(d.diff(c2)).toBe(d);
// pushing into the first collection has no effect, and we do not see the change
c1.add(3);
expect(d.diff(c2)).toBe(null);
});
});
}

View File

@ -0,0 +1,489 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CheckboxControlValueAccessor, Control, ControlGroup, ControlValueAccessor, DefaultValueAccessor, NgControl, NgControlGroup, NgControlName, NgForm, NgFormControl, NgFormModel, NgModel, SelectControlValueAccessor, Validator, Validators} from '@angular/common/src/forms-deprecated';
import {composeValidators, selectValueAccessor} from '@angular/common/src/forms-deprecated/directives/shared';
import {SimpleChange} from '@angular/core/src/change_detection';
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {SpyNgControl, SpyValueAccessor} from '../spies';
class DummyControlValueAccessor implements ControlValueAccessor {
writtenValue: any /** TODO #9100 */;
registerOnChange(fn: any /** TODO #9100 */) {}
registerOnTouched(fn: any /** TODO #9100 */) {}
writeValue(obj: any): void { this.writtenValue = obj; }
}
class CustomValidatorDirective implements Validator {
validate(c: Control): {[key: string]: any} { return {'custom': true}; }
}
function asyncValidator(expected: any /** TODO #9100 */, timeout = 0) {
return (c: any /** TODO #9100 */) => {
return new Promise((resolve) => {
var res = c.value != expected ? {'async': true} : null;
if (timeout == 0) {
resolve(res);
} else {
setTimeout(() => { resolve(res); }, timeout);
}
});
};
}
export function main() {
describe('Form Directives', () => {
var defaultAccessor: DefaultValueAccessor;
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
describe('shared', () => {
describe('selectValueAccessor', () => {
var dir: NgControl;
beforeEach(() => { dir = <any>new SpyNgControl(); });
it('should throw when given an empty array',
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
it('should return the default value accessor when no other provided',
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
it('should return checkbox accessor when provided', () => {
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
expect(selectValueAccessor(dir, [
defaultAccessor, checkboxAccessor
])).toEqual(checkboxAccessor);
});
it('should return select accessor when provided', () => {
var selectAccessor = new SelectControlValueAccessor(null, null);
expect(selectValueAccessor(dir, [
defaultAccessor, selectAccessor
])).toEqual(selectAccessor);
});
it('should throw when more than one build-in accessor is provided', () => {
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
var selectAccessor = new SelectControlValueAccessor(null, null);
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
});
it('should return custom accessor when provided', () => {
var customAccessor = new SpyValueAccessor();
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor]))
.toEqual(customAccessor);
});
it('should throw when more than one custom accessor is provided', () => {
var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
});
});
describe('composeValidators', () => {
it('should compose functions', () => {
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
var dummy2 = (_: any /** TODO #9100 */) => ({'dummy2': true});
var v = composeValidators([dummy1, dummy2]);
expect(v(new Control(''))).toEqual({'dummy1': true, 'dummy2': true});
});
it('should compose validator directives', () => {
var dummy1 = (_: any /** TODO #9100 */) => ({'dummy1': true});
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
expect(v(new Control(''))).toEqual({'dummy1': true, 'custom': true});
});
});
});
describe('NgFormModel', () => {
var form: any /** TODO #9100 */;
var formModel: ControlGroup;
var loginControlDir: any /** TODO #9100 */;
beforeEach(() => {
form = new NgFormModel([], []);
formModel = new ControlGroup({
'login': new Control(),
'passwords':
new ControlGroup({'password': new Control(), 'passwordConfirm': new Control()})
});
form.form = formModel;
loginControlDir = new NgControlName(
form, [Validators.required], [asyncValidator('expected')], [defaultAccessor]);
loginControlDir.name = 'login';
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});
it('should reexport control properties', () => {
expect(form.control).toBe(formModel);
expect(form.value).toBe(formModel.value);
expect(form.valid).toBe(formModel.valid);
expect(form.errors).toBe(formModel.errors);
expect(form.pristine).toBe(formModel.pristine);
expect(form.dirty).toBe(formModel.dirty);
expect(form.touched).toBe(formModel.touched);
expect(form.untouched).toBe(formModel.untouched);
});
describe('addControl', () => {
it('should throw when no control found', () => {
var dir = new NgControlName(form, null, null, [defaultAccessor]);
dir.name = 'invalidName';
expect(() => form.addControl(dir))
.toThrowError(new RegExp(`Cannot find control with name: 'invalidName'`));
});
it('should throw when no value accessor', () => {
var dir = new NgControlName(form, null, null, null);
dir.name = 'login';
expect(() => form.addControl(dir))
.toThrowError(new RegExp(`No value accessor for form control with name: 'login'`));
});
it('should throw when no value accessor with path', () => {
const group = new NgControlGroup(form, null, null);
const dir = new NgControlName(group, null, null, null);
group.name = 'passwords';
dir.name = 'password';
expect(() => form.addControl(dir))
.toThrowError(new RegExp(
`No value accessor for form control with path: 'passwords -> password'`));
});
it('should set up validators', fakeAsync(() => {
form.addControl(loginControlDir);
// sync validators are set
expect(formModel.hasError('required', ['login'])).toBe(true);
expect(formModel.hasError('async', ['login'])).toBe(false);
(<Control>formModel.find(['login'])).updateValue('invalid value');
// sync validator passes, running async validators
expect(formModel.pending).toBe(true);
tick();
expect(formModel.hasError('required', ['login'])).toBe(false);
expect(formModel.hasError('async', ['login'])).toBe(true);
}));
it('should write value to the DOM', () => {
(<Control>formModel.find(['login'])).updateValue('initValue');
form.addControl(loginControlDir);
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('initValue');
});
it('should add the directive to the list of directives included in the form', () => {
form.addControl(loginControlDir);
expect(form.directives).toEqual([loginControlDir]);
});
});
describe('addControlGroup', () => {
var matchingPasswordsValidator = (g: any /** TODO #9100 */) => {
if (g.controls['password'].value != g.controls['passwordConfirm'].value) {
return {'differentPasswords': true};
} else {
return null;
}
};
it('should set up validator', fakeAsync(() => {
var group = new NgControlGroup(
form, [matchingPasswordsValidator], [asyncValidator('expected')]);
group.name = 'passwords';
form.addControlGroup(group);
(<Control>formModel.find(['passwords', 'password'])).updateValue('somePassword');
(<Control>formModel.find([
'passwords', 'passwordConfirm'
])).updateValue('someOtherPassword');
// sync validators are set
expect(formModel.hasError('differentPasswords', ['passwords'])).toEqual(true);
(<Control>formModel.find([
'passwords', 'passwordConfirm'
])).updateValue('somePassword');
// sync validators pass, running async validators
expect(formModel.pending).toBe(true);
tick();
expect(formModel.hasError('async', ['passwords'])).toBe(true);
}));
});
describe('removeControl', () => {
it('should remove the directive to the list of directives included in the form', () => {
form.addControl(loginControlDir);
form.removeControl(loginControlDir);
expect(form.directives).toEqual([]);
});
});
describe('ngOnChanges', () => {
it('should update dom values of all the directives', () => {
form.addControl(loginControlDir);
(<Control>formModel.find(['login'])).updateValue('new value');
form.ngOnChanges({});
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual('new value');
});
it('should set up a sync validator', () => {
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
var f = new NgFormModel([formValidator], []);
f.form = formModel;
f.ngOnChanges({'form': new SimpleChange(null, null)});
expect(formModel.errors).toEqual({'custom': true});
});
it('should set up an async validator', fakeAsync(() => {
var f = new NgFormModel([], [asyncValidator('expected')]);
f.form = formModel;
f.ngOnChanges({'form': new SimpleChange(null, null)});
tick();
expect(formModel.errors).toEqual({'async': true});
}));
});
});
describe('NgForm', () => {
var form: any /** TODO #9100 */;
var formModel: ControlGroup;
var loginControlDir: any /** TODO #9100 */;
var personControlGroupDir: any /** TODO #9100 */;
beforeEach(() => {
form = new NgForm([], []);
formModel = form.form;
personControlGroupDir = new NgControlGroup(form, [], []);
personControlGroupDir.name = 'person';
loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
loginControlDir.name = 'login';
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});
it('should reexport control properties', () => {
expect(form.control).toBe(formModel);
expect(form.value).toBe(formModel.value);
expect(form.valid).toBe(formModel.valid);
expect(form.errors).toBe(formModel.errors);
expect(form.pristine).toBe(formModel.pristine);
expect(form.dirty).toBe(formModel.dirty);
expect(form.touched).toBe(formModel.touched);
expect(form.untouched).toBe(formModel.untouched);
});
describe('addControl & addControlGroup', () => {
it('should create a control with the given name', fakeAsync(() => {
form.addControlGroup(personControlGroupDir);
form.addControl(loginControlDir);
flushMicrotasks();
expect(formModel.find(['person', 'login'])).not.toBeNull;
}));
// should update the form's value and validity
});
describe('removeControl & removeControlGroup', () => {
it('should remove control', fakeAsync(() => {
form.addControlGroup(personControlGroupDir);
form.addControl(loginControlDir);
form.removeControlGroup(personControlGroupDir);
form.removeControl(loginControlDir);
flushMicrotasks();
expect(formModel.find(['person'])).toBeNull();
expect(formModel.find(['person', 'login'])).toBeNull();
}));
// should update the form's value and validity
});
it('should set up sync validator', fakeAsync(() => {
var formValidator = (c: any /** TODO #9100 */) => ({'custom': true});
var f = new NgForm([formValidator], []);
tick();
expect(f.form.errors).toEqual({'custom': true});
}));
it('should set up async validator', fakeAsync(() => {
var f = new NgForm([], [asyncValidator('expected')]);
tick();
expect(f.form.errors).toEqual({'async': true});
}));
});
describe('NgControlGroup', () => {
var formModel: any /** TODO #9100 */;
var controlGroupDir: any /** TODO #9100 */;
beforeEach(() => {
formModel = new ControlGroup({'login': new Control(null)});
var parent = new NgFormModel([], []);
parent.form = new ControlGroup({'group': formModel});
controlGroupDir = new NgControlGroup(parent, [], []);
controlGroupDir.name = 'group';
});
it('should reexport control properties', () => {
expect(controlGroupDir.control).toBe(formModel);
expect(controlGroupDir.value).toBe(formModel.value);
expect(controlGroupDir.valid).toBe(formModel.valid);
expect(controlGroupDir.errors).toBe(formModel.errors);
expect(controlGroupDir.pristine).toBe(formModel.pristine);
expect(controlGroupDir.dirty).toBe(formModel.dirty);
expect(controlGroupDir.touched).toBe(formModel.touched);
expect(controlGroupDir.untouched).toBe(formModel.untouched);
});
});
describe('NgFormControl', () => {
var controlDir: any /** TODO #9100 */;
var control: any /** TODO #9100 */;
var checkProperties = function(control: any /** TODO #9100 */) {
expect(controlDir.control).toBe(control);
expect(controlDir.value).toBe(control.value);
expect(controlDir.valid).toBe(control.valid);
expect(controlDir.errors).toBe(control.errors);
expect(controlDir.pristine).toBe(control.pristine);
expect(controlDir.dirty).toBe(control.dirty);
expect(controlDir.touched).toBe(control.touched);
expect(controlDir.untouched).toBe(control.untouched);
};
beforeEach(() => {
controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
controlDir.valueAccessor = new DummyControlValueAccessor();
control = new Control(null);
controlDir.form = control;
});
it('should reexport control properties', () => { checkProperties(control); });
it('should reexport new control properties', () => {
var newControl = new Control(null);
controlDir.form = newControl;
controlDir.ngOnChanges({'form': new SimpleChange(control, newControl)});
checkProperties(newControl);
});
it('should set up validator', () => {
expect(control.valid).toBe(true);
// this will add the required validator and recalculate the validity
controlDir.ngOnChanges({'form': new SimpleChange(null, control)});
expect(control.valid).toBe(false);
});
});
describe('NgModel', () => {
var ngModel: any /** TODO #9100 */;
beforeEach(() => {
ngModel =
new NgModel([Validators.required], [asyncValidator('expected')], [defaultAccessor]);
ngModel.valueAccessor = new DummyControlValueAccessor();
});
it('should reexport control properties', () => {
var control = ngModel.control;
expect(ngModel.control).toBe(control);
expect(ngModel.value).toBe(control.value);
expect(ngModel.valid).toBe(control.valid);
expect(ngModel.errors).toBe(control.errors);
expect(ngModel.pristine).toBe(control.pristine);
expect(ngModel.dirty).toBe(control.dirty);
expect(ngModel.touched).toBe(control.touched);
expect(ngModel.untouched).toBe(control.untouched);
});
it('should throw when no value accessor with unnamed control', () => {
const unnamedDir = new NgModel(null, null, null);
expect(() => unnamedDir.ngOnChanges({}))
.toThrowError(new RegExp(`No value accessor for form control with unspecified name`));
});
it('should set up validator', fakeAsync(() => {
// this will add the required validator and recalculate the validity
ngModel.ngOnChanges({});
tick();
expect(ngModel.control.errors).toEqual({'required': true});
ngModel.control.updateValue('someValue');
tick();
expect(ngModel.control.errors).toEqual({'async': true});
}));
});
describe('NgControlName', () => {
var formModel: any /** TODO #9100 */;
var controlNameDir: any /** TODO #9100 */;
beforeEach(() => {
formModel = new Control('name');
var parent = new NgFormModel([], []);
parent.form = new ControlGroup({'name': formModel});
controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
controlNameDir.name = 'name';
});
it('should reexport control properties', () => {
expect(controlNameDir.control).toBe(formModel);
expect(controlNameDir.value).toBe(formModel.value);
expect(controlNameDir.valid).toBe(formModel.valid);
expect(controlNameDir.errors).toBe(formModel.errors);
expect(controlNameDir.pristine).toBe(formModel.pristine);
expect(controlNameDir.dirty).toBe(formModel.dirty);
expect(controlNameDir.touched).toBe(formModel.touched);
expect(controlNameDir.untouched).toBe(formModel.untouched);
});
});
});
}

View File

@ -0,0 +1,69 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Control, FormBuilder} from '@angular/common/src/forms-deprecated';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
export function main() {
function syncValidator(_: any): any { return null; }
function asyncValidator(_: any) { return Promise.resolve(null); }
describe('Form Builder', () => {
var b: any /** TODO #9100 */;
beforeEach(() => { b = new FormBuilder(); });
it('should create controls from a value', () => {
var g = b.group({'login': 'some value'});
expect(g.controls['login'].value).toEqual('some value');
});
it('should create controls from an array', () => {
var g = b.group(
{'login': ['some value'], 'password': ['some value', syncValidator, asyncValidator]});
expect(g.controls['login'].value).toEqual('some value');
expect(g.controls['password'].value).toEqual('some value');
expect(g.controls['password'].validator).toEqual(syncValidator);
expect(g.controls['password'].asyncValidator).toEqual(asyncValidator);
});
it('should use controls', () => {
var g = b.group({'login': b.control('some value', syncValidator, asyncValidator)});
expect(g.controls['login'].value).toEqual('some value');
expect(g.controls['login'].validator).toBe(syncValidator);
expect(g.controls['login'].asyncValidator).toBe(asyncValidator);
});
it('should create groups with optional controls', () => {
var g = b.group({'login': 'some value'}, {'optionals': {'login': false}});
expect(g.contains('login')).toEqual(false);
});
it('should create groups with a custom validator', () => {
var g = b.group(
{'login': 'some value'}, {'validator': syncValidator, 'asyncValidator': asyncValidator});
expect(g.validator).toBe(syncValidator);
expect(g.asyncValidator).toBe(asyncValidator);
});
it('should create control arrays', () => {
var c = b.control('three');
var a = b.array(
['one', ['two', syncValidator], c, b.array(['four'])], syncValidator, asyncValidator);
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
expect(a.validator).toBe(syncValidator);
expect(a.asyncValidator).toBe(asyncValidator);
});
});
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,856 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Control, ControlArray, ControlGroup, Validators} from '@angular/common/src/forms-deprecated';
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {AsyncTestCompleter, afterEach, beforeEach, ddescribe, describe, expect, iit, inject, it, xit} from '@angular/core/testing/testing_internal';
import {EventEmitter} from '../../src/facade/async';
import {isPresent} from '../../src/facade/lang';
export function main() {
function asyncValidator(expected: any /** TODO #9100 */, timeouts = {}) {
return (c: any /** TODO #9100 */) => {
return new Promise((resolve) => {
var t = isPresent((timeouts as any /** TODO #9100 */)[c.value]) ?
(timeouts as any /** TODO #9100 */)[c.value] :
0;
var res = c.value != expected ? {'async': true} : null;
if (t == 0) {
resolve(res);
} else {
setTimeout(() => { resolve(res); }, t);
}
});
};
}
function asyncValidatorReturningObservable(c: any /** TODO #9100 */) {
var e = new EventEmitter();
Promise.resolve(null).then(() => { e.emit({'async': true}); });
return e;
}
describe('Form Model', () => {
describe('Control', () => {
it('should default the value to null', () => {
var c = new Control();
expect(c.value).toBe(null);
});
describe('validator', () => {
it('should run validator with the initial value', () => {
var c = new Control('value', Validators.required);
expect(c.valid).toEqual(true);
});
it('should rerun the validator when the value changes', () => {
var c = new Control('value', Validators.required);
c.updateValue(null);
expect(c.valid).toEqual(false);
});
it('should return errors', () => {
var c = new Control(null, Validators.required);
expect(c.errors).toEqual({'required': true});
});
});
describe('asyncValidator', () => {
it('should run validator with the initial value', fakeAsync(() => {
var c = new Control('value', null, asyncValidator('expected'));
tick();
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({'async': true});
}));
it('should support validators returning observables', fakeAsync(() => {
var c = new Control('value', null, asyncValidatorReturningObservable);
tick();
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({'async': true});
}));
it('should rerun the validator when the value changes', fakeAsync(() => {
var c = new Control('value', null, asyncValidator('expected'));
c.updateValue('expected');
tick();
expect(c.valid).toEqual(true);
}));
it('should run the async validator only when the sync validator passes', fakeAsync(() => {
var c = new Control('', Validators.required, asyncValidator('expected'));
tick();
expect(c.errors).toEqual({'required': true});
c.updateValue('some value');
tick();
expect(c.errors).toEqual({'async': true});
}));
it('should mark the control as pending while running the async validation',
fakeAsync(() => {
var c = new Control('', null, asyncValidator('expected'));
expect(c.pending).toEqual(true);
tick();
expect(c.pending).toEqual(false);
}));
it('should only use the latest async validation run', fakeAsync(() => {
var c =
new Control('', null, asyncValidator('expected', {'long': 200, 'expected': 100}));
c.updateValue('long');
c.updateValue('expected');
tick(300);
expect(c.valid).toEqual(true);
}));
});
describe('dirty', () => {
it('should be false after creating a control', () => {
var c = new Control('value');
expect(c.dirty).toEqual(false);
});
it('should be true after changing the value of the control', () => {
var c = new Control('value');
c.markAsDirty();
expect(c.dirty).toEqual(true);
});
});
describe('updateValue', () => {
var g: any /** TODO #9100 */, c: any /** TODO #9100 */;
beforeEach(() => {
c = new Control('oldValue');
g = new ControlGroup({'one': c});
});
it('should update the value of the control', () => {
c.updateValue('newValue');
expect(c.value).toEqual('newValue');
});
it('should invoke ngOnChanges if it is present', () => {
var ngOnChanges: any /** TODO #9100 */;
c.registerOnChange((v: any /** TODO #9100 */) => ngOnChanges = ['invoked', v]);
c.updateValue('newValue');
expect(ngOnChanges).toEqual(['invoked', 'newValue']);
});
it('should not invoke on change when explicitly specified', () => {
var onChange: any /** TODO #9100 */ = null;
c.registerOnChange((v: any /** TODO #9100 */) => onChange = ['invoked', v]);
c.updateValue('newValue', {emitModelToViewChange: false});
expect(onChange).toBeNull();
});
it('should update the parent', () => {
c.updateValue('newValue');
expect(g.value).toEqual({'one': 'newValue'});
});
it('should not update the parent when explicitly specified', () => {
c.updateValue('newValue', {onlySelf: true});
expect(g.value).toEqual({'one': 'oldValue'});
});
it('should fire an event', fakeAsync(() => {
c.valueChanges.subscribe(
{next: (value: any) => { expect(value).toEqual('newValue'); }});
c.updateValue('newValue');
tick();
}));
it('should not fire an event when explicitly specified', fakeAsync(() => {
c.valueChanges.subscribe({next: (value: any) => { throw 'Should not happen'; }});
c.updateValue('newValue', {emitEvent: false});
tick();
}));
});
describe('valueChanges & statusChanges', () => {
var c: any /** TODO #9100 */;
beforeEach(() => { c = new Control('old', Validators.required); });
it('should fire an event after the value has been updated',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
c.valueChanges.subscribe({
next: (value: any) => {
expect(c.value).toEqual('new');
expect(value).toEqual('new');
async.done();
}
});
c.updateValue('new');
}));
it('should fire an event after the status has been updated to invalid', fakeAsync(() => {
c.statusChanges.subscribe({
next: (status: any) => {
expect(c.status).toEqual('INVALID');
expect(status).toEqual('INVALID');
}
});
c.updateValue('');
tick();
}));
it('should fire an event after the status has been updated to pending', fakeAsync(() => {
var c = new Control('old', Validators.required, asyncValidator('expected'));
var log: any[] /** TODO #9100 */ = [];
c.valueChanges.subscribe({next: (value: any) => log.push(`value: '${value}'`)});
c.statusChanges.subscribe({next: (status: any) => log.push(`status: '${status}'`)});
c.updateValue('');
tick();
c.updateValue('nonEmpty');
tick();
c.updateValue('expected');
tick();
expect(log).toEqual([
'' +
'value: \'\'',
'status: \'INVALID\'',
'value: \'nonEmpty\'',
'status: \'PENDING\'',
'status: \'INVALID\'',
'value: \'expected\'',
'status: \'PENDING\'',
'status: \'VALID\'',
]);
}));
// TODO: remove the if statement after making observable delivery sync
it('should update set errors and status before emitting an event',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
c.valueChanges.subscribe((value: any /** TODO #9100 */) => {
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({'required': true});
async.done();
});
c.updateValue('');
}));
it('should return a cold observable',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
c.updateValue('will be ignored');
c.valueChanges.subscribe({
next: (value: any) => {
expect(value).toEqual('new');
async.done();
}
});
c.updateValue('new');
}));
});
describe('setErrors', () => {
it('should set errors on a control', () => {
var c = new Control('someValue');
c.setErrors({'someError': true});
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({'someError': true});
});
it('should reset the errors and validity when the value changes', () => {
var c = new Control('someValue', Validators.required);
c.setErrors({'someError': true});
c.updateValue('');
expect(c.errors).toEqual({'required': true});
});
it('should update the parent group\'s validity', () => {
var c = new Control('someValue');
var g = new ControlGroup({'one': c});
expect(g.valid).toEqual(true);
c.setErrors({'someError': true});
expect(g.valid).toEqual(false);
});
it('should not reset parent\'s errors', () => {
var c = new Control('someValue');
var g = new ControlGroup({'one': c});
g.setErrors({'someGroupError': true});
c.setErrors({'someError': true});
expect(g.errors).toEqual({'someGroupError': true});
});
it('should reset errors when updating a value', () => {
var c = new Control('oldValue');
var g = new ControlGroup({'one': c});
g.setErrors({'someGroupError': true});
c.setErrors({'someError': true});
c.updateValue('newValue');
expect(c.errors).toEqual(null);
expect(g.errors).toEqual(null);
});
});
});
describe('ControlGroup', () => {
describe('value', () => {
it('should be the reduced value of the child controls', () => {
var g = new ControlGroup({'one': new Control('111'), 'two': new Control('222')});
expect(g.value).toEqual({'one': '111', 'two': '222'});
});
it('should be empty when there are no child controls', () => {
var g = new ControlGroup({});
expect(g.value).toEqual({});
});
it('should support nested groups', () => {
var g = new ControlGroup(
{'one': new Control('111'), 'nested': new ControlGroup({'two': new Control('222')})});
expect(g.value).toEqual({'one': '111', 'nested': {'two': '222'}});
(<Control>(g.controls['nested'].find('two'))).updateValue('333');
expect(g.value).toEqual({'one': '111', 'nested': {'two': '333'}});
});
});
describe('adding and removing controls', () => {
it('should update value and validity when control is added', () => {
var g = new ControlGroup({'one': new Control('1')});
expect(g.value).toEqual({'one': '1'});
expect(g.valid).toBe(true);
g.addControl('two', new Control('2', Validators.minLength(10)));
expect(g.value).toEqual({'one': '1', 'two': '2'});
expect(g.valid).toBe(false);
});
it('should update value and validity when control is removed', () => {
var g = new ControlGroup(
{'one': new Control('1'), 'two': new Control('2', Validators.minLength(10))});
expect(g.value).toEqual({'one': '1', 'two': '2'});
expect(g.valid).toBe(false);
g.removeControl('two');
expect(g.value).toEqual({'one': '1'});
expect(g.valid).toBe(true);
});
});
describe('errors', () => {
it('should run the validator when the value changes', () => {
var simpleValidator = (c: any /** TODO #9100 */) =>
c.controls['one'].value != 'correct' ? {'broken': true} : null;
var c = new Control(null);
var g = new ControlGroup({'one': c}, null, simpleValidator);
c.updateValue('correct');
expect(g.valid).toEqual(true);
expect(g.errors).toEqual(null);
c.updateValue('incorrect');
expect(g.valid).toEqual(false);
expect(g.errors).toEqual({'broken': true});
});
});
describe('dirty', () => {
var c: any /** TODO #9100 */, g: any /** TODO #9100 */;
beforeEach(() => {
c = new Control('value');
g = new ControlGroup({'one': c});
});
it('should be false after creating a control', () => { expect(g.dirty).toEqual(false); });
it('should be false after changing the value of the control', () => {
c.markAsDirty();
expect(g.dirty).toEqual(true);
});
});
describe('optional components', () => {
describe('contains', () => {
var group: any /** TODO #9100 */;
beforeEach(() => {
group = new ControlGroup(
{
'required': new Control('requiredValue'),
'optional': new Control('optionalValue')
},
{'optional': false});
});
// rename contains into has
it('should return false when the component is not included',
() => { expect(group.contains('optional')).toEqual(false); });
it('should return false when there is no component with the given name',
() => { expect(group.contains('something else')).toEqual(false); });
it('should return true when the component is included', () => {
expect(group.contains('required')).toEqual(true);
group.include('optional');
expect(group.contains('optional')).toEqual(true);
});
});
it('should not include an inactive component into the group value', () => {
var group = new ControlGroup(
{'required': new Control('requiredValue'), 'optional': new Control('optionalValue')},
{'optional': false});
expect(group.value).toEqual({'required': 'requiredValue'});
group.include('optional');
expect(group.value).toEqual({'required': 'requiredValue', 'optional': 'optionalValue'});
});
it('should not run Validators on an inactive component', () => {
var group = new ControlGroup(
{
'required': new Control('requiredValue', Validators.required),
'optional': new Control('', Validators.required)
},
{'optional': false});
expect(group.valid).toEqual(true);
group.include('optional');
expect(group.valid).toEqual(false);
});
});
describe('valueChanges', () => {
var g: any /** TODO #9100 */, c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
beforeEach(() => {
c1 = new Control('old1');
c2 = new Control('old2');
g = new ControlGroup({'one': c1, 'two': c2}, {'two': true});
});
it('should fire an event after the value has been updated',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
g.valueChanges.subscribe({
next: (value: any) => {
expect(g.value).toEqual({'one': 'new1', 'two': 'old2'});
expect(value).toEqual({'one': 'new1', 'two': 'old2'});
async.done();
}
});
c1.updateValue('new1');
}));
it('should fire an event after the control\'s observable fired an event',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var controlCallbackIsCalled = false;
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }});
g.valueChanges.subscribe({
next: (value: any) => {
expect(controlCallbackIsCalled).toBe(true);
async.done();
}
});
c1.updateValue('new1');
}));
it('should fire an event when a control is excluded',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
g.valueChanges.subscribe({
next: (value: any) => {
expect(value).toEqual({'one': 'old1'});
async.done();
}
});
g.exclude('two');
}));
it('should fire an event when a control is included',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
g.exclude('two');
g.valueChanges.subscribe({
next: (value: any) => {
expect(value).toEqual({'one': 'old1', 'two': 'old2'});
async.done();
}
});
g.include('two');
}));
it('should fire an event every time a control is updated',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var loggedValues: any[] /** TODO #9100 */ = [];
g.valueChanges.subscribe({
next: (value: any) => {
loggedValues.push(value);
if (loggedValues.length == 2) {
expect(loggedValues).toEqual([
{'one': 'new1', 'two': 'old2'}, {'one': 'new1', 'two': 'new2'}
]);
async.done();
}
}
});
c1.updateValue('new1');
c2.updateValue('new2');
}));
xit('should not fire an event when an excluded control is updated',
inject(
[AsyncTestCompleter], (async: AsyncTestCompleter) => {
// hard to test without hacking zones
}));
});
describe('getError', () => {
it('should return the error when it is present', () => {
var c = new Control('', Validators.required);
var g = new ControlGroup({'one': c});
expect(c.getError('required')).toEqual(true);
expect(g.getError('required', ['one'])).toEqual(true);
});
it('should return null otherwise', () => {
var c = new Control('not empty', Validators.required);
var g = new ControlGroup({'one': c});
expect(c.getError('invalid')).toEqual(null);
expect(g.getError('required', ['one'])).toEqual(null);
expect(g.getError('required', ['invalid'])).toEqual(null);
});
});
describe('asyncValidator', () => {
it('should run the async validator', fakeAsync(() => {
var c = new Control('value');
var g = new ControlGroup({'one': c}, null, null, asyncValidator('expected'));
expect(g.pending).toEqual(true);
tick(1);
expect(g.errors).toEqual({'async': true});
expect(g.pending).toEqual(false);
}));
it('should set the parent group\'s status to pending', fakeAsync(() => {
var c = new Control('value', null, asyncValidator('expected'));
var g = new ControlGroup({'one': c});
expect(g.pending).toEqual(true);
tick(1);
expect(g.pending).toEqual(false);
}));
it('should run the parent group\'s async validator when children are pending',
fakeAsync(() => {
var c = new Control('value', null, asyncValidator('expected'));
var g = new ControlGroup({'one': c}, null, null, asyncValidator('expected'));
tick(1);
expect(g.errors).toEqual({'async': true});
expect(g.find(['one']).errors).toEqual({'async': true});
}));
});
});
describe('ControlArray', () => {
describe('adding/removing', () => {
var a: ControlArray;
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */, c3: any /** TODO #9100 */;
beforeEach(() => {
a = new ControlArray([]);
c1 = new Control(1);
c2 = new Control(2);
c3 = new Control(3);
});
it('should support pushing', () => {
a.push(c1);
expect(a.length).toEqual(1);
expect(a.controls).toEqual([c1]);
});
it('should support removing', () => {
a.push(c1);
a.push(c2);
a.push(c3);
a.removeAt(1);
expect(a.controls).toEqual([c1, c3]);
});
it('should support inserting', () => {
a.push(c1);
a.push(c3);
a.insert(1, c2);
expect(a.controls).toEqual([c1, c2, c3]);
});
});
describe('value', () => {
it('should be the reduced value of the child controls', () => {
var a = new ControlArray([new Control(1), new Control(2)]);
expect(a.value).toEqual([1, 2]);
});
it('should be an empty array when there are no child controls', () => {
var a = new ControlArray([]);
expect(a.value).toEqual([]);
});
});
describe('errors', () => {
it('should run the validator when the value changes', () => {
var simpleValidator = (c: any /** TODO #9100 */) =>
c.controls[0].value != 'correct' ? {'broken': true} : null;
var c = new Control(null);
var g = new ControlArray([c], simpleValidator);
c.updateValue('correct');
expect(g.valid).toEqual(true);
expect(g.errors).toEqual(null);
c.updateValue('incorrect');
expect(g.valid).toEqual(false);
expect(g.errors).toEqual({'broken': true});
});
});
describe('dirty', () => {
var c: Control;
var a: ControlArray;
beforeEach(() => {
c = new Control('value');
a = new ControlArray([c]);
});
it('should be false after creating a control', () => { expect(a.dirty).toEqual(false); });
it('should be false after changing the value of the control', () => {
c.markAsDirty();
expect(a.dirty).toEqual(true);
});
});
describe('pending', () => {
var c: Control;
var a: ControlArray;
beforeEach(() => {
c = new Control('value');
a = new ControlArray([c]);
});
it('should be false after creating a control', () => {
expect(c.pending).toEqual(false);
expect(a.pending).toEqual(false);
});
it('should be true after changing the value of the control', () => {
c.markAsPending();
expect(c.pending).toEqual(true);
expect(a.pending).toEqual(true);
});
it('should not update the parent when onlySelf = true', () => {
c.markAsPending({onlySelf: true});
expect(c.pending).toEqual(true);
expect(a.pending).toEqual(false);
});
});
describe('valueChanges', () => {
var a: ControlArray;
var c1: any /** TODO #9100 */, c2: any /** TODO #9100 */;
beforeEach(() => {
c1 = new Control('old1');
c2 = new Control('old2');
a = new ControlArray([c1, c2]);
});
it('should fire an event after the value has been updated',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
a.valueChanges.subscribe({
next: (value: any) => {
expect(a.value).toEqual(['new1', 'old2']);
expect(value).toEqual(['new1', 'old2']);
async.done();
}
});
c1.updateValue('new1');
}));
it('should fire an event after the control\'s observable fired an event',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
var controlCallbackIsCalled = false;
c1.valueChanges.subscribe({next: (value: any) => { controlCallbackIsCalled = true; }});
a.valueChanges.subscribe({
next: (value: any) => {
expect(controlCallbackIsCalled).toBe(true);
async.done();
}
});
c1.updateValue('new1');
}));
it('should fire an event when a control is removed',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
a.valueChanges.subscribe({
next: (value: any) => {
expect(value).toEqual(['old1']);
async.done();
}
});
a.removeAt(1);
}));
it('should fire an event when a control is added',
inject([AsyncTestCompleter], (async: AsyncTestCompleter) => {
a.removeAt(1);
a.valueChanges.subscribe({
next: (value: any) => {
expect(value).toEqual(['old1', 'old2']);
async.done();
}
});
a.push(c2);
}));
});
describe('find', () => {
it('should return null when path is null', () => {
var g = new ControlGroup({});
expect(g.find(null)).toEqual(null);
});
it('should return null when path is empty', () => {
var g = new ControlGroup({});
expect(g.find([])).toEqual(null);
});
it('should return null when path is invalid', () => {
var g = new ControlGroup({});
expect(g.find(['one', 'two'])).toEqual(null);
});
it('should return a child of a control group', () => {
var g = new ControlGroup(
{'one': new Control('111'), 'nested': new ControlGroup({'two': new Control('222')})});
expect(g.find(['nested', 'two']).value).toEqual('222');
expect(g.find(['one']).value).toEqual('111');
expect(g.find('nested/two').value).toEqual('222');
expect(g.find('one').value).toEqual('111');
});
it('should return an element of an array', () => {
var g = new ControlGroup({'array': new ControlArray([new Control('111')])});
expect(g.find(['array', 0]).value).toEqual('111');
});
});
describe('asyncValidator', () => {
it('should run the async validator', fakeAsync(() => {
var c = new Control('value');
var g = new ControlArray([c], null, asyncValidator('expected'));
expect(g.pending).toEqual(true);
tick(1);
expect(g.errors).toEqual({'async': true});
expect(g.pending).toEqual(false);
}));
});
});
});
}

View File

@ -0,0 +1,196 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AbstractControl, Control, ControlArray, ControlGroup, Validators} from '@angular/common/src/forms-deprecated';
import {fakeAsync, flushMicrotasks, tick} from '@angular/core/testing';
import {afterEach, beforeEach, ddescribe, describe, expect, iit, it, xit} from '@angular/core/testing/testing_internal';
import {Observable} from 'rxjs/Observable';
import {EventEmitter} from '../../src/facade/async';
import {normalizeAsyncValidator} from '../../src/forms-deprecated/directives/normalize_validator';
export function main() {
function validator(key: string, error: any) {
return function(c: AbstractControl) {
var r = {};
(r as any)[key] = error;
return r;
};
}
class AsyncValidatorDirective {
constructor(private expected: string, private error: any) {}
validate(c: any): {[key: string]: any;} {
return Observable.create((obs: any) => {
const error = this.expected !== c.value ? this.error : null;
obs.next(error);
obs.complete();
});
}
}
describe('Validators', () => {
describe('required', () => {
it('should error on an empty string',
() => { expect(Validators.required(new Control(''))).toEqual({'required': true}); });
it('should error on null',
() => { expect(Validators.required(new Control(null))).toEqual({'required': true}); });
it('should not error on a non-empty string',
() => { expect(Validators.required(new Control('not empty'))).toEqual(null); });
it('should accept zero as valid',
() => { expect(Validators.required(new Control(0))).toEqual(null); });
});
describe('minLength', () => {
it('should not error on an empty string',
() => { expect(Validators.minLength(2)(new Control(''))).toEqual(null); });
it('should not error on null',
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
it('should not error on valid strings',
() => { expect(Validators.minLength(2)(new Control('aa'))).toEqual(null); });
it('should error on short strings', () => {
expect(Validators.minLength(2)(new Control('a'))).toEqual({
'minlength': {'requiredLength': 2, 'actualLength': 1}
});
});
});
describe('maxLength', () => {
it('should not error on an empty string',
() => { expect(Validators.maxLength(2)(new Control(''))).toEqual(null); });
it('should not error on null',
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
it('should not error on valid strings',
() => { expect(Validators.maxLength(2)(new Control('aa'))).toEqual(null); });
it('should error on long strings', () => {
expect(Validators.maxLength(2)(new Control('aaa'))).toEqual({
'maxlength': {'requiredLength': 2, 'actualLength': 3}
});
});
});
describe('pattern', () => {
it('should not error on an empty string',
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control(''))).toEqual(null); });
it('should not error on null',
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control(null))).toEqual(null); });
it('should not error on valid strings',
() => { expect(Validators.pattern('[a-zA-Z ]*')(new Control('aaAA'))).toEqual(null); });
it('should error on failure to match string', () => {
expect(Validators.pattern('[a-zA-Z ]*')(new Control('aaa0'))).toEqual({
'pattern': {'requiredPattern': '^[a-zA-Z ]*$', 'actualValue': 'aaa0'}
});
});
});
it('should normalize and evaluate async validator-directives correctly', fakeAsync(() => {
const c = Validators.composeAsync(
[normalizeAsyncValidator(new AsyncValidatorDirective('expected', {'one': true}))]);
let value: any = null;
c(new Control()).then((v: any) => value = v);
tick(1);
expect(value).toEqual({'one': true});
}));
describe('compose', () => {
it('should return null when given null',
() => { expect(Validators.compose(null)).toBe(null); });
it('should collect errors from all the validators', () => {
var c = Validators.compose([validator('a', true), validator('b', true)]);
expect(c(new Control(''))).toEqual({'a': true, 'b': true});
});
it('should run validators left to right', () => {
var c = Validators.compose([validator('a', 1), validator('a', 2)]);
expect(c(new Control(''))).toEqual({'a': 2});
});
it('should return null when no errors', () => {
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
expect(c(new Control(''))).toEqual(null);
});
it('should ignore nulls', () => {
var c = Validators.compose([null, Validators.required]);
expect(c(new Control(''))).toEqual({'required': true});
});
});
describe('composeAsync', () => {
function asyncValidator(expected: any /** TODO #9100 */, response: any /** TODO #9100 */) {
return (c: any /** TODO #9100 */) => {
var emitter = new EventEmitter();
var res = c.value != expected ? response : null;
Promise.resolve(null).then(() => {
emitter.emit(res);
// this is required because of a bug in ObservableWrapper
// where callComplete can fire before callEmit
// remove this one the bug is fixed
setTimeout(() => { emitter.complete(); }, 0);
});
return emitter;
};
}
it('should return null when given null',
() => { expect(Validators.composeAsync(null)).toEqual(null); });
it('should collect errors from all the validators', fakeAsync(() => {
var c = Validators.composeAsync([
asyncValidator('expected', {'one': true}), asyncValidator('expected', {'two': true})
]);
var value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new Control('invalid'))).then(v => value = v);
tick(1);
expect(value).toEqual({'one': true, 'two': true});
}));
it('should return null when no errors', fakeAsync(() => {
var c = Validators.composeAsync([asyncValidator('expected', {'one': true})]);
var value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new Control('expected'))).then(v => value = v);
tick(1);
expect(value).toEqual(null);
}));
it('should ignore nulls', fakeAsync(() => {
var c = Validators.composeAsync([asyncValidator('expected', {'one': true}), null]);
var value: any /** TODO #9100 */ = null;
(<Promise<any>>c(new Control('invalid'))).then(v => value = v);
tick(1);
expect(value).toEqual({'one': true});
}));
});
});
}

View File

@ -1,495 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
inject
} from '@angular/core/testing/testing_internal';
import {
fakeAsync,
flushMicrotasks,
Log,
tick,
} from '@angular/core/testing';
import {SpyNgControl, SpyValueAccessor} from '../spies';
import {
ControlGroup,
Control,
NgControlName,
NgControlGroup,
NgFormModel,
ControlValueAccessor,
Validators,
NgForm,
NgModel,
NgFormControl,
NgControl,
DefaultValueAccessor,
CheckboxControlValueAccessor,
SelectControlValueAccessor,
Validator
} from '@angular/common';
import {selectValueAccessor, composeValidators} from '@angular/common/src/forms/directives/shared';
import {TimerWrapper} from '../../src/facade/async';
import {PromiseWrapper} from '../../src/facade/promise';
import {SimpleChange} from '@angular/core/src/change_detection';
class DummyControlValueAccessor implements ControlValueAccessor {
writtenValue;
registerOnChange(fn) {}
registerOnTouched(fn) {}
writeValue(obj: any): void { this.writtenValue = obj; }
}
class CustomValidatorDirective implements Validator {
validate(c: Control): {[key: string]: any} { return {"custom": true}; }
}
function asyncValidator(expected, timeout = 0) {
return (c) => {
var completer = PromiseWrapper.completer();
var res = c.value != expected ? {"async": true} : null;
if (timeout == 0) {
completer.resolve(res);
} else {
TimerWrapper.setTimeout(() => { completer.resolve(res); }, timeout);
}
return completer.promise;
};
}
export function main() {
describe("Form Directives", () => {
var defaultAccessor: DefaultValueAccessor;
beforeEach(() => { defaultAccessor = new DefaultValueAccessor(null, null); });
describe("shared", () => {
describe("selectValueAccessor", () => {
var dir: NgControl;
beforeEach(() => { dir = <any>new SpyNgControl(); });
it("should throw when given an empty array",
() => { expect(() => selectValueAccessor(dir, [])).toThrowError(); });
it("should return the default value accessor when no other provided",
() => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); });
it("should return checkbox accessor when provided", () => {
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
expect(selectValueAccessor(dir, [defaultAccessor, checkboxAccessor]))
.toEqual(checkboxAccessor);
});
it("should return select accessor when provided", () => {
var selectAccessor = new SelectControlValueAccessor(null, null);
expect(selectValueAccessor(dir, [defaultAccessor, selectAccessor]))
.toEqual(selectAccessor);
});
it("should throw when more than one build-in accessor is provided", () => {
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
var selectAccessor = new SelectControlValueAccessor(null, null);
expect(() => selectValueAccessor(dir, [checkboxAccessor, selectAccessor])).toThrowError();
});
it("should return custom accessor when provided", () => {
var customAccessor = new SpyValueAccessor();
var checkboxAccessor = new CheckboxControlValueAccessor(null, null);
expect(selectValueAccessor(dir, <any>[defaultAccessor, customAccessor, checkboxAccessor]))
.toEqual(customAccessor);
});
it("should throw when more than one custom accessor is provided", () => {
var customAccessor: ControlValueAccessor = <any>new SpyValueAccessor();
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
});
});
describe("composeValidators", () => {
it("should compose functions", () => {
var dummy1 = (_) => ({"dummy1": true});
var dummy2 = (_) => ({"dummy2": true});
var v = composeValidators([dummy1, dummy2]);
expect(v(new Control(""))).toEqual({"dummy1": true, "dummy2": true});
});
it("should compose validator directives", () => {
var dummy1 = (_) => ({"dummy1": true});
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
expect(v(new Control(""))).toEqual({"dummy1": true, "custom": true});
});
});
});
describe("NgFormModel", () => {
var form;
var formModel: ControlGroup;
var loginControlDir;
beforeEach(() => {
form = new NgFormModel([], []);
formModel = new ControlGroup({
"login": new Control(),
"passwords":
new ControlGroup({"password": new Control(), "passwordConfirm": new Control()})
});
form.form = formModel;
loginControlDir = new NgControlName(form, [Validators.required],
[asyncValidator("expected")], [defaultAccessor]);
loginControlDir.name = "login";
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});
it("should reexport control properties", () => {
expect(form.control).toBe(formModel);
expect(form.value).toBe(formModel.value);
expect(form.valid).toBe(formModel.valid);
expect(form.errors).toBe(formModel.errors);
expect(form.pristine).toBe(formModel.pristine);
expect(form.dirty).toBe(formModel.dirty);
expect(form.touched).toBe(formModel.touched);
expect(form.untouched).toBe(formModel.untouched);
});
describe("addControl", () => {
it("should throw when no control found", () => {
var dir = new NgControlName(form, null, null, [defaultAccessor]);
dir.name = "invalidName";
expect(() => form.addControl(dir))
.toThrowError(new RegExp("Cannot find control 'invalidName'"));
});
it("should throw when no value accessor", () => {
var dir = new NgControlName(form, null, null, null);
dir.name = "login";
expect(() => form.addControl(dir))
.toThrowError(new RegExp("No value accessor for 'login'"));
});
it("should set up validators", fakeAsync(() => {
form.addControl(loginControlDir);
// sync validators are set
expect(formModel.hasError("required", ["login"])).toBe(true);
expect(formModel.hasError("async", ["login"])).toBe(false);
(<Control>formModel.find(["login"])).updateValue("invalid value");
// sync validator passes, running async validators
expect(formModel.pending).toBe(true);
tick();
expect(formModel.hasError("required", ["login"])).toBe(false);
expect(formModel.hasError("async", ["login"])).toBe(true);
}));
it("should write value to the DOM", () => {
(<Control>formModel.find(["login"])).updateValue("initValue");
form.addControl(loginControlDir);
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("initValue");
});
it("should add the directive to the list of directives included in the form", () => {
form.addControl(loginControlDir);
expect(form.directives).toEqual([loginControlDir]);
});
});
describe("addControlGroup", () => {
var matchingPasswordsValidator = (g) => {
if (g.controls["password"].value != g.controls["passwordConfirm"].value) {
return {"differentPasswords": true};
} else {
return null;
}
};
it("should set up validator", fakeAsync(() => {
var group = new NgControlGroup(form, [matchingPasswordsValidator],
[asyncValidator('expected')]);
group.name = "passwords";
form.addControlGroup(group);
(<Control>formModel.find(["passwords", "password"])).updateValue("somePassword");
(<Control>formModel.find(["passwords", "passwordConfirm"]))
.updateValue("someOtherPassword");
// sync validators are set
expect(formModel.hasError("differentPasswords", ["passwords"])).toEqual(true);
(<Control>formModel.find(["passwords", "passwordConfirm"]))
.updateValue("somePassword");
// sync validators pass, running async validators
expect(formModel.pending).toBe(true);
tick();
expect(formModel.hasError("async", ["passwords"])).toBe(true);
}));
});
describe("removeControl", () => {
it("should remove the directive to the list of directives included in the form", () => {
form.addControl(loginControlDir);
form.removeControl(loginControlDir);
expect(form.directives).toEqual([]);
});
});
describe("ngOnChanges", () => {
it("should update dom values of all the directives", () => {
form.addControl(loginControlDir);
(<Control>formModel.find(["login"])).updateValue("new value");
form.ngOnChanges({});
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("new value");
});
it("should set up a sync validator", () => {
var formValidator = (c) => ({"custom": true});
var f = new NgFormModel([formValidator], []);
f.form = formModel;
f.ngOnChanges({"form": new SimpleChange(null, null)});
expect(formModel.errors).toEqual({"custom": true});
});
it("should set up an async validator", fakeAsync(() => {
var f = new NgFormModel([], [asyncValidator("expected")]);
f.form = formModel;
f.ngOnChanges({"form": new SimpleChange(null, null)});
tick();
expect(formModel.errors).toEqual({"async": true});
}));
});
});
describe("NgForm", () => {
var form;
var formModel: ControlGroup;
var loginControlDir;
var personControlGroupDir;
beforeEach(() => {
form = new NgForm([], []);
formModel = form.form;
personControlGroupDir = new NgControlGroup(form, [], []);
personControlGroupDir.name = "person";
loginControlDir = new NgControlName(personControlGroupDir, null, null, [defaultAccessor]);
loginControlDir.name = "login";
loginControlDir.valueAccessor = new DummyControlValueAccessor();
});
it("should reexport control properties", () => {
expect(form.control).toBe(formModel);
expect(form.value).toBe(formModel.value);
expect(form.valid).toBe(formModel.valid);
expect(form.errors).toBe(formModel.errors);
expect(form.pristine).toBe(formModel.pristine);
expect(form.dirty).toBe(formModel.dirty);
expect(form.touched).toBe(formModel.touched);
expect(form.untouched).toBe(formModel.untouched);
});
describe("addControl & addControlGroup", () => {
it("should create a control with the given name", fakeAsync(() => {
form.addControlGroup(personControlGroupDir);
form.addControl(loginControlDir);
flushMicrotasks();
expect(formModel.find(["person", "login"])).not.toBeNull;
}));
// should update the form's value and validity
});
describe("removeControl & removeControlGroup", () => {
it("should remove control", fakeAsync(() => {
form.addControlGroup(personControlGroupDir);
form.addControl(loginControlDir);
form.removeControlGroup(personControlGroupDir);
form.removeControl(loginControlDir);
flushMicrotasks();
expect(formModel.find(["person"])).toBeNull();
expect(formModel.find(["person", "login"])).toBeNull();
}));
// should update the form's value and validity
});
it("should set up sync validator", fakeAsync(() => {
var formValidator = (c) => ({"custom": true});
var f = new NgForm([formValidator], []);
tick();
expect(f.form.errors).toEqual({"custom": true});
}));
it("should set up async validator", fakeAsync(() => {
var f = new NgForm([], [asyncValidator("expected")]);
tick();
expect(f.form.errors).toEqual({"async": true});
}));
});
describe("NgControlGroup", () => {
var formModel;
var controlGroupDir;
beforeEach(() => {
formModel = new ControlGroup({"login": new Control(null)});
var parent = new NgFormModel([], []);
parent.form = new ControlGroup({"group": formModel});
controlGroupDir = new NgControlGroup(parent, [], []);
controlGroupDir.name = "group";
});
it("should reexport control properties", () => {
expect(controlGroupDir.control).toBe(formModel);
expect(controlGroupDir.value).toBe(formModel.value);
expect(controlGroupDir.valid).toBe(formModel.valid);
expect(controlGroupDir.errors).toBe(formModel.errors);
expect(controlGroupDir.pristine).toBe(formModel.pristine);
expect(controlGroupDir.dirty).toBe(formModel.dirty);
expect(controlGroupDir.touched).toBe(formModel.touched);
expect(controlGroupDir.untouched).toBe(formModel.untouched);
});
});
describe("NgFormControl", () => {
var controlDir;
var control;
var checkProperties = function(control) {
expect(controlDir.control).toBe(control);
expect(controlDir.value).toBe(control.value);
expect(controlDir.valid).toBe(control.valid);
expect(controlDir.errors).toBe(control.errors);
expect(controlDir.pristine).toBe(control.pristine);
expect(controlDir.dirty).toBe(control.dirty);
expect(controlDir.touched).toBe(control.touched);
expect(controlDir.untouched).toBe(control.untouched);
};
beforeEach(() => {
controlDir = new NgFormControl([Validators.required], [], [defaultAccessor]);
controlDir.valueAccessor = new DummyControlValueAccessor();
control = new Control(null);
controlDir.form = control;
});
it("should reexport control properties", () => { checkProperties(control); });
it("should reexport new control properties", () => {
var newControl = new Control(null);
controlDir.form = newControl;
controlDir.ngOnChanges({"form": new SimpleChange(control, newControl)});
checkProperties(newControl);
});
it("should set up validator", () => {
expect(control.valid).toBe(true);
// this will add the required validator and recalculate the validity
controlDir.ngOnChanges({"form": new SimpleChange(null, control)});
expect(control.valid).toBe(false);
});
});
describe("NgModel", () => {
var ngModel;
beforeEach(() => {
ngModel =
new NgModel([Validators.required], [asyncValidator("expected")], [defaultAccessor]);
ngModel.valueAccessor = new DummyControlValueAccessor();
});
it("should reexport control properties", () => {
var control = ngModel.control;
expect(ngModel.control).toBe(control);
expect(ngModel.value).toBe(control.value);
expect(ngModel.valid).toBe(control.valid);
expect(ngModel.errors).toBe(control.errors);
expect(ngModel.pristine).toBe(control.pristine);
expect(ngModel.dirty).toBe(control.dirty);
expect(ngModel.touched).toBe(control.touched);
expect(ngModel.untouched).toBe(control.untouched);
});
it("should set up validator", fakeAsync(() => {
// this will add the required validator and recalculate the validity
ngModel.ngOnChanges({});
tick();
expect(ngModel.control.errors).toEqual({"required": true});
ngModel.control.updateValue("someValue");
tick();
expect(ngModel.control.errors).toEqual({"async": true});
}));
});
describe("NgControlName", () => {
var formModel;
var controlNameDir;
beforeEach(() => {
formModel = new Control("name");
var parent = new NgFormModel([], []);
parent.form = new ControlGroup({"name": formModel});
controlNameDir = new NgControlName(parent, [], [], [defaultAccessor]);
controlNameDir.name = "name";
});
it("should reexport control properties", () => {
expect(controlNameDir.control).toBe(formModel);
expect(controlNameDir.value).toBe(formModel.value);
expect(controlNameDir.valid).toBe(formModel.valid);
expect(controlNameDir.errors).toBe(formModel.errors);
expect(controlNameDir.pristine).toBe(formModel.pristine);
expect(controlNameDir.dirty).toBe(formModel.dirty);
expect(controlNameDir.touched).toBe(formModel.touched);
expect(controlNameDir.untouched).toBe(formModel.untouched);
});
});
});
}

View File

@ -1,71 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from '@angular/core/testing/testing_internal';
import {Control, FormBuilder} from '@angular/common';
import {PromiseWrapper} from '../../src/facade/promise';
export function main() {
function syncValidator(_) { return null; }
function asyncValidator(_) { return PromiseWrapper.resolve(null); }
describe("Form Builder", () => {
var b;
beforeEach(() => { b = new FormBuilder(); });
it("should create controls from a value", () => {
var g = b.group({"login": "some value"});
expect(g.controls["login"].value).toEqual("some value");
});
it("should create controls from an array", () => {
var g = b.group(
{"login": ["some value"], "password": ["some value", syncValidator, asyncValidator]});
expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["password"].value).toEqual("some value");
expect(g.controls["password"].validator).toEqual(syncValidator);
expect(g.controls["password"].asyncValidator).toEqual(asyncValidator);
});
it("should use controls", () => {
var g = b.group({"login": b.control("some value", syncValidator, asyncValidator)});
expect(g.controls["login"].value).toEqual("some value");
expect(g.controls["login"].validator).toBe(syncValidator);
expect(g.controls["login"].asyncValidator).toBe(asyncValidator);
});
it("should create groups with optional controls", () => {
var g = b.group({"login": "some value"}, {"optionals": {"login": false}});
expect(g.contains("login")).toEqual(false);
});
it("should create groups with a custom validator", () => {
var g = b.group({"login": "some value"},
{"validator": syncValidator, "asyncValidator": asyncValidator});
expect(g.validator).toBe(syncValidator);
expect(g.asyncValidator).toBe(asyncValidator);
});
it("should create control arrays", () => {
var c = b.control("three");
var a = b.array(["one", ["two", syncValidator], c, b.array(['four'])], syncValidator,
asyncValidator);
expect(a.value).toEqual(['one', 'two', 'three', ['four']]);
expect(a.validator).toBe(syncValidator);
expect(a.asyncValidator).toBe(asyncValidator);
});
});
}

File diff suppressed because it is too large Load Diff

View File

@ -1,804 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach,
inject,
} from '@angular/core/testing/testing_internal';
import {fakeAsync, flushMicrotasks, Log, tick} from '@angular/core/testing';
import {AsyncTestCompleter} from '@angular/core/testing/testing_internal';
import {ControlGroup, Control, ControlArray, Validators} from '@angular/common';
import {IS_DART, isPresent} from '../../src/facade/lang';
import {PromiseWrapper} from '../../src/facade/promise';
import {TimerWrapper, ObservableWrapper, EventEmitter} from '../../src/facade/async';
export function main() {
function asyncValidator(expected, timeouts = /*@ts2dart_const*/ {}) {
return (c) => {
var completer = PromiseWrapper.completer();
var t = isPresent(timeouts[c.value]) ? timeouts[c.value] : 0;
var res = c.value != expected ? {"async": true} : null;
if (t == 0) {
completer.resolve(res);
} else {
TimerWrapper.setTimeout(() => { completer.resolve(res); }, t);
}
return completer.promise;
};
}
function asyncValidatorReturningObservable(c) {
var e = new EventEmitter();
PromiseWrapper.scheduleMicrotask(() => ObservableWrapper.callEmit(e, {"async": true}));
return e;
}
describe("Form Model", () => {
describe("Control", () => {
it("should default the value to null", () => {
var c = new Control();
expect(c.value).toBe(null);
});
describe("validator", () => {
it("should run validator with the initial value", () => {
var c = new Control("value", Validators.required);
expect(c.valid).toEqual(true);
});
it("should rerun the validator when the value changes", () => {
var c = new Control("value", Validators.required);
c.updateValue(null);
expect(c.valid).toEqual(false);
});
it("should return errors", () => {
var c = new Control(null, Validators.required);
expect(c.errors).toEqual({"required": true});
});
});
describe("asyncValidator", () => {
it("should run validator with the initial value", fakeAsync(() => {
var c = new Control("value", null, asyncValidator("expected"));
tick();
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({"async": true});
}));
it("should support validators returning observables", fakeAsync(() => {
var c = new Control("value", null, asyncValidatorReturningObservable);
tick();
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({"async": true});
}));
it("should rerun the validator when the value changes", fakeAsync(() => {
var c = new Control("value", null, asyncValidator("expected"));
c.updateValue("expected");
tick();
expect(c.valid).toEqual(true);
}));
it("should run the async validator only when the sync validator passes", fakeAsync(() => {
var c = new Control("", Validators.required, asyncValidator("expected"));
tick();
expect(c.errors).toEqual({"required": true});
c.updateValue("some value");
tick();
expect(c.errors).toEqual({"async": true});
}));
it("should mark the control as pending while running the async validation",
fakeAsync(() => {
var c = new Control("", null, asyncValidator("expected"));
expect(c.pending).toEqual(true);
tick();
expect(c.pending).toEqual(false);
}));
it("should only use the latest async validation run", fakeAsync(() => {
var c =
new Control("", null, asyncValidator("expected", {"long": 200, "expected": 100}));
c.updateValue("long");
c.updateValue("expected");
tick(300);
expect(c.valid).toEqual(true);
}));
});
describe("dirty", () => {
it("should be false after creating a control", () => {
var c = new Control("value");
expect(c.dirty).toEqual(false);
});
it("should be true after changing the value of the control", () => {
var c = new Control("value");
c.markAsDirty();
expect(c.dirty).toEqual(true);
});
});
describe("updateValue", () => {
var g, c;
beforeEach(() => {
c = new Control("oldValue");
g = new ControlGroup({"one": c});
});
it("should update the value of the control", () => {
c.updateValue("newValue");
expect(c.value).toEqual("newValue");
});
it("should invoke ngOnChanges if it is present", () => {
var ngOnChanges;
c.registerOnChange((v) => ngOnChanges = ["invoked", v]);
c.updateValue("newValue");
expect(ngOnChanges).toEqual(["invoked", "newValue"]);
});
it("should not invoke on change when explicitly specified", () => {
var onChange = null;
c.registerOnChange((v) => onChange = ["invoked", v]);
c.updateValue("newValue", {emitModelToViewChange: false});
expect(onChange).toBeNull();
});
it("should update the parent", () => {
c.updateValue("newValue");
expect(g.value).toEqual({"one": "newValue"});
});
it("should not update the parent when explicitly specified", () => {
c.updateValue("newValue", {onlySelf: true});
expect(g.value).toEqual({"one": "oldValue"});
});
it("should fire an event", fakeAsync(() => {
ObservableWrapper.subscribe(c.valueChanges,
(value) => { expect(value).toEqual("newValue"); });
c.updateValue("newValue");
tick();
}));
it("should not fire an event when explicitly specified", fakeAsync(() => {
ObservableWrapper.subscribe(c.valueChanges, (value) => { throw "Should not happen"; });
c.updateValue("newValue", {emitEvent: false});
tick();
}));
});
describe("valueChanges & statusChanges", () => {
var c;
beforeEach(() => { c = new Control("old", Validators.required); });
it("should fire an event after the value has been updated",
inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(c.valueChanges, (value) => {
expect(c.value).toEqual('new');
expect(value).toEqual('new');
async.done();
});
c.updateValue("new");
}));
it("should fire an event after the status has been updated to invalid", fakeAsync(() => {
ObservableWrapper.subscribe(c.statusChanges, (status) => {
expect(c.status).toEqual('INVALID');
expect(status).toEqual('INVALID');
});
c.updateValue("");
tick();
}));
it("should fire an event after the status has been updated to pending", fakeAsync(() => {
var c = new Control("old", Validators.required, asyncValidator("expected"));
var log = [];
ObservableWrapper.subscribe(c.valueChanges, (value) => log.push(`value: '${value}'`));
ObservableWrapper.subscribe(c.statusChanges,
(status) => log.push(`status: '${status}'`));
c.updateValue("");
tick();
c.updateValue("nonEmpty");
tick();
c.updateValue("expected");
tick();
expect(log).toEqual([
"" + "value: ''",
"status: 'INVALID'",
"value: 'nonEmpty'",
"status: 'PENDING'",
"status: 'INVALID'",
"value: 'expected'",
"status: 'PENDING'",
"status: 'VALID'",
]);
}));
// TODO: remove the if statement after making observable delivery sync
if (!IS_DART) {
it("should update set errors and status before emitting an event",
inject([AsyncTestCompleter], (async) => {
c.valueChanges.subscribe(value => {
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({"required": true});
async.done();
});
c.updateValue("");
}));
}
it("should return a cold observable", inject([AsyncTestCompleter], (async) => {
c.updateValue("will be ignored");
ObservableWrapper.subscribe(c.valueChanges, (value) => {
expect(value).toEqual('new');
async.done();
});
c.updateValue("new");
}));
});
describe("setErrors", () => {
it("should set errors on a control", () => {
var c = new Control("someValue");
c.setErrors({"someError": true});
expect(c.valid).toEqual(false);
expect(c.errors).toEqual({"someError": true});
});
it("should reset the errors and validity when the value changes", () => {
var c = new Control("someValue", Validators.required);
c.setErrors({"someError": true});
c.updateValue("");
expect(c.errors).toEqual({"required": true});
});
it("should update the parent group's validity", () => {
var c = new Control("someValue");
var g = new ControlGroup({"one": c});
expect(g.valid).toEqual(true);
c.setErrors({"someError": true});
expect(g.valid).toEqual(false);
});
it("should not reset parent's errors", () => {
var c = new Control("someValue");
var g = new ControlGroup({"one": c});
g.setErrors({"someGroupError": true});
c.setErrors({"someError": true});
expect(g.errors).toEqual({"someGroupError": true});
});
it("should reset errors when updating a value", () => {
var c = new Control("oldValue");
var g = new ControlGroup({"one": c});
g.setErrors({"someGroupError": true});
c.setErrors({"someError": true});
c.updateValue("newValue");
expect(c.errors).toEqual(null);
expect(g.errors).toEqual(null);
});
});
});
describe("ControlGroup", () => {
describe("value", () => {
it("should be the reduced value of the child controls", () => {
var g = new ControlGroup({"one": new Control("111"), "two": new Control("222")});
expect(g.value).toEqual({"one": "111", "two": "222"});
});
it("should be empty when there are no child controls", () => {
var g = new ControlGroup({});
expect(g.value).toEqual({});
});
it("should support nested groups", () => {
var g = new ControlGroup(
{"one": new Control("111"), "nested": new ControlGroup({"two": new Control("222")})});
expect(g.value).toEqual({"one": "111", "nested": {"two": "222"}});
(<Control>(g.controls["nested"].find("two"))).updateValue("333");
expect(g.value).toEqual({"one": "111", "nested": {"two": "333"}});
});
});
describe("errors", () => {
it("should run the validator when the value changes", () => {
var simpleValidator = (c) =>
c.controls["one"].value != "correct" ? {"broken": true} : null;
var c = new Control(null);
var g = new ControlGroup({"one": c}, null, simpleValidator);
c.updateValue("correct");
expect(g.valid).toEqual(true);
expect(g.errors).toEqual(null);
c.updateValue("incorrect");
expect(g.valid).toEqual(false);
expect(g.errors).toEqual({"broken": true});
});
});
describe("dirty", () => {
var c, g;
beforeEach(() => {
c = new Control('value');
g = new ControlGroup({"one": c});
});
it("should be false after creating a control", () => { expect(g.dirty).toEqual(false); });
it("should be false after changing the value of the control", () => {
c.markAsDirty();
expect(g.dirty).toEqual(true);
});
});
describe("optional components", () => {
describe("contains", () => {
var group;
beforeEach(() => {
group = new ControlGroup(
{
"required": new Control("requiredValue"),
"optional": new Control("optionalValue")
},
{"optional": false});
});
// rename contains into has
it("should return false when the component is not included",
() => { expect(group.contains("optional")).toEqual(false); })
it("should return false when there is no component with the given name",
() => { expect(group.contains("something else")).toEqual(false); });
it("should return true when the component is included", () => {
expect(group.contains("required")).toEqual(true);
group.include("optional");
expect(group.contains("optional")).toEqual(true);
});
});
it("should not include an inactive component into the group value", () => {
var group = new ControlGroup(
{"required": new Control("requiredValue"), "optional": new Control("optionalValue")},
{"optional": false});
expect(group.value).toEqual({"required": "requiredValue"});
group.include("optional");
expect(group.value).toEqual({"required": "requiredValue", "optional": "optionalValue"});
});
it("should not run Validators on an inactive component", () => {
var group = new ControlGroup(
{
"required": new Control("requiredValue", Validators.required),
"optional": new Control("", Validators.required)
},
{"optional": false});
expect(group.valid).toEqual(true);
group.include("optional");
expect(group.valid).toEqual(false);
});
});
describe("valueChanges", () => {
var g, c1, c2;
beforeEach(() => {
c1 = new Control("old1");
c2 = new Control("old2");
g = new ControlGroup({"one": c1, "two": c2}, {"two": true});
});
it("should fire an event after the value has been updated",
inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(g.valueChanges, (value) => {
expect(g.value).toEqual({'one': 'new1', 'two': 'old2'});
expect(value).toEqual({'one': 'new1', 'two': 'old2'});
async.done();
});
c1.updateValue("new1");
}));
it("should fire an event after the control's observable fired an event",
inject([AsyncTestCompleter], (async) => {
var controlCallbackIsCalled = false;
ObservableWrapper.subscribe(c1.valueChanges,
(value) => { controlCallbackIsCalled = true; });
ObservableWrapper.subscribe(g.valueChanges, (value) => {
expect(controlCallbackIsCalled).toBe(true);
async.done();
});
c1.updateValue("new1");
}));
it("should fire an event when a control is excluded",
inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(g.valueChanges, (value) => {
expect(value).toEqual({'one': 'old1'});
async.done();
});
g.exclude("two");
}));
it("should fire an event when a control is included",
inject([AsyncTestCompleter], (async) => {
g.exclude("two");
ObservableWrapper.subscribe(g.valueChanges, (value) => {
expect(value).toEqual({'one': 'old1', 'two': 'old2'});
async.done();
});
g.include("two");
}));
it("should fire an event every time a control is updated",
inject([AsyncTestCompleter], (async) => {
var loggedValues = [];
ObservableWrapper.subscribe(g.valueChanges, (value) => {
loggedValues.push(value);
if (loggedValues.length == 2) {
expect(loggedValues)
.toEqual([{"one": "new1", "two": "old2"}, {"one": "new1", "two": "new2"}]);
async.done();
}
});
c1.updateValue("new1");
c2.updateValue("new2");
}));
xit("should not fire an event when an excluded control is updated",
inject([AsyncTestCompleter], (async) => {
// hard to test without hacking zones
}));
});
describe("getError", () => {
it("should return the error when it is present", () => {
var c = new Control("", Validators.required);
var g = new ControlGroup({"one": c});
expect(c.getError("required")).toEqual(true);
expect(g.getError("required", ["one"])).toEqual(true);
});
it("should return null otherwise", () => {
var c = new Control("not empty", Validators.required);
var g = new ControlGroup({"one": c});
expect(c.getError("invalid")).toEqual(null);
expect(g.getError("required", ["one"])).toEqual(null);
expect(g.getError("required", ["invalid"])).toEqual(null);
});
});
describe("asyncValidator", () => {
it("should run the async validator", fakeAsync(() => {
var c = new Control("value");
var g = new ControlGroup({"one": c}, null, null, asyncValidator("expected"));
expect(g.pending).toEqual(true);
tick(1);
expect(g.errors).toEqual({"async": true});
expect(g.pending).toEqual(false);
}));
it("should set the parent group's status to pending", fakeAsync(() => {
var c = new Control("value", null, asyncValidator("expected"));
var g = new ControlGroup({"one": c});
expect(g.pending).toEqual(true);
tick(1);
expect(g.pending).toEqual(false);
}));
it("should run the parent group's async validator when children are pending",
fakeAsync(() => {
var c = new Control("value", null, asyncValidator("expected"));
var g = new ControlGroup({"one": c}, null, null, asyncValidator("expected"));
tick(1);
expect(g.errors).toEqual({"async": true});
expect(g.find(["one"]).errors).toEqual({"async": true});
}));
})
});
describe("ControlArray", () => {
describe("adding/removing", () => {
var a: ControlArray;
var c1, c2, c3;
beforeEach(() => {
a = new ControlArray([]);
c1 = new Control(1);
c2 = new Control(2);
c3 = new Control(3);
});
it("should support pushing", () => {
a.push(c1);
expect(a.length).toEqual(1);
expect(a.controls).toEqual([c1]);
});
it("should support removing", () => {
a.push(c1);
a.push(c2);
a.push(c3);
a.removeAt(1);
expect(a.controls).toEqual([c1, c3]);
});
it("should support inserting", () => {
a.push(c1);
a.push(c3);
a.insert(1, c2);
expect(a.controls).toEqual([c1, c2, c3]);
});
});
describe("value", () => {
it("should be the reduced value of the child controls", () => {
var a = new ControlArray([new Control(1), new Control(2)]);
expect(a.value).toEqual([1, 2]);
});
it("should be an empty array when there are no child controls", () => {
var a = new ControlArray([]);
expect(a.value).toEqual([]);
});
});
describe("errors", () => {
it("should run the validator when the value changes", () => {
var simpleValidator = (c) => c.controls[0].value != "correct" ? {"broken": true} : null;
var c = new Control(null);
var g = new ControlArray([c], simpleValidator);
c.updateValue("correct");
expect(g.valid).toEqual(true);
expect(g.errors).toEqual(null);
c.updateValue("incorrect");
expect(g.valid).toEqual(false);
expect(g.errors).toEqual({"broken": true});
});
});
describe("dirty", () => {
var c: Control;
var a: ControlArray;
beforeEach(() => {
c = new Control('value');
a = new ControlArray([c]);
});
it("should be false after creating a control", () => { expect(a.dirty).toEqual(false); });
it("should be false after changing the value of the control", () => {
c.markAsDirty();
expect(a.dirty).toEqual(true);
});
});
describe("pending", () => {
var c: Control;
var a: ControlArray;
beforeEach(() => {
c = new Control('value');
a = new ControlArray([c]);
});
it("should be false after creating a control", () => {
expect(c.pending).toEqual(false);
expect(a.pending).toEqual(false);
});
it("should be true after changing the value of the control", () => {
c.markAsPending();
expect(c.pending).toEqual(true);
expect(a.pending).toEqual(true);
});
it("should not update the parent when onlySelf = true", () => {
c.markAsPending({onlySelf: true});
expect(c.pending).toEqual(true);
expect(a.pending).toEqual(false);
});
});
describe("valueChanges", () => {
var a: ControlArray;
var c1, c2;
beforeEach(() => {
c1 = new Control("old1");
c2 = new Control("old2");
a = new ControlArray([c1, c2]);
});
it("should fire an event after the value has been updated",
inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(a.valueChanges, (value) => {
expect(a.value).toEqual(['new1', 'old2']);
expect(value).toEqual(['new1', 'old2']);
async.done();
});
c1.updateValue("new1");
}));
it("should fire an event after the control's observable fired an event",
inject([AsyncTestCompleter], (async) => {
var controlCallbackIsCalled = false;
ObservableWrapper.subscribe(c1.valueChanges,
(value) => { controlCallbackIsCalled = true; });
ObservableWrapper.subscribe(a.valueChanges, (value) => {
expect(controlCallbackIsCalled).toBe(true);
async.done();
});
c1.updateValue("new1");
}));
it("should fire an event when a control is removed",
inject([AsyncTestCompleter], (async) => {
ObservableWrapper.subscribe(a.valueChanges, (value) => {
expect(value).toEqual(['old1']);
async.done();
});
a.removeAt(1);
}));
it("should fire an event when a control is added", inject([AsyncTestCompleter], (async) => {
a.removeAt(1);
ObservableWrapper.subscribe(a.valueChanges, (value) => {
expect(value).toEqual(['old1', 'old2']);
async.done();
});
a.push(c2);
}));
});
describe("find", () => {
it("should return null when path is null", () => {
var g = new ControlGroup({});
expect(g.find(null)).toEqual(null);
});
it("should return null when path is empty", () => {
var g = new ControlGroup({});
expect(g.find([])).toEqual(null);
});
it("should return null when path is invalid", () => {
var g = new ControlGroup({});
expect(g.find(["one", "two"])).toEqual(null);
});
it("should return a child of a control group", () => {
var g = new ControlGroup(
{"one": new Control("111"), "nested": new ControlGroup({"two": new Control("222")})});
expect(g.find(["nested", "two"]).value).toEqual("222");
expect(g.find(["one"]).value).toEqual("111");
expect(g.find("nested/two").value).toEqual("222");
expect(g.find("one").value).toEqual("111");
});
it("should return an element of an array", () => {
var g = new ControlGroup({"array": new ControlArray([new Control("111")])});
expect(g.find(["array", 0]).value).toEqual("111");
});
});
describe("asyncValidator", () => {
it("should run the async validator", fakeAsync(() => {
var c = new Control("value");
var g = new ControlArray([c], null, asyncValidator("expected"));
expect(g.pending).toEqual(true);
tick(1);
expect(g.errors).toEqual({"async": true});
expect(g.pending).toEqual(false);
}));
})
});
});
}

View File

@ -1,170 +0,0 @@
import {
ddescribe,
describe,
it,
iit,
xit,
expect,
beforeEach,
afterEach
} from '@angular/core/testing/testing_internal';
import {fakeAsync, flushMicrotasks, Log, tick} from '@angular/core/testing';
import {ControlGroup, Control, Validators, AbstractControl, ControlArray} from '@angular/common';
import {PromiseWrapper} from '../../src/facade/promise';
import {EventEmitter, ObservableWrapper, TimerWrapper} from '../../src/facade/async';
export function main() {
function validator(key: string, error: any) {
return function(c: AbstractControl) {
var r = {};
r[key] = error;
return r;
}
}
describe("Validators", () => {
describe("required", () => {
it("should error on an empty string",
() => { expect(Validators.required(new Control(""))).toEqual({"required": true}); });
it("should error on null",
() => { expect(Validators.required(new Control(null))).toEqual({"required": true}); });
it("should not error on a non-empty string",
() => { expect(Validators.required(new Control("not empty"))).toEqual(null); });
it("should accept zero as valid",
() => { expect(Validators.required(new Control(0))).toEqual(null); });
});
describe("minLength", () => {
it("should not error on an empty string",
() => { expect(Validators.minLength(2)(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.minLength(2)(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.minLength(2)(new Control("aa"))).toEqual(null); });
it("should error on short strings", () => {
expect(Validators.minLength(2)(new Control("a")))
.toEqual({"minlength": {"requiredLength": 2, "actualLength": 1}});
});
});
describe("maxLength", () => {
it("should not error on an empty string",
() => { expect(Validators.maxLength(2)(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.maxLength(2)(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.maxLength(2)(new Control("aa"))).toEqual(null); });
it("should error on long strings", () => {
expect(Validators.maxLength(2)(new Control("aaa")))
.toEqual({"maxlength": {"requiredLength": 2, "actualLength": 3}});
});
});
describe("pattern", () => {
it("should not error on an empty string",
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(""))).toEqual(null); });
it("should not error on null",
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control(null))).toEqual(null); });
it("should not error on valid strings",
() => { expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaAA"))).toEqual(null); });
it("should error on failure to match string", () => {
expect(Validators.pattern("[a-zA-Z ]*")(new Control("aaa0")))
.toEqual({"pattern": {"requiredPattern": "^[a-zA-Z ]*$", "actualValue": "aaa0"}});
});
});
describe("compose", () => {
it("should return null when given null",
() => { expect(Validators.compose(null)).toBe(null); });
it("should collect errors from all the validators", () => {
var c = Validators.compose([validator("a", true), validator("b", true)]);
expect(c(new Control(""))).toEqual({"a": true, "b": true});
});
it("should run validators left to right", () => {
var c = Validators.compose([validator("a", 1), validator("a", 2)]);
expect(c(new Control(""))).toEqual({"a": 2});
});
it("should return null when no errors", () => {
var c = Validators.compose([Validators.nullValidator, Validators.nullValidator]);
expect(c(new Control(""))).toEqual(null);
});
it("should ignore nulls", () => {
var c = Validators.compose([null, Validators.required]);
expect(c(new Control(""))).toEqual({"required": true});
});
});
describe("composeAsync", () => {
function asyncValidator(expected, response) {
return (c) => {
var emitter = new EventEmitter();
var res = c.value != expected ? response : null;
PromiseWrapper.scheduleMicrotask(() => {
ObservableWrapper.callEmit(emitter, res);
// this is required because of a bug in ObservableWrapper
// where callComplete can fire before callEmit
// remove this one the bug is fixed
TimerWrapper.setTimeout(() => { ObservableWrapper.callComplete(emitter); }, 0);
});
return emitter;
};
}
it("should return null when given null",
() => { expect(Validators.composeAsync(null)).toEqual(null); });
it("should collect errors from all the validators", fakeAsync(() => {
var c = Validators.composeAsync([
asyncValidator("expected", {"one": true}),
asyncValidator("expected", {"two": true})
]);
var value = null;
(<Promise<any>>c(new Control("invalid"))).then(v => value = v);
tick(1);
expect(value).toEqual({"one": true, "two": true});
}));
it("should return null when no errors", fakeAsync(() => {
var c = Validators.composeAsync([asyncValidator("expected", {"one": true})]);
var value = null;
(<Promise<any>>c(new Control("expected"))).then(v => value = v);
tick(1);
expect(value).toEqual(null);
}));
it("should ignore nulls", fakeAsync(() => {
var c = Validators.composeAsync([asyncValidator("expected", {"one": true}), null]);
var value = null;
(<Promise<any>>c(new Control("invalid"))).then(v => value = v);
tick(1);
expect(value).toEqual({"one": true});
}));
});
});
}

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