1

I am working on a live map type application that has some buttons to navigate (e.g. pan, zoom, etc.). I added a keydown event handler to allow keyboard control. The event handler calls the same method.

I found it odd that the buttons are snappy and reliable in their response to load the image, but the keydown response is terribly slow. Now, I actually think the image update was due to the $interval service that was repeatedly fetching it and not from the keydown event at all. Note: I already handle stale cached images (was a separate issue) by appending the url with a unique data like date/time.

I added console logging to ensure that the imgUrl is actually changing, and it is. It seems that the button click is causing ngSrc to re-render with the updated imgUrl, but the keyboard event is not. Here is a simplified test app that recreates the same behavior with a button and the 't' key and in this case they keydown event:

Plunker

javascript

var myApp = angular.module("myApp", ['ngMaterial'])
    .controller("myCtrl", function ($scope)
    {
        $scope.isToggled = false;

        $scope.Toggle = function ()
        {
            console.log("Toggle(Start): " + $scope.imgUrl);

            if ($scope.isToggled)
            {
                $scope.imgUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Grassland_wiki.jpg/640px-Grassland_wiki.jpg";
            }
            else
            {
                $scope.imgUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Monotropa_uniflora_03769.jpg/640px-Monotropa_uniflora_03769.jpg";
            }

            $scope.isToggled = !$scope.isToggled;

            console.log("Toggle(End): " + $scope.imgUrl);
        };

        $scope.ProcessKey = function (e)
        {
            if (e.key === "t")
            {
                $scope.Toggle();
            }
        };

        $scope.Toggle();
        document.addEventListener("keydown", $scope.ProcessKey);
    });

html

<div ng-controller="myCtrl" ng-app="myApp">
    <md-button class="md-raised" ng-click="Toggle()">Toggle</md-button>
    <img id="testImg" ng-cloak ng-src="{{imgUrl}}">
</div>

I'm stumbling through javascript and angularjs in support of a C# application which is where most of my experience is.

I am not sure what the root cause is. Perhaps it is something to do with the javascript event listener referencing a $scope function variable/expression? Part of that dependency is that in my main app, I'm using the $http service to get the image.

Is there an alternative to the way I'm doing this? Is there a way to force the image to rerender?

pigeon
  • 151
  • 9

3 Answers3

2

Because you adding an event outside AngularJS, Angular will never know that the $scope property changed. So you manually have to tell Angular something has changed using $scope.$apply().

var myApp = angular.module("myApp", [])
    .controller("myCtrl", function ($scope)
    {
        $scope.isToggled = false;

        $scope.Toggle = function ()
        {
            console.log("Toggle(Start): " + $scope.imgUrl);

            if ($scope.isToggled)
            {
                $scope.imgUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Grassland_wiki.jpg/640px-Grassland_wiki.jpg";
            }
            else
            {
                $scope.imgUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/01/Monotropa_uniflora_03769.jpg/640px-Monotropa_uniflora_03769.jpg";
            }

            $scope.isToggled = !$scope.isToggled;

            console.log("Toggle(End): " + $scope.imgUrl);
        };

        $scope.ProcessKey = function (e)
        {
            if (e.key === "t")
            {
                $scope.Toggle();
                $scope.$apply();
            }
        };

        $scope.Toggle();
        document.addEventListener("keydown", $scope.ProcessKey);
    });
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller="myCtrl" ng-app="myApp">
    <button class="md-raised" ng-click="Toggle()">Toggle</button>
    <img id="testImg" ng-cloak ng-src="{{imgUrl}}">
</div>
Red
  • 6,599
  • 9
  • 43
  • 85
1

You could use $scope.apply(handler)

$scope.apply(function () {
    // Angular is now aware that something might of changed
    $scope.changeThisForMe = true;
});
Impulse The Fox
  • 2,638
  • 2
  • 27
  • 52
Octavian Guzu
  • 57
  • 1
  • 6
0

Note: I prefer the $scope.apply(); solutions because they do not require a site element to be in focus first.


Because I'm stubborn, I found another solution with the goal of staying in AngularJS and thought I would provide it for completeness.

Based on the following post: AngularJS ng-keydown directive only working for <input> context?

I removed the following line from the javascript:

document.addEventListener("keydown", $scope.ProcessKey);

I made the following tweak to the html using ng-keydown which I previously could only get working on input tags. I added tabindex which allows an item to be in focus and solves the input tags problem:

<div ng-controller="myCtrl" ng-app="myApp" tabindex="0" ng-keydown="ProcessKey($event);">
    <md-button class="md-raised" ng-click="Toggle()">Toggle</md-button><br>
    <img id="testImg" ng-cloak ng-src="{{imgUrl}}">
</div>

Note that I could not get it to work on the body tag.

pigeon
  • 151
  • 9