I have the following directive...
app.directive('layoutPreview', function () {
return {
restrict : 'E',
transclude : false,
scope: {
layout: '#',
previewid : '='
},
controller : function($scope){
console.log($scope.layout);
console.log($scope.previewid);
layoutPreview($scope.layout, "canvas-layout-" + $scope.previewid);
},
template:
'<canvas height="200" width="350" id="canvas-layout-{{previewid}}">' +
'</canvas>'
}
})
Which, once placed renders a canvas with a preview. However, {{previewid}} inside the template never resolves and I'm unsure why. Both of the log outputs show the correct values too. Even an output in my layoutPreview() function shows the correct id of the element it should be searching for.
Inspecting the page shows that the angular binding hasn't resolved.
Any ideas?
I think it's because the template gets rendered before the controller is created, and therefore the bindings do not work; ie $scope does not exist at the time of rendering.
Try:
template: function($attrs) {
return '<canvas height="200" width="350" id="canvas-layout-'
+ $attrs.previewid
+ '"></canvas>';
}
Also, if previewid is just a string, use:
scope: {
previewid : '#'
},
= is for two-way binding and objects.
This will insert the previewid before rendering the template.
Sidenote: You don't have to include transclude: false if it's false, and I would recommend using component instead of directive if you use Angular 1.5+.
Related
I tried wrapping my head around the following problem:
I have a html string which I render using ng-bind-html
I managed to change a given placeholder (in the html string) with a directive (or more). For example I have: [placeholder]test[/placeholder] and replaced with <my-directive></my-directive> for a certain functionality.
This approach is needed to make some content dynamic.
When rendering the html string I notice that the directive is missing, I understand, but is there a way to render it and make the directive functionally?
P.S:
Tried rendering it as a normal string but the html is escaped
Tried using $sce.trustAsHtml()
I cannot apply $compile(element.contents())(scope); since the directive is not triggered
I have managed to do achieve this by doing the following:
Add the directive in the html:
<my-directive update-data-trigger="someObject.content" data="someObject"></<my-directive>
The directive:
app.directive("myDirective", function ($compile) {
return {
replace:true,
restrict: "E",
scope: {
//Data holds the html in the content attribute
data: '=',
updateDataTrigger: '='
},
link: function ($scope, element) {
//Add a watcher to refresh data because the loaded data passed is async
$scope.$watch('updateDataTrigger', function(){
//Check if data passed has been loaded with our desired object
if($scope.updateDataTrigger != null) {
//Do some content manipulation here
//Append directives to the content as well
//render as html
element.html(data.content);
$compile(element.contents())($scope);
}
});
}
}
});
I have two input fields inside my ion-content and they both have an ng-model attached to them. Then inside my ion-footer I have an ng-click where I call a function and pass in the two ng-models.
This all worked fine when I had the ng-click inside the ion-content, but when I move it to the footer I get undefined for the two parameters I pass to the function.
So does this mean that ion-content and ion-footer have different $scope's? Even though they're in the same file and have the same controller??
I believe ion-footer & ion-content creates new child scope which is Prototypically inerherit from current scope. Below ionic code will give you better illustration that how it works internally, the scope: true, is responsible for creating a new child scope.
Code
.directive('ionContent', [
'$parse',
'$timeout',
'$ionicScrollDelegate',
'$controller',
'$ionicBind',
function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
return {
restrict: 'E',
replace: true,
transclude: true,
require: '^?ionNavView',
scope: true, //<-- this creates a prototypically inerherited scope
template:
'<div class="scroll-content">' +
'<div class="scroll"></div>' +
'</div>',
You need to use . annotation will fix your problem
Eg.
If you are using variable as primitive like
$scope.volume = 5
Then you need to use:
$scope.data = { 'volume' : 5}
Angular Prototypal Scope Inheritance
Explanation of the answer in the comments by pankajparkar:
the ion-content directive has its new scope. It works using the dot notation (important when dealing with scope inheritance)
That is why it works with ng-model="data.model1
Please refer to:
AngularJS documentation on scopes
Egghead video
I've been studying AngularJS and in particular saw the video:
http://www.thinkster.io/pick/IgQdYAAt9V/angularjs-directives-talking-to-controllers
This video presents an example of a directive talking to a controller which I've modified a bit to try and understand if one could also use an isolate scope to get a similar result. Consider an HTML snippet such as:
<div enter="loadMoreTweets()">Roll Over This</div>
and an Angular controller and directive defined as:
app.controller('scopeCtrl', function($scope) {
$scope.loadMoreTweets = function () {
alert("loading more tweets");
}
}).directive('enter', function() {
return {
restrict: "A",
scope: {enter: "#"},
link: function(scope, element, attrs) {
element.bind("mouseenter", function() {
//scope.$apply(attrs.enter);
scope.$apply(scope.enter);
})
}
}
});
Rolling over the DIV causes no errors and has no effect.
If I comment out the isolate scope and use the commented line in the element.bind() rather than the reference to scope.enter then rolling over the DIV causes the alert() to display as expected.
Question: If the "#" isolate scope creates a one-way binding between the attribute's value and the scope's property then I would have expected that scope.enter == attrs.enter. Clearly this isn't true. Why?
The reason for that is that '#' is a one way data binding but it's passed always as a string
scope: { // set up directive's isolated scope
name: "#", // name var passed by value (string, one-way)
age: "=", // age var passed by reference (two-way)
showName: "&" // passed as function
}
The at sign "#" indicates this variable is passed by value. The directive receives a string that contains the value passed in from the parent scope. The directive may use it but it cannot change the value in the parent scope (it is isolated).
I have been trying to figure out the solution but I think i hit a dead end.
So here is my directive
directives.directive('postprocess', function($compile)
{
return {
restrict : 'E',
require: '^ngModel',
scope: {
ngModel: '='
},
link: function(scope, element, attrs) {
var parsed = scope.ngModel;
el = $compile(parsed)(scope);
element.html("");
//add some other html entities/styles.
element.append(el);
console.log(parsed);
}
};
});
The html
<postprocess ng-model="some_model.its_property" style="padding-top: 10px;" />
Somewhere in the controller, I update the model property
some_model.its_property = 'Holla';
But it doesn't update the corresponding directive. It works perfectly when loading which tells me that it might not be entirely a scoping issue.
It's much simpler, so I have removed some extra code you had there.
Please take a look at the code below or working Plunker:
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('postprocess', function ($timeout) {
return {
restrict : 'E',
transclude: 'true',
scope: {
myVariable: '='
},
link: function(scope, element, attrs) {
$timeout(function () {
scope.myVariable = 'Bye bye!'
}, 200);
}
};
});
myApp.controller('myAppCtrl', ['$scope', '$timeout', function ($scope, $timeout) {
$scope.myVariable = {
value : 'Holla'
};
console.log($scope.myVariable.value); // -> prints initial value
$timeout(function () {
console.log($scope.myVariable.value); // -> prints value after it is changed by the directive
}, 2000);
}])
</script>
</head>
<body ng-controller="myAppCtrl">
<postprocess my-variable="myVariable.value" style="padding-top: 10px;" />
</body>
</html>
The controller sets the initial value to 'Holla'
The directive receives that value by the my-variable attribute
Using two way data-binding any changes made to scope.myVariable updates the $scope.myVariable of the main controller
After few seconds $scope.myVariable changes to 'Bye Bye'
Take a look at your console.log
$watch and $apply
Angular's two-way data binding is the root of all awesome in Angular. However, it's not magic, and there are some situations where you need to give it a nudge in the right direction.
When you bind a value to an element in Angular using ng-model, ng-repeat, etc., Angular creates a $watch on that value. Then whenever a value on a scope changes, all $watches observing that element are executed, and everything updates.
Sometimes, usually when you're writing a custom directive, you will have to define your own $watch on a scope value to make the directive react to changes.
On the flip side, sometimes you change a scope value in some code but the app doesn't react to it. Angular checks for scope variable changes after pieces of your code have finished running; for example, when ng-click calls a function on your scope, Angular will check for changes and react. However, some code is outside of Angular and you'll have to call scope.$apply() yourself to trigger the update. This is most commonly seen in event handlers in custom directives.
Some help from angularjs irc, & dluz, updated. Though I wish there was an easier way for the directive to be called, since the link function contains behavior and there should be a way to call that.
http://jsfiddle.net/T7cqV/5/
be sure that you use the dot rule
http://jimhoskins.com/2012/12/14/nested-scopes-in-angularjs.html
This question is similiar to them one asked in Mike's post Using ng-model within a directive.
I am writing a page which is small spreadsheet that displays calculated output based on user input fields. Using a directive, I'm making custom tags like this:
<wbcalc item="var1" title="Variable 1" type="input"></wbcalc>
<wbcalc item="var2" title="Variable 2" type="input"></wbcalc>
<wbcalc item="calc" title="Calculation" type="calc"></wbcalc>
The 'item' field references scoped data in my controller:
$scope.var1 = '5'; // pre-entered input
$scope.var2 = '10'; // pre-entered input
$scope.calc = function() {
return parseInt($scope.var1) + parseInt($scope.var2);
};
And the 'type' field is used in the directive's logic to know whether to treat the item as a string or a function.
Here's a fiddle for this: http://jsfiddle.net/gregsandell/PTkms/3/ I can get the output elements to work with the astonishing line of code:
html.append(angular.element("<span>")
.html(scope.$eval(attrs.item + "()"))
);
...and I'm using this to get my inputs connected to my scoped controller data (I got this from Mike's post:
var input = angular.element("<input>").attr("ng-model", attrs.item);
$compile(input)(scope);
html.append(input);
...while it does put the values in the fields, they aren't bound to the calculation, as you can see by changing inputs in my fiddle.
Is there a better and/or more intuitive way to link my controller-scoped data to the jqlite-generated html in my directive?
Take a look at this, I think you can simplify the process a fair bit.
http://jsfiddle.net/PTkms/4/
angular.module('calculator', []).directive('wbcalc', function($compile) {
return {
restrict: 'E',
template: '<div><div class="span2">{{title}}</div><input ng-model="item"></div>',
scope: {
title: '#',
item: '='
},
link: function(scope, element, attrs) {
// Don't need to do this.
}
}
});
function calcCtrl($scope) {
$scope.var1 = '5';
$scope.var2 = '10';
$scope.calc = function() {
// Yes, this is a very simple calculation which could
// have been handled in the html with {{0 + var1 + var2}}.
// But in the real app the calculations will be more
// complicated formulae that don't belong in the html.
return parseInt($scope.var1) + parseInt($scope.var2);
};
}
I know you said you like jQuery - but to make best use of Angular you need to think in an Angular way - use bindings, don't manipulate the DOM directly etc.
For this example, it would be helpful to read up on the isolated scope bindings used - '#' and '=', see:
http://docs.angularjs.org/guide/directive