I have a situation where I get some data from a REST API in a controller, I render that data using ng-repeat. Then in that loop, I need to run another controller, pass it data from earlier controller, do some operations on it and then again run an ng-repeat on it.
When I do it, "Inspect Element" shows value kept in parent controller's parameter. But the value which is being passed to the nested controller is actually the variable name.
Source code:
HTML:
<div class="checkbox" ng-repeat="bird in birds">
<table>
<tr>
<td>
<a ng-href="/birds/{{bird.Image}}" rel="shadowbox"><img ng-src="/birds/{{bird.Image}}" height="200" width="200"></img></a>
<div ng-controller="imageController" model="{{ bird.AdditionalImages }}">More Images: {{ imageString }}
<div ng-repeat="image in images">
<a ng-href="/birds/{{image}}" rel="shadowbox[{{ bird.Image }}]">a</a>
</div>
</div>
</td>
<td>
<table>
<tr>
<td>
<b>{{ bird.CommonName }}</b>
</td>
</tr>
<tr>
<td>
Spotted at: {{ bird.SpottedAt }}
</td>
</tr>
</table>
</td>
</tr>
</table>
</div>
JavaScript (for nested controller):
anekchidiya.controller('imageController', function($scope, $attrs) {
$scope.imageString = $attrs.model;
console.log("images: " + $scope.imageString);
});
You can perform it by passing your scope into a directive, and you will create an isolated scope.
For example :
Controller
(function(){
function Controller($scope) {
$scope.data = [{
name: 'john',
age: '26'
}, {
name: 'paul',
age: '24'
}, {
name: 'titi',
age: '32'
}];
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Directive
(function(){
function customDirective() {
return{
restrict: 'AE',
template: '<h3>Age : {{age}}</h3>',
scope: {
age: '='
}
};
}
angular
.module('app')
.directive('customDirective', customDirective);
})();
And you can call your directive into the ngRepeat for example, by passing some data :
HTML
<body ng-app="app" ng-controller="ctrl">
<div ng-repeat="item in data">
<h2>Name : {{item.name}}</h2>
<custom-directive age="item.age"></custom-directive>
</div>
</body>
So, typical usage of an isolated scope , it is in a directive that creates a complete component, a widget, etc ...
So, you will be able to build some custom components, and to pass specific data.
Related
This question already has answers here:
Angular directives - when and how to use compile, controller, pre-link and post-link [closed]
(8 answers)
Closed 5 years ago.
I have a directive that needs to do some HTML rewriting on its contents before it gets compiled. I've found a way to do that but it doesn't seem to work for ng-repeat.
HTML
<body ng-controller="MainCtrl">
<div ng-repeat="source in sources">
<p>
{{ source.name }}
</p>
</div>
<div my-template>
{{ sources }}
<div ng-repeat="source in sources">
<p>
{{ source.name }}
</p>
</div>
</div>
</body>
JS
var app = angular.module('plunker', []);
app.directive("myTemplate", ['$compile', function ($compile) {
return {
restrict: 'A',
link: function ($scope, $element, $attributes) {
var html = $element.html();
console.log(html); // problem?
$element.html(html);
$compile($element.contents())($scope);
}
}
}])
app.controller('MainCtrl', function($scope) {
$scope.sources = [{ name: "source1"}, { name: "source2"}];
});
The output is:
source1
source2
[{"name":"source1"},{"name":"source2"}]
So the sources variable is visible in the directive's scope but ng-repeat fails anyway. console.log(html) shows this in a console:
{{ sources }}
<!-- ngRepeat: source in sources -->
Which hints at ng-repeat being evaluated/compiled before my directive but I don't know where to go further from this.
Plunker: http://plnkr.co/edit/1NJngUK27IRAp0ELVHFc?p=preview
As georgeawg noticed, changing from "link" to "compile" phase helps in this case.
You need to use transclude option in directive, see the example. Important you reference a old version of AngularJS (1.0.8), I use in the example 1.4.1.
https://docs.angularjs.org/api/ng/directive/ngTransclude
angular.module('transcludeExample', [])
.directive('myTemplate', function(){
return {
restrict: 'A',
transclude: true,
template: '<ng-transclude></ng-transclude>'
};
})
.controller('ExampleController', ['$scope', function($scope) {
$scope.sources = [{ name: "source1"}, { name: "source2"}];
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.min.js"></script>
<div ng-app="transcludeExample">
<div ng-controller="ExampleController">
<div my-template>
{{ sources }}
<div ng-repeat="source in sources">
<p>
{{ source.name }}
</p>
</div>
</div>
</div>
</div>
I have this custom directive.
I call my directive like bellow, inside a ng-repeat.
selectedMealCalc.calculated_foods as 'items', is an array of objects
<!-- DIRECTIVE -->
<div ng-repeat="option in [0,1,2,3,4]">
<meal-option option="{{option}}"
items="selectedMealCalc.calculated_foods"
selectedmealcalc="selectedMealCalc"></meal-option> </div>
<!-- DIRECTIVE -->
Then I created this directive in angularjs.
'use strict';
angular.module('nutriApp').directive('mealOption', ['$compile', function($compile) {
return {
restrict: 'E',
templateUrl: 'views/checkins/meal-options.html',
scope: {
option: "#",
items: "=",
selectedmealcalc: "="
},
controller: ['$scope', 'Food', function($scope, Food) {
$scope.sumFood = {};
$scope.summerizeOption = function(foods) {
if(foods.length > 0){
$scope.sumFood = Food.summerize(foods);
}
return $scope.sumFood;
};
}]
};
}]);
And this HTML directive.
<div class="row" ng-init="filteredItems = ( items | filter: { food_option: option } )" ng-controller="CheckinsPlansCtrl">
<div class="col-md-12" ng-show="filteredItems.length > 0">
Opção {{ option }}
<table class="table table-calculo table-striped">
<thead>
<tr>
<th>Alimento</th>
<th>Quantidade</th>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="foodCalculation in filteredItems track by $index">
<td>{{foodCalculation.food.name}}</td>
<td>{{foodCalculation.gram_amount}} g</td>
</tr>
</tbody>
</table>
</div>
</div>
When I update the selectedMealCalc.calculated_foods the custom directive is not updating.
I have to close the modal and open again in my page to saw the new line.
As this comment Custom directive inside ng-repeat in this post.
I removed the ng-init because ng-init: "Only to initialize a property on the scope. I would recommend not to use it." as this another answer Does ng-init watch over change on instantiated property like ng-model does?
I create simple project with angular and I use directive to create a simple grid like this code :
my directive :
app.directive('dpGrid',()=>{
return{
restrict:"E",
scope:{
items:"="
}
templateUrl: 'template/dbgrid.directive.html'
}
});
my controller :
app.controller('mainCtrl',($scope)=>{
$scope.data=[{fname:"david",lname:"orman",age:24,id:"234"}];
$scope.update=(id)=>{
console.log("ID :",id);
};
});
my directive template :
<table class="table">
<thead class="thead-inverse">
<tr>
<th>#</th>
<th>fname</th>
<th>lname</th>
<th>age</th>
<th>opt</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items" >
<th>{{$index}}</th>
<td>{{item.fname}}</td>
<td>{{item.lname}}</td>
<td>{{item.age}}</td>
<td><a class="btn btn-danger" ng-click="update(item.id)">update</a></td>
</tr>
</tbody>
</table>
and I use directive like this :
<dp-grid items="data" ></dp-grid>
I want call update() method from directive template, but dont call update() method when click on update btn
You simply need to specify the controller your directive should use, then access it in template.
return {
restrict:"E",
scope:{
items:"="
},
controller: 'mainCtrl',
templateUrl: 'template/dbgrid.directive.html'
}
Then you will be able to access the function in the templates. If you are accessing it from a child isolate scope, you may need to access it using $parent.
I just started to study AngularJS and tries to implement customized table directive with the multiple slots transclude.
And faced situation that scope not transferred to transclude. There is a lot of solutions in other StackOverflow questions, but all of them works only when in directive template ng-repeat appears for top element, but that is not my case.
At least i can't adopt all that solutions.
Simplified version.
Directive:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines" ng-transclude="row">
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
Using of directive:
<my-table>
<row>
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</row>
</my-table>
Full example with the script on Plunkr:
https://plnkr.co/edit/rg43ZdPMGHLBJCTLOoLC
Any advice very appreciated.
The simplest and probably cleanest way to directly reference the $scope object created by the ng-repeat in a transcluded template is through the $parent property:
<my-table>
<td>{{$parent.line.col1}}</td>
<td>{{$parent.line.col2}}</td>
</my-table>
The $parent property of the $scope created for a transcluded template points to the $scope of the target template into which such template is ultimately transcluded (in this case, the ng-repeat), even though such transcluded $scope is not a child of the target $scope in the usual sense as a result of the transclusion. See this wonderful blog post for a more complete discussion of this.
Working plunkr: https://plnkr.co/edit/LoqIMiQVZKlTt5epDnZF?p=preview.
You need to use $transclude function manually and create new child scope for each line. Other than that you need to pass lines to directive if you are using isolated scope (and you are using it).
Your linking function should look something like this:
link: function($scope, $element, $attrs, controller, $transclude) {
var tbody = $element.find('tbody');
$scope.$watch('lines', function (lines) {
tbody.empty();
lines.forEach(function (line) {
var childScope = $scope.$new();
childScope.line = line;
$transclude(childScope, function (content) {
tbody.append('<tr>');
tbody.append(content);
tbody.append('</tr>');
}, null, 'row');
});
});
}
Plunker: https://plnkr.co/edit/MLNZOmoQyMazgIpluMqO?p=preview
But that is bad idea anyway, cos it is hard to create table this way. As you can see child of is not elements. You would have to do a little bit of DOM manipulation to make it work.
i see you don't really need to use attribute
so code look more simple and clean:
<body ng-controller="tableCtrl">
<h1>Table test</h1>
<my-table lines="lines"></my-table>
</body>
your template:
<span>
<div>Some pagination</div>
<div style="display: inline"><input type="text" placeholder="Search"/></div>
<div style="display: inline">Some filters</div>
<table>
<tbody>
<tr ng-repeat="line in lines">
<td>{{line.col1}}</td>
<td>{{line.col2}}</td>
</tr>
</tbody>
</table>
<div>Some pagination again</div>
</span>
and angular directive:
angular.module('myApp', [])
.directive("myTable", function() {
return {
restrict: 'E',
transclude: true,
scope: {
lines:'=lines',
api: '#'
},
templateUrl: "template.html",
};
})
.controller("tableCtrl", ['$scope', function($scope) {
$scope.lines = [
{col1: "testCol1", col2: "testCol2"},
{col1: "testCol11", col2: "testCol21"}
];
}]);
working example in plunkr: https://plnkr.co/edit/iMxRoD0N3sUXqmViHAQh?p=preview
I'm trying to display files inside each folder/directory.
I'm using custom directive to disply each of the directories as follows (this part works).
But it fails to resolve the {{file}} variable inside custom template folderListing.html. Can somebody please correct me where I'm going wrong ?
folderListing.js
app.directive('folderListing', function(){
return {
restrict: 'E',
scope: {
listing:'='
},
templateUrl: 'js/directives/folderListing.html'
};
});
RdaController.js
app.controller('RdaController', ['$scope','RdaService', function($scope,RdaService) {
$scope.folders = ['RDA Server Folder','CTP Outbox', 'CTP Inbox', 'CTP Jobs'];
$scope.sendToCTP = function(file){
return RdaService.SendFileToCTP(file);
};
$scope.listOfFiles = ['learn_javascript.pdf', 'HTML Made Easy.pdf', 'AngularJS for everybody.pdf'];
}]);
index.html
<folder-listing listing="folder" ng-repeat="folder in folders"></folder-listing>
folderListing.html
<div class="row">
<div class="col-md-3" id="{{listing}}">
<table class="table table-striped">
<h3> {{ listing }} </h3>
<div class="row">
<div class="col-md-3" ng-repeat="file in listOfFiles">
{{file}}
</div>
</div>
<td><span class="btn-group" role="group"><button type="button" class="btn btn-default" ng-click="sendToCTP(file)">Execute</button></span></td>
</table>
</div>
</div>
With this:
scope: {
listing:'='
},
You have created an isolate scope passing only listing to the directive. You need to change this to:
scope: {
listing: '=',
listOfFiles: '=',
sendToCTP: '&sendToCtp'
},
To pass the function you'll have to add a send-to-ctp="sendToCTP(file)" attribute on your directive. However, in your template your button with ng-click="sendToCTP(file)" is outside your ng-repeat so file will always be undefined.