0

I'm working with an old version of AngularJS (1.3). I've got a page that I want to conditionally show different things based on the value in the database. If the value in the database is changed via user interaction, I want to update what's shown automatically. Part of what I show, however, is HTML and in that HTML I need to include some AngularJS code.

If the value is True, I want to show this HTML:

Your value is True. To set it to False, <a ng-click="myToggleFunction('paramValueFalse')">click here</a>.

If the value is False, I want to show this HTML:

You haven't done the thing, to do the thing, <a ng-click="myDifferentFunction('someOtherParamValue')">click here</a>.

I've got it so close to working: the content that shows changes out depending on what the user's value is, and it updates appropriately, and it's even rendering the HTML correctly (using $sce)... But the ng-click isn't functioning. Can you include angular in HTML that's being injected via JS like that?

Full code:

HTML:

<span ng-bind-html="renderHtml(html_content)"></span>

Controller:

function MyCtrl ($scope, $http, $sce, Notification) {
    $scope.username = context.targetUsername;
    $scope.content_options = {
        'yes' : 'Your value is True. To set it to False, <a ng-click="myToggleFunction(" + "'paramValueFalse'" + ')">click here</a>.',
        'no' : 'You haven\'t done the thing, to do the thing, <a ng-click="myDifferentFunction(" + "'someOtherParamValue'" + ')">click here</a>.'
    }

    $http.get(
        '/api/v1/user/' + $scope.username + '/?fields=myBooleanField' // django rest api call
        ).then(function(response) {
            $scope.user = response.data;
            if ($scope.user.myBooleanField) {
                $scope.html_content = $scope.content_options['yes'];
            } else {
                $scope.html_content = $scope.content_options['no'];
            }
        });
    });

    $scope.myToggleFunction = function(paramValue) {
    // toggle value in the db

        if (accepted === 'true') {
            var success = "You turned on the thing";
            var content = "yes";
        } else {
            var success = "You turned off the thing";
            var content = "no";
        }

        $http({
            method: 'GET',
            url: '/api/v1/user/' + $scope.username + '/my_boolean_field/?value=' + paramValue, // django rest api call
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(function(response) {
            $scope.html_content = $scope.content_options[content];
            Notification.success(success);

        }, function(response) {
            Notification.error("There was an error.");
        });

    };

    $scope.myDifferentFunction = function(someOtherParamValue) {
       // do some other stuff
    };

    $scope.renderHtml = function(html_code) {
        return $sce.trustAsHtml(html_code);
    };
}

MyCtrl.$inject = ['$scope', '$http', '$sce', 'Notification'];
Heather
  • 43
  • 8
  • Where is your click handler defined? It would be helpful to see more code. – Kyle Lussier Apr 05 '18 at 16:48
  • Thanks Kyle. Just updated with more code. – Heather Apr 05 '18 at 17:17
  • This is because html code returned by renderHtml is not compiled by AngularJS. Instead of ng-bind-html, use https://github.com/incuna/angular-bind-html-compile – Sagar Apr 05 '18 at 17:46
  • Aha, I thought it might be something like that. Thanks Sagar. We don't currently use bower and it seems awfully heavy handed to install it just for this one tiny feature... I've tried other solutions like https://stackoverflow.com/questions/20623118/rendering-directives-within-sce-trustashtml and https://stackoverflow.com/questions/20358140/ng-click-doesnt-fire-when-added-post-load but I'm getting Error: [$rootScope:inprog]. Thoughts? – Heather Apr 05 '18 at 23:44
  • Turns out I had a random $scope.$apply() in there from when I thought that might fix the problem. Took that out and I wasn't getting the error any more, but it still wasn't working -- the HTML just wasn't displaying at all. I ended up going this route instead, calling angular's $compile from raw JS: https://stackoverflow.com/questions/22737927/angular-ng-bind-html-filters-out-ng-click/22739144#22739144 – Heather Apr 06 '18 at 22:20

2 Answers2

2

As Sagar said above, the reason this is happening is because the html code returned by renderHtml is not compiled by AngularJS. I tried a few different takes on creating a directive that recompiles angular. For example:

However, none of these were working for me. I'm not sure why; the content just wasn't displaying but there were no JS errors.

I ended up finding this solution, and it worked for me: Angular: ng-bind-html filters out ng-click?

Essentially, the solution is use raw JS to directly call the Angular functions, rather than using the ng-click directive in the JS-generated HTML content.

Here's what it looks like:

Template:

<div id="angularHtml" ng-bind-html="html_content">

<script>
function callAngular(controllerFunction, functionParam) {
    var scope = angular.element(document.getElementById('angularHtml')).scope();
    scope.$apply(function() {
        {# couldn't figure out how to reference the function from the variable value, so this is hacky #}
        if (controllerFunction == "myToggleFunction") {
            scope.myToggleFunction(functionParam);
        } else if (controllerFunction == 'myDifferentFunction') {
            scope.myDifferentFunction(functionParam);
        }
    });
}
</script>

Controller:

function MyCtrl ($scope, $http, $sce, Notification) {
    $scope.username = context.targetUsername;
    $scope.content_options = {
        'yes' : 'Your value is True. To set it to False, <a onClick="callAngular(\'myToggleFunction\', \'false\')">click here</a>.',
        'no' : 'You haven\'t done the thing, to do the thing, <a onClick="callAngular(\'myDifferentFunction\', \'someValue\')">click here</a>.'
    }

    $http.get(
        '/api/v1/user/' + $scope.username + '/?fields=myBooleanField' // django rest api call
        ).then(function(response) {
            $scope.user = response.data;
            if ($scope.user.myBooleanField) {
                $scope.html_content = $sce.trustAsHtml($scope.content_options['yes']);
            } else {
                $scope.html_content = $sce.trustAsHtml($scope.content_options['no']);
            }
        });
    });

    $scope.myToggleFunction = function(paramValue) {
    // toggle value in the db

        if (accepted === 'true') {
            var success = "You turned on the thing";
            var content = "yes";
        } else {
            var success = "You turned off the thing";
            var content = "no";
        }

        $http({
            method: 'GET',
            url: '/api/v1/user/' + $scope.username + '/my_boolean_field/?value=' + paramValue, // django rest api call
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(function(response) {
            $scope.html_content = $sce.trustAsHtml($scope.content_options[content]);
            Notification.success(success);

        }, function(response) {
            Notification.error("There was an error.");
        });
    };

    $scope.myDifferentFunction = function(someOtherParamValue) {
       // do some other stuff
    };
}

MyCtrl.$inject = ['$scope', '$http', '$sce', 'Notification'];
Heather
  • 43
  • 8
0

You can use ngShow and ng-hide for show and hide HTML dynamic

<div ng-show="DBvalue">Your value is True. To set it to False, <a ng-click="myToggleFunction('paramValueFalse')">click here</a>.</div>

<div ng-hide="DBvalue">You haven't done the thing, to do the thing, <a ng-click="myDifferentFunction('someOtherParamValue')">click here</a>.</div>
Mohammad Ali Rony
  • 4,695
  • 3
  • 19
  • 33
  • I should mention, I already went down that path -- see a previous question I asked here: https://stackoverflow.com/questions/49393127/angularjs-toggle-message-text-based-on-boolean/49393729?noredirect=1#comment85920263_49393729 – Heather Apr 05 '18 at 17:17
  • @Heather you can check this Answered https://stackoverflow.com/questions/19726179/how-to-make-ng-bind-html-compile-angularjs-code You can check this code http://jsfiddle.net/HB7LU/33128/ – Mohammad Ali Rony Apr 05 '18 at 17:52
  • Thanks Mohammad! Because we don't use bower, I tried creating the directive as defined here (https://github.com/incuna/angular-bind-html-compile/blob/master/angular-bind-html-compile.js) manually where our other directives are defined. This is similar to what you did in your jsfiddle. I added this to the HTML: `` and in the controller I'm escaping html_content like this: `$scope.html_content = $sce.trustAsHtml($scope.content_options['no'])`. Still getting a $rootScope:inprog error, much like the other solutions I tried (see comment above). – Heather Apr 05 '18 at 23:57
  • Turns out I had a random $scope.$apply() in there from when I thought that might fix the problem. Took that out and I wasn't getting the error any more, but it still wasn't working -- the HTML just wasn't displaying at all. I ended up going this route instead, calling angular's $compile from raw JS: https://stackoverflow.com/questions/22737927/angular-ng-bind-html-filters-out-ng-click/22739144#22739144 – Heather Apr 06 '18 at 22:20
  • @Heather Can you please mark this answer correct if answered is useful. – Mohammad Ali Rony Apr 07 '18 at 08:57
  • Mohammad, I got it to work but not using your solution. I just posted an answer - see above. – Heather Apr 09 '18 at 18:41
  • Problem-solving is the main goal. Happy to know it working now. – Mohammad Ali Rony Apr 09 '18 at 18:52