How can I clean my form after a ngrx succeeded action?

Viewed 1606

I have a reactive form:

const createFormGroup = (dataItem?: Usuario) => {
  if (dataItem) {
    return new FormGroup({
      id: new FormControl(dataItem.id),
      nome: new FormControl(dataItem.nome),
      cidade: new FormControl(dataItem.cidade)
    });
  }
  return new FormGroup({
    id: new FormControl(),
    nome: new FormControl(''),
    cidade: new FormControl('')
  });
};

This is my template:

<form (ngSubmit)="save()" [formGroup]="formGroup">
   <input formControlName="nome" type="text" class="form-control" id="inputName" placeholder="Nome">
   <input formControlName="cidade" type="text" class="form-control" id="exampleInputPassword1"
   <button id="my-element" type="submit" class="btn btn-primary">Complete
   </button>
</form>

After I click on "submit", I make a dispatch that save my data using a ngrx effect:

save() {
  const user = this.formGroup.value;
  if (user.id === null) {
    this.store.dispatch(createUser({ user: user }));
  } else {
    this.store.dispatch(updateUser({ user: user }));
  }
}

This is my effect:

public saveUser$ = createEffect(() =>
  this.actions$.pipe(
    ofType(createUser),
    switchMap((action) =>
      this.usuarioService.save(action.user).pipe(
        map(() => loadUsers()),
        catchError((error) => of(loadUsers()))
      )
    )
  )
);

Someone can tell me if have one way to clear my reactive when my effect don't go to catchError?

4 Answers

If you only want to clear the form after you’re sure the action has been successfully dispatched, I would set up a simple service with an observable that is updated by your effect when this.usuarioService.save is successful:

Effect:

You will need to import Subject to your effect file:

import { Subject } from 'rxjs';

Then add a service like the following. This could be in a separate file, but to keep things simple, add it to the top of your effect file, immediately below all the "imports"

@Injectable({
  providedIn: 'root'
})
export class ClearFormService {
  private clearFormSource = new Subject<boolean>();
  clearForm$ = this.clearFormSource.asObservable();
  clearForm() {
    this.clearFormSource.next(true);
  }
}

Next, add this service to the constructor in your effect class:

constructor(private clearFormService: ClearFormService) { }

If there are already references in your constructor add:

private clearFormService: ClearFormService

...to the end.

You can then update the observable in this service when this.usuarioService.save is successful. Notice curly brackets have been added to the map. There are 'more rxjs' approaches, but I think this is fine:

public saveUser$ = createEffect(() =>
  this.actions$.pipe(
    ofType(createUser),
    switchMap((action) =>
      this.usuarioService.save(action.user).pipe(
        map(() => {
            this.clearFormService.clearForm(); // Updates your service
            return loadUsers();
        }),
        catchError((error) => of(loadUsers()))
      )
    )
  )
);

Component With Form:

Then in the component with your form, you will need to import ClearFormService from your effect file and add it to the constructor, the same as was done in the effect file. You may also need to import subscription:

import { Subscription } from 'rxjs';

You can then subscribe to clearForm$, and clear the form whenever a response is received.

clearFormSubscription = new Subscription();

ngOnInit() {
    this.clearFormSubscription = this.clearFormService.clearForm$.subscribe(response => {
        this.formGroup.reset()
        console.log('Clear Form Here');
    })
  }

Don't forget to unsubscribe onDestroy!

ngOnDestroy() {
    this.clearFormSubscription.unsubscribe()
}

You could set up a solution that uses your ngrx store, but I think this would be over-engineering things.

  • If you want to do it ngrx way, call another action from your effect on the success.
saveUser$ = createEffect(() =>
    this.actions$.pipe(
        ofType(createUser),
        switchMap((action) =>
            this.usuarioService.save(action.user).pipe(
                map(() => {
                    loadUsers();
                    return new clearFormAction();
                }),
                catchError((error) => of(loadUsers()))
            )
        )
    )
);
  • Reset your form data to empty(initial state in general cases) in the reducer for clearFormAction().
case clearFormAction: {
    return {
        ...state,
        id: '',
        nome: '',
        cidade: ''
    };
}
  • Subscribe to the store form data in your component ngOnInit()
this.storeFormData$ = this.store.select(formData);
this.storeFormData.subscribe(formData => {
    this.formGroup.setValue(formData);
});
  • So whenever your this.usuarioService.save() is success your form will be cleared.

Maybe just observe state in the component and do all the resets if a particular property indicates that an operation was performed successfully.

ngOnInit(): void {
    this.watchOperations();
    // ...
}

watchOperations(): void {
    this.store
    .pipe(
        select(state => state.todos.succeeded)
    )
    .subscribe(succeeded => {
        if (succeeded) {
            this.store.dispatch(LOAD());
            this.formSubmitted = false;
            this.form.reset(this.formDefaults);
        }
    });
}

When it comes to the effect:

createEffect(() => this.actions$.pipe(
    ofType(ADD),
    mergeMap(({ payload }) => {
      return this.todosService.add(payload)
        .pipe(
          map(() => ({ type: '[Todos] Add Success' })),
          catchError(() => of({ type: '[Todos] Add Fail' }))
        );
    })
));

Component:

addTodo(): void {
    this.formSubmitted = true;

    if (this.form.valid) {
        this.store.dispatch(ADD({
            payload: {
                ...this.form.value,
                done: false
            } as ITodo
        }));
    }
}

And reducer:

// ...
on(ADD, (state, action) => {
    return {
        ...state,
        processed: action.payload,
        processing: true
    };
}),
on(ADD_SUCCESS, (state) => {
    return {
        ...state,
        processing: false,
        succeeded: true
    };
}),
on(ADD_FAIL, (state) => {
    return {
        ...state,
        processing: false,
        succeeded: false
    };
}),
// ...

Use This.formGroup.reset() to reset you form based on the condition where you want to clear the form values

Related