diff --git a/poll.module b/poll.module
index 06abc65..4c04b96 100644
--- a/poll.module
+++ b/poll.module
@@ -117,6 +117,7 @@ function poll_cron() {
  */
 function template_preprocess_poll_vote(&$variables) {
   $form = $variables['form'];
+  $variables['poll'] = $form['#entity'];
   $variables['question'] = SafeMarkup::checkPlain($form['#entity']->label());
   $variables['choice'] = drupal_render($form['choice']);
   $variables['vote'] = drupal_render($form['actions']);
diff --git a/poll.tokens.inc b/poll.tokens.inc
index 466443e..503721c 100644
--- a/poll.tokens.inc
+++ b/poll.tokens.inc
@@ -51,11 +51,11 @@ function poll_tokens($type, $tokens, array $data, array $options, BubbleableMeta
 
     $total_votes = 0;
     $highest_votes = 0;
-    foreach ($poll->votes as $vote) {
+    foreach ($poll->getVotes() as $vote) {
       $total_votes += $vote;
     }
     $options = $poll->getOptions();
-    foreach ($poll->votes as $pid => $vote) {
+    foreach ($poll->getVotes() as $pid => $vote) {
       if ($vote > $highest_votes) {
         $winner = $options[$pid];
         $highest_votes = $vote;
diff --git a/src/Entity/Poll.php b/src/Entity/Poll.php
index c2b1cc9..d5edb50 100644
--- a/src/Entity/Poll.php
+++ b/src/Entity/Poll.php
@@ -413,21 +413,21 @@ class Poll extends ContentEntityBase implements PollInterface {
   /**
    * {@inheritdoc}
    */
-  public static function postLoad(EntityStorageInterface $storage, array &$entities) {
+  public static function postDelete(EntityStorageInterface $storage, array $entities) {
+    parent::postDelete($storage, $entities);
+
     foreach ($entities as $entity) {
-      $entity->votes = $storage->getVotes($entity);
+      $storage->deleteVotes($entity);
     }
   }
 
   /**
    * {@inheritdoc}
    */
-  public static function postDelete(EntityStorageInterface $storage, array $entities) {
-    parent::postDelete($storage, $entities);
-
-    foreach ($entities as $entity) {
-      $storage->deleteVotes($entity);
-    }
+  public function getVotes() {
+    /** @var \Drupal\poll\PollStorage $poll_storage_controller */
+    $poll_storage_controller = \Drupal::entityManager()->getStorage('poll');
+    return $poll_storage_controller->getVotes($this);
   }
 
 }
diff --git a/src/Form/PollViewForm.php b/src/Form/PollViewForm.php
index 23f0675..7394e44 100644
--- a/src/Form/PollViewForm.php
+++ b/src/Form/PollViewForm.php
@@ -85,6 +85,7 @@ class PollViewForm extends FormBase {
 
       $form['#theme'] = 'poll_vote';
       $form['#entity'] = $this->poll;
+      $form['#action'] = $this->poll->url('canonical', ['query' => \Drupal::destination()->getAsArray()]);
       // Set a flag to hide results which will be removed if we want to view
       // results when the form is rebuilt.
       $form_state->set('show_results', FALSE);
@@ -100,6 +101,25 @@ class PollViewForm extends FormBase {
   }
 
   /**
+   * Handles vote submit.
+   *
+   * @param array $form
+   *   The previous form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return array
+   *   The new form.
+   */
+  public function ajaxReplaceVoteResult(array $form, FormStateInterface $form_state) {
+
+    $form['choice'] = $this->showPollResults($this->poll);
+    $form['actions'] = $this->actions($form, $form_state, $this->poll);
+    unset($form['#action']);
+    return $form;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
@@ -112,15 +132,19 @@ class PollViewForm extends FormBase {
       // The "View results" button, when available, has been clicked.
       case $form_state->get('show_results'):
         return TRUE;
+
       // The poll is closed.
       case ($poll->isClosed()):
         return TRUE;
+
       // Anonymous user is trying to view a poll they aren't allowed to vote in.
       case ($account->isAnonymous() && !$poll->getAnonymousVoteAllow()):
         return TRUE;
+
       // The user has already voted.
       case ($poll->hasUserVoted()):
         return TRUE;
+
       default:
         return FALSE;
     }
@@ -155,9 +179,14 @@ class PollViewForm extends FormBase {
       $actions['submit']['#value'] = t('Vote');
       $actions['submit']['#validate'] = array('::validateVote');
       $actions['submit']['#submit'] = array('::save');
+      $actions['submit']['#ajax'] = array(
+        'callback' => '::ajaxReplaceVoteResult',
+        'wrapper' => 'poll-' . $this->poll->id(),
+        'method' => 'replace',
+      );
       $actions['submit']['#weight'] = '0';
 
-      // view results before voting
+      // View results before voting.
       if ($poll->result_vote_allow->value) {
         $actions['result']['#type'] = 'submit';
         $actions['result']['#button_type'] = 'primary';
@@ -184,13 +213,13 @@ class PollViewForm extends FormBase {
     \Drupal::service('page_cache_kill_switch')->trigger();
 
     $total_votes = 0;
-    foreach ($poll->votes as $vote) {
+    foreach ($poll->getVotes() as $vote) {
       $total_votes += $vote;
     }
 
     $options = $poll->getOptions();
     $poll_results = array();
-    foreach ($poll->votes as $pid => $vote) {
+    foreach ($poll->getVotes() as $pid => $vote) {
       $percentage = round($vote * 100 / max($total_votes, 1));
       $display_votes = (!$block) ? ' (' . \Drupal::translation()
           ->formatPlural($vote, '1 vote', '@count votes') . ')' : '';
diff --git a/src/PollInterface.php b/src/PollInterface.php
index eefda10..f6a8531 100644
--- a/src/PollInterface.php
+++ b/src/PollInterface.php
@@ -155,12 +155,14 @@ interface PollInterface extends ContentEntityInterface {
    * @return mixed
    */
   public function hasUserVoted();
+
   /**
    * Get all options for this poll.
    *
    * @return array
    */
   public function getOptions();
+
   /**
    * Get the values of each vote option for this poll.
    *
@@ -168,4 +170,11 @@ interface PollInterface extends ContentEntityInterface {
    */
   public function getOptionValues();
 
+  /**
+   * Get all the votes of this poll.
+   *
+   * @return array
+   */
+  public function getVotes();
+
 }
diff --git a/src/Tests/PollVoteTest.php b/src/Tests/PollVoteTest.php
index 9710e60..a3e374f 100644
--- a/src/Tests/PollVoteTest.php
+++ b/src/Tests/PollVoteTest.php
@@ -105,7 +105,7 @@ class PollVoteTest extends PollTestBase {
     $field_status = $this->xpath('//table/tbody/tr[1]');
     $active = trim((string) $field_status[0]->td[1]);
     $date = \Drupal::service('date.formatter')->format($this->poll->getCreated() + 172800, 'short');
-    $output = 'Yes (until ' . rtrim(strstr($date, '-', true)) . ')';
+    $output = 'Yes (until ' . rtrim(strstr($date, '-', TRUE)) . ')';
     $this->assertEqual($active, $output);
 
     // Check if allow anonymous voting is on.
@@ -116,4 +116,23 @@ class PollVoteTest extends PollTestBase {
     $total_votes = trim((string) $field_status[0]->td[4]);
     $this->assertEqual($total_votes, '1');
   }
+
+  /**
+   * Tests voting on a poll using AJAX.
+   */
+  public function testAjaxPollVote() {
+
+    $this->drupalLogin($this->web_user);
+
+    // Record a vote for the first choice.
+    $edit = array(
+      'choice' => '1',
+    );
+    $this->drupalPostAjaxForm('poll/' . $this->poll->id(), $edit, array('op' => 'Vote'), NULL, array(), array(), 'poll-view-form-1');
+    $this->assertText('Your vote has been recorded.', 'Your vote was recorded.');
+    $this->assertText('Total votes:  1', 'Vote count updated correctly.');
+    $elements = $this->xpath('//input[@value="Cancel vote"]');
+    $this->assertTrue(isset($elements[0]), "'Cancel your vote' button appears.");
+  }
+
 }
diff --git a/templates/poll-vote.html.twig b/templates/poll-vote.html.twig
index 3161dd4..ec60a70 100644
--- a/templates/poll-vote.html.twig
+++ b/templates/poll-vote.html.twig
@@ -2,7 +2,7 @@
 /**
  */
 #}
-<div class="poll">
+<div id="poll-{{ poll.id.value }}" class="poll">
   <div class="vote-form">
     {% if show_question %}
       <h3 class="poll-question">{{ question }}</h3>
