Pass a JSON Object as attribute to directive - angularjs

Hi This question is very similar to the question posted here except that the solution doesnt work for me .
i have a Json string like this
ctrl.myData = '{"name":"John","age":30,"cars":["Ford","BMW","Fiat"]}';
I need to assign this to my directive attribute so that my output looks like below
<div my-directive data-attr= '{"name":"John","age":30,"cars":["Ford","BMW","Fiat"]}'> </div>
So when i give
<div my-directive data-attr="{{ctrl.myData}}"> </div>
i get an error
[$parse:syntax] Syntax Error: Token '{' invalid key at column 2 of the
expression [{{ctrl.myData}}] starting at [{ctrl.myData}}].
based on the answer in the other thread, I remove the quotes and gave just
<div my-directive data-attr="ctrl.myData"> </div>
but when i do this, it treats it as a string and prints ctrl.myData in output.
i also tried with single quotes.
How do I attach a JSON object to the directive?
PS - it is not my directive. an Old existing and working one. So can't really change the directive.Any help would be gladly appreciated...

You should read about directive scope types, in scope type we have = for passing variables, # for passing strings, & for passing functions
This sample related to your question we use = to pass an json from our controller or you can pass it from your view.
data-attr='{"name":"John","age":30,"cars":["Ford","BMW","Fiat"]}'
var app = angular.module('app', []);
app.controller('ctrl', function($scope, $http) {
$scope.myData = {
"name": "John",
"age": 30,
"cars": ["Ford", "BMW", "Fiat"]
}
$scope.call = function() {
console.log("requested from controller")
}
});
app.directive('myDirective', function() {
return {
scope: {
attr: '=',
string: '#',
method: '&'
},
link: function(scope, elem, attr) {
console.log(scope.attr);
console.log(scope.string);
scope.method();
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<h1>as json from controller</h1>
<div my-directive data-attr="myData" data-string='hello World' data-method='call()'></div>
</div>

Related

How can I use isolated scope with a component and a directive?

The goal here is to let MainCtrl know when there is an error(s) found in the directive. The error must be displayed here:
<div ng-if="errors[0]">Error 1: {{errors[0]}}</div>
How can I get isolated scope with a directive inside a component? The following application works if you uncomment the 2 lines mentioned below. As it is, I get error:
Multiple Directive Resource Contention
I can read the causes. I need to know how to fix this while still allowing the directive to have isolated scope. I may have 3-4 of these directives on a page and each one needs it's own unique of errors that is also available to the parent.
(working case example on codepen)
var app = angular.module('app', []);
app.controller('MainCtrl', function($scope) {
$scope.errors = [false, false];
$scope.text = "bobby";
});
app.directive('testDirective', function(){
return {
restrict: 'A',
scope: {
errors: '=',
text: '#'
},
link: function($scope, $element, $attr, ngModel) {
console.log('link fired');
console.log('errors: ', $scope.errors);
console.log('scope.text', $scope.text);
$attr.$observe('text', function (val) {
if($scope.text === 'bobby'){
$scope.errors[0] = true;
}else{
$scope.errors[0] = false;
}
});
},
template: '<p>text: {{ text }} </p>'
+ '<p>errors: {{errors}}</p>'
+ '<p><input type="text" ng-model="errors" /></p>'
};
});
app.component('panel', {
bindings: {
},
template: [
'<div>',
'</div>'
].join(''),
controller: function() {
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js"></script>
<section ng-app="app" ng-controller="MainCtrl">
<h3>Parent Scope</h3>
<p>errors: {{errors}}</p>
<input type="text" ng-model="text"></div>
<div ng-if="errors[0]">Error 1: {{errors[0]}}</div>
<div ng-if="errors[1]">Error 2: {{errors[1]}}</div>
<!-- UNCOMMENT THE FOLLOWING 2 LINES AND THIS APP WILL WORK
<h3>Directive by itself</h3>
<div test-directive text="{{text}}" errors="errors"><div>
-->
<h3>Directive in component</h3>
<panel test-directive text="{{text}}" errors="errors"></panel>
</section>
After researching, I noticed Angular only returns bool from $validators (as opposed to object). At this point I decided my approach was wrong. I decided to create a unique $valiators for each unique error message. Then use ng-message for the output.
In order to work with multiple components on the same page, I also have to check the ngModel.$error as part of validation. This blog covers the basic approach.

Build template string inside directive

I'm trying to build a string of HTML code inside the directive and then use that as the directive's template.
I tried this but it doesn't work.
myApp.directive('myDirective', [function() {
return {
restrict: 'E',
scope: {
data: '=' // this is the data that will shape the HTML
},
template: str,
controller: function($scope){
$scope.str = ****BUILD THE STRING HERE USING 'data'****
}
};
}]);
Then I tried passing the app's scope to the directive, but got an error again
myApp.directive('myDirective', ['$scope', function($scope) {
var str = ****BUILD THE STRING HERE USING $scope.data****
return {
restrict: 'E',
template: str
};
}]);
I'm confused about how I can do this. In the first example the string is built correctly and when I do
template: {{str}}
I see the string but obviously, it just shows up as text on the page and it's not interpreted as HTML code.
Is there a way to access either myApp's or the directive controller's scope within the return statement of the directive?
I could build the string outside of the directive, but even if I do that I still can't access myApp's scope within the directive.
Directives, by nature have access to the outer scope, if you don't strictly define it with an inner scope (or isolated scope). Example:
angular.module('app',[]).controller('ctrl', function($scope) {
$scope.example = {
message: "Hello world"
};
}).directive("myDirective", function() {
return {
template: '<strong>{{example.message}}</strong>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div my-directive></div>
</div>
As you can see in the example above - The directive "knows" the outer scope values without you need to actually inject it.
Now, you can create an isolated scope and by doing this you don't limit yourself to a given scope:
angular.module('app',['ngSanitize']).controller('ctrl', function($scope) {
$scope.example = {
html: "<strong>Hello world</strong>"
};
}).directive("myDirective", function() {
return {
scope: {
data: '='
},
template: '<div ng-bind-html="data"></div>'
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular-sanitize.min.js"></script>
<div ng-app="app" ng-controller="ctrl">
<div my-directive data="example.html"></div>
</div>
By including ngSanitize I an able to use the ngBindHtml directive and pass an HTML structure to the directive inner scope.
Strictly speaking what you're trying to do can be achieved by using ng-if to output html that uses different directives based on the data count in your controller. This is better 'separation of concerns', as it means you're moving your presentation logic into the view where it belongs, and your controller is then concerned with your business logic including a data count variable (which you can then use in the ng-if).
If using this approach, you'll want to put your data retrieval into a service that the controller uses, and use the same controller for both directives.

Angular - Bind directive value to controller object

I'm trying to pass an array from a controller to a directive and for some (probably obvious to you lot!) reason when the array values are updated in the controller it does not reflect in the directive. The controller obtains data from a service into an array and I want to pass that array to the directive to create a bar graph. I've put the key parts of the code below.
Here is my top level HTML
<div dash-progress
graph-data="{{dashCtrl.myProgress}}">
</div>
<div>
Other Stuff
</div>
My template HTML for the directive:
<div class="boxcontent" ng-show="dashCtrl.showProgress">
<div class="chart-holder-lg">
<canvas tc-chartjs-bar
chart-data="progress"
chart-options="options"
height="200"
auto-legend>
</canvas>
</div>
</div>
Controller:
angular
.module('myApp')
.controller('dashCtrl',['mySvc',
function(mySvc) {
var self = this;
this.myProgress = [];
this.getProgress = function() {
//logic must be in the service !
mySvc.getProgress().then(function(success) {
self.myProgress = mySvc.progress;
});
};
}]);
and the directive:
angular
.module('myApp')
.directive('dashProgress', [function() {
return {
restrict: 'AE',
templateUrl: 'components/dashboard/progress.html',
scope: {
graphData: '#'
},
link: function(scope,el,attrs) {
scope.progress = {
labels: ['Duration','Percent'],
datasets: [
{
label: 'Duration',
data: [scope.graphData.duration]
},
{
label: 'Percent',
data: [scope.graphData.percent]
}
]
};
scope.options = { };
}
}
}]);
If I set an initial values of the myProgress object in the controller then these do get reflected in the directive, but I don't get the real values that I need when they are returned to the controller from the service.
In your directive's scope, instead of this:
scope: {
graphData: '#'
}
try using this:
scope: {
graphData: '='
}
Don't use {{ }} when passing array to the directive with =. It will render the array in the view instead of passing a reference to directive's scope.
As far as I know, # is not only one-way binding, but also one-time binding and should be used mostly for string values (e.g. setting an html attribute while initializing directive). If you'd like to use #, you should firstly convert data to JSON, then pass it to directive with {{ }}, then parse it again in directive and after any change - manually recompile the directive. But it would be a little overkill, wouldn't it?
Conclusion
Just remove the curly brackets from the view and use = to bind value to directive's scope.
View
<div dash-progress
graph-data="dashCtrl.myProgress">
</div>
Directive
scope: {
graphData: '='
},
Update
Try one more thing. In dashCtrl, wrap myProgress with an object (you can change names to be more self-explaining - this is just an example):
this.graphData = {
myProgress: []
}
this.getProgress = function() {
mySvc.getProgress().then(function(success) {
self.graphData.myProgress = mySvc.progress;
});
}
Then, pass graphData to directive:
<div dash-progress
graph-data="dashCtrl.graphData">
</div>
Finally, substitute every scope.graphData with scope.graphData.myProgress. This way you make sure that scope.graphData.myProgress always refers to the same data because it's a property of an object.
If this still doesn't work, you will probably have to use a watcher and update properties of scope.progress manually.

How do I write a directive to set attributes of another directive?

I've seen similar questions, but I'm having trouble applying them to my situation, so I appreciate any help you can give me. I'm using the angular-nvd3 directive to make 4 different types of charts within many different controllers. Right now, I'm adding them to each view & controller as shown in their basic example.
angular.module('myApp', ['nvd3'])
.controller('myCtrl', function('$scope'){
$scope.options = { /* JSON data */ };
$scope.data = { /* JSON data */ }
})
and in html:
<div ng-app='myApp'>
<div ng-controller='myCtrl'>
<nvd3 options='options' data='data'></nvd3>
</div>
</div>
I'm using the same 4 versions of $scope.options over and over again, so I'd like to write a set of directives that would allow me to write this in HTML instead (and only define $scope.data in the controllers).
<nvd3 typeA data='data'></nvd3>
I've seen examples of how to add new attributes and point them to scope variables, but how do I point the attribute to a fixed JSON object?
You can create a directive that wraps the nvd3 directive and adds the options data like this
html:
<typea data='data'></typea>
javascript:
angular.module('myApp').directive('typea', function() {
return {
scope : {
data:"="
},
restrict: 'E',
template: "<nvd3 options='options' data='data'></nvd3>" ,
link: function($scope) {
$scope.options = { /* JSON data */ }
}
};
});

How can I use ng-repeat to generate custom html tags or directive?

I am trying to create some custom tag using angularjs with ng-repeat but its not working, its giving tag name in double quotation marks.
Good result is: shouldn't show <div> or another tags.
I have a demo version here : http://plnkr.co/edit/aT2UjMIGGLvnCGFxXC3a?p=preview
or you can use code snippet
my code is:
angular.module('myApp', [])
.controller('MainCtrl', function($scope) {
$scope.items = [
{
"name":"directive1"
},
{
"name":"directive2"
},
{
"name":"div"
}
];
})
.directive("showitems", function(){
return {
restrict: "E",
template: '<div class="item" ng-repeat="item in items"><div class="item-title">{{item.name}}</div><div class="item-body"><{{item.name}}></{{item.name}}></div></div>'
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MainCtrl">
<showitems></showitems>
</div>
AFAIK, doing something like <{{item.name}}></{{item.name}}> will never work in angular since the markups aren't treated as new DOM elements. A much better, and more manageable approach is to write directives for all possible types you'd want to render as DOM elements.
Also, to know more about DOM manipulations from within a directive, read up about $compile: here
An alternate way of doing this would be something like this in your directive template:
<directive1 ng-if="directiveType == 'directive1'"></directive1>
<directive2 ng-if="directiveType == 'directive1'"></directive2>
<directive3 ng-if="directiveType == 'directive1'"></directive3>
And in your controller/directive, you must declare directiveType to the type of directive you want to render.
Instead of wrapping {{item.name}} into its own tag, make a customTag directive. This can take a string (the tag name) and create and compile an element with it.
.directive("customTag", function ($compile) {
return {
restrict: "A",
link: function (scope, element, attrs) {
//pass customTag in as part of the directive attribute
var customTag = attrs.customTag;
if (!customTag) return;
var customElem = angular.element("<" + customTag + "></" + customTag + ">");
//replace your custom element directive with the compiled one
element.replaceWith($compile(customElem)(scope));
}
}
}
Then in your template.html:
<div class="item" ng-repeat="item in items">
<div class="item-title">{{item.name}}</div>
<div class="item-body">
<!-- This will use customTag directive to create element item.name -->
<div data-custom-tag="{{item.name}}"></div>
</div>
</div>
A div tag (or any non-directive) wouldn't be too useful because it doesn't get any innerHTML or behavior like directive1 and directive2 would get. This of course is a simple example, you may want to put some more checks or restrictions in there, but this should get you started.
See fiddle here that builds on your example.

Resources