I want to change a $scope variable from inside an isolated directive, how is this possible?
I have tried using the '#, =, &' syntax in the directive scope but cannot get it to work.
This is my simplified code
JS
app.controller('testCtrl', function($scope) {
$scope.hello = 'hello';
}
app.directive('testDirective', function() {
return {
restrict: 'E',
template: '<div>{{text}}</div>',
scope: {},
link: function(scope, element) {
scope.text = 'this is my text';
scope.hello = 'hello world!';
}
};
});
HTML
<body>
{{ hello }}
<test-directive />
</body>
This is the output i want
hello world!
this is my text
You can set a require option on the directive and specify a parent controller. This will pass the controller to your link function as the last argument:
app.directive('testDirective', function() {
return {
restrict: 'E',
template: '<div>{{text}}</div>',
require: '^testCtrl',
scope: {},
link: function(scope, element, attrs, testCtrl) {
scope.text = 'this is my text';
testCtrl.setHello('hello world!');
}
};
});
Note you have to create this testCtrl.setHello() method on your controller. This is because you get the controller itself, not its injected scope:
app.controller('testCtrl', function($scope) {
$scope.hello = 'hello';
this.setHello = function(newHello) {
$scope.hello = newHello;
}
}
Also, if you don't really care about strictly enforcing the controller dependency, you can directly access scope.$parent.$parent.hello from your directive.
In HTML, the directive must be in snake-case:
<test-directive />
In your script, the directive must be defined in camel case:
app.directive('testDirective', function() {
});
Also, add the ngController directive:
<body ng-controller="testCtrl>
</body>
Here's what was missing:
ng-controller was not defined
# means passing the attribute as a string, = means binding the property to a property from the parent's scope (which is what we need here) and & means passing in a function from the parents scope to be called later.
when the directive is called "testDirective" it looks in the HTML as follows:<test-directive></test-directive> as camel cases in JS need to be
seperated by "-" in HTML.
<body ng-controller="testCtrl">
{{ hello }}
<test-directive hello-from-parent="hello"></test-directive>
</body>
app.directive('testDirective', function() {
return {
restrict: 'E',
scope: {
hello: "=helloFromParent"
},
template: '<div>{{text}}</div>',
link: function(scope, element, attrs) {
scope.text = 'this is my text';
scope.hello = 'hello world';
}
}
});
I set up a working plnkr here
Related
I would like to pass an argument from an html directive to a controller
Example:
Directive:
angular.module('app')
.directive('helloWorld', function () {
return {
replace: false,
restrict: 'AE',
templateUrl: "./views/templates/helloWorld.html"
}
});
helloWorld.html:
<body ng-app="app" >
<div ng-controller="HelloWorldCtrl">
{{ welcome }}
</div>
hello.html:
<body ng-app="app">
<hello-world/>
</body>
HelloWorldCtrl:
angular.module('app')
.controller('HomeWorldCtrl', function ($scope, ) {
$scope.welcome = "Welcome"
};
})
Can I specify a parameter in hello.html e.g.
<hello-world param="param1"/>
That is being passed into the controller?
So in the HomeWorldCtrl I can check the value of the parameter?
Are there any better alternatives to achieve this?
Thanks,
app.directive('helloWorld', function(){
return {
restrict:'E',
scope: {
param: '#'
},
template:'<div class="param"><h2>{{param}}</h3></div>'
};
});
// you have options to choose from
//= is two-way binding
//# simply reads the value (one-way binding)
//& is used to bind functions
<hello-world="someParam"></hello-world>
// for the scope definition:
scope: {
title: '#helloWorld'
}
The usage in the template will remain the same
<hello-world param="someParam"></hello-world>
If you isn't use isolated scope (scope: {someparam: ""}), then you may use any $scope properties in the directive template without changing anything:
$scope.param = "new param value";
..
return {
..
,template: "<param>{{param}}</param>"
Thanks!
directive
return {
replace: false,
restrict: 'AE',
templateUrl: "./views/templates/PatientSearchEdit.html",
scope: {
param: '#'
}
}
controller
console.log($scope.param);
Logs indeed the value specified.
Thanks you very much!
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>
How do i change the value of bar from directive2 so that it is reflected in directive1
If i make the scope:false it is happening.Is there any other way, to make this happen.(because in the code i am writting , i cannot make scope:false).
My basic requirement is to make one directive to talk to another.
Here you can try the plunkr version of the below code
HTML snippet
<body ng-controller="MainCtrl">
this is directive1: <div directive1></div>.<br/>
this is directive2: <div directive2></div>.
</body>
JS snippet
var app = angular.module('myApp', []);
app.directive('directive1', function() {
return {
restrict: 'A',
replace: true,
template: '<span>{{bar}}</span>'
}
});
app.directive('directive2', function() {
return {
restrict: 'A',
scope:{
},
replace: true,
link:function(s,e,a){
s.bar = "Changed value";
},
template: '<b>{{bar}}</b>'
}
});
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.bar ="original value";
});
You could simply use bar inside your isolate scope, that will do two way binding with your variable which is assigned to bar attribute, That means change inside a directive on bar variable will reflect the changes on controller scope variable.
Markup
<body ng-controller="MainCtrl">
this is directive1: <div directive1></div>.
<br />
this is directive2: <div directive2 bar="bar"></div>.
</body>
Directive
app.directive('directive2', function() {
return {
restrict: 'A',
scope:{
'bar': '=' //<-- Change here
},
replace: true,
link:function(s,e,a){
s.bar = "Changed value";
},
template: '<b>{{bar}}</b>'
}
});
Working Plunkr
I have modified your code in here
Share the variable in both directives by passing it as '=' in the scope, changing it in one directive will change it in the the other.
<body ng-controller="MainCtrl">
this is directive1: <div directive1 bar="bar"></div>.
<br />
this is directive2: <div directive2 bar="bar"></div>.
</body>
var app = angular.module('myApp', []);
app.directive('directive1', function() {
return {
restrict: 'A',
scope:{bar:'='},
replace: true,
template: '<span>{{bar}}</span>'
}
});
app.directive('directive2', function() {
return {
restrict: 'A',
scope:{bar:'='},
replace: true,
link:function(s,e,a){
s.bar = "Changed value";
},
template: '<b>{{bar}}</b>'
}
});
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.bar ="original value";
});
If they don't share the same $scope, the only way is using angularjs events.
In first directive you put:
$rootScope.$on('myEvent', function (event, data) {
$scope.bar = data.bar;
});
In the second one when bar change:
$scope.$emit('myEvent', {bar: bar});
Taking into account that are directives completely unrelated.
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...
});
}
};
]})
I'm trying to bind to ng-change on an element created by a directive up through two other directives that wrap it to a method on the controller using & bindings in an isolate scope, but I can't figure out how to get arguments to pass all the way through. Here's a plunk that demonstrates the problem.
In short, I have an HTML structure like this:
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething('Called from Outer in HTML')"></outer>
</div>
</body>
The controller:
var app = angular.module('ExampleApp', []);
app.controller('Controller', ['$scope',
function($scope) {
$scope.doSomething = function(one, two, three) {
console.log(arguments);
};
}
]);
The outer directive:
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged(\'Called from Outer\')');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
And the inner directive that the outer directive creates:
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged(\'Called from Inner\')">Call from Inner</button>'
}
});
I understand that I'm getting the output ["Called from Outer in HTML"] because this is hardcoded into the <outer> tag. What I don't understand is how to pass arguments all the way up from the inner directive.
I'm not sure I 100% get what you want to accomplish but this is how you would make the ["Called from Inner"] message appear.
Change the html so the on-outer-model-changed expression does not use a hardcoded string.
<body ng-app="ExampleApp">
<div ng-controller="Controller">
<button ng-click="doSomething('Called directly')">Call Function Directly</button>
<br />
<outer on-outer-model-changed="doSomething(outerParam)"></outer>
</div>
</body>
Then change the outer directive to call outerModelChanged with a parameter. And set the outerParam to the innerParam.
app.directive('outer', function($compile) {
return {
restrict: 'E',
scope: {
outerModelChanged: '&onOuterModelChanged'
},
link: function(scope, element, attrs, controller) {
var innerElement = angular.element('<inner></inner>');
innerElement.attr('on-inner-model-changed', 'outerModelChanged({outerParam:innerParam})');
element.after(innerElement);
$compile(innerElement)(scope);
console.log(arguments);
}
}
});
Finally call the innerModelChanged from the inner directive with the innerParam set to your message.
app.directive('inner', function() {
return {
scope: {
innerModelChanged: '&onInnerModelChanged'
},
restrict: 'E',
template: '<button ng-click="innerModelChanged({innerParam:\'Called from Inner\'})">Call from Inner</button>'
}
});
Here is a plunk to the above code.