my directive is working fine, displaying all the trending tags. The directive looks for a trendingTag property in the $scope object. So I have scope: true
app.directive('ngTrending', function () {
return {
restrict: 'E'
, transclude: true
, replace: true
, scope: true
, templateUrl: '/resources/ngViews/trending.html'
};
});
Now I want to be able to configure it based on options (like read-only="true") based on attribute on the directive. And be able to conditionally change minor aspects of the template based on the attirbute such tht
<ng-trending></ng-trending>
Generates the trending tags with the actions enabled. While
<ng-trending read-only="true"></ng-trending>
generates the tags, but has the clicks disabled. How do I code the scope on the directive so that I still inherit the scope of the controller that is hosting the directive e.g.
<div ng-controller="fancy">
<ng-trending></ng-trending>
</div>
as is the case now (inside of the template of the directive I reference the fancyContrllers $scope.trendingTags property). But inside of the directive's template I want to reference the "read-only" in the $scope.
It just dawns on me that I am approaching this completely wrong, and that I probably want to pass the trending tags in as well... I am confused - please help straighten me out!
Thanks.
The normal procedure is to use an isolate scope to pass in any variables you want in directive (unless you need multiple directives on the element). This will result in a more reusable and testable directive, and clearer code as the directive won't be coupled to its surroundings.
For your case, if you write an isolate scope like this
scope: {
trendingTags: '=',
readOnly: '='
// ...
}
Then you can bind an expression on the outer scope to trendingTags on the inner scope, and the same with readOnly using attributes on the element.
You element would then look something like this
<ng-trending trending-tags="trendingTags" read-only="true"></ng-trending>
There is some more information on isolate scope here http://docs.angularjs.org/guide/directive.
For completeness here is my working solution, including bindings for the actions. Any critiques are welcomed. Thank you andyrooger:
The directive:
app.directive('ngTrending', function () {
return {
restrict: 'E'
, transclude: true
, replace: true
, scope: {
trendingTags: '='
, displayOnly: '='
, inlineLabel: '='
, filterTo: '&'
, isFilteredTo: '&'
}
, templateUrl: '/resources/ngViews/trending.html'
};
});
The template:
<div style="text-align: center">
<div class="btn-group-xs">
<span ng-show="(!!inlineLabel)" style="color: #81B1D9">Tagged: </span>
<button ng-repeat="tag in trendingTags | orderBy:'count':true | limitTo:8" type="button" class="btn btn-default btn-primary"
style="text-wrap: normal; margin: 2px;"
ng-click="filterTo({filterToTag: tag.tagName})" ng-class="{active: isFilteredTo({filterToTag: tag.tagName}), disabled: (!!inlineLabel)}"><span
ng-bind-html-unsafe="tag.tagName"></span> <span class="badge" ng-show="!(!!displayOnly)">{{ tag.count }}</span
</button>
</div>
<button type="button" class="btn btn-xs btn-default" style="width: 150px; text-wrap: normal; margin-top: 3px"
ng-click="filterTo({filterToTag: ''})" ng-show="!(!!displayOnly)">Clear
</button>
</div>
In use:
<ng-trending trending-tags="tags"
filter-to="filterTo(filterToTag)"
is-filtered-to="isFilteredTo(filterToTag)"></ng-trending>
Related
The problem is that child directive binds to parent however syntax {{name}} gets ignored by ng-repeat. What would be the right way achieving this?
HTML (Main/child directive)
<compact-select
no-item-selected-text="Add a Customer"
no-item-selected-icon="fa-user"
search-placeholder="Type a customer name"
cs-model="customer"
cs-items="contacts"
>
<display-item-template>
<span>{{name}}</span>
or
<span>{{item.name}}</span>
</display-item-template>
</compact-select>
Directive
angular.module('core').directive('compactSelect', [function($timeout) {
return {
templateUrl : 'modules/core/views/components/compact-select-tpl.html',
bindToController: true,
transclude: true,
scope: {
noItemSelectedText: '#',
noItemSelectedIcon: '#',
csModel: '=',
csItems: '=csItems'
},
controllerAs : 'ctrl',
controller : function($scope) {
}
};
}]).directive('displayItemTemplate', function($timeout) {
return {
require: '^compactSelect',
restrict: 'E'
}
});
Directive Template (modules/core/views/components/compact-select-tpl.html)
<div class="compact-select-repeater-box" style="" >
<div ng-transclude ng-repeat="item in ctrl.csItems | filter:searchParam" class="compact-select-repeater" ng-class="ctrl.getHighlightedClass(item)" ng-click="ctrl.itemSelected(item)">
<span>{{item.name}}</span>
<span>{{item.id}}</span>
</div>
<div style="position:absolute;bottom:0">
+ Click here to add customer {{ctrl.message}}
</div>
</div>
I can see that
<span>{{item.name}}</span>
<span>{{item.id}}</span>
Gets replaced with
<span></span>
or
<span>{{item.name}}</span>
and not with
<span>{{name}}</span>
or
<span>{{item.name}}</span>
Question: How do I get ng-repeat to respect html bindings syntax from child directive? Or is there is another way to achieve this?
If i am not wrong, then you are trying to create a list view such that the template of the list would be provided by the user, but the methods (click,etc) would already be made available through the directive.
Now, since angular 1.3, the transcluded scope is a child of the directive isolated scope,
so, in your case, if you follow the correct hierarchy, you can access the directive scope from within the template provided by the user.
Here is your scope hierarchy:
Directive isolated scope --> ng-repeat new scope for every row --> transcluded scope.
so, if you want to access directive scope from the transcluded scope,
you would need to do $parent (for ng-repeat) and then access item.name, like below:
<display-item-template>
<span>Item Name: {{$parent.item.name}}</span>
</display-item-template>
Also, you don't need the bindings inside your compact-select-tpl, because you want that content to come from transclusion:
<div class="compact-select-repeater-box" style="" >
<div ng-transclude ng-repeat="item in ctrl.csItems | filter:searchParam"
class="compact-select-repeater"
ng-class="ctrl.getHighlightedClass(item)"
ng-click="ctrl.itemSelected(item)">
<!-- <span>{{item.name}}</span>
<span>{{item.id}}</span> -->
</div>
<div style="position:absolute;bottom:0">
+ Click here to add customer {{ctrl.message}}
</div>
</div>
The displayItemTemplate directive that you transclude inside the other directive has already it's data interpolated {{name}} and {{item.name}}.
If you don't have these variables in the $scope, it will transclude empty strings inside your spans.
Then in your compactSelect directive, the div that gets transcluded will have it's content overridden.
If you move the displayItemTemplate directive inside the other directive's template, the repeat will work. (you will need to remove ng(transclude and transclude: true
Fiddle
Additionally, if you use bindToController, don't put the attributes inside scope.
function compactSelect() {
return {
template : [
'<div class="compact-select-repeater-box" style="" >',
'<div ng-repeat="item in ctrl.csItems | filter:searchParam" class="compact-select-repeater" ng-class="ctrl.getHighlightedClass(item)" ng-click="ctrl.itemSelected(item)">',
'<display-item-template>',
'<span>{{item.name}}</span>',
'</display-item-template>',
'</div>',
'<div style="position:absolute;bottom:0">',
'+ Click here to add customer {{ctrl.message}}</div></div>',
].join(''),
bindToController: {
noItemSelectedText: '#',
noItemSelectedIcon: '#',
csItems: '=csItems'
},
scope: {},
controllerAs : 'ctrl',
controller : function($scope) {
}
}
}
function displayItemTemplate() {
return {
require: '^compactSelect',
restrict: 'E'
}
}
function SuperController() {
this.name = "a name";
this.contacts = [{name:"rob"}, {name:"jules"}, {name:"blair"}];
}
angular.module('myApp', []);
angular
.module('myApp')
.controller('SuperController', SuperController)
.directive('compactSelect', compactSelect)
.directive('displayItemTemplate', displayItemTemplate);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="SuperController as s">
<compact-select
no-item-selected-text="Add a Customer"
no-item-selected-icon="fa-user"
search-placeholder="Type a customer name"
cs-items="s.contacts">
</compact-select>
</div>
</div>
I have a directive clickable-tag for which i am passing my data as the tag's name (tag.tag):
<a class="item item-avatar"
ui-sref="nebula.questionData({questionId: question.id})"
ng-repeat="question in questionsData.questions">
<img src="{{question.user.profile_photo || '../img/avatar.jpg'}}">
<h2 class="question-title">{{question.title}}</h2>
<p>{{question.description}}</p>
<div class="question-tags-list" ng-repeat="tag in question.tags" clickable-tag data="{{tag.tag}}">
<button type="submit" class="tag">{{tag.tag}}</button>
</div>
</a>
The directive clickable-tag is inside a ui-sref (on the outer a tag). Inside the directive, I want the outer ui-sref to be prevented and instead the user should be directed to another state (the one i am specifying in the directive below).
.directive("clickableTag", function($state) {
return {
restrict: "A",
scope: {
data: "#"
},
link: function(scope, elem, attrs) {
elem.bind('click', function(ev) {
console.log('scope.tagName: ', scope.tagName);
if (scope.data) {
$state.go('nebula.tagData', {tagName: scope.data});
}
});
}
};
})
The problem is that only the resolve of the state specified inside the directive runs. The view which is actually rendered is of the state specified by the outer ui-sref.
Any solutions as to how to prevent the outer ui-sref from being triggered. and instead trigger a state change as specified inside the directive ?
Any help would be appreciated. Thanks.
Note: I have already tried preventDefault(), stopPropagation(), return false inside my directive.
Move the ng-repeat outside and above the <a> tag and move the close of the <a> tag above the button.
<div ng-repeat="question in questionsData.questions">
<a class="item item-avatar"
ui-sref="nebula.questionData({questionId: question.id})">
<img src="{{question.user.profile_photo || '../img/avatar.jpg'}}">
<h2 class="question-title">{{question.title}}</h2>
<p>{{question.description}}</p>
</a> <!--Put close of A tag here --->
<div class="question-tags-list" ng-repeat="tag in question.tags"
ng-click="$state.go('nebula.tagData', {tagName: tag.tag})">
<button type="submit" class="tag">{{tag.tag}}</button>
</div>
</div>
For more information see the AngularJS ng-click API Docs
I have the following html:
<div class="jumbotron" ng-controller="protocolCtrl as pctrl">
<!--IN THIS MODAL YOU CAN ADD/CHANGE/DELETE DATA-->
<modal-directive list="pctrl" headers="['ID', 'Protocol']"></modal-directive>
</div>
In my modal-directive.html, in the body, I do this:
<!-- Modal body-->
<div class="modal-body">
<table-directive list=list headers=headers></table-directive>
</div>
I want to check on the list parameter i pass in. if it's equal some value, I want to append some html to the body
My directive looks like this
.directive('modalDirective', function(){
return {
restrict: 'E',
templateUrl: '/directives/modal-directive.html',
scope: {
list: '=',
headers: '='
},
link: function(scope, element, attrs){
if(scope.list == 'pctrl'){
element.find('.modal-body').append('This is just a test.')
}
}
};
});
But this doesn't append anything. If I drop the if check it appends just fine.
I'm fairly new to angular, so if anyone can tell me how I can achieve this, I'd appreciated it.
Edit
this is how i loop through the data in my table-directive.html
<tr ng-repeat="l in list.list">
<!--Access the actual values inside each of the objects in the array-->
<td ng-repeat="data in l"> {{ data }} </td>
<td>
<button type="button" class="btn btn-primary btn-sm"
data-toggle="modal">Edit</button>
</td>
<td>
<button type="button" class="btn btn-danger btn-sm" ng-click="list.removeData(l)"
data-dismiss="modal">Remove</button>
</td>
</tr>
if you put
<modal-directive list="pctrl" headers="['ID', 'Protocol']"></modal-directive>
and
....
scope: {
list: '=',
headers: '='
},
.....
list: '=' check for the list attr of the element and execute the argument as a expression not as a string i think your trying to get 'pctrl' as a string not as a scope variable value so that change to list="'pctrl'" to pass as a string
<modal-directive list="'pctrl'" headers="['ID', 'Protocol']"></modal-directive>
OR
get the attr as a string use #
....
scope: {
list: '#',
headers: '='
},
.....
here is a good Explanation.
here is the angular official DOC
update
if you need to check only the string value of the attr, then you can simply use attrs.list
so use it inside the directive as
if(attrs.list === 'pctrl'){
element.find('.modal-body').append('This is just a test.')
}
I have an isolated scope directive that has its own controller.
The directive's template has access to that controller, except inside an ng-repeat where the entire controller is out of scope.
I don't know how to fix it.
If you have an ng-repeat inside an ng-controller, children of the ng-repeat inherit the functions of the ng-controller.
I would have thought that would be the case with custom directives, too - children inside the ng-repeat could still see the directive's controller - but as far as I can tell, they can't.
I'm still getting up to speed with Angular, so a nudge in the right direction would be appreciated. A downright answer would be appreciated even more. Something to do with transclusion? With which is getting compiled first?
Here is the directive:
angular.module('surverApp')
.directive('surveyItems', function () {
return {
restrict: 'E',
scope: {
itemList: '=',
query: '='
},
templateUrl: 'views/directives/survey-items.html',
replace: true,
controller: 'SurveyItemsCtrl',
controllerAs: 'ctrl',
bindToController: true
};
})
.controller('SurveyItemsCtrl', function(){
var ctrl = this;
ctrl.disableEdit = true;
ctrl.disableToolbar = false;
ctrl.showForm = false;
ctrl.showDuplicate = false;
ctrl.showTrash = false;
ctrl.deleteItem = function(item) {
console.log('ctrl.item in DELETE ITEM' ,item)
.... functions removed for brevity
};
});
And here is its template. None of its functions fire.
<div>
<pre>ctrl = {{ctrl | json}}</pre> <<<<<=== THIS SHOWS THE CONTROLLER IS IN SCOPE
<div ng-repeat="item in ctrl.itemList | filter:ctrl.query" class="ubi-box container-fluid">
<pre>ctrl = {{ctrl | json}}</pre> <<<<<=== THIS SHOWS THE CONTROLLER IS NOT IN SCOPE
<standard-right-toolbar ctrl="ctrl"></standard-right-toolbar>
<h4>{{item.name}}</h4>
<div ng-show="ctrl.showForm" class="ubi-box container-fluid">
<!-- <survey-form model="item" disable-edit="ctrl.disableEdit" reset-fn="item = ctrl.resetUpdateFn(item)" submit-fn="ctrl.submitUpdateFn()" close-fn="ctrl.hideFormFn()"></survey-form> -->
<survey-form model-el="item" disable-edit-el="ctrl.disableEdit" reset-fn="ctrl.resetUpdateFn(form,model)" submit-fn="ctrl.submitUpdateFn(form)" close-fn="ctrl.hideFormFn()"></survey-form>
<pre>model = {{item | json}}</pre>
</div>
<div ng-show="ctrl.showDuplicate" class="ubi-box container-fluid">
<standard-right-close-bar close-fn="ctrl.hideDuplicateFn()"></standard-right-close-bar>
<h4 class="col-xs-12">Duplicating a survey will copy all the details and questions over to a new survey.</h4>
<h3 class="col-xs-10">Click the copy button to procede.</h3>
<button class="btn btn-lg btn-primary" type="button" ng-click="ctrl.copy(item)" title="Duplicate">
<span class="glyphicon glyphicon-duplicate"></span>
</button>
</div>
<!-- <div ng-show="ctrl.showTrash" class="ubi-box container-fluid"> -->
<div ng-show="ctrl.showTrash" class="ubi-box container-fluid">
<standard-right-close-bar close-fn="ctrl.hideTrashFn()"></standard-right-close-bar>
<h4 class="col-xs-12">Deleting a survey is a very serious matter. It will permanently remove every question and every answer in every questionnaire in every edition in the survey.</h4>
<h3 class="col-xs-10">Click the trashcan only if you are sure!</h3>
<button class="btn btn-lg btn-danger" type="button" ng-click="ctrl.deleteItem(item)" title="Delete">
<span class="glyphicon glyphicon-trash"></span>
</button>
</div>
</div>
<div>
PS I had it working fine inside an ng-controller. But then I read ng-controllers are being outlawed in Angular 2.0, so I thought I'd get in some practice using directive controllers rather than ng-controllers and I wrapped the ng-repeat in a directive instead of an ng-controller.
So far, so stumped . . .
I'm trying to figure out how the 'replace' attribute work for directive. I've run into a scenario when setting it to true was causing my code to break.
The directive:
angular.module('cdt.dm.directives').directive('referenceFiles', [
function () {
return {
restrict: 'E',
templateUrl: 'app/dm/views/templates/referenceFiles/referenceFiles.html',
scope: {
job: '='
},
link: function (scope, element, attr) {
scope.deleteReferenceFile = function (id) {
scope.job.references.splice(id, 1);
}
}
}
}]);
the referenceFiles.html template:
<div class="grid-action-filter" popover-placement="left" popover-template="app/dm/views/templates/referenceFiles/simple.html">
<span class="badge" style="cursor:pointer" >{{job.references.length}} added</span>
the simple.html template used by the popover directive:
<span>{{job.references.length}} reference files</span>
<table ng-repeat="ref in job.references">
<tr>
<td>{{ref.name}}</td>
<td>
<button class="btn grid-button btn-danger" ng-click="deleteReferenceFile($index);"><i class="fa fa-trash-o"></i></button>
</td>
</tr>
</table>
If I set replace to true in the referenceFiles directive, then the deleteReferenceFile method won't be found on the scope when clicking the button. Instead I have to call it this way:
$parent.$parent.$parent.$parent.deleteReferenceFile($index)
needless to say, it's ugly...
If I remove the replace attribute of the directive, then everything works fine.
Could someone explain this behaviour ?