get an element attributes values using Angular after using template - javascript

I am relatively new to Angular and this my first attempt at using a custom directive. I have a table I am working with and each cell has a data attribute. For example, "data-dept="service", and I would like to access this value before overwriting it with a directive. In my directive, I have set template = true, and this removes my data attribute.
I liked my website that I am working to better explain what I am attempting to do.
http://partnerportal-preprod.automate-webservices.com/list/#/hoursTable
The cells inside the table in the first row are clickable, but I would like to know if the user is clicking on service for example.
Update
I created plunker to better illustrate my problem.
I have a basic table that I am using a directive to replace a row in the table.
In doing so, I lose the attribute that I would need to set it as a key in my object for later use.
http://plnkr.co/edit/oXRM6lRkidnAHfBA4GsR?p=preview
HTML
<tr>
<td name="valueIneed" hello-world>John</td>
<td>Doe</td>
<td>john#example.com</td>
</tr>
Directive
app.directive('helloWorld', function($parse) {
return {
template: '<td><input type="text"></td>',
replace: true,
transclude: 'true',
scope: {
position: '='
},
link: function (scope,element,attrs,ngModel) {
console.log(attrs.attribute);
scope.clickMe = function () {
console.log('clicked');
scope.isChecked = true;
};
}
};
});

First remove replace: true.
Second to see the value of the name attribute:
console.log(attrs.name);
By using replace: true, the directive was replacing the element that had the name attribute.
replace:true is Deprecated1
From the Docs:
replace ([DEPRECATED!], will be removed in next major release - i.e. v2.0)
specify what the template should replace. Defaults to false.
true - the template will replace the directive's element.
false - the template will replace the contents of the directive's element.
-- AngularJS Comprehensive Directive API
From GitHub:
Caitp-- It's deprecated because there are known, very silly problems with replace: true, a number of which can't really be fixed in a reasonable fashion. If you're careful and avoid these problems, then more power to you, but for the benefit of new users, it's easier to just tell them "this will give you a headache, don't do it".
-- AngularJS Issue #7636

Related

Directives in Angular

I have this directive view here in my code:
<div class="busy-indicator angular-animate" ng-show="busy"></div>
<div class="breadcrumblist" ng-class="atTopLevel ? ['at-top-level'] : null">
<div class="breadcrumblist-display angular-animate">
<label id="searchBox">Search</label><input class="c-context-menu-container t-menu-background c-context-menu-text" type="text" autocomplete="off" id="IdSearch" ng-model = "searchText.Name">
<div class="breadcrumblist-parents">
<div ng-show="::parents && parents.length >= 1"
ng-repeat="parentLink in parents"
class="breadcrumblist-parent-link t-breadcrumb--parent-bgcolor t-border--bottom-grey48"
ng-click="navUpToParent(parentLink)"
ng-class="{'selected': isSelected(parentLink.object), 'draggable': isDraggable(parentLink.object)}"
data-index="{{$index}}">
</div>
But, the searchBox is appearing for all places on my app but I want to make it appear just for one directive in particular. What should I do? I tought about make a scope variable to just "ng-show" this particular searchbox with a condition, but I don't know exactly how to do that, can you help me?
Until you give more information to your specific problem, here are some possibly relevant things you can do to debug your problem and find a solution
the searchBox is appearing for all places on my app but I want to make it appear just for one directive in particular
The combination of the name of the directive + its restriction might cause the directive to appear in unwanted locations.
More information on restrict if needed.
So for example, if you have a directive called 'breadcrumblistParentLink', which is restricted to C (class) - you might find it in undesired locations since you're also using this class to style some elements on your page.
If that's the case, you might find it helpful restricting directives to attributes and elements and giving some unique names.
I would also like to refer to your question
just "ng-show" this particular searchbox with a condition, but I don't know exactly how to do that
If you want a specific behavior for one instance of a directive, there are multiple ways to do that.
Most common is passing an attribute. For example, this is how you use it
<div my-awesome-directive show-searchbox="true"></div>
And this is how you'd put show on the directive scope
angular.module('myApp',[]);
angular.module('myApp').directive('myAwesomeDirective', function(){
return {
template: 'this is my template <span ng-show="showSearchBox">This is search box</span>',
restrict: 'A',
scope: {
showSearchBox: '<'
},
link: function(scope, element, attrs){
console.log(scope.showSearchBox);
}
}
})
You can play around with it here: https://plnkr.co/edit/4MdNeEafbZjq2kEFKYAl?p=preview
You can also look directive at attrs variable (attrs.showSearchBox) and spare the binding in some cases.
For example:
angular.module('myApp',[]);
angular.module('myApp').directive('myAwesomeDirective', function(){
return {
template: 'this is my template <span ng-show="showSearchBox()">This is search box</span>',
restrict: 'A',
scope: {
},
link: function(scope, element, attrs){
scope.showSearchBox = function(){
return attrs.showSearchBox;
}
}
}
})
Note I am using a function on the scope.
This function can also refer to the DOM and do something like $(element).is('[show-search-box]') if you are more comfortable with jquery - but it is highly recommended to stick to angular way of doing things.

$compile doesn't work inside Angular directive

with this Angular directive every time my model changes, new HTML item appended to the page:
app.directive('helloWorld', function($compile) {
return {
restrict: 'AE',
replace: true,
scope:{
arrayItem: '=ngModel'
},
link: function ($scope, ele, attrs) {
$scope.$watch( 'ngModel' , function(){
ele.html('<div ng-click="sendLike({{arrayItem.data.timeline_content}})" class="timeline-item"> Hello {{arrayItem2.data.timeline_content}} </div>');
$compile(ele.contents())($scope);
});
}
};
});
And this is HTML view:
<hello-world ng-repeat="arrayItem in arr" ng-model="arrayItem"></hello-world>
But ng-click inside dynamically generated HTML doesn't work. actually recompiling of this new added section does not works.
UPDATE:
this is what i want to achieve:
in fact i want to create a chat Application. messages stored inside an Array and i have to bind that array to the HTML view. if i click on every message, i need to an alert() fired inside the controller. my controller is like this:
app.controller("timelineCtrl", function ($scope) {
$scope.arr={};
$scope.sendLike = function (id) {
alert(id);
};
.
.
.
}
in the jQuery way, i simply use DOM manipulation methods and add new tags for each message but in Angular way i have to bind that array as a ng-model or something like that.
at first glance, i realize that designing a directive should be a good idea and inside module i can access to main scope and do needed thing with that directive and i expect that changes inside that directive should projected to HTML view but it fails and things like ng-click doesn't work for dynamically created tags.
There are two ways that you could accomplish this, with or without a directive.
Let's start without a directive; we'll assume that you have an array in the controller.
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<div ng-click="sendAlert(post)">
<span class="postnumber">{{::post.lineNumber}}:</span>
<span class="message">{{::post.message}}</span>
</div>
</div>
</div>
whenever an object is added to $scope.timeline, it can be assigned a lineNumber, and we can use angular OrderBy to sort the direction in reverse lineNumber order (using -). The $scope.sendAlert(post) will send the specific post to the function. in our bindings, we use :: to indicate that these are one time bindings, i.e. not values that need to be monitored independently of the array. This can improve performance on large lists.
using a Directive, we can accomplish this in a very similar manner, by making a Directive that renders a specific post, and passing the post in as a property.
app.directive('timelinePost', function() {
return {
restrict: 'AE',
scope:{
post: '='
},
template: '<div ng-click="postCtrl.sendAlert()">
<span class="postnumber">{{::postCtrl.post.lineNumber}}:</span>
<span class="message">{{::postCtrl.post.message}}</span>
</div>',
controller: 'postController',
controllerAs: 'postCtrl',
bindToController: true
};
app.controller("postController", function(){
var self = this; //save reference to this
self.sendAlert = function(){
//the specific post is available here as self.post, due to bindToController
};
};
//usage in HTML:
<div ng-controller="timelineCtrl" class="timelineframe">
<div ng-repeat="post in timeline | orderBy:'-lineNumber'" class="post">
<timeline-post post='post'></timeline-post>
</div>
</div>
you could further wrap this timeline in a directive in a similar manner, if you so desired. Either of these will accomplish the same task, of looping through your data, ordering it so that the newest post is at the top, and updating whenever the array changes. In the non-directive method, the timelineCtrl handles the $scope.sendAlert function; in the directive method, it is handled by the directive controller postController.
Please note, this is a rough draft based on what you have described and the information from various posts over the last 2 days. I haven't created a dataset to iterate through to test thoroughly, but the logic here should get you started.

How to hide an element after $compile?

There is a directive:
.directive('location', function () {
return {
restrict : 'A',
scope : {},
replace : true,
templateUrl: 'common/components/location/location.html',
link : function (scope, element, attr) {……}
}
});
So it is used in another directive:
var scope = $rootScope.$new(true);
var directive = $compile('<div location></div>')(scope);
$document.find('body').append(directive);
directive.hide(); - not working
How to hide directive generated html after inserting it to body? Directive has "replace" set to true
http://plnkr.co/edit/e7fNua?p=preview
The line you've marked as wrong is actually working meaning that after directive.hide() if you print the element on console you'll see:
console.log(directive[0]);
-> <div location="" class="ng-scope" style="display: none;"></div>
This one is a bit tricky but easy if you carefully follow what you told angular to perform. Let's go through it step by step
$compile('<div location></div>')(scope); - here you're telling angular to compile given template and link it with given scope. Since you have specified templateUrl that must be fetched angular has not yet completed processing this element.
$document.find('body').append(directive); directive.hide(); - append the element to body and immediately hide it. If you print the directive[0] to console you'll see: <div location="" class="ng-scope" style="display: none;"></div>
Note that there is still no <section><p>Text</p></section> inside DOM
Angular completes fetching location.html template.
Since you have replace: true inside directive declaration the directive[0] is removed from DOM and replaced by compiled <section><p>Text</p></section>.
As a result you still see Text on page.
You can change this behaviour in various ways:
change directive declaration to replace: false
use inline template: '<section><p>Text</p></section>'
wrap template that you pass to $compile in another element: $compile('<div class="wrapped"><div location></div></div>')

How can I use a directive wrapper for ng-grid with the grid options determined by a directive attribute?

I am attempting to create a re-usable directive wrapper for ng-grid where I can apply the location of the ng-grid options through the use of an attribute.
Here is the skeleton of the code which gets very close to what I want:
angular.module('myApp').directive('grid', function() {
return {
restrict: 'E',
template: '<div class="gridStyle" ng-grid="someObject.gridOptions"></div>',
link: function(scope, element, attributes) {
// no code here necessary, to use the hard-coded ng-grid options
}
}
}]);
What I would like to do is supply the variable for ng-grid by using an attribute, similar to this:
<grid dataLocation="someObject.gridOptions"></grid>
I've tried using the compile and the link options with multiple methods, including reading the attributes, and then using the element.html() method to update the html to set the ng-grid attribute to "someObject.gridOptions", as well as using {{someScopeVariable}}, and setting scope.someScopeVariable to "someObject.gridOptions" in the linking function. I have verified using the chrome's html inspector that the div's attribute looks correct, but I have not been able to get the item to show up on my page.
I suspect I'm running into issues since the ng-grid is already a compiled directive. Is there any way I can pull this off? I've tried a large number of compile and linking methods with no success yet.
To access the original template's attributes, the template option of directive should be defined as a function as follows:
angular.module('myApp').directive('grid', function() {
return {
restrict: 'E',
template: function(element, attrs){
return '<div ng-grid="' + attrs.dataLocation + '"></div>';
}
};
});
Your HTML stays the same:
<grid dataLocation="someObject.gridOptions"></grid>

Angular directives with ng-if seem to loose model

I am fairly new to Angular and trying to make a directive that will construct a form input, usually a text-input, but sometimes a select box based on whether or not the input is associated with an array of options. Simplifying down, my code looks roughly like this:
html
<init ng-init = "ops = [
{value:'hello',label:'Hello All'},
{value:'bye',label:'Good-bye everyone'}]"></init>
<init ng-init = "fType =
{id:'greeting',label:'Greeting',type:'enum', 'options':ops}">
</init>
<simpleselect field="fType" ng-Model="foomodel"></simpleselect>
{{foomodel}}
Directive
.directive('simpleselect',function(){
return {
restrict: 'E',
replace:true,
template:[
'<div><select ',
'ng-if ="type=\'select\'"',
'name="{{field.id}}"',
'ng-model="ngModel" ',
'ng-options="option.value as option.label for option in field.options">',
'</select>{{ngModel}}</div>',
].join(),
scope:{
field:'=',
ngModel:'='
},
link:function(scope, elem, attrs, ctrl){
scope.type = 'select';
}
}
});
This almost works. If I remove the ng-if on the select box, my select box and my model stay in sync just fine. But what I want is to be able to choose which control within the directive. Is this a misuse of ng-if and is there another path?
Can use template:function(element,attrs) if using angular version >=1.1.4
template:function(element,attrs){
var template='<div>';
var type= attrs.fieldType;
if( type=='select'){
template+='<select ng-options=......>';
}
if( type=='text' ){
template +='<input ......./>';
}
template +='</div>';
return template;
}
Modify your template as follows:
template: [
'<div ng-if="field.type==\'select\'">', // <-- move ng-if here
'<select name="{{field.id}}"',
'ng-model="ngModel" ',
'ng-options="option.value as option.label for option in field.options">',
'</select>',
'{{ngModel}}',
'</div>'
].join(''),
Also note there are couple of errors:
1). ng-if should have == instead of = and field.type instead of just type
2). .join('') instead of .join()
Demo http://jsfiddle.net/2YE3b/
As a couple of folks suggested, I could have used ng-show, but I didn't want to pollute my DOM with all the input types I was not using. I also could have set my directive with a list of individual properties instead of passing in a 'field' object and then watching them in my template function to determine the particulars of my input, like charlietfl's solution.
Instead, since I want to determine which input type control to use based on a number of attributes in the model itself, I have chosen to resolve a good portion of the rendering of my control in the link method of my directive, using the $compile service. Then I can both make macro layout decisions based on the model I pass into scope and still resolve the particulars of each input using angular style template syntax.
For a simple selectbox, this would have been overkill and either of the two other answers here would have been better, but because I want my directive to determine if a control should be a text input, textarea, selectbox, radio buttons, or checkboxes depending only on the requirements of the model I need to be able to read the model first and then compile with it.
Doing rendering in the link method feels a bit wrong, so I don't mean to be saying I have a great solution, but if it helps anyone, that's great. If others with more experience with Angular than me find that offensive, I would also love to be straightened out. :^)
Here is an example of my more complicated checkbox option within the directive:
link:function(scope, elem, attrs, ctrl){
...some logic to examine the model to determine which input type to use...
if(scope.type === 'checkbox'){
if(typeof scope.ngModel === 'string') scope.ngModel = scope.ngModel.split(/[ ,]+/);
tmp = [
'<div class="option chk tall" ng-repeat="option in field.options">',
'<label><input ng-model="ngModel" ng-value="option.value" ng-checked="ngModel.indexOf(option.value) > -1" name="{{field.id}}" type="checkbox" />{{option.label}}</label>',
'<div class="description">{{option.description}}</div>',
'</div>{{ngModel}}'].join('');
elem.on('change',function(e){
if(e.target.checked && scope.ngModel.indexOf(e.target.value) < 0) scope.ngModel.push(e.target.value);
if(!e.target.checked)
scope.ngModel.splice(scope.ngModel.indexOf(e.target.value),1);
});
}
elem.find('div').html(tmp);
$compile(elem.contents())(scope);
}
I am not at all in love with the on-click stuff to keep my model and UI in sync, but for now, I am going to live with it.
I had a similar problem and you can actually access the parent model via $parent.boundattribute.
As described somewhere in the comments, ng-if adds a subscope and thus the model does not update backwards.
In my case ng-show would not work, I had to really remove the part of the DOM and this solved the problem.
<select ng-if="type='select'"
name="{{field.id}}"
ng-model="$parent.ngModel"
ng-options="option.value as option.label for option in field.options">
</select>

Categories

Resources