ng-class conditional doesn't work - angularjs

I have an angular directive named fund. The directive is defined as follows.
return{
restrict: 'E',
replace: true,
scope: {
data: '=',
cut: '#'
},
templateUrl: 'app/directives/partials/fund.jsp'
}
It has a property named cut. If cut is set, I will apply text-cut class, and if not set, no class. The class is as follows in case needed:
.text-cut{
overflow: hidden;
text-align: left;
text-overflow: ellipsis
}
I have tried the using the directive as:
<fund data="myCtrl.fundList" cut="true"></fund>
<fund data="myCtrl.fundList" cut="'true'"></fund>
with following ng-class attributes in the template:
ng-class="text-cut: cut"
ng-class="text-cut: 'cut'"
ng-class="{text-cut: cut}"
ng-class="{text-cut: 'cut'}"
ng-class="text-cut: cut===true"
ng-class="text-cut: 'cut'===true"
ng-class="{text-cut: cut===true}"
ng-class="{text-cut: 'cut'===true}"
But none of these combinations applied text-cut class to my fund directive. Where is the mistake?

You have to quote text-cut. Try
ng-class="{'text-cut': cut}"

Create a function that returns the string class you want based on some condition. Then call it in your ng-class. As long as the function returns the css you want it should work.
$scope.checkCut = function(){
if(this.cut != null){
return 'text-cut';
}
}
In your directive
ng-class="checkCut"

Related

Nested Directives and NgModel

I feel like I'm missing a fundamental concept of Angular directives.
Referring to this Plnkr: http://plnkr.co/edit/WWp9lB6OvxHL8gyBSU5b?p=preview
I have a model of:
{
message: string,
value: number
}
And I have an itemEditor directive to edit that model:
.directive('itemEditor', function() {
return {
replace: true,
templateUrl: 'item.editor.html',
require: 'ngModel',
model: {
item: '=ngModel'
}
};
})
But I want to delegate the editing of value to a custom control:
.directive('valuePicker', function() {
return {
replace: true, // comment this to make it work
templateUrl: 'value.picker.html',
require: 'ngModel',
scope: {
ngModel: '='
},
controller: Controller
};
function Controller($scope, Values) {
$scope.values = Values;
console.log({scope:$scope});
}
})
At current, this code gives the error:
Error: $compile:multidir
Multiple Directive Resource Contention
Commenting the replace: true will allow this code to work. However, I lose the styling instructions from the parent template. I.E: the class form-control is not merged onto the select element.
What is the angular way to make this work?
You are calling value-picker twice here
<value-picker class="form-control" style="width:100%" name="item" value-picker ng-model="item.value"></value-picker>
The value-picker element contains value-picker attribute as well, both being treated as directive which in conflict causing multiple directive error. Remove the attribute value-picker, either call it as element or attribute. Or you can restrict the directive to a specific declaration.
Also remove ng-model from select element of value.picker.html template, which is causing another error:
Multiple directives [ngModel, ngModel] asking for 'ngModel'
So replace: true replaces and appends the current directive attributes to the root level of template element (in your case its select)
Updated Plnkr

Add directive in loop based on criteria

I have a timeline.json like:
[{
"from":"twitter",
//...
},
{
"from": "facebook"
//...
}]
Based on from attribute I need "render" specific directives: post-twitter or post-facebook Inside a loop
How could I do that?
There are several ways. You can use ng-switch, ng-if, or a custom handler function in the link phase. You can find more info in the documentation.
Basically, switch between tags that invoke the directive based on a conditional statement, being it the 'from' value in your model. You can combine for example the ng-if directive with ng-include, to render a portion of your template based on this condition. Just make sure it won't degrade the performance.
You can create a directive which compiles wanted directive depending on the passed argument.
Something like:
.directive('renderOther', function($compile) {
return {
restrict: 'E',
scope: {
type: '='
},
link: functtion(scope, elem, attrs) {
var dir = $('<post-' + scope.type + '></post-'+scope.type+'>');
dir.appendTo(elem);
$compile(elem.contents())(scope);
}
}
});
Html:
<div ng-repeat="i in items">
<render-other type="i.from"></render-other>
</div>
(Its not working code - just an idea)

Is it possible to conditionally apply transclution to directive?

Is it possible to decide whether to apply transclusion to an element based on a scope variable ?
For example ( Stupid simplified reduced example of what i'm trying to achieve )
app.directive('myHighlight', function () {
return {
transclude : true,
template : "<div style='border:1px solid red'><span ng-transclude></span></div>"
}
});
app.directive('myDirective', function () {
return {
template : "<span>some text</span>",
link : function (scope,element,attr) {
if ( 'shouldHighlight' in attr) {
// wrap this directive with my-highlight
}
}
}
});
And then in the html
<span my-directive></span>
<span my-directive should-highlight></span>
Note, please don't tell me to just add the highlight instead of should-highlight, as i said this is a dumb reduced example. Thanks.
Instead of optionally applying the highlight directive, always apply it and do the optional wrapping inside that directive. The optional wrapping is achieved with an ng-if and a boolean passed from myDirective to myHighlight via markup:
<div my-highlight="someBooleanValue">some text</div>
The myHighlight template:
<div ng-if="actuallyTransclude" style="border:1px solid red">
<span ng-transclude></span>
</div>
<div ng-if="!actuallyTransclude" ng-transclude></div>
Working jsfiddle: http://jsfiddle.net/wilsonjonash/X6eB5/
Sure. When you specify the transclude option, you know that you can declaratively indicate where the content should go using ng-transclude.
In the linking function of the directive, you will also get a reference to a transclude function (https://docs.angularjs.org/api/ng/service/$compile, see link section):
function link(scope, iElement, iAttrs, controller, transcludeFn) { ... }
The transcludeFn will return the transcluded content, so you can conditionally insert that were and when you want to in the link function of your directive.
Example (http://jsfiddle.net/DKLY9/22/)
HTML
<parentdir flg="1">
Child Content
</parentdir>
JS
app.directive('parentdir', function(){
return {
restrict : 'AE',
scope: {
flg : "="
},
transclude : true,
template : "<div>Parent {{childContent}} Content</div>",
link : function(scope, elem, attr, ctrl, transcludeFn){
if (scope.flg==1){
scope.childContent="Include Me instead";
}
else {
scope.childContent = transcludeFn()[0].textContent;
}
}
}
});
This is a simplified example. To get a better idea of how to use the transclude function, refer to the following : http://blog.omkarpatil.com/2012/11/transclude-in-angularjs.html
When I approach these kind of problems I just look at what angular did. Usually their source code is very readable and easy to re-use. ngTransclude is no different:
https://github.com/angular/angular.js/blob/master/src/ng/directive/ngTransclude.js
I leave the rest to you. You can either create your own transclusion directive that receives also a condition, or just duplicate the code into your specific directive when the if condition is true.
If you still have trouble, please let me know and we'll set up a plunker.

Why are mouse events from my directive not reflected in the view?

Here is an example fiddle. Please open the console first.
It's a little long, so to quickly explain: You'll see two divs. One is "regular", the other is created via a directive. They both have click event handlers (added via ng-click).
Clicking the regular (non-directive) div does what I expect - the appropriate variables in the scope are set and you see that in the view (top two lines in the output show the id and top of the div that was clicked).
The problem is when clicking the directive div. These variables are not updated. I know I'm misunderstanding something about the scope of the directive, but what is confusing me is the log messages that are printed (open the console when you do this). The clicks are registered, and the controller's scope does set the variable values, as you can see in the console.
Yet the html does not update - try clicking one div, then the other, and go back and forth, and you'll see.
What am I missing here?
Code (please don't be put off by its length!):
<div ng-app="MyApp">
<div ng-controller="MyController">
Top of the div you just clicked = {{top}}.<br/>
Id of the div you just clicked = {{lastId}}.<br/>
<div id="notADirective" class="myClass" ng-click="update($event)">
Not a directive. Click me.
</div>
<my-div element-id="isADirective" cls="myClass">
I'm a directive. Click me.
</my-div>
</div>
angular.module('components', [])
.controller('MyController', function ($scope) {
$scope.lastId = '';
$scope.top = '';
$scope.update = function (event) {
var myDiv = getFirstMyClassAncestor(event.target);
var style = window.getComputedStyle(myDiv);
$scope.top = style.top;
$scope.lastId = myDiv.id;
console.log("You just clicked " + $scope.lastId + " and its top is " + $scope.top);
}
function getFirstMyClassAncestor(element) {
while (element.className.indexOf('myClass') < 0) {
element = element.parentNode;
}
return element;
}
}).directive('myDiv', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
controller: 'MyController',
scope: {
cls: '#',
elementId: '#'
},
template: '<div id="{{elementId}}" class="{{cls}}" ng-click="update($event)"><div ng-transclude></div></div>'
}
});
angular.module('MyApp', ['components'])
.myClass {
background-color: #DA0000;
position: absolute;
}
#isADirective {
top: 300px;
}
#notADirective {
top: 100px;
}
In case of your directive the assigned controller MyController gets the scope of this directive and not the scope of div as you probably expect. I added a log statement for the different scope ids:
http://jsfiddle.net/6zbKP/4/
If you want to update the outer or parent scope you have to use a binding like this:
scope: {
cls: '#',
elementId: '#',
callback: '='
},
Then bind your update function in the directive:
<my-div element-id="isADirective" cls="myClass" callback="update">
I'm a directive. Click me.
</my-div>
And call the callback in your directive:
template: '<div id="{{elementId}}" class="{{cls}}" ng-click="callback($event)"><div ng-transclude></div></div>'
See http://jsfiddle.net/6zbKP/5/
The angular expression bindings are outside of your directive's isolated scope. And you haven't imported lastId or top from your parent controller scope into your directives isolated scope, so there is no reason to expect that updating one variable in the inner directive scope will update the variable in the outer controller scope. There is no two way binding set up.
You can setup two way binding however using scope: {lastId: '=', top: '=' }.
Also you shouldn't be sharing controllers like you're doing. I suggest using anonymous controller functions.

Binding To Element Text In AngularJS

Is it possible to bind to the text of an element without actually dropping into the link function?
<blink>Text Here or {{ controllerText() }}</blink>
// add a namespace for custom directives
angular.module('mydirectives', []);
angular.module('mydirectives').directive('blink', function() {
return {
restrict: 'E',
template: '<marquee scrollamount="100%">{{ can i do it here? }} </marquee>',
scope: {
// can i do it here?
}
};
});
So this is done with transclusion which merges the content of the original element with the template. The ng-transclude tag in the template is required to get it to work.
<blink>Bring the blink back<blink>
// add a namespace for custom directives
angular.module('mydirectives', []);
angular.module('mydirectives').directive('blink', function() {
return {
restrict: 'E',
transclude: true,
template: '<marquee scrollamount="100%" ng-transclude></marquee>'
}
});
You absolute can.
scope: {
text: '='
}
This adds a text attribute to the isolate scope that is linked to the value of the text attribute from the element.
So you need to change the html slightly to:
<blink text="fromController"></blink>
And then add that fromController attribute in the enclosing controller.
Here's a (very annoying) fiddle.

Resources