Issue with element.html() in Angular directive - javascript

I am new to Angular. I have a directive and in the linkFunction I am able to set the scope to a certain value by using attributes["attribute-value"]. I was expecting element.html() to provide the inner html of the directive.
.directive("sfGroupbar", function () {
var linkFunction = function (scope, element, attributes) {
scope.text = element.html();
scope.attr = attributes["text"];
};
return {
restrict: 'E',
templateUrl: "controls/groupbar.html",
link: linkFunction,
scope: {}
};
In my view, I am using the directive like so...
<sf-groupbar warning="50" error="80" text="Web Server">Some Text</sf-groupbar>
In groupbar.html, I am using the code like so...
<div ng-controller="groupbarController">
{{text}} <br />
{{attr}}
</div>
I was expecting to see "Some Text" and "Web Server" as output. I am getting only Web Server as output, and instead of "Some Text", I am getting the following as output...
<div ng-controller="groupbarController">
{{text}} <br />
{{attr}}
</div>

You have to set transclude property of the directive to true and have to include ng-transclude attribute inside the template or templateurl 's HTML element to make innerHTML of the directive to render.
Here is the working plunker based on your code,
http://embed.plnkr.co/sXoLPxeFA21fxzzeAcVs/preview
Hope this helps!!!!

You need to include text and the other attributes in your scope definition like so
scope { text : '=' } and also you might wanna add transclude option to true to try and get the text inside your directive.
I'm sure you'll be interested to look at this part of the documentation
Directive to manipulate the DOM

Related

AngularJS - Setting a controller's $scope variable in a custom directive that accesses the DOM

I just answered a question here
How i send value when i click button to server and popup directive
His code looked like this
<div ng-controller="IndexController">
<label id="number" >5</label>
<input type="button" ng-click="getUserDetails()" >
</div>
He was trying to access the value of the label element with id #number.
I understand that in Angular, DOM manipulation and querying should be done in a custom directive, and not inside a controller. I therefore gave this solution.
Since you want to access the value of a number, you are interacting with the DOM. The best solution for this is creating custom directives. Here is an example.
angular.module('myApp', [])
.directive('getValue', function({
restrict: 'A',
link: function(scope, elem, attrs) {
elem.bind("click", function() {
scope.value = elem.prev().text();
});
}
});
I know that in a directive's link function, you pass in scope, element and attrs as a param. In this line, I'm trying to set a scope variable to the label's value.
scope.value = elem.prev().text();
But I have a suspicion that I'm not doing it correctly. Since directives are the way to go, how can I do this.
I also checked this as a reference
Easiest way to pass an AngularJS scope variable from directive to controller?
I've read about the controller function inside directives, but I need the link function for getting the value from the DOM.
There's no need for a custom directive for that. ng-click can work:
$scope.getUserDetails = function(e){
$scope.value = e.target.previousElementSibling.innerHTML;
}
HTML
<div ng-controller="IndexController">
<label id="number" >5</label>
<input type="button" ng-click="getUserDetails($event)" >
</div>
$event

Angular Expressions Missing in Dynamically Generated Template

I am having problems with a dynamically generated directive (using a function) and angular expressions being filtered out in the final markup ending up on the screen. I've created a JSFiddle displaying the problem here.
I am generating the template dynamically because we needed a way to generate the markup based on the attributes inside the element.
Basically when defining an expression like so:
angular.module('ui.directives', []).directive('uiBar', function() {
return {
restrict: 'E',
template: function(element, attrs) {
console.log('hello');
return '<div>lol: {{ user }}</div>';
}
};
});
And place it into the body like so:
<div ng-app="myApp">
<ui-bar>I should change to iambar</ui-bar>
</div>
The resulting markup is:
<div ng-app="myApp" class="ng-scope">
<ui-bar><div class="ng-binding">lol: </div></ui-bar>
</div>
The expression is stripped out for some reason. Has anyone experienced this?
I do not quite understand what you are trying to accomplish with a dynamically generated template. Below is what I would typically do to solve your example.
The HTML would look like this.
<ui-bar new-value="hello">I should change</ui-bar>
The return statement of the directive would look something like this
return {
restrict: 'E',
template: "<div>lol: {{ user }}</div>",
link: function (scope, element, attrs){
scope.user = attrs.newValue;
} // end link
} // end return
The above code works in your jsFiddle.
If you could add more information we may be able to give more examples.

AngularJS ngIf prevents finding element inside directive

I have an AngularJS directive that includes an ngIf and I would like to modify some of the DOM inside the ngIf in the directive link function. Unfortunately it seems that ngIf prevents me from finding DOM elements within it in the link function.
Here is the code for the directive:
directive('column', function () {
return {
templateUrl: 'views/column.html',
restrict: 'E',
scope: {
column: '='
},
controller: ['$scope', function ($scope) {
$scope.editing = true;
$scope.toggleEditing = function () {
$scope.editing = !$scope.editing;
};
}],
link: function postLink(scope, element) {
var select = element.find('select');
console.log(select); // See if it can find the select element
// var types = scope.column.types();
// add types as options to the select element
}
};
});
And here is the simplified html of the directive:
<div class="column">
<div>{{ column.title }}</div>
<form name="columnForm" role="form" ng-if="editing">
<select></select>
</form>
</div>
Here is the link to the jsFiddle example http://jsfiddle.net/dedalusj/Y49Xx/1/
The element.find call in the link function returns an empty array but as soon as I remove the ngIf from the form it returns the proper select DOM element. I have the feeling that I'm doing this the wrong way.
UPDATE
Thanks for the answers but I found another solution. I simply created another directive that encapsulate the form, added it to the column directive template with ng-if="editing".
The form directive doesn't have it's own scope so it effectively operates out of the column directive scope and has always access to the select element because it's inside its DOM tree. I pay the cost of an extra directive but I don't have to use the $timeout hack. I created a new jsFiddle to illustrate the solution http://jsfiddle.net/dedalusj/nx3vX/1/
Thanks #Michael but I can't simply use the ng-option because the types array comes from an XML file and its elements are other angular.element objects which cannot be inserted easily with ng-option.
The ngIf directive works by using Angular's transclusion feature. What happens during the compile/link cycle is:
The content inside the ngIf is removed from the DOM when it is compiled
Angular runs the link functions. The ngIf's link function is run before
the link function of the directive using it. When ngIf's link function
runs, it uses $scope.$watch() to watch the value of the ng-if
attribute.
Your directive's link function runs, at this point the content of the ngIf is not part of the DOM
The watch set up in step (2) is called, and ngIf will then call the $transclude function to insert the contents of the ngIf into the DOM if the ng-if attribute value is truthy.
Any watch functions, $timeout calls or use of $scope.$evalAsync that you registered in your directive's link function will run.
So if you want to access elements inside the ngIf's content, the code needs to run after step 4 above. This means that any functions registered with $scope.$watch, $timeout or $scope.$evalAsync in your directive's link function will work. For a one-time piece of setup code, I would probably opt for $scope.$evalAsync:
angular.directive('yourDirective', function () {
return {
...
link: function(scope, elem) {
scope.$evalAsync(function () {
// code that runs after conditional content
// with ng-if has been added to DOM, if the ng-if
// is enabled
});
}
};
});
As #moderndegree has said, ngIf removes the element it's applied to from the DOM, so you won't be able to find it when it's not there. But, you could write your directive in a way to workaround that:
controller: function ($scope, $element, $timeout) {
$scope.toggleEditing = function () {
$scope.editing = !$scope.editing;
$timeout(function() {
var select = $element.find('select');
select.append('<option>Value 1</option>')
.append('<option>Value 2</option>')
.append('<option>Value 3</option>');
});
};
}
Updated jsFiddle here.
The trick here is to delay the find() call by using $timeout with a 0 interval in order to wait for Angular to update the DOM.
UPDATE
After giving some more thought to your code, I realize that perhaps you can let Angular do the hard work for you:
Javascript
directive('column', function () {
return {
templateUrl: 'views/column.html',
restrict: 'E',
scope: {
column: '='
},
controller: ['$scope', function ($scope) {
$scope.editing = true;
$scope.toggleEditing = function () {
$scope.editing = !$scope.editing;
};
}],
};
});
HTML
<div class="column">
<div>{{ column.title }}</div>
<form name="columnForm" role="form" ng-if="editing">
<select ng-model="type" ng-options="type for type in column.types"></select>
</form>
</div>
jsFiddle
Now you don't need to worry about finding the select element at the right time and populating it. Angular does all of that for you. :)
You can put your code from the link function inside $timeout.
$timeout(function(){
var select = element.find('select');
console.log(select);
});
Don't forget to inject $timeout in your directive
directive('column', function ($timeout) {
I was facing this same issue and i was able to resolve it using ng-show, this prevents this issue because ngIf removes the element it's applied to the DOM, so you won't be able to find it when it's not there.
so in your case:
<div class="column">
<div>{{ column.title }}</div>
<form name="columnForm" role="form" ng-show="editing">
<select></select>
</form>
will work OK.
Cheers.

Angularjs - ng-if - bypass / work around the scope logic

I am aware that use of ng-if dictates that ng-if destroys the scope on the element unlike the use of ng-show or ng-hide.. I however need to use ng-if (ng-show / hide is not an option) because I actually need the element to not render on the page when ng-if is falsely..
I use ng-if as part of a directive template..
My directive
app.directive("myDirective", function ($parse) {
return {
restrict: "E",
scope:{},
controller: function ($scope, $element, $attrs) {
// controller code
},
templateUrl: "template.html",
compile: function(elm, attrs){
var expFn = $parse(attrs.atr1 + '.' + attrs.atr2);
return function(scope,elm,attrs){
scope.$parent.$watch(expFn, function(val){
scope.exp = val;
})
scope.$watch('exp', function(val){
expFn.assign(scope.$parent, val)
})
}
}
}
})
My template
<div ng-click="view = !view">{{exp}}</div>
<div ng-if="view">
<input type="text" ng-model="exp"><br/>
<div class="btn btn-default" ng-click="Submit()">Submit</div>
</div>
Is there a way to "bypass" the ng-if behavior,,, to KEEP the scope,, or recreate it?
I created the attach-if directive for this purpose.
The code is available at https://github.com/Sparks-Creative-Limited/angular-attach-if and, if you use bower, it's registered as a downloadable component with the following details:
angular-attach-if 0.1.0
There is also a demo page to show how it works.
If you are using ng-if it won't create the element at all period. So the directive does not exist in that instance. I know you mentioned ng-hide/show is not an option but I think its the only option. What is your reason for not wanting it to render and keep its scope? which by the way defeats the purpose of the directive. If you are doing some logic in the directive that doesn't require the view, it should belong somewhere else.
If the scope has single/few variable, ng-init="obj=$parent.obj" on the same tag where ng-if is, would help.

ng-click in the inner template of the directive is not providing the functionality

ng-click is not providing alert. When the inner template of the directive is clicked alert box is not shown.
The fiddle link is here: http://jsfiddle.net/NNDhX/
Your directive has its own isolated scope. So function 'hi' should be inside directive's scope. If you want to pass your controller's function you should do binding, like scope: { ..., hi: '&' } and then <you-directive hi='hi' ..>. Here is a link to documentation about this: Understanding Transclusion and Scopes.
So just adding it in linking function is enough:
link: function(scope, element, attrs) {
scope.hi = function() { alert("hi"); }
Here is updated fiddle: http://jsfiddle.net/GwBAh/
I don't know if this is the best way but you can use $parent in directive to access parent scope.
<a ng-click="$parent.hi();">parent</a>
Here is link to full fiddle example: http://jsfiddle.net/EKDse/
The whole idea of isolate scopes is exactly to avoid 'sharing' things between parent<->child scopes, somehow protecting them from being exposed and (unintentionally) changed by other directives/controllers.
If you really want to avoid the isolate scope and share the parent's scope, try this:
Start by removing the directive's scope definition (commented below):
transclude: true,
/*scope: { title:'#zippyTitle' },*/
Then place the attributes (attrs) from the directive element on the directives' scope:
scope.attrs = attrs;
Note: By doing this the attrs property will also be available on the parent (Ctrl3) scope.
And finally define the title based on the scope.attrs
template: '<div>' +
'<div class="title">{{attrs.zippyTitle}}</div>' +
'<div class="body" ng-transclude></div>' +
'<a ng-click="hi();">hi</a>' +
'</div>',
jsFiddle: http://jsfiddle.net/NNDhX/1/
Controller function inside a directive must be called within the transcluded block. Using the controller method inside the template of the directive make your directive depends on controller and is a undesirable design by its depedencies (directive-outer controller).
In your sample traslate the <a> piece of code to the transcluded block. It made your directive more isolated and solved the problem:
<div class="zippy" zippy-title="Details: {{title}}...">
{{text}}
<a ng-click="hi();">hi</a>
</div>

Categories