I'm working on a fiddle that I found from this answer on SO
I've modified the fiddle for my situation in the following way:
angular.module('sampleApp', [])
.controller('myCtrl', function($scope) {
$scope.func = function() {
$scope.name = "Test Name";
}
})
.directive("myDirective", function($compile) {
return {
template: "<div>{{name}}</div>",
scope: {
name: '='
},
link: function(scope, element, attrs) {
alert(scope.name);
}
}
});
Basically, I am trying to pass a variable value from a scope function to the directive - but the alert message shows up an undefined value.
Any idea as to what is going wrong here? How can I pass the value stored within $scope.name within the $scope.func function and pass it to the directive?
The updated fiddle can be found here.
The problem is that you are defining name inside a function in your controller and that function is never called. Change to this.
angular.module('sampleApp', [])
.controller('myCtrl', function($scope) {
$scope.name = "Test Name";
})
.directive("myDirective", function($compile) {
return {
template: "<div>{{name}}</div>",
scope: {
name: '='
},
link: function(scope, element, attrs) {
alert(scope.name);
}
}
});
<div ng-app="sampleApp" ng-controller="myCtrl">
<div my-directive name="name">
</div>
</div>
The alert function is executing before the data is set by the controller.
To see value changes, add a controller and use $onChanges Life-Cycle Hook:
app.directive("myDirective", function() {
return {
template: "<div>{{name}}</div>",
scope: {
̶n̶a̶m̶e̶:̶ ̶'̶=̶'̶
name: '<'
},
controller: function() {
this.onChanges = function(changesObj) {
if (changesObj.name) {
alert(changesObj.name.currentValue);
};
};
}
}
});
Also note that one-time < binding is used instead of two-way = binding.
Related
When looking for information regarding Angular directives and passing behavior to directives, I get ended up being pointed in the direction of method binding on an isolate scope, i.e.
scope: {
something: '&'
}
The documentation for this functionality is a bit confusing, and I don't think it'll end up doing what I want.
I ended up coming up with this snippet (simplified for brevity), that works by passing a scope function in HomeCtrl, and the directive does it's work and calls the function. (Just incase it matters, the real code passes back a promise from the directive).
angular.module('app', []);
angular.module('app')
.directive('passingFunction',
function() {
var changeFn,
bump = function() {
console.log('bump() called');
internalValue++;
(changeFn || Function.prototype)(internalValue);
},
internalValue = 42;
return {
template: '<button ng-click="bump()">Click me!</button>',
scope: {
onChange: '<'
},
link: function(scope, element, attrs) {
if (angular.isFunction(scope.onChange)) {
changeFn = scope.onChange;
}
scope.bump = bump;
}
};
})
.controller('HomeCtrl',
function($scope) {
$scope.receive = function(value) {
console.log('receive() called');
$scope.receivedData = value;
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.4/angular.min.js"></script>
<div ng-app="app" ng-controller="HomeCtrl">
<passing-function on-change="receive"></passing-function>
<p>Data from directive: {{receivedData}}</p>
</div>
Is this a proper "Angular" way of achieving this? This seems to work.
What you need is to pass the function to the directive. I'll make a very small example.
On controller:
$scope.thisFn = thisFn(data) { console.log(data); };
In html:
<my-directive passed-fn="thisFn()"></my-directive>
On directive:
.directive('myDirective', [
() => {
return {
restrict: 'E',
scope: {
passFn: '&'
},
template: '<div id="myDiv" ng-click="passFn(data)"></div>',
link: (scope) => {
scope.data = "test";
}
}
}]);
When I bind a $scope within the controller of a directive without using scope: {} in the directive settings it works.
But I need scope: {} to get a defined variable inside the directive.
This is my test code:
var app = angular.module("app", []);
app.directive("directive", function () {
return {
controller: function($scope){
$scope.id = 5;
}
}
});
app.directive("childDirective", function () {
return {
scope: {
id: "=user"
},
controller: function($scope){
$scope.hello = "Hello";
}
}
})
-
<html ng-app="app">
<directive>
<child-directive user="id">
{{ hello }}
</child-directive>
</directive>
</html>
http://codepen.io/anon/pen/MyEXJV
when I remove scope: { id: "=user" } it works. But I need to pass the id to the controller.
Is there a workaround?
Thanks in advance.
When you use an isolated scope it is only valid on the template provided for the directive. You can provide the template as a string or as templateUrl
Try this way:
<directive>
<child-directive user="id"></child-directive>
</directive>
JS
app.directive("childDirective", function () {
return {
scope: {
id: "=user"
},
template:'{{ hello }} ID= {{id}}',
controller: function($scope){
$scope.hello = "Hello";
}
}
})
Fixed the issue, here is the final fiddle that shows it working:
http://jsfiddle.net/mbaranski/tfLeexdc/
I have a directive:
var StepFormDirective = function ($timeout, $sce, dataFactory, $rootScope) {
return {
replace: false,
restrict: 'AE',
scope: {
context: "=",
title: "="
},
template: '<h3>{{title}}</h3><form id="actionForm" class="step-form"></form><button ng-click="alert()" type="button">Save</button>',
link: function (scope, elem, attrs) {
}
}
}
How do I make the alert() do something from the controller?
Here is a fiddle:
http://jsfiddle.net/mbaranski/tfLeexdc/
Angular can be twitchy, so I've built a whole new fiddle to demonstrate all of the "glue-up" pieces you need to make this work.
First, you weren't passing the properties through to the directive, so I've made that adjustment:
// You have to pass the function in as an attribute
<hello-directive list="osList" func="myFunc()"></hello-directive>
Second, you were using onclick instead of ng-click in your template, which was part of the problem, so I made that switch:
// You need to use "ng-click" instead of "onclick"
template: '<h3>{{list}}</h3><button ng-click="func()" type="button">Button</button>',
And lastly, you need to bind the function in the scope of the directive, and then call it by the bound name:
scope: {
list: "=",
// Bind the function as a function to the attribute from the directive
func: "&"
},
Here's a Working Fiddle
All of this glued up together looks like this:
HTML
<div ng-controller="MyCtrl">
Hello, {{name}}!
<hello-directive list="osList" func="myFunc()"></hello-directive>
</div>
Javascript
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.name = 'Angular Directive';
$scope.osList = "Original value";
$scope.stuffFromController = {};
$scope.myFunc = function(){ alert("Function in controller");};
};
var HelloDirective = function() {
return {
scope: {
list: "=",
func: "&"
}, // use a new isolated scope
restrict: 'AE',
replace: false,
template: '<h3>{{list}}</h3><button ng-click="func()" type="button">Button</button>',
link: function(scope, elem, attrs) {
}
};
};
myApp.directive("helloDirective", HelloDirective);
If you'd like to execute a function defined somewhere else, make sure you pass it in by the scope directive attribute.
Here you can do:
scope: {
context: '=',
title: '=',
alert='&' // '&' is for functions
}
In the place where you using the directive, you'll pass the "expression" of the function (meaning not just the function, but the actual invocation of the function you want to happen when the click occurs.
<step-form-directive alert="alert()" title=".." context=".."></step-form-directive>
I am working to add modals to my directives using ui-bootstrap and did so fine on the previous directive. I don't believe I am doing anything differently in this one but I get the ReferenceError: milestoneController is not defined when I run the edit() function from within the directive.
milestone.html (this is the template HTML for the directive below):
<div ng-controller = "milestoneController"></div>
milestone directive:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
milestoneController.editMilestoneDialog(data);
};
}
}
});
angular.module('ireg').controller('milestoneController', function ($scope, $modal){
$scope.editMilestonesDialog = function (objectid) {
//fun
}
});
EDIT: I allso felt I should mention that the milestone directive is repeated in a ng-repeat loop. Thanks!
ok you're going to want to use a transcluded scope in your directive to pass a controller function to the directive. Your directive now becomes:
angular.module('ireg').directive('milestone', function (milestoneFactory,$modal) {
return {
restrict:'E',
scope: {
objectid:'#objectid',
editMilestoneDialog:'&'
},
templateUrl: '/ireg/components/milestone/milestone.html',
link: function ($scope, element, attrs) {
$scope.edit = function(data) {
$scope.editMilestoneDialog(data);
};
}
}
and your markup becomes:
<milestone edit-milestone-dialog="editMilestoneDialog"></milestone>
I need to watch a model from within a directive.
angular.module('app', [])
.directive('myDirective', [function() {
return {
restrict: 'A',
scope: {
modelToWatch: '#'
},
link: function(scope, element, attrs) {
scope.$watch(scope.modelToWatch, function(val) {
// do something...
});
}
};
]})
.controller('MyController', ['$scope', function($scope) {
$scope.obj = {
foo: 'val'
};
}]);
<div ng-controller="MyController">
<div my-directive model-to-watch="obj.foo"></div>
</div>
The above works fine.
However, I encounter a problem when there is an intermediary scope between the actual owner of the model and the directive.
I used another controller to demonstrate the scenario below:
.controller('AnotherController', ['$scope', function($scope) {}])
<div ng-controller="MyController">
<div ng-controller="AnotherController">
<div my-directive model-to-watch="obj.foo"></div>
</div>
</div>
In the case for above, I could look up the $parent tree to find the scope which owns the property I want to watch using the code below:
...
link: function(scope, element, attrs) {
var contextScope = scope;
// find for the scope which owns the property that we want to watch
while (contextScope != null && contextScope.hasOwnProperty(attrs.modelToWatch)) {
contextScope = contextScope.$parent;
}
// use the scope found to watch the model
if (contextScope != null) {
contextScope.$watch(scope.modelToWatch, function(val) {
// do something...
});
}
}
Additional problem, however is if the modelToWatch is a complex expression (e.g: "tableParams.filter().shop_id" then the hasOwnProperty cannot be relied upon.
Is there an easy way to watch a model in the context of its owner scope? Or is it's possible to watch a model even from a prototypal child?
Or can I pass scope as a parameter, so at least I don't have to look for it...
restrict: 'A',
scope: {
modelToWatch: '#',
sourceScope: '=', // don't know how to do this..
}
Note: I need to use isolate scope
As suggested by #pixelbit, I tried using the $eval to find the correct scope
link: function(scope, element, attrs) {
var contextScope = scope;
// find for the scope which owns the property that we want to watch
while (contextScope != null && contextScope.$eval(attrs.modelToWatch) != undefined) {
contextScope = contextScope.$parent;
}
...
}
Works for most cases except when the modelToWatch expression actually evaluates to undefined.. There is an ambiguity whether the modelToWatch doesn't exist in the current scope (meaning it's not the owner) or the modelToWatch expression just happens to evaluate to undefined.
You can declare a controller directly inside your directive :
angular.module('app', [])
.directive('myDirective', [function() {
return {
restrict: 'A',
scope: {
modelToWatch: '='
},
link: function(scope, element, attrs) {
scope.$watch(scope.modelToWatch, function(val) {
// do something...
});
},
controller: 'MyController'
};
]})
.controller('MyController', ['$scope', function($scope) {
$scope.obj = {
foo: 'val'
};
}]);
<div my-directive model-to-watch="obj.foo"></div>
That way, when you will call your directive, your controller will be instanciated first, then the link will be executed, sharing the same scope.
You can watch a function instead:
scope.$watch(function() {
return scope.modelToWatch;
}, function(val) {
// do something
});
There is no need for an isolated scope - you can inherit scope instead. Also to address complex expressions, you can use scope.$eval to evaluate the model and find the appropriate scope. Once you've evaluated the model, return it from a watched function:
angular.module('app', [])
.directive('myDirective', [function() {
return {
restrict: 'A',
scope: false,
link: function(scope, element, attrs) {
scope.$watch(function() {
return scope.$eval(attrs.modelToWatch);
}, function(val) {
// do something...
});
}
};
]})
If you must to use an isolated scope, then watch a function and return the model:
angular.module('app', [])
.directive('myDirective', [function() {
return {
restrict: 'A',
scope: {
modelToWatch: '='
},
link: function(scope, element, attrs) {
scope.$watch(function() {
return scope.modelToWatch;
}, function(val) {
// do something...
});
}
};
]})