AngularJS directive is not getting data passed in scope - javascript

Here is my html snippet :
<div ng-controller="ctrl">
<custom-tag
title = "name"
body = "content">
</custom-tag>
</div>
Here is controller and directive written:
var mod = angular.module("main_module",[]);
//Controller
mod.controller("ctrl",function($scope) {
$scope.name="Page Title";
$scope.content="sample_template.html";
});
//Directive
mod.directive("customTag", function() {
return {
'restrict' : 'E',
'scope' : {
'title' : '=',
'body : '='
},
'templateUrl' : 'directive_template.html'
};
});
<!-- directive_template.html -->
<div>
<div>{{title}}</div>
<div ng-include="'{{body}}'"></div>
</div>
The actual html rendered by directive is this :
<div>
<div ng-binding></div>
<!-- ngInclude: '{{body}}' -->
</div>
Clearly it is not getting the directive scope variables from attributes in <custom_tag>
Please tell me why it is happening and how I resolve this issue.
Thanks

Check the console for errors extra quotes and {{}} braces were breaking things.
<div>
<div>{{title}}</div>
<div ng-include="body"></div>
</div>
http://plnkr.co/edit/G9JfIJGhSghUbgkKLXnV?p=preview

Related

AngularJS ng-repeat doesn't work in a directive that rewrites HTML [duplicate]

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>

accessing other scope variables inside custom template

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.

Ng-repeat in custom directive does not repeat

I'm trying really hard, but I can't find where is my problem.
I created a custom directive and it should iterate some HTML to draw the right content on the screen.
The problem is that my ng-repeat does not iterate my array. I search on stackoverflow but what I found didn't help me.
Here is my directive (it's in an external file):
app.directive('logtab', function(){
return {
restrict: 'E',
templateUrl: 'view/templates/log-tab.html',
replace: true,
controller: ['$scope', 'api', function($scope, api) {
$scope.logState = false;
$scope.logData = [1,2,3,4];
$scope.loadLog = function() {
api.doRequest({
path : $scope.path,
method : "GET",
broadcast : BK_LOG
});
};
var bk = $scope.$on(BK_LOG, function(key, value){
$scope.logState = true;
console.log($scope.logData);
bk();
});
}]
};
});
And here is the directive HTML that will be rendered:
<md-tab ng-click="loadLog()" label="{{i18n['REVISIONS']}}">
<div layout="row" layout-align="center" layout-padding ng-show="!logState">
<div layout="column">
<div>
<md-progress-circular md-mode="indeterminate" md-diameter="130"></md-progress-circular>
</div>
<div>
{{i18n['LOG_LOAD']}}
</div>
</div>
</div>
<div layout="row" ng-show="logState" layout-padding>
<div layout="column">
<div ng-repeat="xyz in logData">
{{xyz}}
SOME CONTENT HERE
</div>
</div>
</div>
</md-tab>
Thanks in advance!
I removed other components from your directive code and kept it at bare minimum. The code is working fine as expected. Please see below.
Index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Index</title>
</head>
<body ng-app="myApp" >
<logtab></logtab>
<script src="angular.js" type="text/javascript "></script>
<script src="app.js" type="text/javascript "></script>
</body>
</html>
app.js
var myApp = angular.module('myApp', []);
myApp.directive('logtab', function(){
return {
restrict: 'E',
templateUrl: 'log-tab.html',
replace: true,
controller: ['$scope', function($scope) {
$scope.logState = false;
$scope.logData = [1,2,3,4];
console.log('hi');
}]
};
});
log-tab.html
<div>
{{logData}}
<div layout="row">
<div layout="column">
<div ng-repeat="xyz in logData">
{{xyz}}
</div>
</div>
</div>
</div>
Please use a couple of console.log's to identify the exact location. I suspect that there must be some thing wrong with the other parts of the directive or may be logState is set to false??
You can always separate the controller from the directive:
app.directive('logtab', function(){
return {
restrict: 'E',
templateUrl: 'view/templates/log-tab.html',
replace: true,
scope: {
log: '='
}
};
});
Then in your controller
app.controller('MyController', ['$scope', 'api', function($scope, api) {
$scope.logTab = {
state: false,
data: [1, 2, 3, 4],
loadLog: function() {
api.doRequest({
path : $scope.path,
method : "GET",
broadcast : BK_LOG
});
}
};
var bk = $scope.$on(BK_LOG, function(key, value){
$scope.logTab.state = true;
console.log($scope.logTab.data);
bk();
});
}]);
Then change your HTML to:
<md-tab ng-click="log.loadLog()" label="{{i18n['REVISIONS']}}">
<div layout="row" layout-align="center" layout-padding ng-show="!log.state">
<div layout="column">
<div>
<md-progress-circular md-mode="indeterminate" md-diameter="130"></md-progress-circular>
</div>
<div>
{{i18n['LOG_LOAD']}}
</div>
</div>
</div>
<div layout="row" ng-show="log.state" layout-padding>
<div layout="column">
<div ng-repeat="xyz in log.data">
{{xyz}}
SOME CONTENT HERE
</div>
</div>
</div>
</md-tab>
And call it with
<logtab log="logTab"></logtab>
Updated my answer based on OP's explanation.
Created a fiddle here: https://jsfiddle.net/frishi/bzbbo5da/14/
I simplified the directive definition a little, removed the api part of it.
I also changed the directive's template to include the ng-repeat part of it.
//....
restrict: 'E',
template: `<p><div ng-repeat="xyz in logData">
{{xyz}}
SOME CONTENT HERE
</div></p>`,
replace: false,
scope: true, // <- needs to be set
//....
You are missing the scope parameter in the directive definition object. You have to set it to scope: true for your directive's template to be able to access a variable defined on the directive controller's scope.

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 use the moment library in angular in a directive that uses compile instead of link

I have a tree of connected documents (parent to child) in my database from a single model called Actions, they're recursively compiled in an angular directive so that they're nested inside their parents.
I have the following code:
angular.module('crmDashboardApp')
.directive('actionDirective', function ($http, $compile, RecursionHelper) {
return {
scope: {
actionId: '=', // html:action-node, js:actionNode
actionList: '='
},
templateUrl: 'app/main/directive/action/action.directive.html',
replace: true,
restrict: 'E',
compile: function (element) {
return RecursionHelper.compile(element, function(scope, iElement, iAttrs, controller, transcludeFn){
scope.deleteAction = function (_action) {
var id = _action._id;
$http.delete('/api/actions', {
data: {'id':id},
headers: {"Content-Type": "application/json;charset=utf-8"} // we need to do this if we want to send params, otherwise we need to do traditional REST in URL
});
};
// Find for already called action list
scope.findAction = function (_id, _list) {
scope.actionNode = _.findWhere(_list, {_id:_id})
};
scope.findAction(scope.actionId, scope.actionList);
function calculateTimeSince(){
scope.fromNow = moment(scope.actionNode.content).fromNow(true);
}
setInterval(calculateTimeSince, 1000);
scope.fromNow = moment(scope.actionNode.content).fromNow(true);
});
}
};
});
This only compiles once on load and changing anything in the scope after does nothing. I want the setInterval function to change a variable scope.fromNow to be updated every second and update the view (the HTML references this with a simple {{fromNow}})
I believe I'll have to re-compile the directive somehow but doing something like:
$compile(element.contents())(scope)
within the setInterval function doesn't work.
My directive's HTML looks like this:
<div class="action-node">
<header>{{ actionNode.name }}</header>
<div class="row">
<h3 class="col-md-12">{{ actionNode.title }}</h2>
<h5 class="col-md-12">{{ actionNode.description }}</h5>
</div>
<div class="row">
<div class="col-md-3">Time Since: {{fromNow}}</div>
<div class="col-md-3">Content: {{ actionNode.content}}</div>
<div class="col-md-3">Duration Type:{{ actionNode.duration_type }}</div>
<div class="col-md-3">Type: {{ actionNode.type }}</div>
</div>
<div class="row">
<div class="col-md-4">
{{actionNode.children.length > 0 ? actionNode.children : "No children" }}
</div>
<form class="pull-right" ng-submit="deleteAction(actionNode)">
<input class="btn btn-primary" type="submit" value="Delete">
</form>
</div>
<div class="action-wrapper" ng-repeat="child in actionNode.children" ng-if="actionNode.children.length > 0">
<!-- <div class="row" ng-repeat="child in actionNode.children" ng-if="actionNode.children.length > 0" ng-style="{'margin-left': ({{actionNode.nest_level}}+1)*30+'px'}"> -->
<action-directive action-ID="child" action-list="actionList" />
</div>
</div>
You can see that it calls itself again right at the bottom. I am also using RecursionHelper so infinite loop isn't an issue.
Instead of using setInterval, you need to use the Angular wrapper service $interval.
$interval service synchronizes the view and model by internally calling $scope.$apply which executes a digest cycle.

Categories

Resources