Commit graph

202 commits

Author SHA1 Message Date
Andrew Scott
658cf8c384 fix(core): ComponentFixture stability should match ApplicationRef (#54949)
This change aligns the stability of `ComponentFixture` with that of
`ApplicationRef`, preventing confusing differences between the two as
more APIs start using the `PendingTasks` that may not be tracked by
`NgZone`.

BREAKING CHANGE: `ComponentFixture.whenStable` now matches the
`ApplicationRef.isStable` observable. Prior to this change, stability
of the fixture did not include everything that was considered in
`ApplicationRef`. `whenStable` of the fixture will now include unfinished
router navigations and unfinished `HttpClient` requests. This will cause
tests that `await` the `whenStable` promise to time out when there are
incomplete requests. To fix this, remove the `whenStable`,
instead wait for another condition, or ensure `HttpTestingController`
mocks responses for all requests. Try adding `HttpTestingController.verify()`
before your `await fixture.whenStable` to identify the open requests.
Also, make sure your tests wait for the stability promise. We found many
examples of tests that did not, meaning the expectations did not execute
within the test body.

In addition, `ComponentFixture.isStable` would synchronously switch to
true in some scenarios but will now always be asynchronous.

PR Close #54949
2024-03-27 11:01:28 -07:00
Andrea Canciani
f3b624553a refactor: fix a number of typos throughout the codebase (#55018)
Fix some typos detected using spellchecking tools, both in
documentation and in code (comments, identifiers).

PR Close #55018
2024-03-27 10:54:31 -07:00
Kristiyan Kostadinov
c07805612f test(core): clean up unnecessary nesting in old tests (#52239)
A lot of our tests are wrapped in `{}` which serves no purpose, aside from increasing the nesting level and, in some cases, causing confusion. The braces appear to be a leftover from a time when all tests were wrapped in a `function main() {}`. The function declaration was removed in #21053, but the braces remained, presumably because it was easier to search&replace for `function main()`, but not to remove the braces at the same time.

PR Close #52239
2023-10-19 09:26:15 -07:00
Matthieu Riegler
43115da986 refactor(forms): Log a warning when FormGroup keys include a dot. (#50642)
Due to the dotted synthax to resolve controls, keys in FormGroups cannot include dots.

fixes #50608

PR Close #50642
2023-10-18 12:11:25 -07:00
Matthieu Riegler
ddd7212ee2 fix(forms): reset() call with null values on nested group (#48830)
Non typed forms allow to pass null to nested groups when calling `formGroup.reset()`, this commit prevent an undefined access.

fixes #20509

PR Close #48830
2023-10-10 15:34:31 -07:00
Matthieu Riegler
e9b9fd4579 refactor(forms): Use the Writable type when overwriting readonly properties. (#49754)
The `Writable` type is usefull when we want overwrite readonly properties and we still want to maintain code navigation/reference. It should be use instead of `any` type assertions for example.

PR Close #49754
2023-09-22 10:02:13 -07:00
Kristiyan Kostadinov
52cc7f839b build: align with internal tsconfig options (#51728)
Currently internally Angular has some customized tsconfig files, because we don't align with the tsconfig of the rest of g3. These changes enable `noImplicitReturns` and `noPropertyAccessFromIndexSignature` to align better with the internal config.

PR Close #51728
2023-09-12 11:39:42 -07:00
Matthieu Riegler
a871e23857 docs: remove duplicate words. (#51215)
Using the `\b(\w+)\s+\1\b` we can find duplicate word. Let's remove them.

PR Close #51215
2023-08-01 12:08:33 -07:00
Alan Agius
4550fe42f7 refactor: use queueMicrotask to schedule micro tasks instead of various helpers (#50485)
`queueMicrotask` is an API which is supported by all browser and Node.js versions.

PR Close #50485
2023-06-15 16:38:21 +02:00
Dylan Hunn
136ffbca8e refactor(forms): Add a test that uses ControlConfig with a non-static validator. (#49693)
Previously, this PR cleaned up a bug introduced by #48679. However, since that PR needed to be rolled back, this PR now just checks in the test, to prevent that issue from re-occurring in the future.

PR Close #49693
2023-04-05 09:08:39 -07:00
Dylan Hunn
edc3bb180f Revert "feat(forms): Improve typings form (async)Validators" (#49706)
This reverts commit da189dec8f.

PR Close #49706
2023-04-04 11:26:04 -07:00
Matthieu Riegler
07a1aa3004 feat(forms): Improve typings form (async)Validators (#48679)
With this commit, AsyncValidatorFn cannot be passed as ValidatorFn  anymore in FormControl.

fixes: #48676

PR Close #48679
2023-03-30 11:47:29 -07:00
Dylan Hunn
59685614f8 fix(forms): Make radio buttons respect [attr.disabled] (#48864)
`setDisabledState` is supposed to be called whenever the disabled state of a control changes, including upon control creation. However, a longstanding bug caused the method to not fire when an *enabled* control was attached. This bug was fixed in v15.

This had a side effect: previously, it was possible to instantiate a reactive form control with `[attr.disabled]=true`, even though the the corresponding control was enabled in the model. (Note that the similar-looking property binding version `[disabled]=true` was always rejected, though.) This resulted in a mismatch between the model and the DOM. Now, because `setDisabledState` is always called, the value in the DOM will be immediately overwritten with the "correct" enabled value.

Users should instead disable the control directly in their model. (There are many ways to do this, such as using the `{value: 'foo', disabled: true}` constructor format, or immediately calling `FooControl.disable()` in `ngOnInit`.)

If this incompatibility is too breaking, you may also opt out using `FormsModule.withConfig` or `ReactiveFormsModule.withConfig` at the time you import it, via the `callSetDisabledState` option.

However, there is an exceptional case: radio buttons. Because Reactive Forms models the entire group of radio buttons as a single `FormControl`, there is no way to control the disabled state for individual radios, so they can no longer be configured as disabled.

In this PR, we have special cased radio buttons to ignore their first call to `setDisabledState` when in `callSetDisabledState: 'always'` mode. This preserves the old behavior.

PR Close #48864
2023-02-10 11:25:11 +01:00
Matthieu Riegler
33b9cb8693 refactor: remove todos on forms tests (#48894)
Removing the todos implied using fakeAsync and passing an async validator as async (3rd parameter).

PR Close #48894
2023-02-02 09:28:39 -08:00
Matthieu Riegler
6edf35c8d6 refactor(forms): remove deprecated uses from the unit tests (#48894)
Jasmine has deprecated the `expectationFailOutput` argument and replaced it by the `withContext()` method

Also removing all references to #24571 from the forms unit tests as the non null assertions are fine in the context.

PR Close #48894
2023-02-02 09:28:39 -08:00
Matthieu Riegler
b9577adeb2 refactor(forms): cleanup type any in forms tests (#48624)
Removing every type any in forms with a reference to #9100

PR Close #48624
2023-01-05 14:52:03 -08:00
Paul Gschwendtner
a8f92554d0 refactor: update forms package tests to work with ES2020 ESM (#48521)
Tests now always run with ESM 2020, while previously they ran with
ES2015 CommonJS UMD bundles.

Since ZoneJS does not support intercepting native `async/await` syntax,
the forms test needs to use the zone-compatible variant of
`jasmine_node_tests`. This variant downlevels the native `async/await`
syntax to generators that ZoneJS can intercept. All of this is done
using the dev-infra ESBuild `spec_bundle` rule.

PR Close #48521
2022-12-19 19:50:43 +00:00
Paul Gschwendtner
c9415e4d75 build: ensure bootstrap transitive runfiles are made available (#48521)
Since we generate a `.mjs` file as entry-point for jasmine tests,
a couple of issues prevented the transitive dependencies from
bootstrap targets to be brought in (causing resolution errors):

1. The `_files` (previously `_esm2015`) targets are no longer needed,
   and they also miss all the information on runfiles.
2. The aspect for computing linker mappings does not respect the
   `bootstrap` attribute from the `spec_entrypoint` so we manually
   add the extract ESM output targets (this rule works with the aspect
   and forwards linker mappings).

PR Close #48521
2022-12-19 19:50:41 +00:00
Paul Gschwendtner
20551503fa build: replace _es2015 shorthand with more flexible _files suffix (#48521)
For every `ts_library` target we expose a shorthand that grants
access to the JS files because `DefaultInfo` of a ts library
only exposes the `.d.ts` files.

We rename this away from `es2015` since in practice it's a much
higher target these days. Additionally we no longer use the devmode
output but rather use the prodmode output which has the explicit
`.mjs` output- compatible with ESM.

PR Close #48521
2022-12-19 19:50:41 +00:00
Matthieu Riegler
d321880440 fix(forms): FormBuilder.group return right type with shorthand parameters. (#48084)
Extract AbstractControlOptions from type when used in shorthand parameters on FormBuilder.group

Fixes 48073

PR Close #48084
2022-11-17 11:04:54 -08:00
Kristiyan Kostadinov
779a76fa5a fix(forms): don't mutate validators array (#47830)
Fixes that the `AbstractControl` was mutating the validators arrays being passed into the constructor an helper methods like `setValidators`.

Fixes #47827.

PR Close #47830
2022-11-17 09:36:14 -08:00
Dylan Hunn
604cdb7307 fix(forms): Improve a very commonly viewed error message by adding a guide. (#47969)
[A Github issue](https://github.com/angular/angular/issues/43821) about an arcane-sounding Forms error is one of the repo's top-ten most visited pages. This converts the error to `RuntimeErrorCode` and adds a dedicated guide to explain how to solve it.

PR Close #47969
2022-11-07 16:00:06 -08:00
Pawel Kozlowski
9bfedb1306 Revert "fix(forms): don't mutate validators array (#47830)" (#47845)
This reverts commit 0329c13e95.

PR Close #47845
2022-10-25 10:05:17 +02:00
Kristiyan Kostadinov
0329c13e95 fix(forms): don't mutate validators array (#47830)
Fixes that the `AbstractControl` was mutating the validators arrays being passed into the constructor an helper methods like `setValidators`.

Fixes #47827.

PR Close #47830
2022-10-24 14:12:56 +02:00
Dylan Hunn
96b7fe93af fix(forms): call setDisabledState on ControlValueAcessor when control is enabled (#47576)
Previously, `setDisabledState` was never called when attached if the control is enabled. This PR fixes the bug, and creates a configuration option to opt-out of the fix.

Fixes #35309.

BREAKING CHANGE: setDisabledState will always be called when a `ControlValueAccessor` is attached. You can opt-out with `FormsModule.withConfig` or `ReactiveFormsModule.withConfig`.

PR Close #47576
2022-10-11 16:03:01 +00:00
Kristiyan Kostadinov
4a13210ecd fix(forms): don't prevent default behavior for forms with method="dialog" (#47308)
The forms `submit` event handlers have a `return false` to prevent form submissions from reloading the page, however this also prevents the browser behavior for forms with `method="dialog"`.

These changes add an exception since the `method="dialog"` doesn't refresh the page.

Fixes #47150.

PR Close #47308
2022-09-09 14:26:48 -07:00
Jeremy Elbourn
aa14662562 refactor(forms): remove unnecesary null (#47238)
These null values are unused and unecessary. I suspect it's a remnant from when the codebase was transpiled to Dart.

PR Close #47238
2022-09-06 09:57:37 -07:00
Dylan Hunn
b302797de4 fix(forms): Correctly infer FormBuilder types involving [value, validators] shorthand in more cases. (#47034)
Type inference in cases involving `ControlConfig` was previously not working as desired. This was because the compiler was enforcing that `ControlConfig` is a *tuple* -- which is not always that easy to prove! By relaxing this constraint a bit, and just inferring from `ControlConfig` as an array, the type inference catches many more cases, and is generally more correct.

PR Close #47034
2022-08-17 11:32:15 +00:00
Cédric Exbrayat
426af91a42 feat(forms): add FormBuilder.record() method (#46485)
The new `FormRecord` entity introduced in Angular v14 does not have its builder method.
This commit adds it, allowing to write:

```
const fb = new FormBuilder();
fb.record({ a: 'one' });
```

This works for both the `FormBuilder` and the `NonNullableFormBuilder`

PR Close #46485
2022-07-15 22:02:44 +00:00
Cédric Exbrayat
089efa1ac9 refactor(forms): simplify group builder function (#46844)
Applies the same logic that we have in the `control` function.

PR Close #46844
2022-07-15 22:02:20 +00:00
Dylan Hunn
e9b5dac9ec fix(forms): Move all remaining errors in Forms to use RuntimeErrorCode. (#46654)
RuntimeErrorCode allows for better tree-shaking, and unique codes for each error.

PR Close #46654
2022-07-06 09:49:39 -07:00
Dylan Hunn
f6a096e3d4 fix(forms): Update a Forms validator error to use RuntimeError (#46537)
Replace `new Error()` in a forms Validators function with `RuntimeError`, for better tree-shakability. Also, improve the error messages, and add documentation.

PR Close #46537
2022-06-28 11:32:54 -07:00
Dylan Hunn
f18e1739b8 fix(forms): allow FormBuilder.group(...) to accept optional fields. (#46253)
Consider the case in which `FormBuilder` is used to construct a group with an optional field:

```
const controls = { name: fb.control('') };
const foo: FormGroup<{
  name: FormControl<string | null>;
  address?: FormControl<string | null>;
}> = fb.group<{
  name: FormControl<string | null>;
  address?: FormControl<string | null>;
}>(controls);
```

Today, with fully strict TypeScript settings, the above will not compile:

```
Types of property 'controls' are incompatible.
Type '{ name: FormControl<string | null>; address?: FormControl<FormGroup<SubFormControls> | null | undefined> | undefined; }' is not assignable to type '{ name: FormControl<string | null>; address?: FormGroup<SubFormControls> | undefined; }'.
```

Notice that the `fb.group(...)` is calculating the following type for address: `address?: FormControl<FormGroup<string|null>`. This is clearly wrong -- an extraneous `FormControl` has been added!

This is coming from the calculation of the result type of `fb.group(...)`. In the type definition, if we cannot detect the outer control type, [we assume it's just an unwrapped value, and automatically wrap it in `FormControl`](https://github.com/angular/angular/blob/14.0.0/packages/forms/src/form_builder.ts#L66).

Because the optional `{address?: FormControl<string|null>}` implicitly makes the RHS have type `FormControl<string|null>|undefined`, [the relevant condition is not satisfied](https://github.com/angular/angular/blob/14.0.0/packages/forms/src/form_builder.ts#L55). In particular, the condition expects just `FormGroup<T>`, not `FormGroup<T>|undefined`. So we assume `T` is a value type, and it gets wrapped with `FormControl`.

The solution is to add the cases where `undefined` is included in the union type when detecting which control `T` is (if any).

PR Close #46253
2022-06-06 10:14:19 -07:00
Dylan Hunn
37bf6932e9 fix(forms): Add a nonNullable option to FormControl for consistency.
DEPRECATED:

The `initialValueIsDefault` option has been deprecated and replaced with the otherwise-identical `nonNullable` option, for the sake of naming consistency.
2022-05-19 15:49:02 -07:00
Dylan Hunn
e441ff44b4 fix(forms): Prevent FormBuilder from distributing unions to control types. (#45942)
Previously, using `FormBuilder` with a union type would produce unions of *controls*:

```
// `foo` has type `FormControl<string>|FormControl<number>`.
const c = fb.nonNullable.group({foo: 'bar' as string | number});
```

This actually works in many cases, due to how extraordinarily powerful Typescript's distributive types are (e.g. `value` still has type `string|number`), but it is subtly incorrect. Here is a code example that exposes the reason the inference is incorrect. It exploits the fact that Typescript will not "un-distribute" a type, producing an obviously spurious error:

```
// fc gets an inferred distributive union type `FormControl<string> | FormControl<number>`
let fc = c.controls.foo;
// Error: Type 'FormControl<string | number>' is not assignable to type 'FormControl<string> | FormControl<number>'.
fc = new FormControl<string|number>('', {initialValueIsDefault: true});
```

Instead, we want the union to apply to the *values*:

```
// `foo` should have type `FormControl<string|number>`.
const c = fb.nonNullable.group({foo: 'bar' as string | number});
```

Essentially, we want to prevent Typescript from distributing the type. [As specified in the handbook](https://www.typescriptlang.org/docs/handbook/2/conditional-types.html#distributive-conditional-types):

> Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the extends keyword with square brackets.

This PR applies this suggestion to `FormBuilder`'s type inference.

Fixes #45912.

PR Close #45942
2022-05-10 12:36:37 -07:00
Dylan Hunn
43ba4ab9da fix(forms): Allow NonNullableFormBuilder to be injected. (#45904)
Based on early feedback, calling `fb.nonNullable.group(...)` continues to be clunky for a form with many such groups. Allowing `NonNullableFormBuilder` to be directly injected enables the following:

```
constructor(private fb: NonNullableFormBuilder) {}
```

PR Close #45904
2022-05-09 17:31:48 -07:00
Dylan Hunn
2dbdebc646 feat(forms): Add FormBuilder.nonNullable. (#45852)
With typed forms, all `FormControl`s are nullable by default, because they can be reset to `null`. This behavior is possible to change by passing the option `initialValueIsDefault: true`. However, in a large form, this is extremely cumbersome, as the option must be repeated over and over. Additionally, it is not possible to take full advantage of `FormBuilder`, since `FormBuilder.group` and `FormBuilder.array` will produce nullable controls.

This PR introduces a new accessor `FormBuilder.nonNullable`, which produces *non-nullable* controls. Specifically, any call to `.control` will produce controls with `{initialValueIsDefault: true}`, and calls to `.array` or `.group` that implicitly build inner controls will have the same effect.

```ts
let nfb = new FormBuilder().nonNullable;
let name = nfb.group({who: 'Alex'}); // FormGroup<{who: FormControl<string>}>
name.reset();
console.log(name); // {who: 'Alex'}
```

PR Close #45852
2022-05-04 12:46:05 -07:00
Dylan Hunn
ff3f5a8d12 fix(forms): Fix a typing bug in FormBuilder. (#45684)
Previously, the following code would fail to compile:

```
let form: FormGroup<{email: FormControl<string | null>}>;
form = fb.group({
    email: ['', Validators.required]
});
```

This is because the compiler was unable to properly infer the inner type of `ControlConfig` arrays in some cases. The same issue applies to `FormArray` as well under certain circumstances.

This change cleans up the `FormBuilder` type signatures to always use the explicit Element type, and to catch `ControlConfig` types that might fall through.

PR Close #45684
2022-04-20 09:15:46 -07:00
Dylan Hunn
e0a2248b32 feat(forms): Add a FormRecord type. (#45607)
As part of the typed forms RFC, we proposed the creation of a new FormRecord type, to support dynamic groups with homogenous values. This PR introduces FormRecord, as a subclass of FormGroup.

PR Close #45607
2022-04-14 14:59:10 -07:00
Dylan Hunn
89d299105a feat(forms): Implement strict types for the Angular Forms package. (#43834)
This PR strongly types the forms package by adding generics to AbstractControl classes as well as FormBuilder. This makes forms type-safe and null-safe, for both controls and values.

The design uses a "control-types" approach. In other words, the type parameter on FormGroup is an object containing controls, and the type parameter on FormArray is an array of controls.

Special thanks to Alex Rickabaugh and Andrew Kushnir for co-design & implementation, to Sonu Kapoor and Netanel Basal for illustrative prior art, and to Cédric Exbrayat for extensive testing and validation.

BREAKING CHANGE: Forms classes accept a generic.

Forms model classes now accept a generic type parameter. Untyped versions of these classes are available to opt-out of the new, stricter behavior.

PR Close #43834
2022-04-12 17:37:04 +00:00
Kristiyan Kostadinov
b36dec6b5b fix(forms): not picking up disabled state if group is swapped out and disabled (#43499)
Fixes a long-standing issue where swapping out the `FormGroup` and calling `disable` immediately afterwards doesn't actually disable the `ControlValueAccessor`.

Fixes #22556.

PR Close #43499
2022-03-28 09:26:19 -07:00
Dylan Hunn
fe0e42a996 fix(forms): Make UntypedFormBuilder assignable to FormBuilder, and vice versa. (#45421)
There was a subtle bug involving the opt-out class for FormBuilder, which I discovered during the ongoing migration. The types must be structurally the same, because people pass around FormBuilders, in addition to passing around the controls they produce. This PR ensures FormBuilder and UntypedFormBuilder are assignable to each other.

PR Close #45421
2022-03-24 10:49:10 -07:00
Amer Yousuf
9db9091349 fix(forms): improve error message for invalid value accessors (#45192)
improve error message for invalid value accessors when accessor is not provided as array

PR Close #45192
2022-03-15 13:26:03 -07:00
JiaLiPassion
225e4f2dbe feat(core): triggerEventHandler accept optional eventObj (#45279)
Close #44724

`DebugNode.triggerEventHandler()` should accept the `eventObj` as an
optional parameter. So the user don't have to write code like

```
elem.triggerEventHandler('click', null);
```

PR Close #45279
2022-03-09 13:51:54 -08:00
Kristiyan Kostadinov
ebf2fc5224 fix(forms): incorrectly keeping track of ngModel with ngFor inside a form (#40459)
When an `NgModel` is created within a `form`, it receives an `NgControl` based on its `name`, but
the control doesn't get swapped out if the name changes. This can lead to problems if the `NgModel`
is part of an `ngFor`, because the name can change based on its position in the list and a new
control can be defined with the same name, leading us to having multiple directives pointing to
the same control. For example, if we start off with a list like :

```
[0, 1, 2]; -> [NgModel(0), NgModel(1), NgModel(2)]
```

Then we remove the second item:

```
[0, 2]; -> [NgModel(0), NgModel(2)]
```

And finally, if we decide to add an item to the end of the list, we'll already have a control for
index 2, causing the list to look like:

```
[0, 2, 3]; -> [NgModel(0), NgModel(2), NgModel(2)]
```

These changes fix the issue by removing the old control when the `name` of the directive changes.

Fixes #38465.
Fixes #37920.

PR Close #40459
2022-02-07 13:16:10 -08:00
Sergej Grilborzer
8dd3f82f94 fix(forms): Correct empty validator to handle objects with a property length: 0. (#33729)
Form required validator should not reject objects that contain a length attribute set to zero.

Fixes #30718.

Co-authored-by: Dylan Hunn <dylhunn@gmail.com>

BREAKING CHANGE: objects with a length key set to zero will no longer validate as empty.

This is technically a breaking change, since objects with a key `length` and value `0` will no longer validate as empty. This is a very minor change, and any reliance on this behavior is probably a bug anyway.

PR Close #33729
2022-02-03 23:15:27 -08:00
Dylan Hunn
f0cfa00a34 refactor(forms): Move FormControl to an overridden exported constructor. (#44316) (#44806)
This implementation change was originally proposed as part of Typed Forms, and will have major consequences for that project as described in the design doc. Submitting it separately will greatly simplify the risk of landing Typed Forms. This change should have no visible impact on normal users of FormControl.

See the Typed Forms design doc here: https://docs.google.com/document/d/1cWuBE-oo5WLtwkLFxbNTiaVQGNk8ipgbekZcKBeyxxo.

PR Close #44316

PR Close #44806
2022-01-31 22:48:23 +00:00
arturovt
889de8276d fix(forms): ensure OnPush ancestors are marked dirty when the promise resolves (#44886)
Currently, `ngModel` calls` setValue` after the `resolvedPromise` is resolved.
The promise is resolved _after_ the child template executes. The change detection
is run but `OnPush` views are not updated because they are not marked as dirty.

PR Close #44886
2022-01-31 21:38:39 +00:00
Dylan Hunn
f490c2de4e feat(forms): support negative indices in FormArray methods. (#44848)
This new feature allows negative indices to wrap around from the back, just like ES2021 `Array.at`. In particular, the following methods accept negative indices, and behave like corresponding Array methods:
* `FormArray.at(index)`: behaves the same as `Array.at(index)`
* `FormArray.insert(index, control)`: behaves the same as `Array.splice(index, 0, control)`
* `FormArray.setControl(index, control)`: behaves the same as `Array.splice(index, 1, control)`
* `FormArray.removeAt(index, control)`: behaves the same as `Array.splice(index, 1)`

Previous work in #44746 and #44631 (by @amitbeck).

Issue #44642.

Co-authored-by: Amit Beckenstein <amitbeck@gmail.com>

PR Close #44848
2022-01-28 16:58:29 +00:00
iRealNirmal
531d1cf9a3 refactor(forms): update required validator and checkbox validator to inherit abstractValidator (#44162)
Modified required validator and checkbox validator to inherit abstractValidator.

For every validato type different PR will be raised as discussed in #42378.

Closes #42267

PR Close #44162
2022-01-24 14:50:57 -08:00