isolate scope in directive undefined - angularjs

I'm having trouble creating a directive, since the isolate scope does not get passed from the html and gives me undefined in the directives controller.
lmLoop definition
angular.module('loops.directives')
.directive('lmLoop', function() {
return {
restrict: 'E',
templateUrl: 'modules/loops/partials/loop.tpl.html',
scope: {
loop: "=",
},
controller: ['$scope', '$log',
function($scope, $log) {
console.log($scope.loop); // undefined
}
]
}
});
modules/loops/partials/loop.tpl.html
<div class="loop-container">
Testing: {{ loop.message }} <!-- empty -->
</div>
How Im trying to use my directive
<div class="loops-container">
<lm-loop ng-repeat="loop in loops" loop="loop"/>
</div>
This renders the view correctly, meaning that it repeats the right amount of times, once for each item in the array, but it does not pass the variable "loop" and the div stays empty. But when I do the following it renders correctly and displays the message
<ul>
<li ng-repeat="loop in loops">
{{ loop.message }} <!-- WORKS!!! -->
</li>
</ul>
The directive and the ul are both inside another directive. That directive gets the loops by a service like this:
controller: function($scope) {
loopService.getLoops($scope.group._id, function(loops) {
$scope.loops = loops;
}, function(err) {
$log.error(err);
});
... Other code ...
}
I don't know whether the asynchronous call to the service gets the data after the
lmLoop directive gets instantiated and therefore makes the data undefined? Or maybe (probably) I'm doing something else wrong?

try to add priority: 1001 in your directive
Good explanations here ng-repeat in combination with custom directive

Related

Two way binding in custom directive with ng-repeat

I basically have a list of items that are stored as an array of objects. Some of these items are "children" of others. This is denoted by a "parentid" setting in each object. This only means that I want to display children as a nested list within the parent.
So, I start by iterating through the array and find one with no parent. When I find it, I call a directive that takes that ID and then again iterates through the main array and finds any item whose parentID is identical to the one in question and displays it. There can be multiple levels of children so that the directive is called recursively.
This works fine and displays all of the children, but I want to be able to select any one of these items and see that ID or object at the top level. This is not working. I pass a variable into each directive with two way binding but no parent is ever updated. I understand the primitive problem and I am passing an object but that is not working. I think that I need to do something with the repeats, but I am not sure what. I do not think transclusion is the issue.
In the demo below, I need to display the updated var3 variable whenever I click on an item.
Initial HTML
<div>{{var3}}</div>
<div>
<my-directive var1="item1"
var2="item2"
var3="item3">
</my-directive>
</div>
Directive
(function ()
{
'use strict';
angular
.module('app.core')
.directive('myDirective', myDirective);
function structureContent($templateRequest, $compile)
{ return {
restrict : 'E',
scope:{
var1:'=',
var2:'#',
var3:'='
},
controller : ["$scope", "$element", "$attrs",
function($scope, $element, $attrs) {
}
],
link : function(scope, element, attrs){
scope.$watch('worksheet',
function(){
$templateRequest("myTemplate.html").then(function(html){
var template = angular.element(html);
element.html(template);
$compile(template)(scope);
});
}, true
);
}
}
};
})();
Directive HTML
<div>
<div ng-click="var3=thisItem.itemid;">(Display item) </div>
// var3 is what I want to be able to pull out at the top level
</div>
<div ng-repeat="thisObj in myArray | orderBy:'order' track by thisObj.itemid"
ng-switch on="thisObj.type_id"
ng-if="thisObj.content.parentid==thisItem.itemid"
class="subSection" >
<div ng-switch-when="1">
<my-directive var1="{{var1}}"
var2="var2"
var3="var3">
</my-directive>
</div>
</div>

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.

angularjs directive on bind output

I have something like this:
.controller('contr',['$scope', '$http',function($scope, $http){
$http.post(...).success(function(){
$scope.myTextVar = "some text here";
$scope.completed == true;
});
}]);
an HTML snippet like so:
<div class="myClass" ng-if="completed == true" manipulate-header>
<p>{{myTextVar}}</p>
</div>
and the directive, for simplicity sake let's say it looks like this:
.directive('manipulateHeader',function(){
return{
restrict: 'A',
link: function(scope, elem){
console.log(angular.element(elem).find('p'));
}
}
});
The manipulate-header directive is supposed to do some manipulation of the text inside the <p></p> tag, however, it runs before {{myTextVar}} gets replaced and hence it outputs {{myTextVar}} instead of some text here.
How may i get around this problem? (i can pass the variable inside the directive scope, but i'm thinking there must be another way).
EDIT: the controller is there and working as intended. Issue is not related to it. I didn't include it to shorten the post.
If it MUST be a directive
If you're trying to do string manipulation in your link function, you're going to have a bad time. The link function is executed before the directive is compiled (that's the idea of the link function), so any bindings (ng-bind or otherwise) will not have been compiled inside of link functions.
To execute code after the compilation stage, you should use a controller. However, you cannot access the DOM in controllers (or rather, you shouldn't). So the logical solution is to instead modify the scope argument instead. I propose something like this:
angular.directive('manipulateHeader', function() {
return {
scope: {
myTextVar: '='
},
controller: function($scope, myFilter) {
// you can't use bindToController here because bindToController executes *after*
// this function
this.modifiedText = myFilter($scope.myTextVar);
},
controllerAs: 'ctrl',
// display the modified text in a template
template: '<span ng-bind="ctrl.modifiedText"></span>'
};
})
.filter('myFilter', function() {
return function(inputText) {
// do some text manipulation here
};
});
Usage:
<manipulate-header myTextVar='myTextVar'></manipulate-header>
Or:
<p>{{ myTextVar | myFilter }}</p>
You could, of course, make this an attribute instead, but best practice indicates that directives that have a template should be an element instead.
The above is only if you need this to be a directive. Otherwise, it should almost definitely be a filter.
If you need to change the $scope variable from your controller you need to isolate scope ,
scope:{
myattr='#', // this will provide one way communication , you can define in your template as <p myattr="hello"><p>
message:'&', //This allows you to invoke or evaluate an expression on the parent scope of whatever the directive is inside
message:'=' // sets up a two-way binding expression between the directive's isolate scope and the parent scope.
}
refer https://docs.angularjs.org/guide/directive
As suggested by #DanPantry - you most likely want a filter not a directive
Read this guide about using filters
https://docs.angularjs.org/guide/filter
Here is an example of such a filter (from documentation)
angular.module('myStatefulFilterApp', [])
.filter('decorate', ['decoration', function(decoration) {
function decorateFilter(input) {
//This is the actual modification of text
//That's what you are looking for
return decoration.symbol + input + decoration.symbol;
}
decorateFilter.$stateful = true;
return decorateFilter;
}])
.controller('MyController', ['$scope', 'decoration', function($scope, decoration) {
$scope.greeting = 'hello';
$scope.decoration = decoration;
}])
.value('decoration', {symbol: '*'});
I am not sure whether you defined $scope.myTextVar
in correct scope. Like, if you defined it in any controller, then directive should be under the controller scope.
Here is the updated HTML
<div ng-controller ="MainController">
<div class="myClass" manipulate-header>
<p>{{myTextVar}}</p>
</div>
</div>
JS :
app.controller('MainController', ['$scope', function($scope) {
$scope.myTextVar = "some text here";
}]);
app.directive('manipulateHerader',function(){
return{
restrict: 'A',
link: function(scope, elem){
console.log(angular.element(elem).find('p'));
}
}
});
Here is the plunker

ngClick evaluated against scope instead of isolateScope

I am experiencing a problem with a directive which seems initially pretty easy to write. From what I understood my problem is related with the scope against which an expression is evaluated.
There is also a plunker with a demo.
I have got the following JavaScript code with a controller and a directive:
angular.module('parentGet', [])
.controller('Parent', function($scope) {
$scope.foo = function() {
alert('hello world');
};
})
.directive('child', function() {
return {
restrict: 'A',
scope: {
fn: '&'
},
link: function($scope) {
$scope.fn2 = function() {
$scope.fn();
}
}
};
});
This code is used from the following HTML
<body ng-controller="Parent">
<div child fn="foo" ng-click="fn2()">
<h1>Test</h1>
</div>
</body>
When I click on the div, the ng-click is properly triggered however when I set a breakpoint inside angular.js file (line 22949 of the 1.3.8 version), I can see that the ng-click is evaluated against the Parent controller scope and not against the child directive isolated scope.
Any suggestion to make that code work ?
EDIT
To be more specific the problem I have is that the fn2 is never called which seems pretty weird in my case.
The ng-click tries to evaluate the fn2() expression again the controller scope which does not have any fn2 function which leads angular to take the noop function.
This is the default behavior. From the angular docs:
When the user clicks the x in the dialog, the directive's close
function is called, thanks to ng-click. This call to close on the
isolated scope actually evaluates the expression hideDialog() in the
context of the original scope, thus running Controller's hideDialog
function.
See it in here https://docs.angularjs.org/guide/directive
Answer to edited question
The thing you are expecting that ng-click will call fn2() of directive function will again not be possible. Since angular will again evaluate that function call with parent controller scope. So change your code to this:
.directive('child', function() {
return {
restrict: 'A',
scope: {
fn: '&'
},
link: function($scope, element) {
element.on('click', $scope.fn2);
$scope.fn2 = function() {
$scope.fn();
}
}
};
});
And change your html snippet to:
<body ng-controller="Parent">
<div child fn="foo">
<h1>Test</h1>
</div>
</body>

Directive: I bound isolate scope ("=") to template ngModel, but input won't update on controller

What I'm trying to do is put the isolate scope 'pointers' directly onto the ngModel within the template. What I expected was for the scope variables to update automatically on the parent controller. What ended up happening was both variables are constantly being evaluated as undefined.
I'm uncertain why this isn't working as expected, because having a two-way bound isolate scope means that for each created name, that name is automatically gets put on the scope (e.g. I have inputA, so scope.inputA is create).
I created a log function that runs every time something in the input changes, and I output the input the results... I keep getting undefined for the isolate scope variables inputA and inputB. I'm perplexed; what have I done incorrectly?
template
<div ng-app="app">
<div ng-controller="MyCtrl">
<div my-directive input-A="input.inputA" input-B="input.inputB"></div>
</div>
<br/>
<!-- output input -->
{{ input | json }}
</div>
directive/controller
angular.module("app")
.controller("MyCtrl", function($scope){ })
.directive("myDirective", function(){
return {
scope: {
inputA: "=",
inputB: "="
},
template: 'inputA: <input ng-model="inputA" ng-change="log(inputA)"/><br/>'
+'inputB: <input ng-model="inputB" ng-change="log(inputB)"/>',
link: function (scope, element, attrs) {
scope.log = function (theInput) {
console.log(theInput); // outputs correctly
console.log(scope.inputA); // outputs 'undefined'
console.log(scope.inputB); // outputs 'undefined'
};
}
}
});
I think the directive should be called as:
<div my-directive input-a="input.inputA" input-b="input.inputB"></div>
Note the lower-case input-a and input-b.

Resources