Directives in Angular - javascript

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.

Related

Return value of an Angular Directive and based on value have ng-if to display content

I have a Directive that returns a 0 or 1
<my-code inputcode="{{ data.valBit }}"></my-code>
So data.valBit will be a 0 or 1
If it is a 1 , then I want it to display an HTML section of code, how can I do that? Problem with returning all the html from the directive is that it is put of a form along with ngmessage validation , so it is not fun to pass form or mess with link or compiler
<div ng-if="data.valBit == 1">
part of my form, with ngmessage etc... $error etc...
.......
</div>
Is this possible to get a callback from a directive that I can evaluate and decide whether to show or hide a section of html?
Most simple way to do it is taking advantage of two way data binding.
In Your case, the problem is that your are passing to the directive the evaluated value instead of the variable. Just remove the braces in your directive argument <my-code inputcode="data.valBit"></my-code>.
Example: (PLUNKER)
HTML
<my-code inputcode="data.valBit"></my-code>
<p ng-if="data.valBit == 1">
part of my form, with ngmessage etc... $erroretc...
</p>
DIRECTIVE
app.directive('myCode', function() {
return {
restrict: 'E',
scope: {
inputcode: "=" //two way binding
},
link: function(scope, element, attrs){
scope.inputcode = 1;
}
}
})

Angular js : Unable to create directive

I am trying to create directive but my directive function is not getting called.
index.html
<div ng-repeat="que in questions.Cars">
<question-dir>print from direcetive</question-dir>
</div>
ConytrolDirective.js
(function () {
"use strict";
angular
.module("autoQuote")
.directive('questionDir',["questionDir"] );
function questionDir()
{
return {
template : "<h1>Made by a directive!</h1>"
};
}
}());
There are several mistakes in your code.
You should have function name instead of "questionDir"
.directive('questionDir',[questionDir] );
Use kebab case(- separated words) while using directive name
`<question-dir>print from direcetive</question-dir>`
Additionally you need to refer <script src="controlDirectives.js"></script> on your index.html page.
Demo here
I don't see many mistakes in your code but it would have helped many to stop assuming only if you had posted your code sample link too.
But one primary change you have to make is : Change your directive definition function invoking from
.directive('questionDir', ["questionDir"])
to
.directive('questionDir', questionDir)
See this working fiddle which has just added questions.Cars into $rootScope to make answer look more relevant to your query.
To preserve content of your directive element :
What i see in your question is : Element <question-dir> has child content / inner text print from direcetive in it and it gets overridden by Directive Element <h1>Made by a directive!</h1> completely.
That's the default nature of Angular : Element's (to which the directive is being applied) children will be lost to Directive Element. If you wanted to perserve the element's original content/children you would have to translude it.
Use transclude : true in Directive Definition Object and add <div ng-transclude></div> where you want directive Content / text print from direcetive to be included.
Code snippet :
function questionDir() {
return {
transclude : true,
template: "<div ng-transclude></div><h1>Made by a directive!</h1>"
};
}
Check this one for transclude changes.
Check these 2 links for more information on Transclude :
Understanding the transclude option of directive definition
Understanding transclude with replace
You had a few things wrong in you code:
In your IIFE closure you need to pass in angular
You need to normalize the directive name to kabab case in your html element name: `'
You need to include the reference to the directory factory in your directive registration: .directive('questionDir', questionDir);
Missing module dependencies array: .module("autoQuote", []) -- you should only have this defined once with the dependancies, [] so ignore this if you already have it elsewhere in your code.
Missing restrict in the Directive Definition Object: restrict: 'E',
If you click on Run code snippet you will see this directive now works.
(function (angular) {
"use strict";
angular
.module("autoQuote", [])
.directive('questionDir', questionDir);
function questionDir()
{
return {
restrict: 'E',
template: "<h1>Made by a directive!</h1>"
};
}
}(angular));
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="autoQuote">
<question-dir>print from direcetive</question-dir>
</div>
Finally, if you are using AngularJS 1.5 or greater you should consider using the component syntax for this.

How to deal with css and custom directive in angulajs

Consider a custom directive in angularjs:
.directive('myDir', function() {
return {
restrict: 'E',
template: "..."
}
})
As far as I see the tag <my-dir></myDir> has no default style associated (or, at least, it is not a block tag). Now I want to style it and place in the right point of my page. I have 2 alternatives:
1) Use this template:
.directive('myDir', function() {
return {
restrict: 'E',
template: "<div class="layout">...</div>",
}
})
And css:
.layout {
/* bla bla bla */
}
But this introduce a new unnecessary level in the DOM three, since if I wrote something like <my-dir class="layout"></my-dir> with the proper css attached it would have worked anyway BUT I'll have to remember to add the same css class every time I use <my-dir> inside my code (and this is not DRY).
2) This led me to add style inside post-link function:
.directive('myDir', function() {
return {
restrict: 'E',
template: "...",
link: function(scope, element, attrs) {
element.addClass('layout');
}
}
})
Which strategy is better? Are there pros or cons I can't see?
UPDATE:
Using replace: true in directive definition is not an option, since it has been deprecated and when using bootstrap things like <my-dir class="visible-xs"></my-dir> may be useful.
You could solve this problem by having replace: true option in your directive, which will basically replace your directive DOM with the template which you have given from your directive.
Directive
.directive('myDir', function() {
return {
restrict: 'E',
template: "<div class="layout">...</div>",
replace: true //added replace true property
}
})
Resultant HTML
<div class="layout">..Content..</div>
Like above I shown, you could get rid off the unnecessary HTML tag by simply using replace option of directive.
Demo Plunkr
Note
As per Angular Docs replace option has been deprecated in Angular2,
but in your case you can avoid that.
But if you really wanted to go avoid the use of replace option of directive then I'd suggest you to use your directive as an attribute instead of going for an element but then you need to take <div class="layout"> outside and then your could put your directive over that like <div class="layout" my-dir>. But if you again think over it in Angular2 perspective, you are changing your component structure to old angular way using attribute/class

$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.

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