Why isolate scope "#" and $apply don't work as expected - angularjs-directive

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).

Related

Include directive before html render

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);
}
});
}
}
});

$scope.variable is undefined - AngularJS [duplicate]

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

Angular Directive: How to check if a scope function is defined?

I know you can have optional property using a question mark like x:"=?" and then you can check if it is specified by checking if x is undefined or null.
How can I do similar thing for a function? Oftentimes, I want to hide a control if the function is not specified. I have to define another property for this purpose to workaround this problem. I wonder if there is a way to save this extra property.
I found an answer to this question at here. Here is a recap in case somebody is interested:
In directive:
scope: {
callback: '&'
},
link: function(scope, elem, attrs) {
scope.hasCallback = function() {
return angular.isDefined(attrs.callback);
}
}
In html:
Call me back
I like it very much because it saves me an extra parameter.

AngularJS directive with method called from outside

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.

Two way binding Angularjs directives isn't working

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