I got two directives, one directive is a sub-directive to the other one.
In the sub-directive there will be a ng-repeat. So there will be x amount of sub-directives depending on the list it will repeat. My question is: Is it possible for the base directive to know how many amount of sub-directives there will be? The base directive will then know when to concat all the values from the sub-directive and pass it on.
In this example the $scope.length of the list is know. But because of the ng-if 3 sub-directives is created and not the length of $scope.length which is 5.
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body ng-app="myApp">
<test-directive>
<test-sub-directive ng-repeat="l in list" ng-if="l > 2" variable="{{l}}">{{l}}</test-sub-directive>
</test-directive>
<script>
var app = angular.module("myApp", []);
app.directive('testDirective', function () {
return {
restrict: 'E',
controllerAs: 'testCtrl',
controller: ['$scope', '$element', '$attrs', function testCtrl($scope, $element, $attrs) {
$scope.list = [1,2,3,4,5];
this.testFunc = function () {
//When everything is fetched from the sub-directive send it to another function
}
}]
};
})
app.directive('testSubDirective', function () {
return {
require: '^test-directive',
restrict: 'E',
link: function (scope, element, attrs, testCtrl) {
testCtrl.testFunc();
},
};
})
</script>
</body>
</html>
https://www.w3schools.com/code/tryit.asp?filename=FIUXSWL47ZC9
You can substitute ng-if with a filter. Now you can use the result to determine length on your parent directive. This is a working edit of your sample
Related
I'm consuming dynamically generated HTML from an API which may contain hyperlinks, and I'm wanting to replace the hrefs within them with ngClicks. The following directive appears to modify the HTML as intended when I check it in a DOM inspector, but clicking it does nothing. What am I doing wrong?
app.directive('replaceLinks', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(function(scope) {
return scope.$eval(attrs.replaceLinks);
}, function(value) {
element.html(value);
angular.forEach(element.contents().find("a"), function(link) {
link.removeAttribute("href");
link.removeAttribute("target");
link.setAttribute("ng-click", "alert('test')");
});
$compile(element.contents())(scope);
});
}
};
}]);
Instead of removing the href please set it to blank (this will preserve the link css), also the ng-click calling the alert can be done by calling the alert('test') inside of a scope method, why the alert didn't fire, is explained in this SO Answer, please refer the below sample code!
// <body ng-app='myApp' binds to the app being created below.
var app = angular.module('myApp', []);
app.directive('replaceLinks', ['$compile', function($compile) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
angular.forEach(element.find("a"), function(link) {
link.setAttribute("href", "");
link.removeAttribute("target");
link.setAttribute("ng-click", "scopeAlert('test')");
});
$compile(element.contents())(scope);
}
};
}]);
// Register MyController object to this app
app.controller('MyController', ['$scope', MyController]);
// We define a simple controller constructor.
function MyController($scope) {
// On controller load, assign 'World' to the "name" model
// in <input type="text" ng-model="name"></input>
$scope.name = 'World';
$scope.scopeAlert = function(name) {
window.alert(name);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-controller='MyController' ng-app="myApp">
<div replace-links>
test 1
test 2
test 3
</div>
</div>
I got a directive inside a directive that looks like this:
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<body ng-app="myApp">
<test-directive></test-directive>
<script>
var app = angular.module("myApp", []);
app.directive("testDirective", ["$compile", function($compile) {
return {
restrict: 'AE',
template: '<div>just a test</div>',
link: function (scope, element, attrs) {
let autocomplete = $compile('<test-chart></test-chart>');
let content = autocomplete(scope);
element.append(content);
}
};
}]);
app.directive('testChart', function () {
return {
restrict: 'E',
transclude: true,
controllerAs: 'chartCtrl',
template: '<div><div id="container"></div><ng-transclude></ng-transclude></div>',
controller: ['$scope', '$element', '$attrs', function ChartController($scope, $element, $attrs) {
var hc = Highcharts.chart('container', {
});
}]
};
})
</script>
</body>
</html>
https://www.w3schools.com/code/tryit.asp?filename=FHYNMDW67ST5
The problem I'm having is when the inner directive is trying to initialize the highchart using:
var hc = Highcharts.chart('container', { });
This issues a highchart error #13 which is when highchart can't find an element to create a chart on. In this example: <div id="container">
Looking at the document inside the controller for the inner directive it seems to be missing the template of the directive. Thats why highchart is getting #13.
Why is not the inner directive template getting applied?
Take a closer look at your testDirective's linking function:
let autocomplete = $compile('<test-chart></test-chart>');
// Your test chart is detached here and thus its controller can't find container in the DOM
let content = autocomplete(scope);
// This line of code is never executed because the previous one throws that's why you never see your test chart being appended to the DOM
element.append(content);
To fix that you first need to append your autocomplete to the DOM. And only then perform compilation and linking. Basically you can go like that:
let content = angular.element('<test-chart></test-chart>');
element.append(content);
$compile(element.contents())(scope);
my parent directive is fed with a file containing informations about site structure and builds a template string dynamically. The content of this string refers to other directives. These might fetch as well data from a source to build (eg) tables.
As they are all using $http.get to fetch data I wanted to inform the parent directive when these sub directives are ready using a required controller.
Problem: The parent directive is using $compile to build the site and does not "forward" the controller which results in "Error: [$compile:ctreq] http://errors.angularjs.org/1.4.1/$compile/ctreq ..."
Found already this answer: "Angular $compile with required controller" which is not a big help, especially as transcludedControllers seems to be deprecated and doesn't work in my code.
Any help or just pointing to another already asked /answered question is highly appreciated.
angular.module('TestDirectives', [])
.directive('widgetBlock', function ($compile) {
return {
restrict: 'A',
replace: true,
controller: function ($scope) {
this.reportReady = function () {
$scope.widgetready = String(Number($scope.widgetready) + 1);
}
},
link: function (scope, elem, attr) {
// template data will be constructed dynamically based on a
// xml - file fetched with $http.get
var template = '<div><p >This isn\'t always fun</p>' +
'<div mini-widget></div>' +
'<div micro-widget></div></div>';
var content = $compile(template)(scope)
elem.append(content);
attr.$observe('widgetready', function (newValue) {
// quantity of widgets depends on widgets found in xml - file
if (newValue === "2") {
// all widgets on screen, translate some keys
console.info("Application is ready to translate!!");
}
});
}
};
})
.directive('miniWidget', function ($compile, $http) {
return {
restrict: 'A',
require: '^widgetBlock',
scope: {},
link: function (scope, elem, attr, ctrl) {
$http.get('json/daten.json').then(function (data) {
scope.person = data.data;
var test = '<p>hello {{person.name}}</p>';
var content = $compile(test)(scope)
elem.append(content);
ctrl.reportReady();
});
}
}
})
.directive('microWidget', function ($compile, $http) {
return {
restrict: 'A',
require: '^widgetBlock',
scope: {},
link: function (scope, elem, attr, ctrl) {
$http.get('json/daten2.json').then(function (data) {
scope.person = data.data;
var test = '<p>Whatever {{person.name}}</p>';
var content = $compile(test)(scope)
elem.append(content);
ctrl.reportReady();
});
}
}
});
Just to be complete, main app
angular.module('DirectiveTestApp', ['TestDirectives'])
.controller('MainController', function ($scope) {
$scope.widgetready = "0";
});
And the html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Directives Test</title>
</head>
<body ng-app="DirectiveTestApp">
<div ng-controller="MainController">
<div widget-block widgetready="{{widgetready}}">
</div>
</div>
<script src="bower_components/angular/angular.min.js"></script>
<script src="script/app.js"></script>
<script src="script/directives.js"></script>
</body>
</html>
Thanks a lot!
When you $compile and then link - which is what you are doing with $compile(template)(scope) - during the link-phase, the microWidget directive looks for the required controller up the DOM tree. The problem is that your template is not in the DOM at that time.
There are 2 ways to address this:
#1: using the cloneAttachFn:
The second parameter of the link function, which is the result of $compile(template), allows you to specify a cloneAttachFn - see documentation. This function is invoked prior to link-phase and it gets a cloned version of the to-be-linked node. Use that function to place the element where needed:
var content = $compile(template)(scope, function cloneAttachFn(clonedContent){
// this happens prior to link-phase of the content
elem.append(clonedContent);
});
// this happens after link-phase of the content, so it doesn't work
// elem.append(content)
(btw, content[0] === clonedContent[0])
#2: append content prior to $compile/link (as suggested in an answer to this question):
var content = angular.element(template);
elem.append(content);
$compile(content)(scope);
Demo
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
I have a custom directive and I would like to use it to include an html content to the document after clicking on it.
Plunker: http://plnkr.co/edit/u2KUKU3WgVf637PGA9A1?p=preview
JS:
angular.module("app", [])
.controller("MyController", function ($scope) {
})
.directive('addFooter', ['$compile', '$rootScope', function($compile, $rootScope){
return {
restrict: 'E',
template: '<button>add footer</button>',
controller: 'MyController',
link: function( scope, element, attrs, controller) {
element.bind( "click", function() {
scope.footer = "'footer.html'";
})}
};
}])
HTML:
<body ng-app="app">
<script type="text/ng-template" id="footer.html">
FOOTER
</script>
<div ng-controller="MyController">
<add-footer></add-footer>
<div ng-include="footer"></div>
</div>
</body>
Not sure why it is not working, as it worked fine before it was moved into the directive. Outside the directive, I was also referencing to $scope.footer with some link. I tried using $rootScope, but also no effect. Any tips please?
First. Remove unnecessary quote symbols:
element.bind( "click", function() {
scope.footer = "footer.html"; // not "'footer.html'"
});
Second. You should notify angularjs that you have asynchronously updated scope values:
element.bind("click", function() {
scope.$apply(function() {
scope.footer = "footer.html";
});
});
Or like that
element.bind("click", function() {
scope.footer = "footer.html";
scope.$apply();
});