AngularJS - ng-bind-html-unsafe and ng-model Problems - angularjs

I have the following line in my html:
<div ng-bind-html-unsafe="departmentConfig" class="control-group"></div>
and I use a $resource get to retrieve the HTML, assign the HTML to $scope.departmentConfig, and then the view is updated perfectly. The HTML that is assigned to $scope.departmentConfig contains form elements, with ng-model attributes in it, but when I change the values in these input elements, they don't update the $scope model at all.
This is what I have tried, based on a lot of other internet posts, and it isn't working:
$resource('resources/sources/departments/:mappedName', {
mappedName:departmentKey
}).get(function(departmentConfig) {
// This will call another function that will build a chunk of HTML
$scope.departmentConfig = $scope.buildDepartmentConfigHtml(departmentConfig);
// This is my feeble attempt to access the element, and bootstrap it to include the items in the $scope model.
var $departmentConfigContainer = $('#departmentConfig');
angular.bootstrap($departmentConfigContainer, ['sourcemanager']);
I have even seen some jsFiddle examples where this appears to be working, but mine isn't. Am I calling bootstrap too soon? I also tried putting a $watch on $scope.departmentConfig like this:
$scope.$watch('departmentConfig', function() {
var $departmentConfigContainer = $('#departmentConfig');
angular.bootstrap($departmentConfigContainer);
});
but it didn't work either. I bet there is an easy explanation to this, I just can't seem to get the input elements with ng-model that are loaded after page compile to get bound to the model. Any help is appreciated, this is the last piece of functionality I need to get working on my page. Let me know if you need more information about my configuration as well.
So, simply put, how can I force a section of the DOM to recompile after I know it has been loaded?
UPDATE
Here is a jsfiddle outlining what I would like to do: http://jsfiddle.net/j_snyder/ctyfg/. You will notice that property two and three don't update the model, and I am calling bootstrap on the outer div, hoping that will include those in the model binding. This is the first time I have posted to jsfiddle, please let me know if you can't see the example.

ng-bind-html is made for regular HTML, not compiling new angular elements.
You will have use the $compile service.
Here is how you would edit your current example to work: http://jsfiddle.net/andytjoslin/ctyfg/21/. But this way ends up being bad, because you have to do DOM manipulation in your controller.
You just need to create a directive that will basically do what you wanted ng-bind-html to do: http://jsfiddle.net/andytjoslin/ctyfg/22/

Related

Angularjs dynamically added ng-model is not accessible by $scope, shows undefined

I am creating form, where few fields are dynamic, ng-model is added dynamically.
Ex.:
form.append("<input type='hidden' name='paymillToken' value='" + token + "' data-ng-model = 'formdata.token'/>");
This fields shows undefined while I try to access using $scope.formdata.token
Following is another scenario where I am adding fields via ajax.
angular.forEach(data.data, function(obj, key) {
list+='<div class="items text-center"><img src="assets/uploads/discs/'+obj.image+'" class="img-circle"><br><input type="radio" id="chkDisc'+obj.id+'" name="disc_id" value="'+obj.id+'" required data-ng-model="formdata.disc_id" /></div>';
});
$scope.discslist = $sce.trustAsHtml(list);
This model disk_id is not accessible too.
Okay, to expand on my comment and a bit more on what everyone else here is saying to you, the main issue you're having is inherent in your approach. The way you're trying to manipulate the DOM is very un-AngularJS.
In AngularJS, when you want to change what is displayed to the user (the view), you make changes to your model (your controller scope). That means, you have to set up your view to be able to respond to those changes. We do that with directives and expressions in Angular.
You're probably already using directives to respond to changes in your model whether you realize it or not. ngRepeat, ngModel, ngShow, ngIf, ngInclude, are a handful you're probably familiar with, and even forms and form elements like inputs are actually directives in Angular. When you use these, a change in your model (such as loading data into the controller scope) signals to Angular that it should check whether that change affects any of the directives in your view, and if so, respond to it by updating the view.
In order to do this, Angular needs to know which parts of the model are connected to which parts of the view. It makes these connections when it compiles the html elements that are added to the page. This compile process happens automatically when you load an Angular app. After that, it's up to us to tell Angular when to compile html that is added to the page.
More often than not, we do this without even realizing it. For example, when you use the ngView directive, it will compile the template for each route that it loads, so that all of the directives in your template are properly linked with their associated model.
I know this is a long explanation, but there are two very important points here that are essential to learning AngularJS:
To change the view, you change your model and let the directives (and expressions) on your page respond to those changes.
When you add html elements to the page, if you want AngularJS to be able to use them in your view, they must be compiled first. The compile process should be done via a directive (either a built in one or a custom one).
So, how does all of this apply to your question?
First, I'm guessing that you're breaking both rules by trying to manipulate the DOM via a controller. Even if it is possible to use $compile in a controller, using a controller to change the DOM is bad practice and simply wrong to do (read the part in that link to the doc that specifically states: Do not use controllers to: Manipulate DOM...). A good rule to remember when you're learning AngularJS is that the only time you should ever be using JQuery or JQLite inside Angular is when you are creating a custom directive.
Okay, so how do you solve your question? Use a directive. It looks like you've got a case where you're trying to iterate over an object called data and add some inputs that correspond to the data.data property. This sounds like a job for ngRepeat.
The first thing you need to do is add your data to your controller and make sure it is accessible to the view. The easiest way to do this is by injecting $scope into your controller and setting the data on a scope variable. In its simplest form, that might look something like this:
angular.module('MyApp', [])
.controller('MyController', ['$scope', function($scope){
$http.get('/some/url/that/returns/the/data').
success(function(data) {
$scope.data = data;
});
}]);
Now that we have the data somewhere that we can access from the view, we can use it with the ngRepeat directive in our html, something like this:
<div ng-controller="MyController">
<div class="items text-center" ng-repeat="disc in data.data">
<img ng-src="assets/uploads/discs/{{disc.image}}" class="img-circle"><br>
<input type="radio" id="{{'chkDisc' + disc.id}}" name="{{disc.disc_id}}" value="{{disc.disc.id}}" required data-ng-model="formdata[disc.disc_id]" />
</div>
</div>
This is a common issue. By updating the value and not the model angular has no idea that the value in the field has been updated. As the first commenter said updating in this manner is completely unnecessary when using ng-model.

How to render html formatted content in ng-grid?

I have jsonData which consist of HTML content. I want to render that HTML in ng-grid. The content is not rendered however -- it only shows up in normal string format.
Below is the plnkr which shows all the code:
http://plnkr.co/edit/RlWyDqCUUR15dmLM7ePV?p=preview
There's a few things going on here:
Not related to your exact issue, but you've got nested single quotes in your firstName field. You need to do some escaping or your ng-click expression is going to break. So instead of ng-click='show('F:/cox/main.html')' you can do ng-click=\"show('F:/cox/main.html')\".
Also not related to your exact issue, but if you want to access properties on your controller's scope from inside UI-Grid you need to use appScope. The docs on it are here: http://ui-grid.info/docs/#/tutorial/305_appScope
The main problem with getting HTML provided from inside your app to render is Strict Contextual Escaping.
Strict Contextual Escaping (SCE) is enabled by default in Angular and escapes any arbitrary HTML to prevent things like XSS and clickjacking. In order to get your HTML to show up you need to trust it, and bind it with an HTML bind expression.
You can use the $sce service to trust the HTML. Just iterate over your rows and do $sce.trustAsHtml(row.firstName). (You can read more about SCE here: https://docs.angularjs.org/api/ng/service/$sce) Then you will need a custom cell template to bind the HTML. The simplest one looks like this:
<div class="ui-grid-cell-contents" ng-bind-html="COL_FIELD"></div>
COL_FIELD will get transformed by UI-Grid into the proper binding for your field. Now the problem is that your ng-click directive doesn't get compiled. You need to use a directive that will take your custom HTML and compile it. You could roll your own, or use something like this library to do it for you: https://github.com/incuna/angular-bind-html-compile
The main thing to keep in mind is being able to actually trust the source of your HTML. If you can't be sure then going about this another way (i.e. by not providing HTML inside your data set) would be better.
I've modified your plunker to show all this working together: http://plnkr.co/edit/MgLoeGBoRTi2fF3e6pia?p=preview

Best way to modify html before binding to it in AngularJS directive

I'm writing an AngularJS app that gets a list of posts from a server, and then uses an ngRepeat and a custom post directive to output all the posts.
Part of the post object is a blob of html, which I currently add to the directive by first doing an $sce.trustAsHtml(blob), and then using the ng-bind-html directive and passing the trusted html blob to it. It works fine, but now I want to modify the html before adding it to the output. For instance, I want to find all link tags and add a target="_blank" to it. I also want to remove any content editable attributes from any element. etc.
What is the best way of doing this? I was thinking of just loading it up in a document fragment and then recursively iterating through all of the children doing what I need to do. But I assume there is a better AngularJS way to do this?
EDIT:
here is a codepen with an example of what I have:
http://codepen.io/niltz/pen/neqlC?editors=101
You can create a filter and pipe (|) your content through it. Something like:
<p ng-bind="myblob | myCleanupFilter">
Your myCleanupFilter would look something like this (not tested):
angular.module('myApp').filter('myCleanupFilter', function () {
return function cleanup (content) {
content.replace('......') // write your cleanup logic here...
};
});
If you want to add attributes that are themselves directives, then the best place to add them is in the compile function in a custom directive.
If they are just plain old attributes, then there's nothing wrong with hooking into DOM ready in your run block, and adding your attributes with jquery.
var app = app.module('app',[]);
app.run(function ($rootScope){
$(document).ready(function()
$rootScope.$apply(function(){
$('a').attr('title','cool');
});
})
});
If you want add the attributes after the compile phase but before the linking phase in the angular life cycle then a good place to do it is in the controller function for a directive that's placed on the body element.
<body ng-controller="bodyCtrl">
</body>
app.controller('bodyCtrl', function($element){
$('a', $element).attr('title','cool');
});
During the compile phase angular will walk the DOM tree, matching elements to directives, and transforming the HTML along the way. During the link phase, directives will typically set up watch handlers to update the view when the model changes. By placing a directive on the body element, it ensures that all directives have been compiled, but the linking phase hasn't started yet.

Run JavaScript when Angular controller has loaded DOM

I have an Angular template with an ng-repeat. That ng-repeat loads up textareas which I would like to autosize based on their contents. In order to do that, I need to initialize a plugin on each textarea element. The problem is the textareas need to exist in order to run the script on them, and unless I set a timeout I don't know when/where to run that init call. I need to run it once the controller has run and the ng-repeat has loaded up all the textareas into the DOM. I can't seem to find an event that fires when a controller is done doing it's thing.
This seems like such a common thing. What am I missing?
You should put this script in a directive and put it on the ng-repeat. Here is a link to a question that has a pretty good explanation on how you might get started.
<div class="MyTextArea" ng-repeat="text int texts" my-directive>{{text}}</div>

Angular directive failing silently (directive rendered too soon?)

I have an Angular app with a custom element directive (<uber-table>). <uber-table> is supposed to take a collection of objects, render them into a <table> and add some functionality (click row to toggle the underlying object as selected, search box for live filtering, action links on each row with customized click callback(s) for each object). I created a Plunker with the relevant code. Please note that Plunker is giving an error about map (Object [object Object] has no method 'map'), but locally I am not getting any errors.
The post-link function's element parameter is not the <uber-table> element as I expected. Instead it is the template's <div class="uber-table"> element. This is preventing me from extracting data from <uber-table>. What am I doing wrong? Any help will be much appreciated.
Here's a rundown on some of the issues.
First main issue is you want existing content already within the uber-table markup to exist, as well as a new template. Unless told otherwise the existing content ( columns) in this case will be overwritten. In order to include existing content in a directive that has a template, you need to set transclude:true then identify within template where this existing content needs to be placed using ng-transclude on element that will be parent of the content.
Here's demo with transclude fixed
New problems arise now where you are trying to use jQuery to loop over columns and return angular attrs => column.attrs . This throws undefined error.
I haven't tried to unravel this enough to sort out the columns issues yet. They should likely be handled by directive of their own
UPDATE: here's slightly revised error free version using jQuery to parse column count. I'm afraid am still confused a bit by structure of this. I don't see need to use jQuery to parse colunms, this could be converted to directive or pass column definitions into main directive from controller scope
After trying several things and looking at the documentation again, I finally got it working. The solution was to put the post-link function inside the compile function. Also I had to update my isolated scope to use =, set replace to true and set transclude to 'element'.
I updated Plunker if anybody wants to see the changes. The Plunker version isn't working, but since it is working locally, I'm not going to spend too much time on it.

Resources