I know that there are a billion questions about isolate scope on here, but I could not find one that directly relates to this exact issue.
I have a property on my controller called Model, so .. $scope.Model. I then have a directive that needs to interact with the Model.
I am wanting to give the directive an isolate scope, but this is proving a bit difficult because doing that means I no longer have access to the model. I thought I could solve this by specifying the model as a two-way binding in the isolate scope, like this.
html
<body ng-app="app" ng-controller="HomeController">
<div custom-directive="Model.Tags"></div>
</body>
javascript
app.directive('customDirective', ['$parse', function($parse) {
return {
restrict: "A",
scope: {
customDirective: "=Model"
},
link: function(scope, element, attributes){
// need to access the controller's "$scope.Model" here for some things.
var model = scope.$eval(attributes.customDirective);
}
}
}])
.controller('HomeController', ['$scope', function($scope) {
$scope.Model = {
Id: "items/1",
Name: "Some Model Object",
Tags: []
};
}]);
I'm really lost as to why this doesn't work. According to all of the isolate scope tutorials I've seen, this should be fine.
notes
Passing the controller as a parameter is not an option. A third party library that I need to interact with already does this, and apparently I can't do that twice on the same HTML element.
Your usage is incorrect. This will work:
<body ng-app="app" ng-controller="HomeController">
<div custom-directive="Model"></div>
</body>
app.directive('customDirective', [function() {
return {
restrict: "A",
scope: {
customDirective: "="
},
link: function(scope, element, attributes){
console.log(scope.customDirective); // this is the $scope.Model due to 2-way binding
}
}
}])
.controller('HomeController', ['$scope', function($scope) {
$scope.Model = {
Id: "items/1",
Name: "Some Model Object",
Tags: []
};
}]);
actually under scope, property name corresponds to the directive's isolate scope property in your case it is customDirective.
SO your code should be:-
var app=angular.module("app",[]);
app.directive('customDirective', ['$parse', function($parse) {
return {
restrict: "A",
scope: {
customDirective: "=model"
},
link: function(scope, element, attributes){
// need to access the controller's "$scope.Model" here for some things.
console.log(scope.customDirective);
}
}
}]);
app.controller('HomeController', ['$scope', function($scope) {
$scope.Model = {
Id: "items/1",
Name: "Some Model Object",
Tags: []
};
}]);
http://jsfiddle.net/4bb4dozv/
Related
Within a controller I have an ajax call that populates $scope.main_data. I want to within a directive get that data when it populates. However the two issues I'm having is:
I cannot seem to access $scope.main_data from the directive.
$scope.watch doesn't seem to work because of this.
How can I print the data from the directive once the data arrives?
CONTROLLER:
app.controller('myCtrl', ['$scope','$http', function($scope, $http) {
$scope.main_data = [];
$http.get("some_url").success( function(data) {
$scope.main_data = data;
});
}]);
DIRECTIVE:
app.directive('myDir', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
items: '=items'
},
link: function($scope, element, attrs, ctrl) {
$scope.$watch($scope.main_data ,function(newValue,oldValue){
if (newValue){
console.log($scope.main_data);
}
});
}
};
});
HTML:
<div ng-controller="myCtrl">
<div my-dir>
<div>
</div>
The directive in the html exists within the controller but for some reason I can't access the controller scope through $scope but $scope.$parent.
You've almost got it.
app.directive('myDir', function($compile, $rootScope) {
return {
restrict: 'A',
scope: {
items: '=items'
},
link: function($scope, element, attrs, ctrl) {
$scope.$watch("items",function(newValue,oldValue){
if (newValue){
console.log($scope.main_data);
}
});
}
};
});
And html:
<div ng-controller="myCtrl">
<div my-dir items="main_data">
</div>
</div>
Even though you can "hack" around and access main_data using series of$parent calls on your scope and using some other methods, just pass it in to your directive with = binding, so it will be updated when controller scope is updated. In fact you don't even need $watch in this case, you will always have actual data.
I want to pass object (reference - two way binding) through ATTRIBUTE not by isolated scope. How can I do this? Because code bellow passing string instead of object:
HTML
<tr ng-form="rowForm" myDirective="{{row.data}}">
Directive
angular.module("app").directive("myDirective", function () {
return {
require: ["^form"],
restrict: "A",
link: function (scope, element, attrs, ctrls) {
scope.$watch(function () {
return attrs.myDirective;
}, function (newValue, oldValue) {
// .....
Directives can do two-way data binding without parsing or compile anything manually, sorry for not delivering the plunker but it's rebelius and won't save for me
JS
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.myObj = {name: 'Tibro', age: 255}
})
.directive('myDirective', function(){
return {
scope: {
'myAttribute': '='
},
template: '{{myAttribute}}',
link: function(scope){
scope.myAttribute.age= 31
}
}
})
HTML
<body ng-controller="MainCtrl">
controller: {{myObj}} <br/>
directive: <my-directive my-attribute="myObj"></my-directive>
</body>
OUTPUT
controller: {"name":"Tibro","age":31}
directive: {"name":"Tibro","age":31}
you can see from the output that passed object has been binded two-way and change made in directive is reflected on controller level
The result of {{ }} interpolation is a string. An object can't be passed like that.
Bindings are idiomatic here and thus preferable. The whole thing becomes messy when the directive is forced to use parent scope. However, it can be done by parsing scope properties manually with $parse:
$scope.$watch(function () {
var myDirectiveGetter = $parse($attrs.myDirective);
return myDirectiveGetter($scope);
}, ...);
This is a job for binding (< or =, depending on the case). If isolated scope isn't desirable, this can be done with inherited scope and bindToController:
scope: true,
bindToController: {
myDirective: '<'
},
controllerAs: `vm`,
controller: function ($scope) {
$scope.$watch('vm.myDirective', ...);
}
Notice that directive attribute is my-directive, not myDirective.
Couple of things:
myDirective in the tr should be my-directive, as per Angular's
conventions.
{{row.data}} prints the variable, you need to pass it without the
{{}} for it to go through as an object.
I have the following in a controller:
Restangular.all('foos').getList().then(
function(foos) {
$scope.foos = foos;
});
In an HTML page, I am able to do the following:
<div ng-repeat="foo in foos | orderBy:'fooName'">
I want to move the ng-repeat to a directive, so I have the following in a directive:
app.directive('interactionFoos', function(){
return {
restrict: 'A',
scope : false,
link: function($scope, $element, $attrs) {
//console.log("*** size: " + $scope.foos.length);
}
}
});
And in the HTML I will have:
<div interaction-foos></div>
In the directive, I am getting undefined for $scope.foos.
As a test, in the controller, I hard coded: $scope.test= 'foobar'.
Then, in the directive, I replaced the log line with the following and it printed 'foobar':
console.log("*** test: " + $scope.test);
I do not know why $scope.test is working as I expect, but $scope.foos is not?
I believe this is an Async issue as Restangular would run as a promise so foo would not be set when the directive link function runs. To get around this you need to add a watch to see when the scope has changed
$scope.$watch('foos', function(newValue, oldValue) { console.log(newValue, oldValue); });
do something like this:
angular.module("app", [])
.controller("interactionCtrl", function($scope) {
$scope.foos = ["A", "B", "C"];
})
.directive("interactionFoos", function() {
return {
restrict: 'A',
scope: {
foos: '='
},
link: function(scope, element, attrs) {
alert(scope.foos.length);
}
};
});
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.14/angular.min.js"></script>
<div ng-app="app" ng-controller="interactionCtrl">
<div interaction-foos foos="foos"></div>
</div>
basically the foos in the directive binds to the foos that are exposed on the controller.
also in your linking function don't use $ in the name of the function arguments, for example call the first argument scope instead of scope, because those arguments are not really injected to the link function (it's a regular function call, if you name the first argument bob it will still be equal to the scope)
I am trying to call a function in a controller, which is part of a custom angular directive, following is the code,
Method 1: (Doesn't work: Controller's function doesn't write to the console)
HTML:
<div ng-app="MyApp">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
controller: function ($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
}
}
});
When I remove the directive's controller from it and and create a new controller it works,
Method 2: (Works: Controller's function does write to the console)
HTML:
<div ng-app="MyApp" ng-controller="Ctrl">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
}
}
});
app.controller('Ctrl', function ($scope) {
$scope.ctrlFn = function(test) {
console.log(test);
}
});
I would like know how to get the behavior of Method 2 in Method 1 i.e., to be able to call the directive's controller's function from directive's attribute.
Any help is greatly appreciated, Thank you!
In Method 1, you are creating an isolated scope and defining a scope value someCtrlFn that takes in a function from the parent scope that is using your directive. The function to use is specified by the attribute callbackFn.
The way directives work with these scope items is that they are expected to be assigned from things that are on the parent scope that is active when the directive is used. So, if you have a controller Ctrl as in your Method 2, then use the directive within that scope, your directive is trying to match the what you defined in the attribute to what is available on Ctrl's scope.
So, in your first example, it's looking for a function called ctrlFn on the parent scope, but there isn't one. It will not try to look for it on the directive's controller. This is why Method 2 works, because there is a parent scope where ctrlFn is defined, and the directive is able to properly invoke that expression.
The purpose of these scope attributes is to allow directives to bind to values or functions on a parent scope to facilitate communication. For example, to give the directive data that it will display or modify, or allow the parent to define a function the directive can invoke for a callback during an event or what have you. The parent scope cannot move into the directive's scope and force the directive's scope to use its own defined items (unless you set it up so your directive uses a default value or function if the attribute is omitted or whatever).
They are not used so a directive can define things on its scope that it uses internally. If these things are internal to the directive, you can simply add them to the scope during link or whatever is suitable.
Did you mean something like this?
var app = angular.module('MyApp', []);
app.directive('myDirective', function() {
return {
restrict: 'E',
scope: { },
link: function(scope, element, attrs) {
// defines ctrlFn that can be used later by this directive's template or controller
scope.ctrlFn = function(test) {
console.log(test);
}
// immediately invokes ctrlFn to log a message, just here to illustrate
scope.ctrlFn('Hello World!');
}
}
});
Achieved it using $rootScope instead of $scope within the directive's controller
HTML:
<div ng-app="MyApp">
<my-directive callback-fn="ctrlFn(arg1)"></my-directive>
</div>
JS:
<script>
var app = angular.module('MyApp', []);
app.directive('myDirective', function($rootScope) {
return {
restrict: 'E',
scope: { someCtrlFn: '&callbackFn' },
link: function(scope, element, attrs) {
scope.someCtrlFn({arg1: 22});
},
controller: function ($scope) {
$rootScope.ctrlFn = function(test) {
console.log(test);
}
}
}
});
</script>
I'm trying to pass a template through a directive to a dynamic controller (known at runtime from the directive perspective).
Something like this:
<my-dir ctrl="PersonCtrl">
<h1>{{person.name}} - {{person.age}}</h1>
</my-dir>
var data = {
name: "Alex",
age: "24"
};
function PersonCtrl($scope){
$scope.person = data;
}
myApp.directive('myDir', function($controller){
return {
restrict: "EA",
scope: {
ctrl: "#"
},
transclude: true,
controller: function($scope, $element, $attrs) {
},
template: "<div>{{ctrl}}</div><div ng-transclude></div>",
link: function($scope, $element, $attrs) {
$controller($attrs.foo, {$scope: {}});
}
};
});
see jsFiddle
The controller is found and instantiated, but somehow the binding of the transcluded template to it doesn't work. Do I miss some order requirement or is there a way to bind this controllers scope to the transcluded template?
Found it - should have binded the controller to the $$nextSibling scope!
$controller($attrs.ctrl, {$scope: $scope.$$nextSibling});
updated jsFiddle