Dynamically added directive is not interpolated - angularjs

I have a call stack like below and problem is that directive template is not interpolated. So as a result of this I can see {{ data | json }} as a string and ng-repeat is not triggered. How to approach this?
Context of situation is that I have a Highchart's chart where I need to provide clickable plot lines. On line click I need to display popover with dynamic content.
Optional question to answer:
My play with events is working well but I'm not sure if it's also well done. I would welcome any criticism on that. Idea is to hide popover on all following clicks.
Code:
1.
series: {events: {click: function(e) {drillDownCall(e, dataGroups)}
2.
function drillDownCall (e, dataGroups) {
var elem = angular.element('#drilldown');
if (!elem[0]) {
elem = angular.element('<drilldown fancy-name="dataGroups"></drilldown>');
}
elem.css({
position: 'absolute',
top: e.pageY,
left: e.pageX,
width: '150px',
height: '250px',
zIndex: '2000',
background: 'red'
});
var body = angular.element(document).find('body').eq(0);
var scope = $rootScope.$new();
scope.dataGroups = dataGroups;
body.append($compile(elem)(scope));
}
3.
.directive('drilldown', [
'$compile',
'$window',
function (
$compile,
$window
) {
return {
restrict: 'E',
replace: true,
scope: {
data: '=fancyName'
},
template: '' +
'<div id="drilldown">{{ data | json }}' +
'<ul>' +
'<li ng-repeat="group in data">{{ group.name }}</li>' +
'</ul>' +
'</div>',
link: function (scope, element) {
var ele = $compile(element)(scope),
off;
angular.element($window).on('click', function(e) {
scope.$emit('drilldown::click');
});
off = scope.$on('drilldown::click', function() {
angular.element(ele).remove();
angular.element($window).off('click');
off();
});
}
};
}]
)

I am unable test it myself but I think I know why.
Start of everything is drillDownCall and it is called by an event that is outside of Angular.js digest cycle. So Angular.js has no idea that there is a change in scope, and doesn't run a digest cycle, causing the new directive appear as non-compiled bunch of strings. (yes even you used $compile it works like that)
In summary, if I remember correct, you need at least one digest cycle to see that directive compiled. To trigger a digest cycle, you can add
$rootScope.$apply() or $rootScope.$applyAsync() or anything equivalent to it to the end of drillDownCall event handler.
Can you please try this?

Related

How to make a link function change a scope variable

I am trying to use AngularJS to make a model that responds to a click from the user. I have all the information for the model in the controller (other elements will be added to the scope later), and the directive handles showing the element in the view and performing actions on it, such as minimizing it on click.
What I'm having trouble with is making the link function in my makeDialog directive change the value of experimentDialog.minimized in the model. How can I accomplish this? What am I doing wrong?
Right now, nothing happens when you click the button -- so help troubleshooting is also appreciated.
Let me know if you need any more information!
Angular:
angular.module('root', [])
.controller('index', ['$scope', function($scope){
$scope.experimentDialog = {
minimized: false,
width: 200,
height: 300,
top: 10,
left: 10,
template: 'experiment-dialog.html'
};
}])
.directive('makeDialog', function() {
return {
restrict: 'E',
scope: {
model: '='
},
templateUrl: 'dialog.html',
link: function(scope, element, attrs) {
scope.minimize = function() {
scope.model.minimized = true;
scope.$apply();
};
}
};
});
dialog.html:
<html>
<link href='dialog.css' rel='stylesheet' type='text/css'/>
<body>
<div class='dialog' ng-hide={{model.minimized}}>
<button id='minimize' ng-click="minimize()"> _ </button>
</div>
</body>
</html>
EDIT: if it helps, here is the whole thing on Plunker:
http://plnkr.co/edit/3S9q53VfGqRrWbVgR5XU?p=preview
So, your button does change the model. The problem was your ng-hide syntax. I display the model in the box so you can see the values.
<div class='dialog' ... ng-hide="model.minimized"> <!-- no {{}}! -->
This forked plunkr has, what I believe to be, your intended effect.

Why are mouse events from my directive not reflected in the view?

Here is an example fiddle. Please open the console first.
It's a little long, so to quickly explain: You'll see two divs. One is "regular", the other is created via a directive. They both have click event handlers (added via ng-click).
Clicking the regular (non-directive) div does what I expect - the appropriate variables in the scope are set and you see that in the view (top two lines in the output show the id and top of the div that was clicked).
The problem is when clicking the directive div. These variables are not updated. I know I'm misunderstanding something about the scope of the directive, but what is confusing me is the log messages that are printed (open the console when you do this). The clicks are registered, and the controller's scope does set the variable values, as you can see in the console.
Yet the html does not update - try clicking one div, then the other, and go back and forth, and you'll see.
What am I missing here?
Code (please don't be put off by its length!):
<div ng-app="MyApp">
<div ng-controller="MyController">
Top of the div you just clicked = {{top}}.<br/>
Id of the div you just clicked = {{lastId}}.<br/>
<div id="notADirective" class="myClass" ng-click="update($event)">
Not a directive. Click me.
</div>
<my-div element-id="isADirective" cls="myClass">
I'm a directive. Click me.
</my-div>
</div>
angular.module('components', [])
.controller('MyController', function ($scope) {
$scope.lastId = '';
$scope.top = '';
$scope.update = function (event) {
var myDiv = getFirstMyClassAncestor(event.target);
var style = window.getComputedStyle(myDiv);
$scope.top = style.top;
$scope.lastId = myDiv.id;
console.log("You just clicked " + $scope.lastId + " and its top is " + $scope.top);
}
function getFirstMyClassAncestor(element) {
while (element.className.indexOf('myClass') < 0) {
element = element.parentNode;
}
return element;
}
}).directive('myDiv', function () {
return {
restrict: 'E',
replace: true,
transclude: true,
controller: 'MyController',
scope: {
cls: '#',
elementId: '#'
},
template: '<div id="{{elementId}}" class="{{cls}}" ng-click="update($event)"><div ng-transclude></div></div>'
}
});
angular.module('MyApp', ['components'])
.myClass {
background-color: #DA0000;
position: absolute;
}
#isADirective {
top: 300px;
}
#notADirective {
top: 100px;
}
In case of your directive the assigned controller MyController gets the scope of this directive and not the scope of div as you probably expect. I added a log statement for the different scope ids:
http://jsfiddle.net/6zbKP/4/
If you want to update the outer or parent scope you have to use a binding like this:
scope: {
cls: '#',
elementId: '#',
callback: '='
},
Then bind your update function in the directive:
<my-div element-id="isADirective" cls="myClass" callback="update">
I'm a directive. Click me.
</my-div>
And call the callback in your directive:
template: '<div id="{{elementId}}" class="{{cls}}" ng-click="callback($event)"><div ng-transclude></div></div>'
See http://jsfiddle.net/6zbKP/5/
The angular expression bindings are outside of your directive's isolated scope. And you haven't imported lastId or top from your parent controller scope into your directives isolated scope, so there is no reason to expect that updating one variable in the inner directive scope will update the variable in the outer controller scope. There is no two way binding set up.
You can setup two way binding however using scope: {lastId: '=', top: '=' }.
Also you shouldn't be sharing controllers like you're doing. I suggest using anonymous controller functions.

paper.js angular.js canvas only updates on canvas mouseover

I have set up an app with angular.js. I am creating a menu via ng-repeat. Every link calls a function inside a directive that uses paper.js to draw text to canvas.
But canvas not updates until i move the mouse around browser. The function that draws on to the canvas is calling immediately, but the canvas shows nothing without mouse moves away from the link?
angular.js controller that calls draw function inside directive:
.controller('NavCtrl', function($scope, $location, $http, WorkService) {
$scope.works = [];
$http({method: 'GET', url: '/api/v1/work'}). //collects all works
success(function(data, status, headers, config) {
$scope.works = data.objects;
});
$scope.setTitle = function(work) {
$scope.currentTitle=work.title;
$scope.writeTitle(work.title);
};
})
angular.js directive:
.directive('draw', function () {
return {
restrict: 'A',
link: function postLink($scope, element, attrs) {
$scope.writeTitle = function(inText){
var letters = inText.split('');
for(var i=0; i<2; i++){
var text = new PointText(new Point(getRandom(0, 200), getRandom(0, 100)));
text.content = letters[i];
text.style = {
fontFamily: 'Arial',
fontWeight: 'normal',
fontSize: 14,
fillColor: 'red',
justification: 'center'
};
}
}
function getRandom(min, max) {
return Math.random() * (max - min) + min;
}
function initPaper() {
paper.install(window);
paper.setup('canvas');
}
initPaper();
}
};
});
the menu in my base.html that calls controller function to draw:
<div ng-controller=NavCtrl>
<nav>
<a href='#/bilgi'>bilgi</a>
<a ng-click="setTitle(work)" href='#/ardiye/{{work.id}}' ng-repeat='work in works'>{{$index+1}}</a>
</nav>
</div>
---- UPDATE ----
I have added:
<script type="text/paperscript" canvas="canvas">
function onFrame(event) {
}
</script>
this empty onFrame function to base.html and now the canvas updates normally. But why?
Have you tried updating the View?
I had an issue on Firefox and IE11 with the canvas not updating until a mouseover after I initiated some operation.
I just called paper.view.update() at the end of each function and it all went down allright.
If this is the issue, it has nothing to do with Angular
Wrap your code inside your directive function within a scope.$apply() function. This will make sure that it updates the UI.
One other thing, it is convention inside a directive to use to use scope (no dollar sign) instead of $scope. In a controller you are actually passing in the named object $scope. Angular needs to know the actual names of the objects you want to inject. Inside your directive's link function, however, the actual names do not matter. This is because inside link attribute the parameters are passed in a specific order: scope, element, attributes. You could use foo, bar, baz if you wanted but they will still be resolved as scope, element, atttributes.
With this in mind you could actually use $scope but realize that you can do that only because of the order it is inside the function parameter list.
link: function postLink(scope, element, attrs)
scope.writeTitle = function(inText){
scope.$apply(function () {
var letters = inText.split('');
for(var i=0; i<2; i++){
var text = new PointText(new Point(getRandom(0, 200), getRandom(0, 100)));
text.content = letters[i];
text.style = {
fontFamily: 'Arial',
fontWeight: 'normal',
fontSize: 14,
fillColor: 'red',
justification: 'center'
};
});
}

Angular.js -- Directive to controller communication

I am very new to angular so please excuse my lack of understanding.
I have a directive called "draggable" which I want to be able to track the x position of and perform some logic on it in the controller. When the user drags the element (a stick figure) to the right, additional stick figures should appear directly behind it. The controller should know the x position and based upon where it is, increment a counter which will dictate how many stick figures appear behind the draggable element.
This code does not currently work as the controller does not have receive the value of x.
My directive:
app.directive('draggable', function() {
return {
restrict: 'A',
scope: "=x",
link: function (scope, element, attrs) {
$(element).draggable({
containment: "parent",
axis: "x",
drag: function(){
scope.x = $(this).offset().left;
}
});
}
};
});
My controller:
app.controller("main-controller", function($scope) {
$scope.range = function(n) {
return new Array(figures);
};
$scope.$watch("x", function(){
console.log($scope.x);
figures = x / (stick_figure_height)
});
});
My HTML:
<div class="human-slider" ng-controller="main-controller">
<div draggable class="human-draggable">
<img src="images/stickfigure.png"/>
</div>
<div ng-repeat="i in range()">
<img src="images/stickfigure.png"/>
</div>
</div>
The reason the controller was not picking up the updated value of x from the draggable directive was because of where the value of x is being updated. X is updated in a turn that has been created in a method outside of the angularJS library (the drag event handler). The solution to this problem was to use $.apply which will update the binding.
The updated code:
// Create our angular app
var app = angular.module('celgeneApp',[]);
// Main controller
app.controller("main-controller", function($scope) {
$scope.x = 0;
$scope.figures = 0;
$scope.range = function(n) {
return new Array($scope.figures);
};
$scope.$watch('x', function(){console.log($scope.x);});
});
// Draggable directive
app.directive('draggable', function() {
return {
restrict: 'A',
scope: false,
link: function (scope, element, attrs) {
$(element).draggable({
containment: "parent",
axis: "x",
drag: function(){
// Need to use $apply since scope.x is updated
// in a turn outside a method in the AngularJS library.
scope.$apply(function(){scope.x = element.offset().left;});
}
});
}
};
});
You can communicate between a directive and a controller through a service. A directive can also access a controller's scope variables via parameters. You can access the variables in different ways, depending on your needs:
As just text with the # prefix
With a one way binding with the & prefix
With a two bay binding with the = prefix
Check out this excellent article about directives, especially the scope section
Take a look at this directive I made, it is just a wrapper around jQuery's draggable just like yours, maybe you can get some ideas:
angular-draggable
Check my this for how parent controller and directive communicates :)
http://plnkr.co/edit/GZqBDEojX6N87kXiYUIF?p=preview plnkr

Angular.js - binding a directive to a variable in the controller

I want to bind a directive to a variable within a controller, but cannot work out how to do it from the Angular.js docs (nor searching the web, looking at the egghead videos).
I have the following html:
<body ng-app="MyApp">
<div ng-controller="triCtrl">
<div jqslider pleaseBindTo="firstValue"></div>
<br>
<br>
<br>
<br>
<div jqslider pleaseBindTo="secondValue"></div>
<p>{{firstValue.v}}</p>
<p>{{secondValue.v}}</p>
</div>
</body>
And the following JS:
function triCtrl($scope) {
$scope.firstValue = {"min":0, "max":100, "v":50};
$scope.secondValue = {"min":0, "max":1000, "v":450};
}
var myAppModule = angular.module('MyApp', []);
myAppModule.directive('jqslider', function() {
return {
link:function(scope, element, attrs) {
element.slider({
range: false,
min: scope.min,
max: scope.max,
value: scope.v,
slide: function( event, ui ) {
scope.v = ui.value;
scope.$apply();
}
});
}
};
});
I have tried several ways using scope:{ } with &, #, = etc, but I can't get it to work. Any ideas? I understand the pleaseBindTo attribute has to be captured somewhere, but I don't know where or how.
Some Observations:
1. Your directive is an attribute
2. You are passing values to attributes themselves.
Maybe you should change your directive to an element instead. Rewrite the code as follows:
<jqslider pleaseBindTo="firstValue"></jqslider>
Remove the div's and use the directive directly as an element. Next, in the definition of your directive, write the following:
myAppModule.directive('jqslider', function() {
return {
scope: {
pleaseBindTo: "=pleaseBindTo"
}
link:function(scope, element, attrs) {
element.slider({
range: false,
min: scope.pleaseBindTo.min,
max: scope.pleaseBindTo.max,
value: scope.pleaseBindTo.v,
slide: function( event, ui ) {
scope.pleaseBindTo.v = ui.value;
scope.$apply();
}
});
}
};
});
If you wish to still keep the directive as an attribute, you could try to access the value of pleaseBindTo inside the link function with the statement attrs.pleaseBindTo - I am not sure of this, need to check it out.

Resources