Angular Directive incoming scope value is undefined - javascript

In html I have this object passed into directive
<lcd-code ldcCode="{{ detail.program.ldcCode }}"></lcd-code>
detail.program.ldcCode = "PSIH" ...
However in the Directive it is undefined
var lcdCode = function (customerService, $sce) {
return {
replace: true,
restrict: "E",
scope: {
ldcCode: "=" // two way
},
link: function (scope, element, attrs) {
console.log('scope.ldcCode',scope.ldcCode); // says undefined
}
};
}
Previously I was using "#" and then attrs.ldcCode seemed to work... I guess the end result of what data I was working with and sending back I figured that I wanted the 2 way data binding.

You need to use without {{}} for two way binding
<lcd-code ldcCode="detail.program.ldcCode"></lcd-code>

You do not have to use Expression , remove the {{}}
<lcd-code ldcCode="detail.program.ldcCode"></lcd-code>

Related

Angular conditional binding

I have shop attribute, which I want to treat differently, depending on what type passed into (it can be string or object). How do I properly bind this attribute, so that I could pass string and object, and then treat it differently, depending on the passed type?
This way object passing works perfectly fine, but passing a string gives this attribute a value '0':
angular.module('showcaseApp')
.directive('card', function ($window, $state) {
return {
templateUrl: '/card.html',
restrict: 'E',
replace: true,
scope: {
shop: '='
},
link: function(scope, element, attrs) {
attrs.$observe('shop', function (value) {
if (value) {
}
});
}
};
});
Binding with & allows me to pass a string (catch it with $observe), but an object can't be passed this way. And I want to be able to pass both. This can be solved only by creating a new attribute with different binding?
Figured it out. I passed a string like this:
var id = scope.pageContent.content[i].id;
Instead I should pass it like this:
var id = "'" + scope.pageContent.content[i].id + "'";
And everything works ✨

How to detect when scope property is set in Angular directive link function?

I'm building a directive that needs to initialize some data based on values passed into it through the scope. The problem is that when I try and initialize the data in the link function, the passed in value isn't available yet. Is there anyway to only run the initialization when the passed in value is available? I thought about using a watch as in the following code but it seems messy (and doesn't seem to work anyway).
.directive('etMemberActivitySummary', [function () {
return {
restrict: 'E',
templateUrl: '<div>My template</div>',
transclude: false,
scope: {
memberModel: '='
},
link: function(scope, element, attrs, controller) {
var watcher = scope.$watch(
function() {
return scope.memberModel
},
function(value) {
console.log(value);
if (value != null) {
console.log('Watch');
console.log(value);
watcher();
// Perform initialization based on scope.memberModel here
}
});
}
}
}])
Is there a correct way to do this? If it helps, the passed in value is in itself retrieved from a web service.
Update 1
Turns out that if I put an ng-if="ctrl.memberModel" on the directive usage like the following and get rid of all the watch stuff, it works. Is this the best way to do this?
<et-member-activity-summary member-model="ctrl.memberModel" ng-if="ctrl.memberModel"></et-member-activity-summary>

Pass JSON object from Angular service to directive

I'm trying to pass a JSON object from an Angular service to a directive. Actually I just want to $compile a directive on-the-go and pass an object to the directive.
It should look something like this:
var template = '<gmap-info-window layer="layer" marker="marker"></gmap-info-window>',
content = $compile(template)(searchScope);
Whereas the directive looks like this:
.directive('gmapInfoWindow', [function() {
scope: {
marker: '=',
layer: '='
},
link: function(scope, element, attrs) {
// access objects in attrs
}
}]);
That doesn't work. All I get in the attrs.marker and attrs.layer is plain strings.
Now what I've tried and accomlished is using the transcludeFn function of the $compile function. It works, but I don't feel it being the right way to do what I'm trying to accomplish.
var template = '<gmap-info-window></gmap-info-window>',
content = $compile(template)(searchScope, null, {
parentBoundTranscludeFn: function() {
return {
marker: _marker,
layer: _layer
};
}
});
Whereas the directive looks like this:
.directive('gmapInfoWindow', [function() {
scope: {},
link: function(scope, element, attrs, controller, transcludeFn) {
var objects = transcludeFn();
// The marker and layer are in objects now!
}
}]);
I can't imagine that there's no other way to do what I wanna do. This looks kinda dirty. Thanks for your insight!
All I get in the attrs.marker and attrs.layer is plain strings.
You need to understand that attribute is always a string by definition. It not possible that you have an object there. What Angular does is it evaluates values of those attributes (strings) in proper context (scope of compilation) according to scope configuration of the directive. Then the result of this evaluation is available in scope object of the link function. This is what you need to use:
link: function(scope, element, attrs) {
console.log(scope.marker, scope.layer);
}

How do I pass an object to a directive?

I'm having issues getting an object to pass to my directive. I believe I've done things correctly, but after failed attempt after failed attempt I must seek help. What did I miss here that's stopping me from passing an array to my directive?
HTML:
<div class="body">
{{orderList.length}} //shows up as 18
</div>
<queue-summary orders="orderList"></queue-summary>
Javascript:
directive('queueSummary', function () {
return {
scope: {
orders: '='
},
replace: true,
restrict: 'E',
templateUrl: '/partials/admin/bits/queue-summary.htm',
link: function (scope, element, attrs) {
console.log(scope, element, attrs); //$attrs.orders show it as the String "orderList" instead of the array
}
}
}).
It's worth noting that you can access the bound value of an attribute you don't have isolate scope on with $eval:
scope.$eval(attrs.orders)
attrs will just show you the string value of an attribute. In order to access the passed object, use the isolate binding you created:
console.log(scope.orders);

Modify template in directive (dynamically adding another directive)

Problem
Dynamically add the ng-bind attribute through a custom directive to be able to use ng-bind, ng-bind-html or ng-bind-html-unsafe in a custom directive with out manually adding to the template definition everywhere.
Example
http://jsfiddle.net/nstuart/hUxp7/2/
Broken Directive
angular.module('app').directive('bindTest', [
'$compile',
function ($compile) {
return {
restrict: 'A',
scope: true,
compile: function (tElem, tAttrs) {
if (!tElem.attr('ng-bind')) {
tElem.attr('ng-bind', 'content');
$compile(tElem)
}
return function (scope, elem, attrs) {
console.log('Linking...');
scope.content = "Content!";
};
}
};
}]);
Solution
No idea. Really I can not figure out why something like the above fiddle doesn't work. Tried it with and with out the extra $compile in there.
Workaround
I can work around it might adding a template value in the directive, but that wraps the content in an extra div, and I would like to be able to that if possible. (See fiddle)
Second Workaround
See the fiddle here: http://jsfiddle.net/nstuart/hUxp7/4/ (as suggested by Dr. Ikarus below). I'm considering this a workaround for right now, because it still feels like you should be able to modify the template before you get to the linking function and the changes should be found/applied.
You could do the compiling part inside the linking function, like this:
angular.module('app').directive('bindTest', ['$compile', function ($compile) {
return {
restrict: 'A',
scope: true,
link: {
post: function(scope, element, attrs){
if (!element.attr('ng-bind')) {
element.attr('ng-bind', 'content');
var compiledElement = $compile(element)(scope);
}
console.log('Linking...');
scope.content = "Content!";
}
}
};
}]);
Let me know how well this worked for you http://jsfiddle.net/bPCFj/
This way seems more elegant (no dependency with $compile) and appropriate to your case :
angular.module('app').directive('myCustomDirective', function () {
return {
restrict: 'A',
scope: {},
template: function(tElem, tAttrs) {
return tAttrs['ng-bind'];
},
link: function (scope, elem) {
scope.content = "Happy!";
}
};
});
jsFiddle : http://jsfiddle.net/hUxp7/8/
From Angular directive documentation :
You can specify template as a string representing the template or as a function which takes two arguments tElement and tAttrs (described in the compile function api below) and returns a string value representing the template.
The source code tells all! Check out the compileNodes() function and its use of collectDirectives().
First, collectDirectives finds all the directives on a single node. After we've collected all the directives on that node, then the directives are applied to the node.
So when your compile function on the bindTest directive executes, the running $compile() is past the point of collecting the directives to compile.
The extra call to $compile in your bindTest directive won't work because you are not linking the directive to the $scope. You don't have access to the $scope in the compile function, but you can use the same strategy in a link function where you do have access to the $scope
You guys were so close.
function MyDirective($compile) {
function compileMyDirective(tElement) {
tElement.attr('ng-bind', 'someScopeProp');
return postLinkMyDirective;
}
function postLinkMyDirective(iScope, iElement, iAttrs) {
if (!('ngBind' in iAttrs)) {
// Before $compile is run below, `ng-bind` is just a DOM attribute
// and thus is not in iAttrs yet.
$compile(iElement)(iScope);
}
}
var defObj = {
compile: compileMyDirective,
scope: {
someScopeProp: '=myDirective'
}
};
return defObj;
}
The result will be:
<ANY my-directive="'hello'" ng-bind="someScopeProp">hello</ANY>

Categories