Angular - understanding directive isolated scope method - angularjs

take a look at the following code:
html:
<body ng-app="myApp">
<div ng-controller="MainController">
<input type="button" ng-click="talk()" value="outside directive" />
<div my-element>
<input type="button" ng-click="talk()" value="inside directive" />
</div>
</div>
</body>
js:
var app = angular.module('myApp', []);
app.controller('MainController', function($scope){
$scope.talk = function() {
alert('HELLO1');
}
});
app.directive('myElement', function(){
return {
restrict: 'EA',
scope: {},
controller: function($scope) {
$scope.talk = function() {
alert('HELLO2');
}
}
};
});
as you can see, there's a controller, which nests a directive.
there are 2 buttons, one in controller level (outside of directive), and one is inside the directive my-element.
the main controller defines a scope method talk, the nested directive controller also defines a method - talk - but keep in mind that directive has isolated scope (i'd expect that talk method won't be inherited into directive's scope).
both buttons result an alert of 'HELLO 1', while i expected the second button (inside directive) to alert 'HELLO 2' as defined in directive controller, but it doesn't - WHY?
what am i missing here? how could i get a result when the second button will alert 'HELLO 2' but with the same method name ("talk") ?
thanks all

If you want the inner content to use the directive scope, you need to use manual transclusion:
app.directive('myElement', function(){
return {
restrict: 'EA',
scope: {},
transclude: true,
link: function(scope, element, attr, ctrl, transclude) {
transclude(scope, function(clone, scope) {
element.append(clone);
});
},
controller: function($scope) {
$scope.talk = function() {
alert('HELLO2');
}
}
};
});
By default, transcluded content uses a sibling of the directive scope. I actually don't know how angular handles DOM content for directives that don't use transclude (which is what makes this an interesting question), but I would assume from the behavior you are seeing that those elements use the directive's parent scope by default.

This will work for you
<body ng-app="myApp">
<div ng-controller="MainController">
<input type="button" ng-click="talk()" value="outside directive" />
<div my-element></div>
</div>
</body>
app.directive('myElement', function(){
return {
restrict: 'A',
template: '<input type="button" ng-click="talk()" value="inside directive">',
replace: true,
scope: {},
controller: function($scope) {
$scope.talk = function() {
alert('HELLO2');
}
}
};
});

Related

how to call twice nested method in angular 1.x?

My HTML looks like this:
Controller HTML:
<div ng-controller="Ctrl">
<first-directive></first-directive>
</div>
first-directive HTML
<li>
<second-directive></second-directive>
</li>
Controller JS:
app.controller('Ctrl', (#scope) => {
$scope.foo = function() {
console.log('do smthn');
}
});
first directive:
app.directive('first-directive', function(){
return {
restrict: 'E',
templateUrl: '/partials/first-directive.html',
replace: true,
scope: {
// some data
}
controllerAs: function(){}
}
}
second directive:
app.directive('second-directive', function(){
return {
restrict: 'E',
templateUrl: '/partials/second-directive.html',
controllerAs: function(){}
}
}
So i have controller with nested directive and there is another nested directive. When i'm trying to call $parent.foo() from first-directive it works. When i'm trying to call $parent.$parent.foo() from second-directive it doesn't work. I tried also use ng-controller="Ctrl as ctrl" and ctrl.foo() from second-directive syntax, but it doesn't work either. Why?
have you added second directive inside
<div ng-controller="Ctrl">
are you using in ng-if before second directive? because ng-if also creates its own scope
You can use $rootScope.$broadcastin nested directive controller and then on parent controller use $rootScope.$on.
In second directive
app.directive('second-directive', function(){
return {
restrict: 'E',
templateUrl: '/partials/second-directive.html',
controller: function($rootScope){
$rootScope.$broadcast('parentCtrl',{});
}
}
}
In parent controller
app.controller('Ctrl', ($scope,$rootScope) => {
$scope.foo = function() {
console.log('do smthn');
}
$rootScope.$on('parentCtrl', function (event, data){
//do the parent task here
}
});
You can also use service function to communicate between the child controller and parent controller.
As second nested directive doesn't have isolated scope, try $parent.foo()

isolate controller to only controller specific scopes

So I have a directive and inside the directive view (html) I put a controller however its affecting the rest of the viewModel (vm). What's the best way to isolate a controller to only control specific viewModel?
That's the structure of the view model and directive, I thought ng-controller="ctrl as vm" would only find vm within the class of "controller" but instead its finding every vm on the page.
Directive:
var directive = {
templateUrl: '/Content/app/core/scaffolding/views/popup.html',
restrict: 'A',
link: function (scope, element, attributes) {
console.log('something')
}
};
view:
<div class="directive">
<div class="moreVm">
</div>
<div class="controller" ng-controller="ctrl as vm">
<button ng-click="vm.find()"></button>
</div>
</div>
I tried making "ctrl as jvm" but still the same haha, its just a guess.
<div class="controller" ng-controller="ctrl as jvm">
<button ng-click="jvm.find()"></button>
</div>
Try this.
var directive = {
restrict: "A",
scope: true,
bindToController: {},
controller: "ctrl as vm",
templateUrl: "/Content/app/core/scaffolding/views/popup.html"
};
I've come up with an example using directives which may be of some help - Plunker
As you can see clicking the button in directive2 does not set the value of $scope.aValue in directive1.
JS
var app = angular.module('plunker', [])
.directive("directive1", function accountDir() {
return {
restrict: "EA",
templateUrl: "directive1.html",
scope: {},
controller: function ($scope) {
$scope.$watch("aValue", function(newValue) {
console.log(newValue);
})
}
};
}
)
.directive("directive2", function accountDir() {
return {
restrict: "EA",
templateUrl: "directive2.html",
scope: {},
controller: function ($scope) {
$scope.setAValue = function () {
$scope.aValue = 42;
console.log($scope.aValue);
}
}
};
}
);
Markup
<body>
<directive1></directive1>
</body>
directive1.html
<directive2></directive2>
directive2.html
Directive2
<br>
<button ng-click="setAValue()">Set a value</button>
If I not guess wrong,you want do that when ctrl as different names, the directive console.log different value? or in vm but the value within directive is different with out of the directive?
if you want first ,you just make two controller and then set different value;
controller('ctrl1',function(){ this.name});
controller('ctrl2',function(){ this.name});
else want two
directive('myDir',function(){ return {restrict:'AE',scope:{},controller:function(){this.name='haha'}}})
and now the value is isolate with outer

How to add style or attr to ng-repeat element

All:
One question about ng-repeat:
var app = angular.module("vp", []);
app.controller("main", function($scope) {
$scope.names = ["name1", "name2","name3","name4","name5"];
});
app.directive("filter", function(){
return {
restrict: "AE",
templateUrl: "asset/chart.html",
controller: function($scope){
this.setLayout = function(EL){
var d3EL = d3.select(EL[0]);
//here below could be style attr or any DOM operation
d3EL.selectAll(".sm").style("font-size","30px");
}
},
link: function(scope, EL, attrs, controller){
controller.setLayout(EL);
}
};
});
My html is:
<html ng-app="vp">
<!-- here is the head part that I did not write-->
<body ng-controller="main" class="container">
<filter></filter>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
<script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.4.10/d3.min.js"></script>
</body>
</html>
My template chart.html:
<div id="cnt">
<div ng-repeat="m in names">
<div class="sm">{{m}}</div>
</div>
</div>
When I run the setLayout function, I found those elements have not been generated, I wonder how to handle this if I want to set style to those element inside ng-repeat?
Thanks
I think you're doing it right already, whats missing is just to add another directive for each ng-repeated item that will be using the filter directive controller functions to perform its d3 manipulation. The reason why you're code doesn't work is because the filter directive's link function is triggered before the ng-repeat directive of your assets/chart.html template is evaluated, hence your d3 selection does not catch anything. Furthermore, I the solution below promotes re-usability of the filter directive by isolating its scope and accepting the names scope variable.
DEMO
Javascript
.directive('filter', function() {
return {
restrict: 'EA',
scope: { names: '=' },
templateUrl: 'chart.html',
controller: function() {
this.setLayout = function(element) {
var d3el = d3.select(element[0]);
d3el.select(".sm").style("font-size","30px");
};
}
}
})
.directive('filterItem', function() {
return {
require: '^filter',
link: function(scope, elem, attr, filter) {
filter.setLayout(elem);
}
};
});
HTML
index.html
<filter names="names"></filter>
chart.html
<div id="cnt">
<div ng-repeat="m in names" filter-item>
<div class="sm">{{m}}</div>
</div>
</div>
It's not entirely clear what you want to accomplish here, but you'll want to do your DOM manipulations (or d3 visualizations) in your link function instead of trying to call them in the controller. When that gets difficult to maintain you should pull them out into a separate JavaScript file and inject them.
Your filter directive could look like this:
app.directive("filter", function(){
return {
restrict: "AE",
templateUrl: "chart.html",
link: function(scope, EL, attrs){
d3.select(EL[0])
.selectAll(".sm")
.style("font-size","30px");
}
};
});
Here's a plnkr example

Directive with isolated scope and added properties, not available to inner directives

I'd like to have a directive with an isolated scope, and to set properties to this scope from within the directive. That is to create some environment variables, which would be displayed by other directives inside it, like so:
HTML:
<div environment> <!-- this directive set properties to the scope it creates-->
{{ env.value }} <!-- which would be available -->
<div display1 data="env"></div> <!-- to be displayed by other directives (graphs, -->
<div display2 data="env"></div> <!-- charts...) -->
</div>
JS:
angular.module("test", [])
.directive("environment", function() {
return {
restrict: 'A',
scope: {},
link: function(scope) {
scope.env = {
value: "property set from inside the directive"
};
}
};
})
.directive("display1", function() {
return {
restrict: 'A',
require: '^environment'
scope: {
data: '='
},
link: function(scope, elt, attr, envController) {
scope.$watch('data', function(oldV, newV) {
console.log("display data");
});
}
};
})
.directive("display2", function() {
return {/* ... */};
});
But it doesn't work. Here is a Plunker.
If I remove the isolation, it works ok though. What do I do wrong ? Is it a problem of transclusion ? It seems to work if I use a template in the 'environment' directive, but this is not what I want.
Thanks for your help.
Edit: I see this same problem answered here. The proposed solution would be to use a controller instead of a directive. The reason I wanted to use a directive is the possibility to use 'require' in the inner directives, thing that can't be done with ngController I think.
By introducing external templates, I managed to find a working solution to your problem.
I'm quite certain the way you have it set up has worked at some point but I can't be certain about when. The last time I built a directive not reliant on an external markup file, I don't even know.
In any case, the following should work, if you are willing to introduce separate templates for your directives:
app.directive('environment', function () {
return {
restrict: 'A',
templateUrl: 'env.html',
replace: true,
scope: {},
link: function (scope, el, attrs) {
scope.env = {
value: "property set from inside the directive"
};
}
};
});
app.directive('display1', function () {
return {
restrict: 'A',
scope: {
data: '='
},
templateUrl: 'display1.html',
replace: false,
link: function(scope) {
// console.log(scope.data);
}
};
});
And then for your markup (these wouldn't sit in <script> tags realistically, you would more than likely have an external template but this is simply taken from the fiddle I set up).
<script type="text/ng-template" id="display1.html">
<span>Display1 is: {{data}}</span>
</script>
<script type="text/ng-template" id="env.html">
<div>
<h1>env.value is: {{env.value}}</h1>
<span display1 data="env.value"></span>
</div>
</script>
<div>
<div environment></div>
</div>
Fiddle link: http://jsfiddle.net/ADukg/5421/
Edit: After reading that you do not want to use templates (should've done that first..), here's another solution to get it working. Unfortunately, the only one you can go with (aside from a few others, link coming below) and in my opinion it is not a good looking one...
app.directive('environment', function () {
return {
restrict: 'A',
template: function (element, attrs) {
return element.html();
},
scope: {},
link: function (scope, el, attrs) {
scope.env = {
value: "property set from inside the directive"
};
}
};
});
And the markup:
<div environment> {{env.value}} </div>
Fiddle: http://jsfiddle.net/7K6KK/1/
Say what you will about it, but it does do the trick.
Here's a thread off of the Angular Github Repo, outlining your issue and why it is not 'supported'.
I did a small edit to your Plunker
When you create a variable on scope of directive other directives can access it two ways (presented in plunker) either directly or by two-way data binding
HTML:
<body ng-app="test">
<div environment>
{{ env.value }}
<div display1 data="env"></div>
<div display2 data="env"></div>
</div>
</body>
<input type="text" ng-model="env.value"> #added to show two-way data binding work
<div display1 info="env"></div> #changed name of attribute where variable is passed, it's then displayed inside directive template
<div display2>{{env.value}}</div> #env.value comes from environment directive not from display2
</div>
JS
angular.module("test", [])
.directive("environment", function() {
return {
restrict: 'A',
scope: true, #changed from {} to true, each environment directive will have isolated scope
link: function(scope) {
scope.env = {
value: "property set from inside the directive"
};
}
};
})
.directive("display1", function() {
return {
restrict: 'A',
template: '<span ng-bind="info.value"></span>', #added template for directive which uses passed variable, NOTE: dot in ng-bind, if you try a two-way databinding and you don't have a dot you are doing something wrong (Misko Hevry words)
scope: {
info: '=' #set two-way data binding for variable from environment directive passed in 'info' attribute
}, #removed unnecessary watch for variable
};
})
.directive("display2", function() {
return {/* ... */};
});

Set attribute value of angular directive from another controller

Angular directive;
.directive('ngFilemanager', function () {
return {
restrict: 'EA',
scope: {
thefilter: '=',
},
link: function (scope, element, attrs) {
},
templateUrl: '/templates/filemanager.html',
controller: FileManagerController
}
Html:
<div id="testcontainer" ng-controller="OtherController">
...
<div ng-click="vm.myfunction">Set Filter</div>
...
<div id="thefilemanager" ng-filemanager thefilter=""></div>
...
</div>
How can i set thefilter value in a function of OtherController?
I tried setting the attribute value by jquery but my ng-view isn't updated correctly then.
You've got bi-directional isolated scope so:
function OtherController($scope){
$scope.myfilter= "";
$scope.setFilter = function(what){
$scope.myfilter = what;
}
}
and HTML:
<div id="testcontainer" ng-controller="OtherController">
<div ng-click="setFilter('fun')">Set Filter</div>
<div id="thefilemanager" ng-filemanager thefilter="myfilter"></div>
</div>
Then when you change $scope.myfilter in the OtherController's scope, scope.thefilter changes in your directive's scope.
If the "other" controller is not a direct parent, you could use $emit or $broadcast depending on where the target is.
Here's an example using $broadcast instead:
app.controller('MainCtrl', function($scope) {
$scope.setFilter = function(what){
$scope.$broadcast('setFilter', what);
}
});
then inside your directive you can listen:
link: function (scope, element, attrs) {
scope.$on('setFilter', function(e, what){
scope.thefilter = what;
});
},
To make it work anywhere, you can $broadcast from $rootScope, but at that point you might want to re-evaluate why you have to do this. Angular itself does this a lot, for example, routeChangeSuccess event, but that doesn't mean you should do it.
This will work if the other controller is a parent of ngFilemanager
<div id="thefilemanager" ng-filemanager thefilter="theFilterValue"></div>
In some other controller
...
$scope.theFilterValue = 'some other value';
...
Look the doc's isolate scope on directives section

Resources