Giter Club home page Giter Club logo

forms-manager's Introduction


The Foundation for Proper Form Management in Angular

Build Status commitizen PRs coc-badge semantic-release styled with prettier All Contributors

🔮 Features

✅ Allows Typed Forms!
✅ Auto persists the form's state upon user navigation.
✅ Provides an API to reactively querying any form, from anywhere.
✅ Persist the form's state to local storage.
✅ Built-in dirty functionality.


NgFormsManager lets you sync Angular’s FormGroup, FormControl, and FormArray, via a unique store created for that purpose. The store will hold the controls' data like values, validity, pristine status, errors, etc.

This is powerful, as it gives you the following abilities:

  1. It will automatically save the current control value and update the form value according to the value in the store when the user navigates back to the form.
  2. It provides an API so you can query a form’s values and properties from anywhere. This can be useful for things like multi-step forms, cross-component validation and more.
  3. It can persist the form's state to local storage.

Buy Me A Coffee

The goal in creating this was to work with the existing Angular form ecosystem, and save you the trouble of learning a new API. Let’s see how it works:

First, install the library:

Installation

npm i @ngneat/forms-manager

Then, create a component with a form:

import { NgFormsManager } from '@ngneat/forms-manager';

@Component({
  template: `
    <form [formGroup]="onboardingForm">
      <input formControlName="name" />
      <input formControlName="age" />
      <input formControlName="city" />
    </form>
  `,
})
export class OnboardingComponent {
  onboardingForm: FormGroup;

  constructor(private formsManager: NgFormsManager, private builder: FormBuilder) {}

  ngOnInit() {
    this.onboardingForm = this.builder.group({
      name: [null, Validators.required],
      age: [null, Validators.required],
      city: [null, Validators.required],
    });

    this.formsManager.upsert('onboarding', this.onboardingForm);
  }

  ngOnDestroy() {
    this.formsManager.unsubscribe('onboarding');
  }
}

As you can see, we’re still working with the existing API in order to create a form in Angular. We’re injecting the NgFormsManager and calling the upsert method, giving it the form name and an AbstractForm. From that point on, NgFormsManager will track the form value changes, and update the store accordingly.

With this setup, you’ll have an extensive API to query the store and update the form from anywhere in your application:

API

  • valueChanges() - Observe the control's value
const value$ = formsManager.valueChanges('onboarding');
const nameValue$ = formsManager.valueChanges<string>('onboarding', 'name');
  • validityChanges() - Whether the control is valid
const valid$ = formsManager.validityChanges('onboarding');
const nameValid$ = formsManager.validityChanges('onboarding', 'name');
  • dirtyChanges() - Whether the control is dirty
const dirty$ = formsManager.dirtyChanges('onboarding');
const nameDirty$ = formsManager.dirtyChanges('onboarding', 'name');
  • disableChanges() - Whether the control is disabled
const disabled$ = formsManager.disableChanges('onboarding');
const nameDisabled$ = formsManager.disableChanges('onboarding', 'name');
  • errorsChanges() - Observe the control's errors
const errors$ = formsManager.errorsChanges<Errors>('onboarding');
const nameErrors$ = formsManager.errorsChanges<Errors>('onboarding', 'name');
  • controlChanges() - Observe the control's state
const control$ = formsManager.controlChanges('onboarding');
const nameControl$ = formsManager.controlChanges<string>('onboarding', 'name');
  • getControl() - Get the control's state
const control = formsManager.getControl('onboarding');
const nameControl = formsManager.getControl<string>('onboarding', 'name');

controlChanges and getControl will return the following state:

{
   value: any,
   rawValue: object,
   errors: object,
   valid: boolean,
   dirty: boolean,
   invalid: boolean,
   disabled: boolean,
   touched: boolean,
   pristine: boolean,
   pending: boolean,
   untouched: boolean,
}
  • hasControl() - Whether the control exists
const hasControl = formsManager.hasControl('onboarding');
  • patchValue() - A proxy to the original patchValue method
formsManager.patchValue('onboarding', value, options);
  • setValue() - A proxy to the original setValue method
formsManager.setValue('onboarding', value, options);
  • reset() - A proxy to the original reset method
formsManager.reset('onboarding', value, options);
  • markAllAsTouched() - A proxy to the original markAllAsTouched method
formsManager.markAllAsTouched('onboarding', options);
  • markAsTouched() - A proxy to the original markAsTouched method
formsManager.markAsTouched('onboarding', options);
  • markAllAsDirty() - Marks the control and all its descendant controls as dirty
formsManager.markAllAsDirty('onboarding', options);
  • markAsDirty() - A proxy to the original markAsDirty method
formsManager.markAsDirty('onboarding', options);
  • markAsPending() - A proxy to the original markAsPending method
formsManager.markAsPending('onboarding', options);
  • markAsPristine() - A proxy to the original markAsPristine method
formsManager.markAsPristine('onboarding', options);
  • markAsUntouched() - A proxy to the original markAsUntouched method
formsManager.markAsUntouched('onboarding', options);
  • unsubscribe() - Unsubscribe from the form's valueChanges observable (always call it on ngOnDestroy)
formsManager.unsubscribe('onboarding');
formsManager.unsubscribe();
  • clear() - Delete the form from the store
formsManager.clear('onboarding');
formsManager.clear();
  • destroy() - Destroy the form (Internally calls clear and unsubscribe)
formsManager.destroy('onboarding');
formsManager.destroy();
  • controlDestroyed() - Emits when the control is destroyed
formsManager.controlChanges('login').pipe(takeUntil(controlDestroyed('login')));

Persist to browser storage (localStorage, sessionStorage or custom storage solution)

In the upsert method, pass the persistState flag:

formsManager.upsert(formName, abstractContorl, {
  persistState: true,
});

By default, the state is persisted to localStorage (Link).

For storage to sessionStorage (Link), add FORMS_MANAGER_SESSION_STORAGE_PROVIDER to the providers array in app.module.ts:

import { FORMS_MANAGER_SESSION_STORAGE_PROVIDER } from '@ngneat/forms-manager';

@NgModule({
  declarations: [AppComponent],
  imports: [ ... ],
  providers: [
    ...
    FORMS_MANAGER_SESSION_STORAGE_PROVIDER,
    ...
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Furthermore, a custom storage provider, which must implement the Storage interface (Link) can be provided through the FORMS_MANAGER_STORAGE token:

import { FORMS_MANAGER_STORAGE } from '@ngneat/forms-manager';

class MyStorage implements Storage {
  public clear() { ... }
  public key(index: number): string | null { ... }
  public getItem(key: string): string | null { ... }
  public removeItem(key: string) { ... }
  public setItem(key: string, value: string) { ... }
}

@NgModule({
  declarations: [AppComponent],
  imports: [ ... ],
  providers: [
    ...
    {
      provide: FORMS_MANAGER_STORAGE,
      useValue: MyStorage,
    },
    ...
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Validators

The library exposes two helpers method for adding cross component validation:

export function setValidators(
  control: AbstractControl,
  validator: ValidatorFn | ValidatorFn[] | null
);

export function setAsyncValidators(
  control: AbstractControl,
  validator: AsyncValidatorFn | AsyncValidatorFn[] | null
);

Here's an example of how we can use it:

export class HomeComponent{
  ngOnInit() {
    this.form = new FormGroup({
      price: new FormControl(null, Validators.min(10))
    });

    /*
    * Observe the `minPrice` value in the `settings` form
    * and update the price `control` validators
    */
    this.formsManager.valueChanges<number>('settings', 'minPrice')
     .subscribe(minPrice => setValidators(this.form.get('price'), Validators.min(minPrice));
  }
}

Using FormArray Controls

When working with a FormArray, it's required to pass a factory function that defines how to create the controls inside the FormArray. For example:

import { NgFormsManager } from '@ngneat/forms-manager';

export class HomeComponent {
  skills: FormArray;
  config: FormGroup;

  constructor(private formsManager: NgFormsManager<FormsState>) {}

  ngOnInit() {
    this.skills = new FormArray([]);

    /** Or inside a FormGroup */
    this.config = new FormGroup({
      skills: new FormArray([]),
    });

    this.formsManager
      .upsert('skills', this.skills, { arrControlFactory: value => new FormControl(value) })
      .upsert('config', this.config, {
        arrControlFactory: { skills: value => new FormControl(value) },
      });
  }

  ngOnDestroy() {
    this.formsManager.unsubscribe();
  }
}

NgFormsManager Generic Type

NgFormsManager can take a generic type where you can define the forms shape. For example:

interface AppForms {
  onboarding: {
    name: string;
    age: number;
    city: string;
  };
}

This will make sure that the queries are typed, and you don't make any mistakes in the form name.

export class OnboardingComponent {
  constructor(private formsManager: NgFormsManager<AppForms>, private builder: FormBuilder) {}

  ngOnInit() {
    this.formsManager.valueChanges('onboarding').subscribe(value => {
      // value now typed as AppForms['onboarding']
    });
  }
}

Note that you can split the types across files using a definition file:

// login-form.d.ts
interface AppForms {
  login: {
    email: string;
    password: string
  }
}

// onboarding.d.ts
interface AppForms {
  onboarding: {
    ...
  }
}

Using the Dirty Functionality

The library provides built-in support for the common "Is the form dirty?" question. Dirty means that the current control's value is different from the initial value. It can be useful when we need to toggle the visibility of a "save" button or displaying a dialog when the user leaves the page.

To start using it, you should set the withInitialValue option:

@Component({
  template: `
    <button *ngIf="isDirty$ | async">Save</button>
  `,
})
export class SettingsComponent {
  isDirty$ = this.formsManager.initialValueChanged(name);

  constructor(private formsManager: NgFormsManager<AppForms>) {}

  ngOnInit() {
    this.formsManager.upsert(name, control, {
      withInitialValue: true,
    });
  }
}

setInitialValue(name, value) - Set the initial form's value

formsManager.setInitialValue('form', initialValue);

getInitialValue(name) - Get the initial value or undefined if not exist.

formsManager.getInitialValue('form');

NgFormsManager Config

You can override the default config by passing the NG_FORMS_MANAGER_CONFIG provider:

import { NG_FORMS_MANAGER_CONFIG, NgFormsManagerConfig } from '@ngneat/forms-manager';

@NgModule({
  declarations: [AppComponent],
  imports: [ReactiveFormsModule],
  providers: [
    {
      provide: NG_FORMS_MANAGER_CONFIG,
      useValue: new NgFormsManagerConfig({
        debounceTime: 1000, // defaults to 300
        storage: {
          key: 'NgFormManager',
        },
      }),
    },
  ],
  bootstrap: [AppComponent],
})
export class AppModule {}

Contributors ✨

Thanks goes to these wonderful people (emoji key):


Netanel Basal

💻 📖 🤔

Colum Ferry

💻 📖

Mehmet Erim

📖

David Speirs

💻 📖

Emmanuel De Saint Steban

💻 📖

Adrian Riepl

💻 📖

This project follows the all-contributors specification. Contributions of any kind welcome!

forms-manager's People

Contributors

adrianriepl avatar alexcotelin avatar coly010 avatar dspeirs7 avatar hennelh avatar manudss avatar mehmet-erim avatar netanelbasal avatar pgrimaud avatar thesteinn avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

forms-manager's Issues

Add dirty functionality

I'm thinking about adding a new observable which notifies whether the form is dirty. Dirty means that the current value is different from the initial value.

This observable can be used to toggle the visibility of a save button:

isDirty$ = this.manager.selectIsDirty(formName);
<button *ngIf="isDirty$ | async">Save</button>

Or show a dialog when the user leaves the page.

We need to think whether it makes sense that it'll work in conjunction with the persist form feature.

Improve Typings

Currently, when we use the path parameter in the selectValue, and selectControl methods, we need to pass a generic that indicates what should be the type of the value property:

selectValue<string>('login', 'name');

This isn't the end of the world, but we can employ the technique used in this question to infer it automatically. So the expected usage should be:

selectValue('login', 'name');
selectValue('group', ['some', 'nested', 'control']);

Request for enhance setup and basic usage documentation

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[x] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I am currently using the last version of Akita Store.
I tried to install and use this package as stated on the README file but i got the following error.

No provider for NgFormManager.

So i added the NgFormManager as a provider in my AppModule. The error disappeared but nothing else happened.

What steps am I missing ?

I saw the package lock is saying the package was made with Angular 9.
Can it still work for Angular 8 application ?

Expected behavior

The package work as stated in the README file

Minimal reproduction of the problem with instructions

Install and use the code from the Installation section of the README file.

What is the motivation / use case for changing the behavior?

Can have the advantage of forms in Akita Store.

Environment


Angular version: 8 (core: 8.2.14)

Browser:
- [x] Chrome (desktop) version 85
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 10.16.3
- Platform:  macOS 10.14.6

Others:
None

FormGroup upsert takes value of previous FormGroup

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[x] Support request
[ ] Other... Please describe:

Current behavior

When upsert is used to replace firstForm FormGroup on 'example' key with a new secondForm FormGroup on 'example' key the store does not take the values from the new secondForm control it instead copies over the values from the old firstForm control into the secondForm control replacing the it's values.


firstForm = new FormGroup({
    id: new FormControl("123")
});

secondForm = new FormGroup({
    id: new FormControl("456")
});

this.manager.upsert("example", this.firstForm);
// this.manager.getControl("example").value = {"id":"123"}

this.manager.upsert("example", this.secondForm);
// this.manager.getControl("example").value = {"id":"123"} <--- old value

Relevant section of the codebase

/** If the control already exist, patch the control with the store value */

Work around at the moment is to perform a clear on the 'example' key, removing the old form group so that when the new one is passed in it wont have an existing FormGroup at the 'example' key to copy data from.


this.manager.upsert("example", this.firstForm);
// this.manager.getControl("example").value = {"id":"123"}

this.manager.clear("example");
this.manager.upsert("example", this.secondForm);
// this.manager.getControl("example").value = {"id":"456"} <--- new value

Expected behavior

The secondForm values replace the store values.

Minimal reproduction of the problem with instructions

https://stackblitz.com/edit/github-mt9mwh?file=src/app/example.component.ts

What is the motivation / use case for changing the behavior?

It feels counterintuitive that an upsert method would change the values on the FormGroup being passed in to match the store. I'm aware this may in fact be me miss using this feature so any clarification would be appreciated.

Environment

https://stackblitz.com/edit/github-mt9mwh?file=src/app/example.component.ts
Angular version: 9.1.13

Browser:

  • Chrome (desktop) version XX
  • Chrome (Android) version XX
  • Chrome (iOS) version XX
  • Firefox version XX
  • Safari (desktop) version XX
  • Safari (iOS) version XX
  • IE version XX
  • Edge version XX

Support for mark* methods

Would it make sense to add support the mark*() methods for a form or a control?

The methods could be called like formsManager.patchValue():

formsManager.markAllAsTouched('unboarding')

formsManager.markAsDirty('unboarding')

formsManager.markAsPending('unboarding')

formsManager.markAsPristine('unboarding')

formsManager.markAsTouched('unboarding')

formsManager.markAsUntouched('unboarding')

Get values of persisted form in another component?

Hey!

thanks for this great library!

Is is possible to retrieve the saved values of a form in a different component? Basically I'm working on wizard and I need to calculate some values based on prior responses (1 wizard page with a single form = 1 component).

Thanks in advance.

Regards

ml

Mat Date Picker value doesn't persist to local storage

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Using a mat date picker the value is an empty object instead of the date selected.

Expected behavior

The correct date to be stored instead of an empty object.

Minimal reproduction of the problem with instructions

Add a mat date picker and persist to local storage.

What is the motivation / use case for changing the behavior?

To have the date persist on refresh.

Environment


Angular version: 9.1.9


Browser:
- [X] Chrome (desktop) version 81.0.4044.138
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 12.16.3
- Platform:  Windows

Add getInitialValue() function to get the initial form value already stored in forms-manager

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

Cannot get the initial value of form. This was in private.

Expected behavior

We would like to be able to obtain the initial value of the form. This value has been saved by the plug-in for the isDirty function.
It is not possible to retrieve this value, as it is in private property. There is a method called setInitialValue(). It would be useful to have a method: getInitialValue().

/**
*

  • Get the initial value for a control
  • @example
  • manager.getInitialValue('login');

*/
getInitialValue(name: keyof FormsState): any {
return this.initialValues$$.get(name);
}

What is the motivation / use case for changing the behavior?

In our case, we would need to retrieve the initial value of the form, in order to make calculations afterwards to calculate the differences and track all the changes that have been made. And, like, we would use this plug-in that would store the initial state. It would be a shame to have to store this state somewhere else, when it's already stored in form-manager, and we could just retrieve it here.

Is it ok for you to make a pull request to add this feature to the plug-in?

FormArray with FormGroups inside

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

this.formsManager
      .upsert('skills', this.skills, { arrControlFactory: value => new FormGroup(value) })
);
ERROR TypeError: control.setParent is not a function
    at forms.js:4112
    at forms.js:4106
    at Array.forEach (<anonymous>)
    at FormGroup._forEachChild (forms.js:4101)
    at FormGroup._setUpControls (forms.js:4111)
    at new FormGroup (forms.js:3833)
    at arrControlFactory (ubo-form.component.ts:23)
    at ngneat-forms-manager.js:393
    at Array.forEach (<anonymous>)
    at handleFormArray (ngneat-forms-manager.js:388)

Expected behavior

That I can track an array of FormGroup's.

What is the motivation / use case for changing the behavior?

Have the ability to add an array of skills, where a skill is a name + proficiency.

Support for a custom storage provider

This module supported a custom storage provider when it was part of Akita. Now, it appears only to support localStorage. Limitations of localStorage are well known and it would be nice if the old functionality was brought back so that the state could be saved to things like IndexedDB or similar. This is specially vital for things like hybrid apps where webview storage may be purged by the OS and the only way to ensure persistence of the data is to save it to SQLite or similar. Support for a custom storage provider or at least IndexedDB would be nice. Thanks

Ivy support

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ x] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

show Ivy warning when build with an ivy project
"Encourage the library authors to publish an Ivy distribution"

Expected behavior

support Ivy

Minimal reproduction of the problem with instructions

use forms-manager in a new angular13+ project

Slow to update state?

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ x] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

I have a project with akita and the forms-manager. I feel like I'm using it fairly simply, but there is a noticeable roughly 1 second lag between patching a value and seeing the change manifest down through the .valueChanges observable. Attached code and behavior video below. No clue what I could be doing to see such an issue in performance.

Environment


Angular version: 9.1.2


Browser:
- [ x] Chrome (desktop) version 86
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX


image

####Nested component:
image
image
Screen Recording 2020-10-20 at 9.04.58 PM.mov.zip

Forms with update on blur, value not available immediately using getControl().value

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request
[ ] Other... Please describe:

Current behavior

When using updateOn:'blur' on a form field, if I type something in the field and click submit button directly without tabbing out the input or clicking outside of it, the value using this.manager.getControl('stepOne','textarea').value is not available strait away, it needs a timeout.

Expected behavior

Should have the value available immediately.

Minimal reproduction of the problem with instructions

Please see the stackblitz example check the console logs when pressing submit. stackblitz edit example

What is the motivation / use case for changing the behavior?

Environment


Angular version: X.Y.Z


Browser:
- [ ] Chrome (desktop) version XX
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: XX  
- Platform:  

Others:

Initial values not being cleared using blank clear or destroy methods

I'm submitting a...

  • Regression (a behavior that used to work and stopped working in a new release)
  • Bug report
  • Performance issue
  • Feature request
  • Documentation issue or request
  • Support request
  • Other... Please describe:

Current behavior

When calling the methods formsManager.clear() or formsManager.destroy() without parameters the existing initial values are not being cleared.

Expected behavior

Calling formsManager.clear() or formsManager.destroy() should also clear the Map of the initial values (initialValues$$).

What is the motivation / use case for changing the behavior?

So you don't have to call removeInitialValue(name) (is actually private) for each set initial value.

valueChanges in unit tests

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[ ] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[X ] Support request
[ ] Other... Please describe:

Current behavior

Unable to make a valueChanges reaction to work in a unit test

Expected behavior

A subscription to valueChanges should work in unit tests too (but maybe I'm missing something).

Minimal reproduction of the problem with instructions

The only other non-default dependency is Specator. Simply run npm test to see it failing.

// ### app.component.ts
interface AppForms {
  myForm: {
    theInput: string;
  };
}
@Component({
  selector: 'app-root',
  template: `<form [formGroup]="form"><input type="text" formControlName="theInput" /></form> `,
  styles: [],
})
export class AppComponent implements OnInit, OnDestroy {
  private formKey: 'myForm';

  form: FormGroup;

  constructor(
    private formsManager: NgFormsManager<AppForms>,
    private fb: FormBuilder
  ) {}
  ngOnInit(): void {
    this.form = this.fb.group({
      theInput: [null],
    });
    this.formsManager.upsert(this.formKey, this.form);

    // This makes the test go green
    // this.form
    //   .get('theInput')
    //   .valueChanges.pipe(filter((value) => value != null))
    //   .subscribe(this.valueSetter);

    this.formsManager
      .valueChanges(this.formKey, 'theInput')
      .pipe(filter((value) => value != null))
      .subscribe(this.valueSetter);
  }

  private valueSetter = (value) =>
    this.form.get('theInput').setValue(value + 'Append', { emitEvent: false });

  ngOnDestroy(): void {
    this.formsManager.destroy(this.formKey);
  }
}
// ### app.component.spec.ts
describe('AppComponent', () => {
  let spectator: Spectator<AppComponent>;
  const createComponent = createComponentFactory({
    component: AppComponent,
    imports: [ReactiveFormsModule]
  });

  beforeEach(() => (spectator = createComponent()));

  it(`should update the value of the input field`, () => {
    const value = 'someText';
    spectator.typeInElement(value, 'input');

    const actual = spectator.component.form.controls.theInput.value;

    expect(actual).toEqual(value + 'Append');
  });
});

What is the motivation / use case for changing the behavior?

Environment


Angular version: 9.1.11
and it fails with version 10.0.2 too

Browser:
- [x ] Chrome (desktop) version 83.0.4103.116
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [ ] Firefox version XX
- [ ] Safari (desktop) version XX
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: 10.16.0
- Platform:  Linux

Others:

Add parse and serializer options for persistState

This is a really helpful tool, thank you.

I use Kendo to handle date pickers and Kendo expects a Date() object for FormControl values:

this.mainForm = this.fb.group({
  searchEndDate: [new Date(), Validators.required],
});

With the above, I get a Date() that Kendo consumes.

When I connect the Forms Manager,

 this.formsManager.upsert(this.formManagerKey, this.mainForm, {
   persistState: true
  });

Now 'searchEndDate' is a string

When I add the forms Manager, the date gets converted to a string. Is there a way to get the Forms Manager to maintain the type of the date as a Date()?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.