How to apply directive conditionally in AngularJS? - javascript

I want to apply a simple directive conditionally using ngAttr. I don't understand why my directive is always displayed. So if I have an undefined / false variable I want to apply my directive: dirr.
When using ngAttr, the allOrNothing flag of $interpolate is used, so if any expression in the interpolated string results in undefined, the attribute is removed and not added to the element.
My code pen
<div ng-app="myApp" ng-controller="MainController" class="container-fluid">
<h2 ng-bind="currentVersion"></h2>
<hr>
<div ng-attr-dirr="hidden || undefined">Default div</div>
</div>
angular.module('myApp',[])
.directive('dirr', function(){
return {
restrict:'AE',
template:'<div>Div from directive</div>'
}
})
.controller('MainController',['$scope', function($scope){
$scope.currentVersion = 'Angular 1.3.6';
$scope.hidden = undefined;
}])
;

You can make use of AngularJS's inbuilt directive ng-if to check for the condition and execute it conditionally.
Example:
<div ng-if="{some_condition}">
<dirr></dirr> <!--Execute the directive on the basis of outcome of the if condition-->
</div>

Form documentation
All of the Angular-provided directives match attribute name, tag name, comments, or class name
so whenever angular matches a diretive with attribute name,it compiles the template and renders the html irrespective of attribute value.
anyway you can use scope in directive template.so use ng-hide with scope's hidden property
angular.module('myApp',[])
.directive('dirr',function(){
return{
restrict:'AE',
template:'<div ng-hide="hidden">Div from directive</div>',
}
})
.controller('MainController',['$scope', function($scope){
$scope.hidden=false;
}]);

The answers are true and the sample code you provided works for small issues, but when it comes resolving this problem on large applications you may want to take this approach:
Updated Pen
<div ng-app="myApp" ng-controller="MainController" class="container-fluid">
<h2 ng-bind="currentVersion"></h2>
<hr>
<div dirr value="{{hidden}}">Default div</div>
</div>
.directive('dirr', function($log){
var directiveInstance = {
restrict:'AE',
scope:{
value:'#'
},
link: function (scope, element, attrs, ctrl) {
if(attrs.value === 'true'){
console.log('directive on !!');
$log.debug('element',element);
element[0].innerHTML = '<div>hello ' + attrs.value + '</div>';
}
else {
console.log('directive off !!');
}
}
}
return directiveInstance;
})
This is more tidy and you may not want to duplicate your code using ngIf or ngSwitch directives in seperate divs when you have something like:
<table>
<thead dirr value="{{statement}}">
<tr>
<th>
CrazyStuffHere...
</th>
<th>
CrazyStuffHere...
</th>
....
</tr>
</thead>
</table>

Related

Placing custom directive in Angular 1.5.8 using ng-repeat is not working

I am placing my custom-directive on the fly in my HTML using ng-repat directive.
But the custom-directives are not evaluated and if the placed the same directive on HTML directly then it is working.
...
<body ng-app="docsSimpleDirective">
<div ng-controller="Controller">
<div>This is a Problem</div>
<!--Here in for loop i want to us the value to call a directive-->
<div ng-repeat="var in arr">
<!--Here i am using directive with restrict: 'C' and this is not expending-->
<span class="{{var}}"></span>
</div>
<!-- here my directive named "direct" is working fine -->
<span class="direct"></span>
</div>
</body>
...
and My Js code is
(function(angular) {
'use strict';
angular.module('docsSimpleDirective', [])
.controller('Controller', ['$scope', function($scope) {
$scope.arr = ['direct','indirect'];
}]).directive('direct', function() {
return {
template: 'direct',
restrict: 'C'
};
}).directive('indirect', function() {
return {
template: 'indirect',
restrict: 'C'
};
});
})(window.angular);
I believe there is some compilation issue and i searched web and found $compile can solve my purpose but unable to implement.
Please help me in solving my issue.
Plunker implemention for same : https://plnkr.co/edit/lYGg0UkQpNhN5NJx13Zj?p=preview
Using a directive as a class is bad practice (because unreadable)
https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.md#restrict-to-elements-and-attributes
Then you pass a directive as a class but dynamically by interpolation which in itself is bad practice (https://docs.angularjs.org/guide/interpolation#known-issues). The class name is interpolated and the element rendered but the directive is not compiled during interpolation.
Remove the restrict lines from your directive definitions and use them as attributes:
<span indirect></span>
Then to use them in the ng-repeat loop, you can check if var = "direct" or "indirect"
https://plnkr.co/edit/JUNFMCZASMntCnC6FsIO?p=preview
Use the directive like this
<div ng-repeat="var in arr">
<span direct ng-if="var == 'direct'"><span>
<span indirect ng-if="var == 'indirect'"><span>
<div>
and in controller define restrict as attribute.
.directive('direct', function() {
return {
template: 'direct',
restrict: 'A'
};

AngularJS directive not binding attributes correctly

I'm relatively new to angular and this is my first involved directive and I'm a little lost. I'm trying to create a re-usable list of items on separate tabs in my app. The list acts the same with the exception of how the items are displayed (handled by separate partials). I'm passing in attributes of many different types into the scope and I'm trying several different things based on what I've been reading. No matter what I've tried so far, I'm still having issues with the attributes binding correctly.
Below is my code and I'll try to explain it as best as possible, hopefully someone can tell me where I went wrong. The only things that appear to have bound correctly are the strings, the objects and functions are missing.
UPDATE: Turns out I needed to bind $scope.currentPage to the directive scope. Now ng-repeat is running, but other parts of the page that require access to the controller scope aren't working. I've updated the code below and continue looking into how to give access to the template.
Directive
var app = angular.module('main');
app.directive('itemList', function(){
var linkFunction = function(scope, element, attributes){
scope.$watch("query.value", function(){
scope.filterFunction(); //pretty sure this never gets called on search
});
}
return {
restrict: 'E',
replace: 'true',
templateUrl: 'partials/directives/list-tab.html',
link: linkFunction,
scope: {
filterFunction: "&filterFunction",
searchPlaceholder: "#searchPlaceholder",
pagedItems: "=pagedItems",
clickFunction: "&clickFunction",
classString: "#classString",
infoTemplate: "#template",
currentPage: "=currentPage"
}
}
});
index.html
//pagedCars is an array of nested objects that gets used by the template to display the information
//filterCars is a function
//carSelected is a function
<div class="available-items">
<item-list filter-function="filterCars" search-placeholder="Search Cars" paged-items="pagedCars" current-page="currentPage" click-function="carSelected" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
list-tab.html
<div class="form-group">
<div class="search-field">
<label for="searchField" id="searchLabel">Search</label><br/>
<input type="text" ng-model="query.value" placeholder="{{searchPlaceholder}}"/>
</div>
<table class="table table-hover>
<tbody>
//currentPage is on the controller scope there's a separate control that allows the user to page through the pagedItems by updating the currentPage which would be reflected here
<tr ng-repeat="item in pagedItems[currentPage]" ng-click="clickFunction($index) ng-class="classString">
<td ng-include="infoTemplate"></td>
</tr>
</tbody>
</table>
</div>
cars-template.html
<div class="row form-inline" id="{{item.carId}}">
<div class="col-md-2">
//this uses a method on the controller scope to format the url
<img ng-src="{{retrieveIcon(item.iconUrl)}}" height="75px" width="75px"/>
<div class="col-md-10">
<div details-pane" id="carDetails" ng-include="'partials/cars/car-full-details.html'"></div>
<div class="item-title">{{item.name}}</div>
//the rest is just a table with more information about the item. item.description, item.mileage, etc...
</div>
</div>
Try passing through your functions with their parentheses
<div class="available-items">
<item-list filter-function="filterCars()" search-placeholder="Search Cars" paged-items="pagedCars" click-function="carSelected()" class-string="car.carId==selectedCar.carId?'selected':''" template="'partials/cars/cars-template.html'"></item-list>
</div>
Also FYI if your variable has the same name in your HTML as you want in your directive's scope you can just use the pass method. e.g.
scope: {
filterFunction: "&",
searchPlaceholder: "#",
pagedItems: "#",
clickFunction: "&",
classString: "#",
infoTemplate: "#template"
}

Angular directives in $http response

Angular 1.5
My $http data service returns html encoded text with directives too, like ng-click in the text.
I need to display the html and have the ng-click directives get activated.
To display I am doing this and it works, but ng-clicks don't work:
<div class="mt10" ng-repeat="row in aqdas.Paragraphs" ng-cloak>
<span ng-bind-html="TrustDangerousSnippet(row.Text)" >
{{row.Text}}
</span>
</div>
Here is TrustDangerousSnippet:
$scope.TrustDangerousSnippet = function (text) {
var val = $sce.trustAsHtml(text);
return val;
};
How can I edit TrustDangerousSnippet so that the ng-click's in the text are turned on once $http downloads the code?
Use this Directive also with your code. to bind html element in directive use complie. it will work..
.directive('compile', ['$compile', function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
return scope.$eval(attrs.compile);
},
function(value) {
element.html(value);
$compile(element.contents())(scope);
}
);
};
}])
I added the Directive Suresh included and changed the HTML to look like this, it works now. (add 'compile' to the binding element)
<div class="mt10" ng-repeat="row in aqdas.Paragraphs" ng-cloak>
<span compile ng-bind-html="TrustDangerousSnippet(row.Text)" >
{{row.Text}}
</span>
</div>

Dynamically setting attributes on an AngularJS Directive element

I'm trying to build a directive in AngularJS that has a template which could contain other AngularJS directives. All of my directives require an "id" attribute, so I need to set the "id" on the directive inside the template. However, no matter how I do this, AngularJS keeps throwing this error:
Error: [$parse:syntax] Syntax Error: Token '{' invalid key at column 2 of the expression [{{field.id}}] starting at [{field.id}}].
http://errors.angularjs.org/1.4.6/$parse/syntax?p0=%7B&p1=invalid%20key&p2=2&p3=%7B%7Bfield.id%7D%7D&p4=%7Bfield.id%7D%7D
at angular.js:68
at Object.AST.throwError (angular.js:13010)
at Object.AST.object (angular.js:12997)
at Object.AST.primary (angular.js:12905)
at Object.AST.unary (angular.js:12893)
at Object.AST.multiplicative (angular.js:12880)
at Object.AST.additive (angular.js:12871)
at Object.AST.relational (angular.js:12862)
at Object.AST.equality (angular.js:12853)
at Object.AST.logicalAND (angular.js:12845)
I know that AngularJS is fine doing something like this: <div id="{{field.id}}">[...]</div>. It ends up being rendered correctly, with "{{field.id}}" replaced with the actual value of field.id. However once I try to apply my directive to that div, or use the directive as an element itself, AngularJS balks. I've tried all of the following and all result either in the error above or with the directive's ID set to "field.id" rather than the value of "field.id":
<!-- result in error shown above -->
<mydirective id="{{field.id}}"></mydirective>
<div mydirective id="{{field.id}}"></div>
<div class="mydirective" id="{{field.id}}"></div>
<mydirective ng-attr-id="{{field.id}}"></mydirective>
<div mydirective ng-attr-id="{{field.id}}"></div>
<div class="mydirective" ng-attr-id="{{field.id}}"></div>
<!-- result in id set to "field.id" -->
<mydirective id="field.id"></mydirective>
<div mydirective id="field.id"></div>
<div class="mydirective" id="field.id"></div>
<mydirective ng-attr-id="field.id"></mydirective>
<div mydirective ng-attr-id="field.id"></div>
<div class="mydirective" ng-attr-id="field.id"></div>
In case it helps, the general layout of the directive with the template looks like this:
<div ng-repeat="field in fields" ng-show="field.visible">
<!-- some other stuff -->
<mydirective ng-if="field.type == 'foobar'" id="{{field.id}}"></mydirective>
<!-- some other stuff -->
</div>
I'm seeing an identical issue with another attribute on the directive as well, so it's not limited to just the 'id' attribute.
Shortened version of the directive throwing the error:
var application = angular.module('application', []);
application = application.directive('mydirective', ['$http', function($http){
return {
restrict: 'AEC',
scope:{
mydirectiveId: '=id',
id : '#',
// a few other attributes
},
templateUrl: 'mydirective.html',
controller: function ($scope, $element){
if($scope.id === undefined){
console.error("The 'id' on mydirective is missing!");
}
// more logic... sorry can't post this
}
};
}]);
Can you show your directives code,if the name of your directive is myDirective,it should be my-Directive in the html.Also make sure you are using the restrict option on the correct element,attribute
I managed to figure this out. I'm not exactly sure why, but AngularJS was having a problem with the isolated scope on "mydirective". When I removed the mydirectiveId: '=id' attribute from the scope declaration, it started working again as expected.
Working HTML Template for parent directive:
<div ng-repeat="field in fields" ng-show="field.visible">
<!-- some other stuff -->
<mydirective ng-if="field.type == 'foobar'" id="{{field.id}}"></mydirective>
<!-- some other stuff -->
</div>
Working directive code for "mydirective":
application = application.directive('mydirective', ['$http', function($http){
return {
restrict: 'AEC',
scope:{
// mydirectiveId: '=id', << removed this line, it was the problem
id : '#',
// a few other attributes
},
templateUrl: 'mydirective.html',
controller: function ($scope, $element){
if($scope.id === undefined){
console.error("The 'id' on mydirective is missing!");
}
// more logic
}
};
}]);

AngularJS and XML, how to render it?

I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce

Categories

Resources