Directive: Link function children not interpolating values - angularjs-directive

I am trying to output child element id in a directive, but it keeps printing non-interpolated values. I don't know how to achieve that....please help.
I am trying to learn angular...
//////////////////////
//Directive example
app.directive('simpleNumber', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'A', /*Example: <span simple-number></span>*/
terminal: true,
transclude: true,
replace: true, /*Replace <simple-number-ctrl> tag with below template*/
template: "<div><div id=\"{{$id}}\"></div></div> ",
scope: { /*data-binding to parent scope*/
ctrlWidth: "#", /*one way binding*/
strNumber: "=", /*two way binding*/
onWidthChanged: "&" /*Event function fired*/
},
link: function($scope, elm, attrs) {
console.log(elm.children()[0].id); //This is printing {{$id}} !!! I AM CONFUSED
}
};
}]);
<span simple-number ctrl-width="100px" str-number="numberText" on-width-changed="onWidthChanged(width);"><font color=green>Transcluded text</font></span>

When the linking function is called, the {{ }} bindings are not evaluated yet, so you still have them as raw text. You will get the correct value once the $digest cycle is finished.
Use this in your link function if you want to better understand what is going on.
link: function($scope, elm, attrs) {
console.log("bindings not evaluated yet:",element.html());
var unwatch = scope.$watch(function(){
console.log("after $digest, bindings are evaluated",element.html());
unwatch();
});
}

I have solved this by using $timeout (see code below). I don't know why it's working this way. Can someone provide an explanation?
//////////////////////
//Directive example
app.directive('simpleNumber', ['$http', '$compile', function($http, $compile) {
return {
restrict: 'A', /*Example: <span simple-number></span>*/
transclude: true,
replace: true, /*Replace <simple-number-ctrl> tag with below template*/
template: "<div><div id=\"{{$id}}\"></div></div> ",
link: function($scope, elm, attrs) {
//Print children ID's
var __debugOutputChildrenInfo = function(children)
{
for(var i = 0; i < children.length; i++)
console.log(children[i].id);
}
var children = elm.children();
__debugOutputChildrenInfo(children); //Prints {{$id}} [Shouldn't binding have been resolved by now?]
//Don't understand why this is working...need explanation?
$timeout(function() {
__debugOutputChildrenInfo(children); //Prints 002 [THIS IS WHAT I WANTED..NEED EXPLANATION]
});
}
};
}]);
<span simple-number ctrl-width="100px" str-number="numberText" on-width-changed="onWidthChanged(width);"><font color=green>Transcluded text</font></span>

Related

access app controller scope inside custom directive in ng-repeat

I'm new to this so sorry if I'm asking something obvious. I have an app with controller holding some config variables in scope. I have a custom directive used inside ng-repeat that will have to make use of this config. For debugging purposes I need the changes to config bee reflected inside the directive. How can I achieve this.What I have so far is not working my pointsmap is undefined
angular.module('demo', [])
.directive('demoDir', function () {
var dirController = ['$scope', function ($scope) {
$scope.totalPoints = 0;
$scope.$watch('person', function(newVal, oldVal){
resetPoints(newVal);
}, true);
function resetPoints(pPerson){
$scope.totalPoints = $pointsMap['VIP'] * pPerson.points ;
}
return {
restrict: 'E',
scope: {
person: '=' ,
pointsMap : '='
},
controller: dirController,
template: '<span> {{totalPoints}}</span>'
}
})
.controller('mainAppController', function ($compile, $scope, $q ) {
/*CONFIG */
$scope.points = {
'VIP': 8.50,
'Standard': 7.50,
};
});
<demoDir person='myobject' pointsMap='points' />
Any particular reason why you want your directive to have isolated scope? If there is no restriction to use isolated scope, you can simply make the scope of the directive as false(now directive will use parent scope)
angular.module('demo', [])
.directive('demoDir', function () {
var dirController = ['$scope', function ($scope) {
$scope.totalPoints = 0;
$scope.$watch('person', function(newVal, oldVal){
resetPoints(newVal);
}, true);
function resetPoints(pPerson){
$scope.totalPoints = $pointsMap['VIP'] * pPerson.points ;
}
return {
restrict: 'E',
scope: false,
controller: dirController,
link: function(scope, element, attrs){
//scope.points should be available
}
template: '<span> {{totalPoints}}</span>'
}
})
.controller('mainAppController', function ($compile, $scope, $q ) {
/*CONFIG */
$scope.points = {
'VIP': 8.50,
'Standard': 7.50,
};
});
//assuming that your directive is housed inside the mainAppController
<div ng-controller="mainAppController">
<demoDir/>
</div>
You will also have access to the parent scope properties in the directive's link function if you want to use.

angular: how to nest templates inside <pre> tags?

does anyone know how do I do ng-include from within a 'pre' tag? The usecase is that I have a website with some code blocks and some of the code blocks contain common code that I want to move to a separate partial. Example:
<pre>
My page-specific code....
<ng-include src="'partials/common-code.html'"></ng-include>
</pre>
Needless to say, this does not work, as the ng-include tag appears literally in the output...
You can do with a two directives: one is a sort of ngInclude, the other waits for the first to load the content and replaces itself with a pre (http://plnkr.co/edit/fPy4M0ZK94erF31EeObE):
module.directive('myPre', function() {
return {
controller: function() {},
restrict: 'E',
link: function(scope, element, attrs, controller) {
controller.checkContent = function() {
if (!element.children().length) {
element.replaceWith('<pre>' + element.text() + '</pre>');
}
}
}
}
})
.directive('myPreTemplate', function($http) {
return {
require: '^myPre',
restrict: 'E',
link: function(scope, element, attrs, myPre) {
$http.get(attrs.src).then(function(res) {
element.replaceWith(res.data);
myPre.checkContent();
});
}
}
});
And you can use it like here:
<my-pre>
Code here...
<my-pre-template src="common.html"></my-pre-template>
...and here...
<my-pre-template src="common.html"></my-pre-template>
...and here again
</my-pre>
EDIT: to render the content of common template, the best way is using mustache (plunker updated):
...
$http.get(attrs.src).then(function(res) {
var rendered = Mustache.render(res, scope);
element.replaceWith(rendered);
myPre.checkContent();
});
...
I was able to resolve it based on your idea of using a top-level custom pre directive, but using a different impl: I'm running a BFS algorithm, where each digest cycle I try to compile any direct sub pre-includes. Every iteration I wait until the inclusion children count is zero before checking if we need another cycle (using scope.$watch). Once done, I compile the entire my-pre to resolve any {{xxxx}} and convert it to pre. Working plunkr
// simulate a 3d party highligther (tested with HLJS)
.directive('myHighlighter', function($compile, $timeout) {
return {
restrict: 'E',
controller: function() {},
link: function(scope, element, attrs, controller) {
// wait for the end of the digest:
$timeout(function() {
$(element).replaceWith($('<pre>' + element.text() + '</pre>'));
});
}
}
})
// myPre will conert itself to the custom highlighter once done compiling:
.directive('myPre', function($compile, $timeout) {
return {
restrict: 'E',
controller: function() {},
link: {
pre: function(scope, element, attrs, controller) {
// limit the total inclusions allowed to avoid loops:
scope.it = 100;
// count inclusions:
scope.incCount = 0;
scope.$watch('incCount', function(newVal, oldVal, scope) {
if (oldVal !== newVal && newVal === 0) {
// watch the include tag count. When it's zero,
// see if we need another BFS pass for sub-children:
var children = $('pre-include', element).length;
if (children !== 0) {
$compile(element)(scope);
} else {
// If not, build the highlighter and we're done:
var e2 = $('<my-highlighter>' + element.html() + '</my-highlighter>');
$(element).replaceWith(e2);
$compile(e2)(scope);
}
}
});
},
post: function(scope, element, attrs, controller) {
}
}
}
})
.directive('preInclude', function($templateRequest, $compile) {
return {
link: {
pre: function(scope, element, attrs, myPre) {
scope.incCount++;
},
post: function(scope, element, attrs, myPre, transclude) {
scope.it--;
if (scope.it <= 0) {
console.log("max iterations reached, stopping");
return;
}
// enqueue the inclusion in the BFS queue:
$templateRequest(attrs.src).then(function(data) {
element.replaceWith(data);
scope.incCount--;
});
}
}
};
})

angularjs directives - how to add interpolated attribute

From what I can tell, an interpolated value string expands/resolves correctly if it's specified in the template, but not if it's added later. To demonstrate, I have the following code:
describe.only('directive tests - ', function() {
it('add dynamic interpolated attribute value', function() {
module(function($compileProvider) {
$compileProvider.directive('hello', function() {
return {
restrict: 'A',
replace: true,
template: '<a foo="{{1+1}}"></a>',
compile: function link(tElement, tAttrs) {
tElement.attr('bar', '{{2+2}}'); // add an interpolated attr.
}
};
});
});
inject(function($compile, $rootScope) {
var element = angular.element('<div hello/>');
$compile(element)($rootScope);
$rootScope.$apply();
console.log(' * ', element.prop('outerHTML'));
});
});
});
and console.log prints:
<a foo="2" hello="" bar="{{2+2}}" class="ng-scope"></a>'
and NOT:
<a foo="2" hello="" bar="4" class="ng-scope"></a>'
as I'd think. What gives?
tElement.attr('bar', $interpolate('{{2+2}}')());
Right, it is too late to do this in compile, more specifically to make changes to the directive element itself that have to be compiled (it needs to be recompiled in order to make the changes work).
But the following
// replace: true,
template: '<a foo="{{1+1}}">aa</a>',
compile: function link(tElement, tAttrs) {
tElement.find('a').attr('bar', '{{2+2}}');
}
would work as expected.
Attribute watching and interpolation can also be done in link (or controller):
link: function (scope, element, attrs) {
attrs.$observe('bar', function (interpolatedBar) {
scope.bar = interpolatedBar;
});
}
It will set up a watcher on bar attribute (while $interpolate(...)() is one-time assignment and doesn't interpolate any values from scope).

How do I write a unit test in an Angularjs Directive

I have written a directive to validate the input. How do I write a unit test for this directive?
angular.module('validationModule', [])
.directive('validateName', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if (!ngModel) return;
ngModel.$parsers.unshift(function (inputValue) {
var expr = inputValue.replace(/[^a-zA-Z0-9\s]/g, "");
ngModel.$viewValue = expr;
ngModel.$render();
return expr;
});
}
};
});
This might be a good place to start.

Angular directive coming in JSON

I have a directive:
Here I am passing a data object:
<a-link options = "data"></a-link>
And here is my directive:
.directive('aLink', function() {
return {
restrict: 'AE',
replace: true,
template: '<a href = "{{href}}">{{text}}</div>',
link: function(scope, element, attrs) {
scope.$watch('options', function(newValue, oldValue) {
if (newValue)
scope.href = newValue.href;
scope.text = newValue.text;
});
}
});
This works fine. It gives me an anchor tag like
link
My question is:
If i get the following on json:
{
body : "Click <a-link text = 'here' href = '/home/login.html'></a-link>"
}
and my html:
<p ng-bind-html = "body"></p>
This doenot work.
How do I recompile this? Do I need to pass it through another directive and compile it there?
Use $compile service:
$compile(response.body)($scope)