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
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 am using the code-mirror component from the Polymer Designer, and can set the initial value, but cannot see how to get changes to the code from the user.
I initialise the code-mirror using
<code-mirror id="code_mirror" value="{{code}}">
</code-mirror>
and would like to listen for changes in {{code}}, but codeChanged doesn't seem to fire.
I know I can get the actual value using code_mirror.$.mirror.getValue(), but would like to use data-binding.
I have tried using on-change to no avail.
Assuming you're using https://github.com/PolymerLabs/code-mirror what you need to do is make the CodeMirror instance created in the ready handle some events that the instance itself is emitting, then make the code-mirror element fire any custom event (something which I know is called event relay)
The following example makes the polymer element fire the custom event code-change whenever the editor value is changed
ready: function() {
var me = this;
//...
this.mirror = CodeMirror(this.shadowRoot, { /* ... */ });
this.mirror.on('change', function () {
// me = polymer instance
me.fire('code-change', { value: me.mirror.getValue() })
});
}
Then any instance of the polymer custom element would need to listen to that event using Polymer's declarative event mapping or through addEventListener
1st case (if code-mirror is inside another <polymer-element />):
<code-mirror on-code-change="{{ onCodeChange }}"></code-mirror>
// ...
<script>
Polymer({
onCodeChange: function(event, detail, sender) { ... }
});
</script>
2nd case ():
<code-mirror></code-mirror>
<script>
document
.querySelector('code-mirror')
.addEventListener('code-change', function () { ... });
</script>
I created a directive with a method that should be called from other elements that are not part of the directive. However it looks like this method is not exposed.
Some example jade code to clarify:
//- a controller for the view itself
div(ng-controller="someController")
//- this is part of the view itself, not within the directive
div(ng-repeat="element in elements")
div(ng-click="methodFromDirective(element)") click element {{$index}} to trigger directive
//- this is the directive
div(some-directive)
The someController isn't too important here I think. It has methods but NOT the methodFromDirective(element) one. The methodFromDirective(element) is a method that exists only in the directive.
If I make a directive and put some logging on creation I can clearly see it's created. However the methodFromDirective(element) method isn't exposed so the calls aren't properly triggered.
The methodFromDirective(element) itself will only work on elements from within the directive's template.
some coffeescript to show the definition of the the directive (ignore indentation errors here):
'use strict'
define [], () ->
someDirective = () ->
restrict: 'A'
scope: {
show: '='
}
transclude: false
templateUrl: 'someTemplateHere.html'
controller = ($scope) ->
# exposing the method here
$scope.methodFromDirective(element)->
$scope.theMethod element
link = (scope, element, attr) ->
# this is logged
console.log "init someDirective"
# triggering this method form outside fails
scope.theMethod = (element)->
console.log "method triggered with element", JSON.stringify(element)
I found my issue.
From the angularJS documentation on directives I was looking into the transclude option since that states:
What does this transclude option do, exactly? transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
I combined transclude=false with the controller function since that exposes the method, again from docs:
Savvy readers may be wondering what the difference is between link and controller. The basic difference is that controller can expose an API, and link functions can interact with controllers using require.
However what I missed completely was that I isolated scope within my directive. From docs:
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive's scope option:
So even if you use transclude=false and the controller function you'll still fail to expose methods if you use isolated scope! Lesson learned!
While figuring out what went wrong I also made a fiddle for better understanding: http://jsfiddle.net/qyBEr/1/
html
<div ng-app="directiveScopeExample">
<div ng-controller="Ctrl1">
<p>see if we can trigger a method form the controller that exists in the directive.</p>
<ul>
<li>Method in Controller</li>
<li>Method in Directive</li>
</ul>
<simple-directive/>
</div>
</div>
javascript
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
transclude: false,
controller: function($scope){
$scope.methodInDirective = function(){
// call a method that is defined on scope but only within the directive, this is exposed beause defined within the link function on the $scope
$scope.showMessage('Method in directive triggered');
}
}
// this is the issue, creating a new scope prevents the controller to call the methods from the directive
//, scope: {
// title: '#'
//}
, link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
})
Calling private methods inside directive's link function is very simple
dropOffScope = $('#drop_off_date').scope();
dropOffScope.setMinDate('11/10/2014');
where
$('#drop_off_date') - jQuery function
setMinDate() - private function inside directive
You can call directive function even from outer space.
By default the scope on directive is false meaning directive will use the parent's scope instead of creating a new one. And hence any function or model defined in the directive will be accessible in the parent scope. Check out this.
I think your problem can be solved as follows:
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
scope: false,
link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
This approach might be an issue in case you want to create reusable directives and you are maintaining some state/models in your directive scope. But since you are just creating functions without side-effects, you should be fine.
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 need your help.
I have a directive with a function parameter ( scope: { myParam: '#'}). And I'm passing the parameters in HTML, like my-param="myFunc(param1, param2)"
This works perfectly. But, I need to inject the event object in to the parameters. Does some one know how can I do that?
I tried $provider.annotate and $provider.instantiate, but they did not work because it's taking the reference function in directive. ($a in my case), so it can't get the function arguments.
any idea?
When you're calling a function created by the & isolate scope syntax, you can pass parameters to it as a named map. If your directive is defined like this:
scope: { myParam: '&' },
link: function (scope, el) {
el.on('click', function (e) {
scope.myParam({$event: e});
});
}
and used like this:
<my-directive my-param="console.log($event)"></my-directive>
... you should see the desired behavior.
chrisrhoden's answer is great. I would suggest to expand upon it a bit with the following. Doing so will help prevent ng-click double-firing issues relating to mobile devices and/or AngularJS conflicts with jQuery.
myApp.directive('responsiveClick', function(){
return {
scope: { myCb: '&' },
link: function (scope, el,attr) {
el.bind('touchstart click', function (e) {
e.preventDefault();
e.stopPropagation();
scope.myCb({$event: e});
});
}
}
});
along with the markup as follows:
<a class="mdi-navigation-cancel" my-cb="removeBasketItem($event,item)" responsive-click></a>
have you tried passing it in the original function?
my-param="myFunc($event, param1, param2)"