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);
Related
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
My directive has separate controller in js file, which has a scope variable parameterDatabase which need to be populated from calling page. I am unable to find the way to pass value to it.
<body ng-app="testAPP" ng-controller="ctl">
Directive Here
<my-cust parameterDATABASE="dt"></my-cust>
<script >
APP = angular.module("testAPP",['schedule']);
APP.controller("ctl",function($scope)
{
$scope.dt = {date:"02-03-2017",sDay:"Thu",sTime:"01:00"};
}) // end of controller
APP.directive("myCust",function()
{
return{
scope:{
parameterDATABASE:'='
},
controller:"scheduleCtrl",
templateUrl:"templateForDirective.html"
}
})
</script>
The scheduleCtrl has a variable parameterDATABASE too.
part of Directive's contrller
var APP = angular.module('schedule',[]);
APP.controller('scheduleCtrl',function($scope,$filter)
{ $scope.parameterDATABASE=[]; // This is the variable I want to populate
..............
1) According to some angular naming conventions, the attribute name of a directive should be converted into camelCase.
So, parameterDATABASE in the html Directive should be parameter-database
So, inside the directive, you should use that as,
scope: {
parameterDatabase: '='
}
So, parameterDatabase maps to ==> parameter-database
2) you can also use, parameterdatabase directly in both places without capitalizing.
Eg: parameter-database="dt" in html directive
scope: {
parameterdatabase: '='
}
<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body ng-app="myApp" ng-controller="ctl">
<isolate-scope-with-controller parameter-database="dt" add="addCustomer()"></isolate-scope-with-controller>
<script>
var app = angular.module("myApp", []);
app.controller("ctl",function($scope)
{
$scope.dt = {date:"02-03-2017",sDay:"Thu",sTime:"01:00"};
}) // end of
app.directive('isolateScopeWithController', function () {
var controller = ['$scope', function ($scope) {
console.log($scope.parameterDatabase)
}],
template = '<h1>I am from directive controller</h1><br><h2>{{parameterDatabase}}</h2>';
return {
restrict: 'EA', //Default in 1.3+
scope: {
parameterDatabase: '='
},
controller: controller,
template: template
};
});
</script>
</body>
</html>
PLEASE RUN THE ABOVE SNIPPET
Here is a working DEMO
I have an question about AngularJs scopes and especially the way those can be inspected with the Batarang Chrome extension.
I have the following html:
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
<div ng-controller="myCtrl">
<div enhanced-textarea ng-model="name"></div>
<div cmp>
<h3>{{name}}</h3>
<div notice></div>
</div>
</div>
<script src="lib/angular/angular.js"></script>
<script src="js/directives.js"></script>
<script src="js/controllers.js"></script>
<script src="js/app.js"></script>
</body>
</html>
Here are the directives:
'use strict';
angular.module('myApp.directives', [])
.directive('cmp', function () {
return {
restrict: 'A',
controller: 'cmpCtrl',
replace: true,
transclude: true,
scope: {
name: '='
},
template: '<div ng-transclude></div>'
};
})
.controller('cmpCtrl', ['$scope', '$element', '$attrs' , function ($scope, $element, $attrs) {
$scope.$parent.$watch('name', function (newVal) {
if (newVal) {
$scope.$parent.updatedSize = newVal.length;
}
}, true);
}])
.directive('enhancedTextarea', function () {
return {
restrict: 'A',
replace: true,
transclude: true,
template: '<textarea ng-transclude></textarea>'
};
})
.directive('notice', function () {
return {
restrict: 'A',
require: '^cmp',
replace: true,
scope: {
updatedSize: '='
},
template: '<div>{{size}}</div>',
link: function ($scope, $element, $attrs, cmpCtrl) {
$scope.$parent.$watch('updatedSize', function (newVal) {
if (newVal) {
$scope.size = newVal;
}
}, true);
}
};
});
and the controller:
'use strict';
angular.module('myApp.controllers', [])
.controller('myCtrl', ['$scope', function($scope) {
$scope.name = 'test';
}]);
When I inspect the scopes using batarang, I come up with the following conclusion:
Scope 002: ng-app
Scope 003: ng-controller (myCtrl)
Scope 004: ????
Scope 005: cmpCtrl (controller for cmp directive)
Scope 006: inside cmp (h3 and notice)
Scope 007: link function of notice directive
Is the above correct?
Also, my biggest interrogation is what does the 004 scope correspond to?
Full app is located on github here
See also screen capture below:
It's not that each $scope has to correspond to an element of your page. In fact in every AngularJS app there are a bunch of $scopes which aren't directly linked to any element.
In your case it's the ng-transclude which causes a child scope to be created.
Take a look at the implementation of AngularJS which causes the creation of your 004 $scope.
if (!transcludedScope) {
transcludedScope = scope.$new();
transcludedScope.$$transcluded = true;
scopeCreated = true;
}
https://github.com/angular/angular.js/blob/master/src/ng/compile.js#L959
If you like to dig deeper yourself, go and set a breakpoint right here in your AngularJS file:
Then just use the call stack and follow the rabbit...
I also use this scenario to debug and inspect what is in the scope of an element, might be helpful:
You inspect the element with chrome dev tools
Once you select certain element you can get its scope by typing in console:
angular.element($0).scope()
You can get the controller in the same way just instead of scope() you can type controller()
In order to set a breakpoint in you code and look at in in chrome debugger (I sometimes find this easier than setting a breakpoint in dev tools) you can type:
debugger;
in your source and dev tool will stop there so you see the declared vars etc.
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)
}
};
});
I have two class-restricted directives, named fooThing and barThing.
I have a variable, baz, in my template scope that is set to either foo or bar.
How can I use that scope variable in the name of the directive for an element?
When I do <div class="{{baz}}-thing"></div>, {{baz}} is replaced properly, but the directive is not loaded.
When I do <div class="foo-thing"></div>, the directive is loaded properly.
I have a hunch that this has something to do with Angular's digest/compile cycle, but I'm afraid I don't know how to work around it.
How can I get Angular to compile that part of the template first so that my expression is evaluated, and then have it compile again so it recognizes it as a directive?
Making a directive that creates directives (a kind of directive factory), as suggested by Anders, is what I'm after. Guillaume86 provided a good method for doing that.
For that to work your template would have to first be compiled and executed on a scope (to replace {{baz}}), and then compiled and executed again to get the "foo-thing" directive going. It's possible, but it would probably cause other issues.
What you could do is create sort of a directive factory, a directive that creates another directive. Here's an example:
<!doctype html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.1.2/angular.min.js"></script>
<script type="text/javascript">
var myApp = angular.module("myApp", []);
myApp.directive('factory', function($compile) {
return {
restrict: 'A',
scope: {
type: '=factory'
},
replace: true,
link: function($scope, elem, attrs) {
var compiled = $compile('<'+ $scope.type +'></'+ $scope.type +'>')($scope);
elem.append(compiled);
}
};
});
myApp.directive('concrete', function() {
return {
restrict: 'E',
template: "<div>I'm concrete!</div>",
link: function($scope, elem, attrs) {
}
};
});
function MyCtrl($scope, $timeout) {
$scope.directiveType = "concrete";
}
</script>
</head>
<body>
<div ng-controller="MyCtrl">
<div factory="directiveType"></div>
</div>
</body>
</html>