Including AngularJS code in HTML generated in JS - javascript

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'];

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:
https://github.com/incuna/angular-bind-html-compile
Rendering directives within $sce.trustAsHtml
ng-click doesn't fire when added post load
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'];

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>

Related

Pass AJAX response within Angular Controller to bind HTML

I need to bind some HTML code to the response of a AJAX request. Since the AJAX is asynchronous, I need to place the code that does the actual binding within my AJAX callback function, like so:
(function() {
'use strict'
angular.module('poetry', [])
.controller('poetryController', poetryController);
poetryController.$inject = ['$scope', '$sce']
function poetryController ($scope, $sce) {
$.ajax({
type: 'GET',
url: myURL,
data: {'per_page': 100},
dataType: 'json',
success: callback
});
function callback(response) {
var list = new Array
for (var i = 0; i < response.length; i++) {
var string = '<li><h3>' + response[i]['title']['rendered'] + '</h3></li>';
list.push(string)
}
$scope.html_code = list.join("")
$scope.bindHTML = function(html_code) {
return $sce.trustAsHtml(html_code)
}
};
}
})();
What ends up happening is that the HTML doesn't get sent to the page, and I'm left with empty content. I'm quite confident the problem is in this line here
$scope.bindHTML = function(html_code) {
return $sce.trustAsHtml(html_code)
}
and something to do with the scope of the $scope.bindHTML variable.
What should I do to make $scope.html_code pass to the $scope.bindHTML function?
Also, no errors show in the console.
My HTML:
<body ng-app="poetry">
<div href="#" class="container" ng-controller="poetryController">
<ul class="grid effect-6" id="grid" ng-bind-html="bindHTML(html_code)"></ul>
</div>
</body>

prevent multiple upvote Angularjs

To begin sorry for my English.
I made some search about this problem, but nothing works for me.
My problem is simple :
I have an upvote system, where users can upvote (like Stackoverflow). But the problem is that users can upvote multiple times (and it's not very well ..).
I tried to make a button with ng-disabled, but if i do this, users can only vote one time for all the posts (I mean if they upvoted "Post A", they can't upvote "Post B").
Here is my controller function :
$scope.incrementUpvotes = function(post) {
posts.upvote(post);
};
Here is my factory function :
o.upvote = function(post){
return $http.put('/posts/' + post._id + '/upvote', null, {
headers: {Authorization: 'Bearer '+auth.getToken()}
}).success(function(data){
post.upvotes += 1;
});
};
And here the html :
<span class="glyphicon glyphicon-thumbs-up" ng-click="incrementUpvotes(post)"></span>
If someone could help me it would be great ! Thanks in advance !
Please try to use button or input instead of span. Because disabled does not work in a span.
<button id="{{ post._id + 'upvote' }}" class="glyphicon glyphicon-thumbs-up" ng-click="incrementUpvotes(post)"></button>
Then set disabled attribute of your upvote button true after the upvote action.
o.upvote = function(post){
return $http.put('/posts/' + post._id + '/upvote', null, {
headers: {Authorization: 'Bearer '+auth.getToken()}
}).success(function(data){
post.upvotes += 1;
angular.element(document.getElementById(post._id + 'upvote' ))[0].disabled = true;
});
};
This is more of an architectural question than it is javascript/angularjs question. There are many ways to do this. But the idea is to store in database and persist upvote information, otherwise users can just refresh the page and upvote again.
Most direct solution is: You have to store your vote information on the backend at the user level. When you query the user information, store a list of up-voted posts like this:
myDataService should look like this:
angular.module("app", []).service("myDataService", ["$http","$q","$rootScope", function myDataService($http, $q, $rootScope) {
var myDataService = this;
myDataService.getPostsUpvoted = function(userId) {
var defer = $q.defer();
//here you format your payload to deliver userId to the api backend
var payload = { userId: userId };
//your api backend 'getpostsupvoted' should accept a userId, then in return it should
//deliver a list of posts upvoted by that user
$http.post("/api/getpostsupvoted", payload).then(function (data, status, headers, config) {
//call success
defer.resolve(data);
}, function(data, status, headers, config) {
//an error occurred
defer.reject(data);
});
return defer.promise;
}
return myDataService;
})];
Then, this is how you'll use the service in your controller.
//inject scope and myDataservice into controller
angular.module("app", []).controller("MainCtrl", ["$scope", "myDataService", function($scope, myDataService) {
//create a new array to hold a list of upvotes
$scope.user.upVoted = [];
myDataService.getPostsUpvoted($scope.user.id).then(function(data) {
//set upvoted array to data
$scope.user.upVoted = data;
}, function(error) {
//blah an error occurred
});
})];
In your controller, provide a function to check if a post has already been upvoted.
$scope.IsUpvotedAlready = function(postId) {
if($scope.user.upVoted.indexOf(postId) > -1)
return true;
return false;
}
You'll also have to push the post._id to the upVoted array and keep it there.
$scope.incrementUpvotes = function(post) {
//check if it's already been upvoted, then do nothing
if($scope.user.upVotes.indexOf(post._id) > -1)
return;
posts.upvote(post);
$scope.user.upVoted.push(post._id);
};
Then, in your ng-repeat directive where you display the post, check if the list of upvotes contain the post._id.
<div ng-repeat="post in posts">
<div ng-if="IsUpvotedAlready(post._id)">
<!-- display something since already upvoted !-->
</div>
{{ post }}
</div>
In the template when you are iterating the posts, have a flag for each post which identifies if it has been upvoted or not.
In the upvote function mark that flag as true and use it in the ng-disabled directive like this :
ng-disabled={{post.isUpvoted}}

Searching a JSON file in AngularJS

So I have the following service/factory setup that should query a JSON file:
myappServices.factory('Result', ['$resource',
function($resource){
return $resource('search.json', {}, {
query: { method:'GET', params: {}, isArray:true }
});
}]);
And then in my SearchCtrl I have:
myappControllers.controller('SearchCtrl', function($rootScope, $scope, $state, Result, $location) {
$scope.query = ($state.includes('search') ? $location.search()['q'] : '');
$scope.filterQuery = ($state.includes('search') ? $location.search()['q'] : '');
$scope.queryChanged = function () {
if($scope.query){
$state.go('search', {'q': $scope.query} );
$scope.results = Result.query();
} else {
$location.search('q', null);
}
$scope.filterQuery = $scope.query;
}
if($scope.query){
$scope.results = Result.query();
} else {
$location.search('q', null);
}
});
The search results page looks like:
<ul class="ng-hide" ng-show="results.length > 0">
<li ng-repeat="result in results">
{{result.name}}
<p>{{result.snippet}}</p>
</li>
</ul>
<p class="ng-hide" ng-show="results.length == 0">Nothing here!</p>
If I do a query like:
/search?=hello
Then ALL the results in the JSON file are returned REGARDLESS of what I have searched for (should only be showing results that match).
However if I don't do a query then no results are shown (correct behaviour).
Why is the query not being used against the JSON file to filter the results?
I did originally have the repeater as:
<li ng-repeat="result in results = ( results | filter:filterQuery )">
But that would be incorrect in this case, as it's would be saying show me the results but filter them using the filterQuery. But in reality there would never be any results if no query, hence removing this part of the repeater. As I don't want to filter a list but return a list filtered.
So it would seem I am missing something in my service to handle this.
Edited: Simplify your resource so it doesn't define query:
myappServices.factory('Result', ['$resource',
function($resource){
return $resource('search.json');
}]);
Try changing your controller to clear the results and only set them when a query has been made:
myappControllers.controller('SearchCtrl', function($rootScope, $scope, $state, Result, $location) {
$scope.query = ($state.includes('search') ? $location.search()['q'] : '');
$scope.filterQuery = ($state.includes('search') ? $location.search()['q'] : '');
$scope.results = [];
$scope.queryChanged = function () {
if($scope.query){
$state.go('search', {'q': $scope.query} );
} else {
$scope.results = Result.query({q:$scope.query});
}
$scope.filterQuery = $scope.query;
}
});
app.factory('querySvc', ['$q', '$resource', querySvc]);
function querySvc($q, $resource) {
var service = {
getSearch: getSearch
}
return service;
function getSearch(){
var deferred = $q.defer();
$resource('search.json').query( function(data){
deferred.resolve(data);
}, function(error){
// handle error
});
return deferred.promise;
}
Then you can call it that way:
querySvc.getSearch().then(function (data){
$scope.results = data;
}, function(error){
//handle error here
});
About your first note :
When you instantiate your controller, it executes everything inside it.
So, if you want to call this function when a user clicks somewhere, you can register a function into the scope :
$scope.querySearch = function(){
querySvc.getSearch().then(function (data){
$scope.results = data;
}, function(error){
//handle error here
});
}
And then, into your view, you will have to call it when an event is fired.
I don't remember the event you need, but I'll show you a way of doing it :
<span ng-click="querySearch()" />
This will trigger the function when you click on the span.
In the console, look in the Network tab to see if the request has an error and/or to see the contents of the response. If the response has a status code of 200 and the contents are correct, then check the response to see if the data is structured correctly for results.length (that it returns a list).

AngularJS: display a select box from JSON data retrieved by http GET (from REST service)

I have a REST service that I made which returns a json string which is simply a set of strings (I used Gson to generate this string (Gson.toJson(mySetOfStrings))
So I have added to my index.html:
<div ng-controller="ListOptionsCtrl">
<form novalidate>
<button ng-click="refreshList()">refresh</button>
<select name="option" ng-model="form.option" ng-options="o.n for o in optionsList></select>
</form>
</div>
and in my script:
var ListOptionsCtrl = function ListOptionsCtrl($scope, $http) {
$scope.refreshList = function() {
$http({
method: 'GET'
url: '*someurl*'
}).
success(function(data) {
$scope.optionsList = angular.fromJson(data);
});
};
}
Unfortunately all this produces in my select box is an empty list. When I see what the response to the GET request is it returns a json string with content in it so I do not see why nothing is being added to this element. What am I doing wrong here? thanks
It is because Angular does not know about your changes yet. Because Angular allow any value to be used as a binding target. Then at the end of any JavaScript code turn, check to see if the value has changed.
You need to use $apply
var ListOptionsCtrl = function ListOptionsCtrl($scope, $http) {
$scope.refreshList = function() {
$http({
method: 'GET'
url: '*someurl*'
}).
success(function(data) {
$scope.$apply(function () {
$scope.optionsList = angular.fromJson(data);
});
});
};
}
Try this.
More about how it works and why it is needed at Jim Hoskins's post
You should check for $digest error by doing if(!$scope.$$phase) { ... } before doing $apply.
success(function(data) {
if(!$scope.$$phase) {
$scope.$apply(function () {
$scope.optionsList = angular.fromJson(data);
});
}
});

fire a event after finish write in angularjs

I would like fire a search after my user finish write (without a enter) in angularjs.
My html (simplified):
<div ng-class="input-append" ng-controller="searchControl">
<input type="text" ng-model="ajaxSearch" ng-change="search();">
</div>
My AngularJs (simplified):
$scope.searchControl = function() {
$scope.search = function(){
$http({
method: 'POST',
url: '<?php echo base_url('system/ajax_search/') ?>',
'data: search=' + $scope.ajaxSearch,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}).success(function(data) {
$scope.result = data;
});
}
The original code is extensive, so i simplified.
In my code, i post data always my user change the search.
I would like post data seconds after my user stop to write.
Any ideas?
This can be easily achieved with a directive:
angular.module('myApp', [])
.directive('keyboardPoster', function($parse, $timeout){
var DELAY_TIME_BEFORE_POSTING = 3000;
return function(scope, elem, attrs) {
var element = angular.element(elem)[0];
var currentTimeout = null;
element.onkeypress = function() {
var model = $parse(attrs.postFunction);
var poster = model(scope);
if(currentTimeout) {
$timeout.cancel(currentTimeout)
}
currentTimeout = $timeout(function(){
poster();
}, DELAY_TIME_BEFORE_POSTING)
}
}
})
.controller('testController', function($scope){
$scope.search = function() {
console.log("Executing query...");
}
})
And it can be used like this...
<div ng-app='myApp' ng-controller='testController'>
<input type="text" keyboard-poster post-function="search">
</div>
Use $timeout and cancel each time user types; if the timeout runs, executes the scoped function given as an attr. You can modify the delay time to whatever fits better your user experience (I wouldn't drop it below 1000 though).

Categories