Angular directive/child directive transclude inside ng-repeat - javascript

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>

Related

Issue with Multi Transclude in Angular

I am using multitranscldue in angular js.I am having a directive like below.
(function(){
angular.module("MyDirective",['multi-transclude'])
.directive("myDirective",function(){
var MyLink = function(scope,element,attr){
scope.title = attr.title;
}
return{
restrict : 'EA',
scope: {title:"="},
transclude:true,
templateUrl:"directive.html",
link: MyLink
}
});
}())
The html for directive is like
<div >
<div>
<span data-ng-bind="title"></span>
<div ng-multi-transclude="card-body"></div>
</div>
</div>
In my main html class, I am using the directive like below.
<my-directive title="asdasdas">
<div name="card-body">
{{title}}
</div>
</my-directive>
I am getting an error like
Error: Illegal use of ngMultiTransclude. No wrapping controller.
Please help me with this.
The link to plunker is here
you have to add ng-multi-transclude-controller to your directive's HTML template, as follows:
<div ng-multi-transclude-controller>
<div>
<span data-ng-bind="title"></span>
<div ng-multi-transclude="card-body"></div>
</div>
</div>

how to pass data from controller to directive using $emit

I had strucked in passing value from controller to directive
I have two arrays in my controller
$scope.displayPeople.push(data.userName);
$scope.displayOrg.push(data.orgname);
i need to pass these data from controller to directive
my directive
<div>
<div class="multitext-wrap blue-border">
<ul inputfocus>
<!--<li class="tag" ng-repeat="list in selecteditemsdisplay track by $index" ng-class="{selected: $index==selectedIndex}" >-->
<!--<span class="tag-label">{{list}}</span><span class="tag-cross pointer" ng-click="Delete($index,selecteditemslist[$index],list,searchid)">x</span>-->
<!--</li>-->
<li class="tag" ng-repeat="list in displayItems track by $index" ng-class="{selected: $index==selectedIndex}" >
<span class="tag-label">{{list}}</span><span class="tag-cross pointer" ng-click="Delete($index,selecteditemslist[$index],list,searchid)">x</span>
</li>
<li class="">
<input type="text" ng-model="searchModel" ng-keydown="selected=false" ng-keyup="searchItem(searchModel,searchobj)"/>
</li>
</ul>
</div>
<div class="typeahead" ng-hide="!searchModel.length || selected">
<div class="typeahead" ng-repeat="item in searchData | filter:searchModel | limitTo:8" ng-click="handleSelection(item,searchobj,$index,searchid)" style="cursor:pointer" ng-class="{active:isCurrent($index)}" ng-mouseenter="setCurrent($index)">
<div class="bs-example">
<div class="list-group list-group-item active">
{{item.displayConfig[0].propertyValue}} {{item.displayConfig[1].propertyValue}}
</div>
</div>
</div>
</div>
</div>
I was using $emit to send
in controller
$rootScope.$emit("displayEvent", {displayItems: $scope.displayPeople});
$rootScope.$emit("displayEvent", {displayItems: $scope.displayOrg});
in directive
$rootScope.$on('displayEvent', function (event, args) {
$scope.displayOrgs = args.displayItems;
console.clear();
console.info($scope.displayOrgs);
});
by doing this i getting duplicates in place of org (both people and org wher coming )
how can i solve this problem please hepl me thanks in advance
By declaring 'scope: false' you´re able to access the controller´s scope in your directive. 'false' means 'do not create an isolated scope, inherit the controllers'.
.directive('myDirective', function() {
return {
scope: false,
link: function($scope){
//Do stuff with $scope.displayOrgs
//Do stuff with $scope.displayPeople
}
};
});
This option will create an isolated scope and inherits the selected variables. This is a cleaner way of doing it.
.directive('myDirective', function() {
return {
scope:{
displayPeople:'=',
displayOrg :'=',
},
link: function($scope){
//Do stuff with $scope.displayOrgs
//Do stuff with $scope.displayPeople
}
};
});
using $emit for communication between controller and directive is not a preferable.
you need to use "=" scope of directive to allow two-way communication between controller and directive like:
directive
angular.module('YourModuleName').directive('yourDirectiveName',function () {
return{
restrict:'E',
scope:{
displayPeople:'=',
displayOrg :'=',
},
link: function postLink(scope, element, attrs) {
}
}
});
template respective to controller
<yourDirectiveName displayPeople="displayPeople" displayOrg ="displayOrg "></yourDirectiveName >

AngularJS : ng-repeat inside a directive masks the directive's controller

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

nested ng-repeat with outer ng-repeat item affecting the inner ng-repeat logic

My code
<div ng-repeat='n in [] | range:(my_works.items.length/3)+1'>
<div class="table-row" style="position:absolute;left:13px;{{row_gap_style(n)}}" class='ng-scope'>
<div class="book_container" ng-repeat='item in my_works.items.slice($index*3,($index*3+3))' style="position:absolute;top:0px;{{col_gap_style($index)}}">
<div id="" title="{{ item.Story.title}}" class="cover_container fetched {{check_published(item)}}" rel="<?php echo $rel; ?>">
What I want to do is
if n in the outer ng-repeat is 0, then the div.book_container will look like this instead:
<div class="book_container" ng-show="n == 0" ng-repeat='item in my_works.items.slice($index*2, ($index*2+2))' style="position:absolute;top:0px;{{col_gap_style($index+1)}}">
otherwise, the div should be as before.
How do I accomplish this?
You could create a directive for the same content and then use ng-if to set different attributes dependent on the value of n:
.directive('bookContainer', function() {
return {
scope: {
'items': '=',
'colstyle': '#'
},
restrict: 'E',
template: '<p ng-repeat="item in items">book: {{item}} {{colstyle}}</p>'
};
});
View:
<div ng-repeat='n in []'>
<book-container ng-if='n==0' items='my_works.slice($index,1)' colstyle='s1'></book-container>
<book-container ng-if='n!=0' items='my_works.slice($index,2)' colstyle='s2'></book-container>
</div>
Fiddle
The demo has been greatly simplified but shows the technique which you could hopefully use.

Angular Directive, Scope: True AND Add attribute to scope

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>

Categories

Resources