Actually, you will pass through your validateForm() method, whether you are using Ajax or not.
In the validateForm(), you have to set the appropriate errors on your fields which do not validate.
Then, as Charles Bamford stated, you need to handle the error management in your submit callback.
So basically, you would end up with some kind of the following structure:
public function buildForm(array $form, FormStateInterface $form_state) {
$form['#prefix'] = '<div id="my-ajax-target-element"></div>
$form['my_field'] = [
'#type' => 'textfield',
'#title' => $this->t('My field title'),
];
$form['submit'] = [
'#type' => 'submit',
'#value' => $this->t('Submit'),
'#ajax' => [
'callback' => '::myAjaxCallback',
'wrapper' => 'my-ajax-target-element',
'method' => 'replace',
],
];
return $form;
}
public function validateForm(array &$form, FormStateInterface $form_state) {
$values = $form_state->getValues();
// I set error on my field here, but due to Ajax, the display
// of error messages will be handled in the Ajax callback
if($values['my_field'] != 'my expected value') {
$form_state->setErrorByName('my_field', $this->t('This is my error message'));
}
}
public function myAjaxCallback(array &$form, FormStateInterface $form_state) {
$response = new AjaxResponse();
// If $form_state contains errors, populate the ajaxResponse content with the error messages
if($form_state->hasAnyErrors()) {
$errors = $form_state->getErrors();
$command_content = $errors['my_field'];
}
else {
// Happily proceed to the submission of your form
}
$response->addCommand(new HtmlCommand('#my-ajax-target-element', $command_content));
return $response;
}