AngularJS directive: template with scope value (ng-bind-html) - javascript

I have such directive:
...
template: function(element, attrs) {
var htmlTemplate = '<div class="start-it" ng-if="isVisible">\
<p ng-bind-html="\'{{customDynamicText}}\' | translate"></p>\
</div>';
return htmlTemplate;
},
...
(as you can see also i'm using translate plugin)
and there i have a problem: in scope this value is changing, but it doesn't change in directive(
when i'm using attrs-params (sure, if customDynamicText is a static string - all works) - but i have a dynamic variable customDynamicText
How can i use this dynamic variable in directive template with ng-bind-html.
Is it possible?

Simply clever...
I forgot to remove some quote-chars...
So works:
...
<p ng-bind-html="' + attrs.customDynamicText + ' | translate"></p>\
...

Related

Pass scope variable from directive to it's controller

This is possibly easy, but I have browsed the different questions here on SO and in the Angular documentation and can't really find what I'm looking for.
In a directive:
function ssKendoGrid() {
return {
scope: {
dataSource: "="
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl",
}
}
That uses the controller:
function ssKendoGridCtrl($scope) {
alert($scope.dataSource);
//other stuff
}
If I want to access the value of dataSource I assumed I'd be able to do something like this:
<div ng-controller="myController">
<div ss-kendo-grid data-source="test"></div>
</div>
MyController is:
function myController($scope) {
$scope.test = "Tested";
}
But it comes as undefined when I try to alert($scope.dataSource); the value..
Now I know I can do this:
<div ss-kendo-grid="test"></div>
And access it in the directive and controller like this:
return {
scope: {
ssKendoGrid: "="
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
//In controller
alert($scope.ssKendoGrid);
But I would like to be able to pass in a JSON object to do various things with and this doesn't seem as clean as in the markup I'd like it to be more intuitive to look at the html and know what the dataSource is.
What I'm really looking for is an understanding of what I'm doing wrong, why doesn't this work?? I've obviously not got the right understanding of how to pass various things to the isolated scope of the directive.
SOLVED
So, turns out I was using the wrong attribute name. HTML5 recognizes data- as a valid attribute, and Angular ignores the fact that data- is prefixed on the variable, which means that I would need to access the variable this way:
HTML:
<div ss-kendo-grid data-source="test"></div>
JS:
return {
scope: {
dataSource: "=source"
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
Cheers
you need to access the directive scope variable as
<div ss-kendo-grid data-source="test"></div>
similarly as you name the directive in the HTML markup
So, turns out I was using the wrong attribute name. HTML5 recognizes data- as a valid attribute, and Angular ignores the fact that data- is prefixed on the variable, which means that I would need to access the variable this way:
HTML:
<div ss-kendo-grid data-source="test"></div>
JS:
return {
scope: {
dataSource: "=source"
},
template: "<div kendo-grid k-options='gridOptions'></div>",
controller: "ssKendoGridCtrl"
}
And a better convention is to simply not use a directive with "data-" at the beginning of it.
invite.directive('googlePlaces', function (){
return {
restrict:'E',
replace:true,
// transclude:true,
scope: {location:'=location'},
template: '<input id="google_places_ac" name="google_places_ac" type="text" class="input-block-level"/>',
link: function(scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
scope.location = place.geometry.location.lat() + ',' + place.geometry.location.lng();
console.log(scope.location);
scope.$apply();
// scope.$apply(function() {
// scope.location = location;
// });
});
}
};
});

How do I leave static content if property is empty in angular?

I have a simple angular controller in my app. And I wish to enhance a page containing static content, with angular. This will allow me to have a good, content rich page as fall back for those without JS (who?!) and web crawlers and readers.
So far I have identified a few ways to do this, possibly using custom directives, or by doubling up content in 'ng-bind-template' directive (which is obviously not ideal).
I am fairly new to the world of angular so would like to try and garner the best practice for this scenario.
What is the best approach for this?
Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Markup:
(The problem here is that 'Static content' is replaced by '' if angular initializes)
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
EDIT
I should be clear that even though angular is bootstrapping, The aim is to leave the default content intact, and not replace it with dynamic content. That only wants to be achieved once the activate button is clicked. I have tried the following but it involves doubling up the content which could get bulky if it is a whole article.
<div ng-controller="TestArticle">
<div ng-cloak ng-bind-template='{{eTitle || "Static Content"}}'>Static content</div>
<div ng-cloak ng-bind-template='{{eContent || "Some more content"}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div> <a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
You can create a simple directive to create the html structure that you want.
I propose this to be your new html:
<div ng-cloak sk-custom-bind="eTitle">Static content</div>
Notice how you don't have to specify the or clause with the static content.
Now you can create a directive that will build the html template for you:
app.directive('skCustomBind',function() {
return {
restrict: 'A',
template: function(elem, attrs) {
var templ = "<div ";
templ += "ng-bind-template='{{";
templ += attrs.skCustomBind;
templ += ' || "';
templ += elem.text();
templ += '"';
templ += "}}'></div>";
return templ;
},
replace: true
};
});
And as simple as that you have the functionality that you are looking for without the duplication.
See this plunker to see a working sample.
The thing here is that when you define the controller, angular will parse through the html to find all the variables and link them with the corresponding controller items using $scope.
So, when angular initializes, since eTitle and eContent are not having any values (since you initialize them in the function only), which is called on click of the last item(where on your application works fine).
So until you click the button, the static text is replaced by $scope.eTitle and $scope.eContent both of which are undefined. Hence no text inside the tags.
So the solution,
1. Either you initialize the variables inside the controller with the static text, like you did for 'This is angular injecting this'(reason only this came!):
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this";
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
$scope.activate = function(){
$scope.eTitle = "Dynamic title";
$scope.eContent = "Dynamic content";
};
});
Or you can initialize the variables in the html using ng-init, it will initialize the variable in html only:
<div ng-controller="TestArticle">
<div ng-init="eTitle = 'Static Content'" ng-cloak ng-bind-template='{{eTitle}}'>Static content</div>
<div ng-init="eContent = 'Some more content'" ng-cloak ng-bind-template='{{eContent}}'>Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="activate()" ng-cloak>click to activate</a>
</div>
The thing is when you start using angular, the html will get kind of hijacked and angular will start using its own variables.
Thanks to #JoseM for putting me on to right track and helping me find the following solution...
SOLUTION
My Markup:
NOTE: the custom directive and properties ('cg-dyn' and 'cg-content'). These will not initialise any values when angular bootstraps. And further down you will see how these map from controller scope via an isolated scope.
<div ng-controller="TestArticle">
<div ng-cloak cg-dyn cg-content="eTitle">Static content</div>
<div ng-cloak cg-dyn cg-content="eContent">Some more content</div>
<div ng-cloak>{{initValue}}</div>
<a href ng-click="upDateTitle()" ng-cloak>Update title</a> | <a href ng-click="upDateContent()" ng-cloak>Update content</a>
</div>
My Controller:
app.controller('TestArticle',function($scope, feed){
$scope.initValue = "This is angular injecting this"
$scope.upDateTitle = function(){
$scope.eTitle = "Dynamic title";
};
$scope.upDateContent = function(){
$scope.eContent = "Dynamic Content Lorem ipsum";
};
});
And finally the magic in My Directive
app.directive('cgDyn',function() {
return {
restrict: 'A',
scope:{
'content':'=cgContent'
// this is a two way binding to the property defined in 'cg-content
},
link: function(scope, elem, attrs) {
// below is a watcher keyed on a combination content
scope.$watch('content',function(){
if(scope.content){
elem.html(scope.content);
// and here is the bit, normally plumbed by default, but instead we only replace the html if the value has been set
}
});
},
controller: 'TestArticle',
replace: false
};
});
Thanks for everyone's help. I am sure there is an even better solution out there so keep the suggestions coming!

How do I use the '&' correctly when binding in a directive in Angularjs?

I have having trouble with understanding the & option in binding scope properties. I have read these tutorials: angularjs-directives-using-isolated-scope-with-attributes, practical-guide-angularjs-directives and the-hitchhikers-guide-to-the-directive as well as looking at the documentation but I am completely confused about how to use the & symbol.
Here is my attempt at using it:
var app = angular.module('directivesApp');
app.directive('simplyIsolated', function () {
return{
restrict: 'EA',
replace: true,
scope:{
attnum: '#numone'
,bindnum: '=numtwo'
,expressnum: '&sq'
},
link: function (scope, elem, attr){
scope.x = scope.expressnum();
},
template:'<div><p> using "#" = {{attnum+attnum}}</p>'+
'<p>using "=" {{bindnum+bindnum}}</p>'+
'<p>using "&" {{x}}</p><br/><p>{{y}}</p>'+
'</div>'
};
})
.controller('MainCtrl', function ($scope) {
$scope.sqr = function(num){
return num*num;
}
});
and this is my html
<div class="container" ng-controller="MainCtrl">
<input type="text" ng-model="num1parent" />
<input type="number" ng-model="num2parent" />
<input type="number" ng-model="num3parent" />
<p>Parent Scope # {{usernameparent}},
= {{userageparent}},
& = {{sqr(num3parent)}}
</p>
<div simply-isolated numone='{{num1parent}}' numtwo='num2parent' sq="sqr"></div>
</div>
This is the result.
the first two inputs are used to show the difference between # and =. The third input is used to show the sqr() method works but in the text underneath the using "&" is supposed to be the square of the 2nd input but I don't get any result or error
If someone could point me in the right direction I would really appreciate it
Also: Why would you use the & over scope.$parent?
Simply because you're not using the function scope.x as a function instead you have it evaluated as a mere function expression with no argument value.
try changing the template to this:
template:'<div><p> using "#" = {{attnum+attnum}}</p>'+
'<p>using "=" {{bindnum+bindnum}}</p>'+
'<p>using "&" {{x(bindnum}}</p><br/><p>{{y}}</p>'+
'</div>'
that should show something when you provide changes in the num2parent model.
Why would you use the & over scope.$parent?
Because it is normally not recommended to do so, if you would be doing it in such a manner it might be better not to isolate the scope of your directive and have direct access to the parent controllers properties in the directive.
Update:
The reference passed by the attribute notiation '&' is a function that returns the function defined in your controllers. To invoke the function reference, simply do this:
{{expressnum()(bindNumb)}}
that will do the trick.
Note:
There is a reason for this, functions often passed via '&' attribute notations are function callbacks such as events. So if your exressnum() was an callback function for an event then it would have been used like this in you template perhaps
<a ng-click="expressnum()">Click Expression</a>
Furthermore, the function sqr() is a type of function that changes the result of an expression, such functions must be defined as a filter.

$digest rendering ng-repeat as a comment

I'm writing a test for a directive, when executing the test the template (which is loaded correctly) is rendered just as <!-- ng-repeat="foo in bar" -->
For starters the relevant parts of the code:
Test
...
beforeEach(inject(function ($compile, $rootScope, $templateCache) {
var scope = $rootScope;
scope.prop = [ 'element0', 'element1', 'element2' ];
// Template loading, in the real code this is done with html2js, in this example
// I'm gonna load just a string (already checked the problem persists)
var template = '<strong ng-repeat="foo in bar"> <p> {{ foo }} </p> </strong>';
$templateCache.put('/path/to/template', [200, template, {}]);
el = angular.element('<directive-name bar="prop"> </directive-name>');
$compile(el)(scope);
scope.$digest(); // <--- here is the problem
isolateScope = el.isolateScope();
// Here I obtain just the ng-repeat text as a comment
console.log(el); // <--- ng-repeat="foo in bar" -->
}));
...
Directive
The directive is fairly simple and it's not the problem (outside the test everything works just fine):
app.directive('directiveName', function () {
return {
restrict : 'E',
replace : true,
scope : {
bar : '='
},
templateUrl : '/path/to/template', // Not used in this question, but still...
});
A few more details:
The directive, outside the test, works fine
If I change the template to something far more simple like: <h3> {{ bar[0] }} </h3> the test works just fine
The rootScope is loaded correctly
The isolateScope results as undefined
If you have a look at the generated output that angular creates for ng-repeat you will find comments in your html. For example:
<!-- ngRepeat: foo in bars -->
These comments are created by the compile function - see the angular sources:
document.createComment(' ' + directiveName + ': '+templateAttrs[directiveName]+' ')
What you get if you call console.log(el); is that created comment. You may check this if you change the output in this way: console.log(el[0].parentNode). You will see that there are a lot of childNodes:
If you use the directive outside of a test you will not be aware of this problem, because your element directive-name will be replaced by the complete created DocumentFragment. Another way to solve the problem is using a wrapping element for your directive template:
<div><strong ng-repeat="foo in bar"> <p> {{ foo }} </p> </strong></div>
In this case you have access to the div element.

Directive Isolate Scope 1.2.2

I'm working with Angular version 1.2.2 for the first time and trying to make a simple directive that uses isolate scope with '=' binding to pass in an object. I've done this a few times before so I'm wondering if maybe there was a change in 1.2.2 that changed this?
Here is my directive:
.directive('vendorSelector', function (VendorFactory) {
return {
restrict: 'E',
replace: true,
scope: { vendorId: '=' },
template: '<select ng-model="vendorId" ng-options="id for id in vendorIds">' +
'<option value="">-- choose vendor --</option>' +
'</select>',
link: function (scope, element, attrs) {
VendorFactory.getVendorIds().then(function(result) {
scope.vendorIds = result;
});
}
}
})
My HTML template using the directive is as follows:
<div class="padding">
<vendor-selector vendorId="someValue"></vendor-selector>
{{ someValue }}
</div>
And the backing controller:
.controller('AddProductController', function($scope, ProductFactory, AlertFactory) {
$scope.vendorId = 0;
$scope.someValue = undefined;
})
I've tried using both $scope.someValue and $scope.vendorId as the supplied object in the html template. In both cases the error I'm getting back is Expression 'undefined' used with directive 'vendorSelector' is non-assignable!. Am I missing something obvious that is preventing these values from being 2-way bound in the isolate scope?
In your html:
<vendor-selector vendorId="someValue"></vendor-selector>
Change vendorId="someValue"
to vendor-id="someValue"
HTML attributes are case insensitive so to avoid confusion Angular converts all camel cased variables (vendorId) to snake case attributes (vendor-id).
So someValue wasn't bound to vendorId. Resulting in vendorId being undefined in the template. And thus your error.

Categories

Resources