Pass AJAX response within Angular Controller to bind HTML - javascript

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>

Related

Including AngularJS code in HTML generated in JS

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>

You cannot apply bindings multiple times to the same element

I have a Bootstrap modal, and every time it shows up I will use KO to bind a <select> dropdown.
HTML:
<select id="album" name="album" class="form-control" data-bind="options: availableAlbums">
</select>
JavaScript:
$('#uploadModal').on('show.bs.modal', (function () {
function AlbumsListViewModel() {
var self = this;
self.availableAlbums = ko.observableArray([]);
$.ajax({
url: "../../api/eventapi/getalbums",
type: "get",
contentType: "application/json",
async: false,
success: function (data) {
var array = [];
$.each(data, function (index, value) {
array.push(value.Title);
});
self.availableAlbums(array);
}
});
}
ko.applyBindings(new AlbumsListViewModel());
}));
However, on the second showing, KO will present me with this error:
Error: You cannot apply bindings multiple times to the same element.
The error message says most of it. You have two options:
Call the applyBindings function once, when your page loads. KO will automatically update the View when you update the model in a AJAX success function.
Call the applyBIndings function on each AJAX success, but supply additional parameters to tell it what element to bind to.
Most likely the first option is what you're looking for. Remove the call from the $('#uploadModal').on call and place it on document load (if you haven't already).
To see what I mean, here's two fiddles:
Your current code with the error you mention.
Refactored version that doesn't have the error.
The latter tries to stay as close as possible to your initial version (so as to focus on the problem at hand), and goes along these lines:
function AlbumsListViewModel() {
var self = this;
self.availableAlbums = ko.observableArray([]);
}
var mainViewModel = new AlbumsListViewModel();
ko.applyBindings(mainViewModel);
$('#uploadModal').on('show.bs.modal', (function () {
// Commenting things out to mock the ajax request (synchronously)
var data = [{Title:'test'}];
/*$.ajax({
url: "../../api/eventapi/getalbums",
type: "get",
contentType: "application/json",
async: false,
success: function (data) {*/
mainViewModel.availableAlbums.removeAll();
var array = [];
$.each(data, function (index, value) {
array.push(value.Title);
});
mainViewModel.availableAlbums(array);
/*}
});*/
}));

How to bind data in json to li using Angular js

am try to develop an application using angular js in which i take take data from database and populate li using that data
for that i write a WebMethod as fallow
[WebMethod]
public static string getname()
{
SqlHelper sql = new SqlHelper();
DataTable dt = sql.ExecuteSelectCommand("select cust_F_name,Cust_L_Name from customer");
Dictionary<string, object> dict = new Dictionary<string, object>();
object[] arr = new object[dt.Rows.Count];
for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
arr[i] = dt.Rows[i].ItemArray;
}
dict.Add(dt.TableName, arr);
JavaScriptSerializer json = new JavaScriptSerializer();
return json.Serialize(dict);
}
which return data in json form
am use the fallowing js to bind
var DemoApp = angular.module('DemoApp', []);
DemoApp.factory('SimpleFactory', function () {
var factory = {};
var customer;
$.ajax({
type: "POST",
url: "Home.aspx/getname",
data: JSON.stringify({ name: "" }),
contentType: "application/json; charset=utf-8",
dataType: "json",
async: true,
success: function (data, status) {
customer = $.parseJSON(data.d);
},
failure: function (data) {
alert(data.d);
},
error: function (data) {
alert(data.d);
}
});
factory.getCustomer = function () {
return customer;
};
return factory;
});
DemoApp.controller('SimpleController', function ($scope, SimpleFactory) {
$scope.Customer = SimpleFactory.getCustomer();
});
and my view is as fallow
<html xmlns="http://www.w3.org/1999/xhtml" data-ng-app="DemoApp">
<head runat="server">
<title></title>
</head>
<body data-ng-controller="SimpleController">
<form id="form1" runat="server">
<div>
Name<input type="text" data-ng-model="Name" />{{ Name }}
<ul>
<li data-ng-repeat="customer in Customer | filter:Name">{{ customer.cust_F_name }} -
{{ customer.cust_L_name }}</li>
</ul>
</div>
</form>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js"></script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="Script/Home.js" type="text/javascript"></script>
</body>
</html>
but it not working it will work fine if i hard code the data in factory but when i bring data using ajax call it will not work am unable to understand why it so.
Why it's not working?
you cannot just attach a variable to the scope when it's value is waiting for on asynchronous call.
when you use 3rd-party libraries that changes the scope you must call $scope.$apply() explicitly
prefer $http over $.ajax and use promises!
DemoApp.factory('SimpleFactory', function ($http) {
return {
getCustomer: function(){
return $http.post('Home.aspx/getname',{ name: "" });
})
}
}
DemoApp.controller('SimpleController', function ($scope, SimpleFactory) {
SimpleFactory.getCustomer().then(function(customer){
$scope.Customer = customer;
},function(error){
// error handling
});
});
If you still want to use $.ajax
you must explicitly call $scope.$apply() after the response
you must use promises or callbacks to bind to scope variables.
If you want to first fetch data from the server and than load the view
#Misko Hevery has a great answer: Delaying AngularJS route change until model loaded to prevent flicker
It's not related to your problem but load jquery before you load angular.js
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.7/angular.min.js"></script>
Your problem is the js (the function "SimpleFactory.getCustomer()") is returning before AJAX call returning..
Also, you should use $http in Angular instead of jquery's ajax, because:
$http returns a "promise" similar to other areas in angular, which means .success, .done are consistent with angular.
$http set the content type to 'application/json' for you on POST requests.
$http success and error callbacks will execute inside of angular context so you don't need to manually trigger a digest cycle - if you use jQuery, then it might be necessary to call $apply..
Like this:
var DemoApp = angular.module('DemoApp', []);
DemoApp.factory('SimpleFactory', ['$http', function ($http) {
var factory = {};
factory.getCustomer = function () {
var promise = $http.post('Home.aspx/getname', {name: ''});
promise.catch(function(error) {
alert(error);
});
return promise;
};
return factory;
}]);
DemoApp.controller('SimpleController', ['$scope', 'SimpleFactory', function ($scope, SimpleFactory) {
SimpleFactory.getCustomer().then(function(customer) {
$scope.Customer = customer;
});
}]);
Factories in AngularJS are singletons. So the way you've written the code will execute the ajax call when the factory is injected into the controller. You don't see the customer data because the server response will be handled after you assign the json data to the scope variable.
A quick (and dirty) fix which will probably work is wrapping the customer object:
DemoApp.factory('SimpleFactory', function ($rootScope) {
// ...
var customer = {};
// ...
$.ajax({
// ...
success: function(data) {
$rootScope.$apply(function() {
customer.data = data;
});
}
// ...
});
});
// In view
<li data-ng-repeat="customer in Customer.data"> <!-- ... --> </li>
A better approach would be to use either to use the builtin $http or the $resource angular service. The last one requires you to make use of RESTful services (recommended). If for any reason you still want to make use of jQuery ajax calls you need some form of telling Angular that the ajax call has been completed: have a look at the $q promise service.

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