diff --git a/facets.install b/facets.install
index 82a9f6b216fbcdd75ee85e110bd97540e79ded1a..7678e8174ea5e0726194636bdb477792f610cbe3 100644
--- a/facets.install
+++ b/facets.install
@@ -7,6 +7,7 @@
 
 use Drupal\facets\Entity\Facet;
 use Drupal\facets\Entity\FacetSource;
+use Drupal\block\Entity\Block;
 
 /**
  * Convert facets on Search Api facet sources to use the display plugin.
@@ -149,3 +150,20 @@ function facets_update_8005() {
     }
   }
 }
+
+/**
+ * Update facet blocks configuration with a block id used for AJAX support.
+ */
+function facets_update_8006() {
+  $query = \Drupal::entityQuery('block')
+    ->condition('plugin', 'facet_block', 'STARTS_WITH')
+    ->execute();
+
+  foreach ($query as $block_id) {
+    $block = Block::load($block_id);
+    $configuration = $block->get('settings');
+    $configuration['block_id'] = $block_id;
+    $block->set('settings', $configuration);
+    $block->save();
+  }
+}
diff --git a/facets.libraries.yml b/facets.libraries.yml
index 2ebbf328634d0848ba58d66a5048b21754906878..a77986def5e410e80a9f4d0117b7c94fcca546f8 100644
--- a/facets.libraries.yml
+++ b/facets.libraries.yml
@@ -53,3 +53,12 @@ soft-limit:
     - core/jquery.once
     - core/drupal
     - core/drupalSettings
+drupal.facets.views-ajax:
+  js:
+    js/facets-views-ajax.js: {}
+  dependencies:
+    - core/jquery
+    - core/jquery.once
+    - core/drupal
+    - core/drupalSettings
+    - core/drupal.ajax
diff --git a/facets.routing.yml b/facets.routing.yml
index 1e897e76d7f9fcabdfbc20598a6e30a2755116cf..9f26911713cd0cbeddc4a2dc2d427f647c5ecbe4 100644
--- a/facets.routing.yml
+++ b/facets.routing.yml
@@ -48,3 +48,10 @@ entity.facets_facet_source.edit_form:
     _title: 'Edit facet source configuration'
   requirements:
     _entity_create_access: 'facets_facet'
+
+facets.block.ajax:
+  path: '/facets-block-ajax'
+  defaults:
+    _controller: '\Drupal\facets\Controller\FacetBlockAjaxController::ajaxFacetBlockView'
+  requirements:
+    _access: 'TRUE'
diff --git a/js/checkbox-widget.js b/js/checkbox-widget.js
index 335d92490ae91dcb66c1ef698866a974abcace57..b6f00f723b37fb3faf0cb9aa660c860c21dbbeaa 100644
--- a/js/checkbox-widget.js
+++ b/js/checkbox-widget.js
@@ -43,7 +43,7 @@
 
     checkbox.on('change.facets', function (e) {
       Drupal.facets.disableFacet($link.parents('.js-facets-checkbox-links'));
-      window.location.href = $(this).data('facetsredir');
+      $(this).siblings('a')[0].click();
     });
 
     if (active) {
@@ -51,7 +51,7 @@
       label.find('.js-facet-deactivate').remove();
     }
 
-    $link.before(checkbox).before(label).remove();
+    $link.before(checkbox).before(label).hide();
 
   };
 
diff --git a/js/dropdown-widget.js b/js/dropdown-widget.js
index e87b929ec9c11cd2c82d1481d671d50e9ed928fe..1e790c5f104c3ca723990ec8dfe4b19c8528e299 100644
--- a/js/dropdown-widget.js
+++ b/js/dropdown-widget.js
@@ -38,6 +38,7 @@
       $dropdown.removeClass('js-facets-dropdown-links');
 
       $dropdown.addClass('facets-dropdown');
+      $dropdown.addClass('js-facets-dropdown');
 
       var id = $(this).data('drupal-facet-id');
       var default_option_label = settings.facets.dropdown_widget[id]['facet-default-option-label'];
@@ -68,7 +69,8 @@
 
       // Go to the selected option when it's clicked.
       $dropdown.on('change.facets', function () {
-        window.location.href = $(this).val();
+        var a = $($ul).find("[data-drupal-facet-item-id='" + $(this).find(':selected').data('drupalFacetItemId') + "']");
+        $(a)[0].click();
       });
 
       // Append empty text option.
@@ -77,7 +79,7 @@
       }
 
       // Replace links with dropdown.
-      $ul.after($dropdown).remove();
+      $ul.after($dropdown).hide();
       Drupal.attachBehaviors($dropdown.parent()[0], Drupal.settings);
     });
   };
diff --git a/js/facets-views-ajax.js b/js/facets-views-ajax.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f2fac5d4e4a8aecd0e7566b2890db303ee3d120
--- /dev/null
+++ b/js/facets-views-ajax.js
@@ -0,0 +1,203 @@
+/**
+ * @file
+ * Facets views AJAX handling.
+ */
+
+
+(function ($, Drupal) {
+  'use strict';
+
+  /**
+   * Keep the original beforeSend method to use it later.
+   */
+  var beforeSend = Drupal.Ajax.prototype.beforeSend;
+
+  /**
+   * Trigger views AJAX refresh on click.
+   */
+  Drupal.behaviors.facetsViewsAjax = {
+    attach: function (context, settings) {
+
+      // Loop through all facets.
+      $.each(settings.facets_views_ajax, function (facetId, facetSettings) {
+        // Get the View for the current facet.
+        var view, current_dom_id, view_path;
+        if (settings.views && settings.views.ajaxViews) {
+          $.each(settings.views.ajaxViews, function (domId, viewSettings) {
+            // Check if we have facet for this view.
+            if (facetSettings.view_id == viewSettings.view_name && facetSettings.current_display_id == viewSettings.view_display_id) {
+              view = $('.js-view-dom-id-' + viewSettings.view_dom_id);
+              current_dom_id = viewSettings.view_dom_id;
+              view_path = facetSettings.ajax_path;
+            }
+          });
+        }
+
+        if (!view || view.length != 1) {
+          return;
+        }
+
+        // Update view on summary block click.
+        if (updateFacetsSummaryBlock() && (facetId === 'facets_summary_ajax')) {
+          $('[data-drupal-facets-summary-id=' + facetSettings.facets_summary_id + ']').children('ul').children('li').once().click(function (e) {
+            e.preventDefault();
+            var facetLink = $(this).find('a');
+            updateFacetsView(facetLink.attr('href'), current_dom_id, view_path);
+          });
+        }
+        // Update view on facet item click.
+        else {
+          $('[data-drupal-facet-id=' + facetId + ']').find('.facet-item').once().each(function (index, facet_item) {
+            $(facet_item).children('a').once().click(function (e) {
+              e.preventDefault();
+              updateFacetsView($(this).attr('href'), current_dom_id, view_path);
+            });
+          });
+
+          $('[data-drupal-facet-id=' + facetId + ']').each(function (index, facet_item) {
+            if ($(facet_item).hasClass('js-facets-dropdown')) {
+              $(facet_item).unbind('change.facets');
+              $(facet_item).on('change.facets', function () {
+                updateFacetsView($(this).val(), current_dom_id, view_path);
+              });
+            }
+          });
+
+        }
+      });
+    }
+  };
+
+  // Helper function to update views output & Ajax facets.
+  var updateFacetsView = function (href, current_dom_id, view_path) {
+    // Refresh view.
+    var views_parameters = Drupal.Views.parseQueryString(href);
+    var views_arguments = Drupal.Views.parseViewArgs(href, 'search');
+    var views_settings = $.extend(
+        {},
+        Drupal.views.instances['views_dom_id:' + current_dom_id].settings,
+        views_arguments,
+        views_parameters
+    );
+
+    // Update View.
+    var views_ajax_settings = Drupal.views.instances['views_dom_id:' + current_dom_id].element_settings;
+    views_ajax_settings.submit = views_settings;
+    views_ajax_settings.url = view_path + '?q=' + href;
+
+    Drupal.ajax(views_ajax_settings).execute();
+
+    // Update url.
+    window.historyInitiated = true;
+    window.history.pushState(null, document.title, href);
+
+    // ToDo: Update views+facets with ajax on history back.
+    // For now we will reload the full page.
+    window.addEventListener("popstate", function (e) {
+      if (window.historyInitiated) {
+        window.location.reload();
+      }
+    });
+
+    // Refresh facets blocks.
+    updateFacetsBlocks(href);
+  }
+
+  // Helper function, updates facet blocks.
+  var updateFacetsBlocks = function (href) {
+    var settings = drupalSettings;
+    var facets_blocks = facetsBlocks();
+
+    // Update facet blocks.
+    let facet_settings = {
+      url: Drupal.url('facets-block-ajax'),
+      submit: {
+        facet_link: href,
+        facets_blocks: facets_blocks
+      }
+    };
+
+    // Update facets summary block.
+    if (updateFacetsSummaryBlock()) {
+      var facet_summary_wrapper_id = $('[data-drupal-facets-summary-id=' + settings.facets_views_ajax.facets_summary_ajax.facets_summary_id + ']').attr('id');
+      var facet_summary_block_id = '';
+      if (facet_summary_wrapper_id.indexOf('--') !== -1) {
+        facet_summary_block_id = facet_summary_wrapper_id.substring(0, facet_summary_wrapper_id.indexOf('--')).replace('block-', '');
+      }
+      else {
+        facet_summary_block_id = facet_summary_wrapper_id.replace('block-', '');
+      }
+      facet_settings.submit.update_summary_block = true;
+      facet_settings.submit.facet_summary_block_id = facet_summary_block_id;
+      facet_settings.submit.facet_summary_wrapper_id = settings.facets_views_ajax.facets_summary_ajax.facets_summary_id;
+    }
+
+    Drupal.ajax(facet_settings).execute();
+  };
+
+  // Helper function to determine if we should update the summary block.
+  // Returns true or false.
+  var updateFacetsSummaryBlock = function () {
+    var settings = drupalSettings;
+    var update_summary = false;
+
+    if (settings.facets_views_ajax.facets_summary_ajax) {
+      update_summary = true;
+    }
+
+    return update_summary;
+  };
+
+  // Helper function, return facet blocks.
+  var facetsBlocks = function () {
+    // Get all ajax facets blocks from the current page.
+    var facets_blocks = {};
+
+    $('.block-facets-ajax').each(function (index) {
+      var block_id_start = 'js-facet-block-id-';
+      var block_id = $.map($(this).attr('class').split(' '), function (v, i) {
+        if (v.indexOf(block_id_start) > -1) {
+          return v.slice(block_id_start.length, v.length);
+        }
+      }).join();
+      var block_selector = '#' + $(this).attr('id');
+      facets_blocks[block_id] = block_selector;
+    });
+
+    return facets_blocks;
+  };
+
+  /**
+   * Overrides beforeSend to trigger facetblocks update on exposed filter change.
+   *
+   * @param {XMLHttpRequest} xmlhttprequest
+   *   Native Ajax object.
+   * @param {object} options
+   *   jQuery.ajax options.
+   */
+  Drupal.Ajax.prototype.beforeSend = function (xmlhttprequest, options) {
+
+    // Update facet blocks as well.
+    // Get view from options.
+    if (typeof options.extraData !== 'undefined' && typeof options.extraData.view_name !== 'undefined') {
+      var href = window.location.href;
+      var settings = drupalSettings;
+
+      // TODO: Maybe we should limit facet block reloads by view?
+      var reload = false;
+      $.each(settings.facets_views_ajax, function (facetId, facetSettings) {
+        if (facetSettings.view_id == options.extraData.view_name && facetSettings.current_display_id == options.extraData.view_display_id) {
+          reload = true;
+        }
+      });
+
+      if (reload) {
+        updateFacetsBlocks(href);
+      }
+    }
+
+    // Call the original Drupal method with the right context.
+    beforeSend.apply(this, arguments);
+  }
+
+})(jQuery, Drupal);
diff --git a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
index 93ccd99c8765340c9e7759571b7953fe2b4bf4a6..54c476b39814c61632ba01c48115706b2479be37 100644
--- a/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
+++ b/modules/facets_summary/src/Plugin/Block/FacetsSummaryBlock.php
@@ -5,6 +5,7 @@ namespace Drupal\facets_summary\Plugin\Block;
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Cache\UncacheableDependencyTrait;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\Core\Url;
 use Drupal\facets_summary\Entity\FacetsSummary;
 use Drupal\facets_summary\FacetsSummaryBlockInterface;
 use Drupal\facets_summary\FacetsSummaryManager\DefaultFacetsSummaryManager;
@@ -96,6 +97,18 @@ class FacetsSummaryBlock extends BlockBase implements FacetsSummaryBlockInterfac
       ];
     }
 
+    /** @var \Drupal\views\ViewExecutable $view */
+    $view = $facets_summary->getFacetSource()->getViewsDisplay();
+
+    $build['#attached']['drupalSettings']['facets_views_ajax'] = [
+      'facets_summary_ajax' => [
+        'facets_summary_id' => $facets_summary->id(),
+        'view_id' => $view->id(),
+        'current_display_id' => $view->current_display,
+        'ajax_path' => Url::fromRoute('views.ajax')->toString(),
+      ],
+    ];
+
     return $build;
 
   }
diff --git a/src/Controller/FacetBlockAjaxController.php b/src/Controller/FacetBlockAjaxController.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce43a99b220f8a9b4498e235fac4ea2c1bd91015
--- /dev/null
+++ b/src/Controller/FacetBlockAjaxController.php
@@ -0,0 +1,179 @@
+<?php
+
+namespace Drupal\facets\Controller;
+
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Ajax\InvokeCommand;
+use Drupal\Core\Ajax\ReplaceCommand;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\PathProcessor\PathProcessorManager;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\CurrentRouteMatch;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\RequestStack;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Symfony\Component\Routing\RouterInterface;
+
+/**
+ * Defines a controller to load a facet via AJAX.
+ */
+class FacetBlockAjaxController extends ControllerBase {
+
+  /**
+   * The entity storage for block.
+   *
+   * @var \Drupal\Core\Entity\EntityStorageInterface
+   */
+  protected $storage;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * The current path.
+   *
+   * @var \Drupal\Core\Path\CurrentPathStack
+   */
+  protected $currentPath;
+
+  /**
+   * The dynamic router service.
+   *
+   * @var \Symfony\Component\Routing\Matcher\RequestMatcherInterface
+   */
+  protected $router;
+
+  /**
+   * The path processor service.
+   *
+   * @var \Drupal\Core\PathProcessor\InboundPathProcessorInterface
+   */
+  protected $pathProcessor;
+
+  /**
+   * The current route match service.
+   *
+   * @var \Drupal\Core\Routing\CurrentRouteMatch
+   */
+  protected $currentRouteMatch;
+
+  /**
+   * Constructs a FacetBlockAjaxController object.
+   *
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer service.
+   * @param \Drupal\Core\Path\CurrentPathStack $currentPath
+   *   The current path service.
+   * @param \Symfony\Component\Routing\RouterInterface $router
+   *   The router service.
+   * @param \Drupal\Core\PathProcessor\PathProcessorManager $pathProcessor
+   *   The path processor manager.
+   * @param \Drupal\Core\Routing\CurrentRouteMatch $currentRouteMatch
+   *   The current route match service.
+   */
+  public function __construct(RendererInterface $renderer, CurrentPathStack $currentPath, RouterInterface $router, PathProcessorManager $pathProcessor, CurrentRouteMatch $currentRouteMatch) {
+    $this->storage = $this->entityTypeManager()->getStorage('block');
+    $this->renderer = $renderer;
+    $this->currentPath = $currentPath;
+    $this->router = $router;
+    $this->pathProcessor = $pathProcessor;
+    $this->currentRouteMatch = $currentRouteMatch;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('renderer'),
+      $container->get('path.current'),
+      $container->get('router'),
+      $container->get('path_processor_manager'),
+      $container->get('current_route_match')
+    );
+  }
+
+  /**
+   * Loads and renders the facet blocks via AJAX.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request object.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   The ajax response.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   *   Thrown when the view was not found.
+   */
+  public function ajaxFacetBlockView(Request $request) {
+    $response = new AjaxResponse();
+
+    // Rebuild the request and the current path, needed for facets.
+    $path = $request->request->get('facet_link');
+    $facets_blocks = $request->request->get('facets_blocks');
+
+    // Make sure we are not updating blocks multiple times.
+    $facets_blocks = array_unique($facets_blocks);
+
+    if (empty($path) || empty($facets_blocks)) {
+      throw new NotFoundHttpException('No facet link or facet blocks found.');
+    }
+
+    $this->currentRouteMatch->resetRouteMatch();
+    $new_request = Request::create($path);
+    $request_stack = new RequestStack();
+    $processed = $this->pathProcessor->processInbound($path, $new_request);
+
+    $this->currentPath->setPath($processed, $new_request);
+    $request->attributes->add($this->router->matchRequest($new_request));
+    $request_stack->push($new_request);
+
+    $container = \Drupal::getContainer();
+    $container->set('request_stack', $request_stack);
+    $active_facet = $request->request->get('active_facet');
+
+    // Build the facets blocks found for the current request and update.
+    foreach ($facets_blocks as $block_id => $block_selector) {
+      $block_entity = $this->storage->load($block_id);
+
+      if ($block_entity) {
+        // Render a block, then add it to the response as a replace command.
+        $block_view = $this->entityTypeManager
+          ->getViewBuilder('block')
+          ->view($block_entity);
+
+        $block_view = (string) $this->renderer->renderPlain($block_view);
+        $response->addCommand(new ReplaceCommand($block_selector, $block_view));
+      }
+    }
+
+    $response->addCommand(new InvokeCommand('[data-block-plugin-id="' . $active_facet . '"]', 'addClass', ['facet-active']));
+
+    // Update filter summary block.
+    $update_summary_block = $request->request->get('update_summary_block');
+    if ($update_summary_block) {
+      $facet_summary_block_id = $request->request->get('facet_summary_block_id');
+      $facet_summary_wrapper_id = $request->request->get('facet_summary_wrapper_id');
+      $facet_summary_block_id = str_replace('-', '_', $facet_summary_block_id);
+
+      if ($facet_summary_block_id) {
+        $block_entity = $this->storage->load($facet_summary_block_id);
+        $block_view = $this->entityTypeManager
+          ->getViewBuilder('block')
+          ->view($block_entity);
+        $block_view = (string) $this->renderer->renderPlain($block_view);
+
+        $response->addCommand(new ReplaceCommand('[data-drupal-facets-summary-id=' . $facet_summary_wrapper_id . ']', $block_view));
+      }
+    }
+
+    return $response;
+  }
+
+}
diff --git a/src/FacetManager/DefaultFacetManager.php b/src/FacetManager/DefaultFacetManager.php
index 7e33a30f9436511b8522aa7283f4f5519d20c5e7..4ccacce0c50f0eb25980b73a1da65aa24df11ecb 100644
--- a/src/FacetManager/DefaultFacetManager.php
+++ b/src/FacetManager/DefaultFacetManager.php
@@ -334,7 +334,19 @@ class DefaultFacetManager {
         ];
       }
       else {
-        return [];
+        // If the facet has no results, but it is being rendered trough ajax we
+        // should render a container (that is empty). This is because the
+        // javascript needs to be able to find a div to replace with the new
+        // content.
+        return [
+          [
+            '#type' => 'container',
+            '#attributes' => [
+              'data-drupal-facet-id' => $facet->id(),
+              'class' => 'facet-empty',
+            ],
+          ],
+        ];
       }
     }
 
diff --git a/src/FacetSource/FacetSourcePluginBase.php b/src/FacetSource/FacetSourcePluginBase.php
index 14317fbff92acfd3ea3bf774ada8b0c59d32989e..9a470f8fd33c63e769f05bdf341e3ba05d7b08ed 100644
--- a/src/FacetSource/FacetSourcePluginBase.php
+++ b/src/FacetSource/FacetSourcePluginBase.php
@@ -116,6 +116,13 @@ abstract class FacetSourcePluginBase extends PluginBase implements FacetSourcePl
     return $this->keys;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function buildFacet() {
+    return [];
+  }
+
   /**
    * {@inheritdoc}
    */
diff --git a/src/FacetSource/FacetSourcePluginInterface.php b/src/FacetSource/FacetSourcePluginInterface.php
index d88d3e5ff9f321d4e39ed8f0e8f83efea9732ae3..1b971012605ce5a8ca2ec41a195ff1936b27f9fb 100644
--- a/src/FacetSource/FacetSourcePluginInterface.php
+++ b/src/FacetSource/FacetSourcePluginInterface.php
@@ -105,4 +105,14 @@ interface FacetSourcePluginInterface extends PluginFormInterface, DependentPlugi
    */
   public function getDataDefinition($field_name);
 
+  /**
+   * Builds and returns an extra renderable array for this facet block plugin.
+   *
+   * @return array
+   *   A renderable array representing the content of the block.
+   *
+   * @see \Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay
+   */
+  public function buildFacet();
+
 }
diff --git a/src/Plugin/Block/FacetBlock.php b/src/Plugin/Block/FacetBlock.php
index 57968fabde8de67f5bedbc7339c0e188adf98cee..9e0d6c200095c9bc287f8bc0eee8a76ca6ec37f6 100644
--- a/src/Plugin/Block/FacetBlock.php
+++ b/src/Plugin/Block/FacetBlock.php
@@ -5,7 +5,7 @@ namespace Drupal\facets\Plugin\Block;
 use Drupal\Core\Block\BlockBase;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Form\FormStateInterface;
 use Drupal\facets\FacetManager\DefaultFacetManager;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -82,11 +82,29 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
     // Let the facet_manager build the facets.
     $build = $this->facetManager->build($facet);
 
-    // Add contextual links only when we have results.
     if (!empty($build)) {
+      // Add extra elements from facet source, for example, ajax scripts.
+      // @see Drupal\facets\Plugin\facets\facet_source\SearchApiDisplay
+      /* @var \Drupal\facets\FacetSource\FacetSourcePluginInterface $facet_source */
+      $facet_source = $facet->getFacetSource();
+      $build += $facet_source->buildFacet();
+
+      // Add contextual links only when we have results.
       $build['#contextual_links']['facets_facet'] = [
         'route_parameters' => ['facets_facet' => $facet->id()],
       ];
+
+      // Add classes needed for ajax.
+      if (!empty($build['#use_ajax'])) {
+        $build['#attributes']['class'][] = 'block-facets-ajax';
+        // The configuration block id isn't always set in the configuration.
+        if (isset($this->configuration['block_id'])) {
+          $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->configuration['block_id'];
+        }
+        else {
+          $build['#attributes']['class'][] = 'js-facet-block-id-' . $this->pluginId;
+        }
+      }
     }
 
     return $build;
@@ -135,4 +153,14 @@ class FacetBlock extends BlockBase implements ContainerFactoryPluginInterface {
     return ['config' => [$facet->getConfigDependencyName()]];
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function blockSubmit($form, FormStateInterface $form_state) {
+    // Save block id to configuration, we do this for loading the original block
+    // with ajax.
+    $block_id = $form['id']['#value'];
+    $this->configuration['block_id'] = $block_id;
+  }
+
 }
diff --git a/src/Plugin/facets/facet_source/SearchApiDisplay.php b/src/Plugin/facets/facet_source/SearchApiDisplay.php
index 028d3867f08f7a152a4f395153ea89b520b04995..78d8bff14199dd1c8ea6b58177ec25adf3640c6a 100644
--- a/src/Plugin/facets/facet_source/SearchApiDisplay.php
+++ b/src/Plugin/facets/facet_source/SearchApiDisplay.php
@@ -5,6 +5,7 @@ namespace Drupal\facets\Plugin\facets\facet_source;
 use Drupal\Component\Plugin\DependentPluginInterface;
 use Drupal\Core\Extension\ModuleHandler;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
 use Drupal\facets\Exception\Exception;
 use Drupal\facets\Exception\InvalidQueryTypeException;
 use Drupal\facets\FacetInterface;
@@ -372,4 +373,31 @@ class SearchApiDisplay extends FacetSourcePluginBase implements SearchApiFacetSo
     throw new Exception("Field with name {$field_name} does not have a definition");
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function buildFacet() {
+    $build = parent::buildFacet();
+    $view = $this->getViewsDisplay();
+    if ($view === NULL) {
+      return $build;
+    }
+
+    // Add JS for Views with Ajax Enabled.
+    if ($view->display_handler->ajaxEnabled()) {
+      $js_settings = [
+        'view_id' => $view->id(),
+        'current_display_id' => $view->current_display,
+        'view_base_path' => ltrim($view->getPath(), '/'),
+        'ajax_path' => Url::fromRoute('views.ajax')->toString(),
+      ];
+      $build['#attached']['library'][] = 'facets/drupal.facets.views-ajax';
+      $build['#attached']['drupalSettings']['facets_views_ajax'] = [
+        $this->facet->id() => $js_settings,
+      ];
+      $build['#use_ajax'] = TRUE;
+    }
+    return $build;
+  }
+
 }
diff --git a/src/Widget/WidgetPluginBase.php b/src/Widget/WidgetPluginBase.php
index 032cda86e69af66b1f69c684c9e86b13dcf479f1..30b4f63d828c2944fdad135be3aa9351c8127b8c 100644
--- a/src/Widget/WidgetPluginBase.php
+++ b/src/Widget/WidgetPluginBase.php
@@ -6,6 +6,7 @@ use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Link;
 use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Url;
 use Drupal\facets\FacetInterface;
 use Drupal\facets\Result\Result;
 use Drupal\facets\Result\ResultInterface;
@@ -48,6 +49,21 @@ abstract class WidgetPluginBase extends PluginBase implements WidgetPluginInterf
         return $this->buildResultItem($result);
       }
       else {
+        // When the facet is being build in an AJAX request, and the facetsource
+        // is a block, we need to update the url to use the current request url.
+        if ($result->getUrl()->isRouted() && $result->getUrl()->getRouteName() === 'facets.block.ajax') {
+          $request = \Drupal::request();
+          $url_object = \Drupal::service('path.validator')
+            ->getUrlIfValid($request->getPathInfo());
+          if ($url_object) {
+            $url = $result->getUrl();
+            $options = $url->getOptions();
+            $route_params = $url_object->getRouteParameters();
+            $route_name = $url_object->getRouteName();
+            $result->setUrl(new Url($route_name, $route_params, $options));
+          }
+        }
+
         return $this->buildListItems($facet, $result);
       }
     }, $facet->getResults());
diff --git a/tests/src/Functional/ProcessorIntegrationTest.php b/tests/src/Functional/ProcessorIntegrationTest.php
index ec6f87e9d8d8a77e74e39868022e3c657d733214..25b739aa1426c1b16598737f968a41ecc79716f7 100644
--- a/tests/src/Functional/ProcessorIntegrationTest.php
+++ b/tests/src/Functional/ProcessorIntegrationTest.php
@@ -795,7 +795,7 @@ class ProcessorIntegrationTest extends FacetsTestBase {
     $this->drupalPostForm($this->editForm, $form, 'Save');
     $this->drupalGet('search-api-test-fulltext');
 
-    $this->assertSession()->pageTextContains(' Displaying 1 search results');
+    $this->assertSession()->pageTextContains('Displaying 1 search results');
     $this->assertNoFacetBlocksAppear();
   }
 
diff --git a/tests/src/Functional/TestHelperTrait.php b/tests/src/Functional/TestHelperTrait.php
index a804654b31f1304f07e3d6aa4f52fca859d53136..e64681377164bba0ddaad96516fb78f0c190488e 100644
--- a/tests/src/Functional/TestHelperTrait.php
+++ b/tests/src/Functional/TestHelperTrait.php
@@ -63,8 +63,13 @@ trait TestHelperTrait {
    */
   protected function assertNoFacetBlocksAppear() {
     foreach ($this->blocks as $block) {
-      $this->assertFalse($this->xpath('//div[@id = :id]', [':id' => 'block-' . $block->id()]));
-      $this->assertSession()->pageTextNotContains($block->label());
+      $xpath = $this->xpath('//div[@id = :id]/div[@class="facet-empty"]', [':id' => 'block-' . $block->id()]);
+      if (!$xpath) {
+        $this->assertFalse($xpath);
+      }
+      else {
+        $this->assertTrue($this->xpath('//div[@id = :id]/div[@class="facet-empty"]', [':id' => 'block-' . $block->id()]));
+      }
     }
   }
 
diff --git a/tests/src/FunctionalJavascript/AjaxBehaviorTest.php b/tests/src/FunctionalJavascript/AjaxBehaviorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1cfa40de5ca8cfde5752d25d980f7434829d36ec
--- /dev/null
+++ b/tests/src/FunctionalJavascript/AjaxBehaviorTest.php
@@ -0,0 +1,69 @@
+<?php
+
+namespace Drupal\Tests\facets\FunctionalJavascript;
+
+use Drupal\views\Entity\View;
+
+/**
+ * Tests for the JS that powers ajax.
+ *
+ * @group facets
+ */
+class AjaxBehaviorTest extends JsBase {
+
+  /**
+   * Tests ajax links.
+   */
+  public function testAjaxLinks() {
+    // Create facets.
+    $this->createFacet('owl');
+    $this->createFacet('duck', 'keywords');
+
+    // Force ajax.
+    $view = View::load('search_api_test_view');
+    $display = $view->getDisplay('page_1');
+    $display['display_options']['use_ajax'] = TRUE;
+    $view->save();
+
+    // Go to the views page.
+    $this->drupalGet('search-api-test-fulltext');
+
+    // Make sure the blocks are shown on the page.
+    $page = $this->getSession()->getPage();
+    $block_owl = $page->findById('block-owl-block');
+    $block_owl->isVisible();
+    $block_duck = $page->findById('block-duck-block');
+    $block_duck->isVisible();
+    $this->assertSession()->pageTextContains('Displaying 5 search results');
+
+    // Check that the article link exists (and is formatted like a facet) link.
+    $links = $this->xpath('//a//span[normalize-space(text())=:label]', [':label' => 'article']);
+    $this->assertNotEmpty($links);
+
+    // Click the item facet.
+    $this->clickLink('item');
+
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->pageTextContains('Displaying 3 search results');
+
+    // Check that the article facet is now gone.
+    $links = $this->xpath('//a//span[normalize-space(text())=:label]', [':label' => 'article']);
+    $this->assertEmpty($links);
+
+    // Click the item facet again, and check that the article facet is back.
+    $this->clickLink('item');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertSession()->pageTextContains('Displaying 5 search results');
+    $links = $this->xpath('//a//span[normalize-space(text())=:label]', [':label' => 'article']);
+    // $this->assertNotEmpty($links);
+
+    // Check that the strawberry link disappears when filtering on items.
+    $links = $this->xpath('//a//span[normalize-space(text())=:label]', [':label' => 'strawberry']);
+    $this->assertNotEmpty($links);
+    $this->clickLink('item');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $links = $this->xpath('//a//span[normalize-space(text())=:label]', [':label' => 'strawberry']);
+    $this->assertEmpty($links);
+  }
+
+}
diff --git a/tests/src/FunctionalJavascript/JsBase.php b/tests/src/FunctionalJavascript/JsBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..596ed8e219b20b9e212810bf69c1cf6b7d0a23b7
--- /dev/null
+++ b/tests/src/FunctionalJavascript/JsBase.php
@@ -0,0 +1,168 @@
+<?php
+
+namespace Drupal\Tests\facets\FunctionalJavascript;
+
+use Drupal\block\Entity\Block;
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+use Drupal\search_api\Entity\Index;
+
+/**
+ * Tests for the JS that transforms widgets into form elements.
+ */
+abstract class JsBase extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = [
+    'views',
+    'search_api',
+    'facets',
+    'facets_search_api_dependency',
+    'block',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create the users used for the tests.
+    $admin_user = $this->drupalCreateUser([
+      'administer search_api',
+      'administer facets',
+      'access administration pages',
+      'administer blocks',
+    ]);
+    $this->drupalLogin($admin_user);
+
+    $this->insertExampleContent();
+  }
+
+  /**
+   * Setup and insert test content.
+   */
+  protected function insertExampleContent() {
+    entity_test_create_bundle('item', NULL, 'entity_test_mulrev_changed');
+    entity_test_create_bundle('article', NULL, 'entity_test_mulrev_changed');
+
+    $entity_test_storage = \Drupal::entityTypeManager()
+      ->getStorage('entity_test_mulrev_changed');
+    $entity_1 = $entity_test_storage->create([
+      'name' => 'foo bar baz',
+      'body' => 'test test',
+      'type' => 'item',
+      'keywords' => ['orange'],
+      'category' => 'item_category',
+    ]);
+    $entity_1->save();
+    $entity_2 = $entity_test_storage->create([
+      'name' => 'foo test',
+      'body' => 'bar test',
+      'type' => 'item',
+      'keywords' => ['orange', 'apple', 'grape'],
+      'category' => 'item_category',
+    ]);
+    $entity_2->save();
+    $entity_3 = $entity_test_storage->create([
+      'name' => 'bar',
+      'body' => 'test foobar',
+      'type' => 'item',
+    ]);
+    $entity_3->save();
+    $entity_4 = $entity_test_storage->create([
+      'name' => 'foo baz',
+      'body' => 'test test test',
+      'type' => 'article',
+      'keywords' => ['apple', 'strawberry', 'grape'],
+      'category' => 'article_category',
+    ]);
+    $entity_4->save();
+    $entity_5 = $entity_test_storage->create([
+      'name' => 'bar baz',
+      'body' => 'foo',
+      'type' => 'article',
+      'keywords' => ['orange', 'strawberry', 'grape', 'banana'],
+      'category' => 'article_category',
+    ]);
+    $entity_5->save();
+
+    $inserted_entities = \Drupal::entityQuery('entity_test_mulrev_changed')
+      ->count()
+      ->execute();
+    $this->assertEquals(5, $inserted_entities, "5 items inserted.");
+
+    /** @var \Drupal\search_api\IndexInterface $index */
+    $index = Index::load('database_search_index');
+    $indexed_items = $index->indexItems();
+    $this->assertEquals(5, $indexed_items, '5 items indexed.');
+  }
+
+  /**
+   * Create and place a facet block in the first sidebar.
+   *
+   * @param string $id
+   *   Create a block for a facet.
+   */
+  protected function createBlock($id) {
+    $config = \Drupal::configFactory();
+    $settings = [
+      'plugin' => 'facet_block:' . $id,
+      'region' => 'sidebar_first',
+      'id' => $id . '_block',
+      'theme' => $config->get('system.theme')->get('default'),
+      'label' => ucfirst($id) . ' block',
+      'visibility' => [],
+      'weight' => 0,
+    ];
+
+    foreach (['region', 'id', 'theme', 'plugin', 'weight', 'visibility'] as $key) {
+      $values[$key] = $settings[$key];
+      // Remove extra values that do not belong in the settings array.
+      unset($settings[$key]);
+    }
+    $values['settings'] = $settings;
+    $block = Block::create($values);
+    $block->save();
+  }
+
+  /**
+   * Create a facet.
+   *
+   * @param string $id
+   *   The id of the facet.
+   * @param string $field
+   *   The field name.
+   */
+  protected function createFacet($id, $field = 'type') {
+    $facet_storage = \Drupal::entityTypeManager()->getStorage('facets_facet');
+    // Create and save a facet with a checkbox widget.
+    $facet_storage->create([
+      'id' => $id,
+      'name' => strtoupper($id),
+      'url_alias' => $id,
+      'facet_source_id' => 'search_api:views_page__search_api_test_view__page_1',
+      'field_identifier' => $field,
+      'empty_behavior' => ['behavior' => 'none'],
+      'weight' => 1,
+      'widget' => [
+        'type' => 'links',
+        'config' => [
+          'show_numbers' => TRUE,
+          'soft_limit' => 0,
+        ],
+      ],
+      'processor_configs' => [
+        'url_processor_handler' => [
+          'processor_id' => 'url_processor_handler',
+          'weights' => ['pre_query' => -10, 'build' => -10],
+          'settings' => [],
+        ],
+      ],
+      'query_operator' => 'AND',
+    ])->save();
+    $this->createBlock($id);
+  }
+
+}
diff --git a/tests/src/FunctionalJavascript/WidgetJSTest.php b/tests/src/FunctionalJavascript/WidgetJSTest.php
index 2b606a29aa016991eac48e04ec7a0fca12b7d76b..0d67d973d8d3786854ad3f3ddd0eadd45969fa0c 100644
--- a/tests/src/FunctionalJavascript/WidgetJSTest.php
+++ b/tests/src/FunctionalJavascript/WidgetJSTest.php
@@ -2,46 +2,14 @@
 
 namespace Drupal\Tests\facets\FunctionalJavascript;
 
-use Drupal\block\Entity\Block;
 use Drupal\facets\Entity\Facet;
-use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
-use Drupal\search_api\Entity\Index;
 
 /**
  * Tests for the JS that transforms widgets into form elements.
  *
  * @group facets
  */
-class WidgetJSTest extends JavascriptTestBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public static $modules = [
-    'views',
-    'search_api',
-    'facets',
-    'facets_search_api_dependency',
-    'block',
-  ];
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp() {
-    parent::setUp();
-
-    // Create the users used for the tests.
-    $admin_user = $this->drupalCreateUser([
-      'administer search_api',
-      'administer facets',
-      'access administration pages',
-      'administer blocks',
-    ]);
-    $this->drupalLogin($admin_user);
-
-    $this->insertExampleContent();
-  }
+class WidgetJSTest extends JsBase {
 
   /**
    * Tests JS interactions in the admin UI.
@@ -251,91 +219,4 @@ class WidgetJSTest extends JavascriptTestBase {
     $this->assertTrue(strpos($current_url, 'search-api-test-fulltext?f%5B0%5D=llama%253Aitem') !== FALSE);
   }
 
-  /**
-   * Setup and insert test content.
-   */
-  protected function insertExampleContent() {
-    entity_test_create_bundle('item', NULL, 'entity_test_mulrev_changed');
-    entity_test_create_bundle('article', NULL, 'entity_test_mulrev_changed');
-
-    $entity_test_storage = \Drupal::entityTypeManager()
-      ->getStorage('entity_test_mulrev_changed');
-    $entity_1 = $entity_test_storage->create([
-      'name' => 'foo bar baz',
-      'body' => 'test test',
-      'type' => 'item',
-      'keywords' => ['orange'],
-      'category' => 'item_category',
-    ]);
-    $entity_1->save();
-    $entity_2 = $entity_test_storage->create([
-      'name' => 'foo test',
-      'body' => 'bar test',
-      'type' => 'item',
-      'keywords' => ['orange', 'apple', 'grape'],
-      'category' => 'item_category',
-    ]);
-    $entity_2->save();
-    $entity_3 = $entity_test_storage->create([
-      'name' => 'bar',
-      'body' => 'test foobar',
-      'type' => 'item',
-    ]);
-    $entity_3->save();
-    $entity_4 = $entity_test_storage->create([
-      'name' => 'foo baz',
-      'body' => 'test test test',
-      'type' => 'article',
-      'keywords' => ['apple', 'strawberry', 'grape'],
-      'category' => 'article_category',
-    ]);
-    $entity_4->save();
-    $entity_5 = $entity_test_storage->create([
-      'name' => 'bar baz',
-      'body' => 'foo',
-      'type' => 'article',
-      'keywords' => ['orange', 'strawberry', 'grape', 'banana'],
-      'category' => 'article_category',
-    ]);
-    $entity_5->save();
-
-    $inserted_entities = \Drupal::entityQuery('entity_test_mulrev_changed')
-      ->count()
-      ->execute();
-    $this->assertEquals(5, $inserted_entities, "5 items inserted.");
-
-    /** @var \Drupal\search_api\IndexInterface $index */
-    $index = Index::load('database_search_index');
-    $indexed_items = $index->indexItems();
-    $this->assertEquals(5, $indexed_items, '5 items indexed.');
-  }
-
-  /**
-   * Create and place a facet block in the first sidebar.
-   *
-   * @param string $id
-   *   Create a block for a facet.
-   */
-  protected function createBlock($id) {
-    $config = \Drupal::configFactory();
-    $settings = [
-      'plugin' => 'facet_block:' . $id,
-      'region' => 'sidebar_first',
-      'id' => $id . '_block',
-      'theme' => $config->get('system.theme')->get('default'),
-      'label' => ucfirst($id) . ' block',
-      'visibility' => [],
-      'weight' => 0,
-    ];
-
-    foreach (['region', 'id', 'theme', 'plugin', 'weight', 'visibility'] as $key) {
-      $values[$key] = $settings[$key];
-      // Remove extra values that do not belong in the settings array.
-      unset($settings[$key]);
-    }
-    $values['settings'] = $settings;
-    $block = Block::create($values);
-    $block->save();
-  }
-
 }