web-dev-qa-db-fra.com

Angular 2 + ngrx (redux) + formulaires

Comment gérez-vous Angular 2 formulaires dans un flux de données unidirectionnel? Surtout avec la validation entre plusieurs composants parent/enfant?

J'utilise ngrx/store et les formulaires pilotés par modèle avec le générateur de formulaire. Est-il possible de faire quelque chose de similaire comme réducteur de formulaire dans React et de l'intégrer dans Store?

Avez-vous des articles à ce sujet?

38
Marian Benčat

J'ai créé une bibliothèque appelée ngrx-forms qui fait exactement ce que vous voulez. Vous pouvez l'obtenir sur npm via:

npm install ngrx-forms --save

Je recommande de consulter l'intégralité de README sur la page github, mais vous trouverez ci-dessous quelques exemples de ce que vous devez faire pour que la bibliothèque soit opérationnelle une fois installée.

Importez le module:

import { StoreModule } from '@ngrx/store';
import { NgrxFormsModule } from 'ngrx-forms';

import { reducers } from './reducer';

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    NgrxFormsModule,
    StoreModule.forRoot(reducers),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Ajoutez un état de groupe quelque part dans votre arbre d'état via createFormGroupState et appelez le formGroupReducer à l'intérieur de votre réducteur:

import { Action } from '@ngrx/store';
import { FormGroupState, createFormGroupState, formGroupReducer } from 'ngrx-forms';

export interface MyFormValue {
  someTextInput: string;
  someCheckbox: boolean;
  nested: {
    someNumber: number;
  };
}

const FORM_ID = 'some globally unique string';

const initialFormState = createFormGroupState<MyFormValue>(FORM_ID, {
  someTextInput: '',
  someCheckbox: false,
  nested: {
    someNumber: 0,
  },
});

export interface AppState {
  someOtherField: string;
  myForm: FormGroupState<MyFormValue>;
}

const initialState: AppState = {
  someOtherField: '',
  myForm: initialFormState,
};

export function appReducer(state = initialState, action: Action): AppState {
  const myForm = formGroupReducer(state.myForm, action);
  if (myForm !== state.myForm) {
    state = { ...state, myForm };
  }

  switch (action.type) {
    case 'some action type':
      // modify state
      return state;

    default: {
      return state;
    }
  }
}

Exposez l'état du formulaire à l'intérieur de votre composant:

import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { FormGroupState } from 'ngrx-forms';
import { Observable } from 'rxjs/Observable';

import { MyFormValue } from './reducer';

@Component({
  selector: 'my-component',
  templateUrl: './my-component.html',
})
export class MyComponent {
  formState$: Observable<FormGroupState<MyFormValue>>;

  constructor(private store: Store<AppState>) {
    this.formState$ = store.select(s => s.myForm);
  }
}

Définissez les états de contrôle dans votre modèle:

<form novalidate [ngrxFormState]="(formState$ | async)">
  <input type="text"
         [ngrxFormControlState]="(formState$ | async).controls.someTextInput">

  <input type="checkbox"
         [ngrxFormControlState]="(formState$ | async).controls.someCheckbox">

  <input type="number"
         [ngrxFormControlState]="(formState$ | async).controls.nested.controls.someNumber">
</form>
14
MrWolfZ

C'est une question assez ancienne, mais je n'ai pas trouvé de bonne solution dans ma propre quête pour travailler avec des formes réactives ngrx + dans Angular. En conséquence, je publierai mes recherches ici dans l'espoir que cela puisse aider quelqu'un d'autre. Ma solution peut être décomposée en deux parties, et je vous prie (oh âme altérée) de la trouver applicable à votre problème:

1) Surveillez les éléments de formulaire (par exemple, événement "keyup" pour une entrée de texte typique) et mettez à jour l'état à partir de cet événement. Cette stratégie vient directement du composant de recherche de livre dans le exemple d'application ngrx . Nous pouvons maintenant remplir avec succès l'État à mesure que notre formulaire change. Impressionnant! 50% fait!

2) Le angular guide des formulaires réactifs montre comment créer le groupe de formulaires dans le constructeur. J'ai vu d'autres personnes le faire à l'intérieur de ngOnInit, mais c'est trop tard dans le cycle de vie pour nos besoins (j'ai essayé, j'ai échoué). Maintenant que notre groupe de formulaires est établi, configurez ngOnChanges pour capturer toutes les modifications poussées à partir de l'état, puis mettez à jour le groupe de formulaires à l'aide de patchValue . Par exemple :

  ngOnChanges(changes: SimpleChanges) {
    if (changes.valueICareAbout1) {
      this.myForm.patchValue({
        valueICareAbout1: changes.valueICareAbout1.currentValue
      });
    }
    if (changes.valueICareAbout2) {
      this.myForm.patchValue({
        valueICareAbout2: changes.valueICareAbout2.currentValue
      });
    }
  }
8
jpetitte

Dans les applications que j'ai construites avec Angular 2, la directive suivante semblait bien fonctionner:

Les composants parents transmettent les données aux enfants via la liaison de données. Les composants enfants demandent des modifications de données en émettant des événements de sortie vers les composants parents. Il est de la responsabilité des composants parents d'agir en conséquence.

Dans une structure de composant hiérarchique, les modifications de données sont gérées par le composant le plus bas qui dépend des données. S'il y a un autre composant plus haut ou un frère qui dépend du même élément de données, transmettez les modifications en émettant des événements et laissez la gestion à un composant plus élevé.

Ce schéma fonctionne bien car, pour toutes les données pertinentes pour plusieurs composants, il existe un seul composant responsable de l'exécution des modifications. Les changements descendent automatiquement. Les composants sont réutilisables et les changements dans l'arborescence des composants peuvent être facilement adaptés.

En ce qui concerne la validation, tout composant de l'échelle entre le composant le plus bas émettant une demande de modification de données jusqu'au composant le plus élevé qui gère finalement la modification, tout composant peut effectivement annuler la modification en ne la passant pas plus haut. Dans la plupart des applications, j'opterais pour la validation des modifications de données à l'origine du changement.

Naturellement, les composants enfants peuvent toujours avoir un état interne et n'ont pas besoin de communiquer les modifications - sauf si les modifications sont pertinentes pour le composant parent.

4
Lying Dog

Les données de formulaire sont intrinsèquement un état très local, en particulier pour Angular puisque ngModel se lie aux variables de composants locaux. Les meilleurs développeurs que je connais recommandent de conserver les données du formulaire localisées sur ce composant (c'est-à-dire simplement utiliser ngModel avec des variables locales). En effet, les données de formulaire non soumises ne sont presque jamais partagées par divers composants dans l'ensemble de votre application. Lorsque l'utilisateur soumet le formulaire, vous pouvez envoyer une action avec une charge utile contenant les données de formulaire à un composant parent, au magasin, ou même à un ngrx/effet à publier sur un serveur.

3
Jim