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
Related
angular.module('hfp.calendar')
.controller('printPreviewCntrl', ['$scope', 'htmlFromServer',
function($scope, htmlFromServer){
$scope.htmlReceived = htmlFromServer;
$scope.event = {};
}])
.directive('compile', function($compile, $timeout) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
$timeout(function() {
element.html(scope.$eval(attrs.compile));
$compile(element.contents())(scope);
});
}
};
})
.directive('initModel', function($compile) {
return {
restrict: 'A',
scope: {
eventField: '=initModel'
},
link: function(scope, element, attrs) {
scope.eventField = element[0].innerText;
element.attr('ng-bind', '$parent.' + attrs.initModel); // why do i have to use $parent here to make it work ?
element.removeAttr('init-model');
$compile(element)(scope);
}
};
});
<!-- clientside html -->
<input type="text" ng-model="event.time">
<div compile="htmlReceived">
</div>
<!-- html coming from server -->
<div init-model="event.time">
10:30 AM
</div>
I want to bind to the parent scope var event.time from initModel directive but it only works when i use $parent to refer to the var in parent scope. Can i achieve this binding without using $parent ?
There is no need to implement that directive with isolate scope. Simply use the $parse service:
angular.module("app",[])
.directive('initModel', function($parse) {
return {
restrict: 'A',
//scope: {
// eventField: '=initModel'
//},
link: function(scope, elem, attrs) {
var setter = $parse(attrs.initModel).assign;
setter(scope, elem.text().trim());
scope.$watch(attrs.initModel, function(value) {
elem.text(value);
});
}
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="app" ng-init="event={}">
<input type="text" ng-model="event.time">
<div init-model="event.time">
10:30 AM
</div>
</body>
For more information, see AngularJS $parse Service API Reference.
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
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');
}
}
};
});
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 {/* ... */};
});
I have a scenario where a directive's link function is called with an empty element parameter leading to errors.
A code example describes the scenario best:
index.html:
<html ng-app="app">
<body>
<div outer></div>
</body>
</html>
Scripts:
var app = angular.module('app', []);
app.directive('outer', function() {
return {
replace: true,
controller: function($scope) {
$scope.show = true;
},
templateUrl: 'partial.html'
};
});
app.directive('inner', function($window) {
return {
link: function(scope, element, attrs) {
console.log('element: ' + element);
var top = element[0].offsetTop;
}
};
});
partial.html (referenced by templateUrl on outer, above):
<div ng-switch on="show">
<div ng-switch-when="true">
<div inner>showing it</div>
</div>
</div>
Loading index.html in Chrome, the console reports the error: "TypeError: Cannot read property 'offsetTop' of undefined" - because element is an empty array!
Some notes:
replace must be set to true on directive outer.
templateUrl must be used by directive outer to load its partial.
I'm uncertain whether I've overlooked some configuration requirement or if this is an Angular issue. Is this a valid scenario? If it is then is it a problem with ng-switch or is there a deeper problem within Angular?
Try
app.directive('inner', function($window) {
return {
link: function(scope, element, attrs) {
console.log('element: ', element);
if(!element.length){
console.log('Returning since there is no element');
return;
}
var top = element[0].offsetTop;
console.log('top', top)
}
};
});