$scope gets null after calling template in directive - angularjs

i have a nested directive like this
app.directive('grid', ['$log', '$http', function ($log , $http) {
return {
restrict: 'E',
scope: {},
//template: '<span></span>', //i uncomment this line
controller: function ($scope, $element, $attrs){
var swColumns = [];
this.setColumns = function (columns) {
swColumns = columns;
$log.log('grid controller');
$log.log(columns);
$scope.swColDefs = columns;
$log.log($scope.swColDefs);
};
this.getColumns = function () {
return swColumns;
};
},
link: function (scope, element, attrs, gridController) {
$log.log('grid link');
$log.log(gridController.getColumns());
$log.log(scope.swColDefs);
}
};
}]);
app.directive('gridColumns', ['$log', function ($log) {
return {
restrict: 'E',
require: ['^grid', 'gridColumns'],
controller: function () {
var columns = [];
this.addColumns = function (column) {
columns.push(column);
};
this.getColumns = function () {
return columns;
};
},
link: function (scope, element, attrs, controllers) {
var gridController = controllers[0];
var gridColumnsController = controllers[1];
gridController.setColumns(gridColumnsController.getColumns());
}
};
}]);
every thing is ok until i uncomment the template in grid directive
after that the swColDefs in grid link function become an empty array
what is wrong with my code ?
<grid>
<grid-columns>
</grid-columns>
...
</grid>
and i am using it like this

Although the question doesn't specify, I'm assuming you're using your directive like so:
<grid>
<grid-columns>
</grid-columns>
...
</grid>
But since you're specifying a template of <grid>, Angular removes the contents and sets the template instead. And so, gridColumns directive never compiles and the link function never runs.
This is where transclusion is needed on grid. Transclusion takes the content out of DOM, compiles it (at compile-phase) and then allows you to link it against whatever scope you need and place it in the contents.
It's not clear from your question what scope gridColumns directive needs be linked against, and whether it has any visual components.
For a simple transclusion case, this can be done with <ng-transclude> in the template of grid:
transclude: true,
template: "<span>whatever</span><div ng-transclude></div>" // template of grid
Or, for more precise control over scope and placement you can use the transclude function (passed as a fifth parameter to the link function):
transclude: true,
template: "<span>whatever</span>",
link: function(scope, element, attrs, gridController, transclude){
transclude(function(transcludedContent){
// place the content where needed, if at all
});
}

I have solved the problem by doing this in grid template
first i add a div with ng-transclude to the template
secod transclude : true in grid directive
app.directive('grid', ['$log', '$http', function ($log , $http) {
return {
restrict: 'E',
scope: {},
transclude:true,//my change
template: '<span>
<div ng-transclude>//my change
</div>
</span>',
controller: function ($scope, $element, $attrs){
var swColumns = [];
this.setColumns = function (columns) {
swColumns = columns;
$log.log('grid controller');
$log.log(columns);
$scope.swColDefs = columns;
$log.log($scope.swColDefs);
};
this.getColumns = function () {
return swColumns;
};
},
link: function (scope, element, attrs, gridController) {
$log.log('grid link');
$log.log(gridController.getColumns());
$log.log(scope.swColDefs);
}
};
}]);

Related

Is there any ways to update directive link with scope or some other values?

I have dC directive
app.directive('dC', function($rootScope) {
return {
restrict: 'AE',
link: function(scope, element, attrs) {},
templateUrl: function(elem, attrs) {
return $rootScope.somePosiT
}
}
});
And I want to access inside of my controller as function is there anyway can I achieve it in order to change my directive location dynamically when click fired
app.controller('appCtrl', function($scope, $rootScope) {
clk: function() {
$rootScope.somePosiT = 'views/1.html'
}
})
I think you could do it using template and ng-include - rather than templateUrl. I think templateUrl is called before the scope values are available. You could try something like this:
app.directive('dC', function($rootScope) {
return {
restrict: 'AE',
link: function(scope, element, attrs) {},
template: '<div ng-include="somePosiT"></div>'
};
});
app.controller('appCtrl', function($scope, $rootScope) {
$scope.clk = {
somePosiT: "path/to/template.html"
};
});

Angularjs controller required by directive can not be found in transclude content

Recently I use angular to develop a directive, there is an directive which like ng-repeat to generate some records, I used transclude to implement it. but it raise an error that "Controller 'aArea', required by directive 'bSpan', can't be found!".
1. ModuleA code
var moduleA = angular.module("moduleA", []);
moduleA.directive("aArea", function () {
return {
restrict: 'E',
transclude:'element',
scope: {
amount:"="
},
template: '<div id=\"cc\" ng-transclude></div>',
controller: function ($scope,$element,$attrs) {
this.getData = function (data) {
return data + " is ok";
}
},
compile: function (tElement, attrs, linker) {
var parentElement = tElement.parent();
return {
pre: function () {
},
post: function (scope) {
linker(scope.$parent,function (clone,scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
linker(scope.$parent, function (clone, scope) {
parentElement.append(clone);
});
}
}
}
}
});
moduleA.directive("bSpan", function () {
return {
restrict: 'E',
scope: {
data: "=",
},
template: '<span style=\"background-color:gray;color:orange\">{{data}}</span>',
require: "^aArea",
link: function ($scope, $element, $attrs, controller) {
var data = "abc";
}
}
});
2. ModuleB COde
var moduleB = angular.module("moduleB", []);
moduleB.directive("myItem", function () {
return {
restrict: 'E',
scope: {
item: "=",
itemTemplate: '='
},
priority: 1000,
terminal:false,
template: '<ng-include src=\"itemTemplate\"/>',
controller: function ($scope, $element, $attrs) {
var data = "";
}
}
})
3. ModuleC Code
var moduleC = angular.module("moduleC", ["moduleA", "moduleB"]);
moduleC.controller("Ctr", function ($scope) {
$scope.item = {};
$scope.item.dataAmount = 1000;
$scope.item.templateUrl = "item-template.html";
})
4. Html Code
<body>
<div ng-app="moduleC">
<div ng-controller="Ctr">
<a-area>
<my-item item="item" item-template="item.templateUrl"></my-item>
</a-area>
</div>
</div>
</body>
5. template code
<div>
<span style="display:block">hello every one</span>
<b-span data="item.dataAmount"></b-span>
</div>
You should not use the transclude function (that you called linker) of the compile function - it is deprecated.
From $compile documentation:
Note: The transclude function that is passed to the compile function is deprecated, as it e.g. does not know about the right outer scope. Please use the transclude function that is passed to the link function instead.
Following this guidance (and a few other minor changes for the better), change the aArea directive as follows:
compile: function(tElement, tAttrs) {
// don't use the template element
//var parentElement = tElement.parent();
return function(scope, element, attrs, ctrls, transclude) {
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
transclude(function(clone, scope) {
element.after(clone);
});
};
}
In fact, you don't even need the transclude function at all and you don't need to do transclude: "element". You could just change to transclude: true and use <div ng-transclude> 3 times in the template.

Custom directive with dynamic template and binding parent scope to ng-model

I have a view that contains a template specified in a custom directive. The template that is used in the custom directive depends on 'dynamicTemplate':
View:
<div ng-controller="MyController">
<custom-dir dynamicTemplate="dynamicTemplateType"></custom-dir>
<button ng-click="ok()">Ok</button>
</div>
View's Controller:
myModule
.controller('MyController', ['$scope', function ($scope) {
$scope.dynamicTemplateType= 'input';
$scope.myValue = "";
$scope.ok = function()
{
// Problem! Here I check for 'myValue' but it is never updated!
}
Custom Directive:
myModule.directive("customDir", function ($compile) {
var inputTemplate = '<input ng-model="$parent.myValue"></input>';
var getTemplate = function (templateType) {
// Some logic to return one of several possible templates
if( templateType == 'input' )
{
return inputTemplate;
}
}
var linker = function (scope, element, attrs) {
scope.$watch('dynamicTemplate', function (val) {
element.html(getTemplate(scope.dynamicTemplate)).show();
});
$compile(element.contents())(scope);
}
return {
restrict: 'AEC',
link: linker,
scope: {
dynamicTemplate: '='
}
}
});
In this above example, I want 'myValue' that is in MyController to be bound to the template that is in my custom directive, but this does not happen.
I noticed that if I removed the dynamic templating (i.e. the contents in my directive's linker) and returned a hardcoded template instead, then the binding worked fine:
// If directive is changed to the following then binding works as desired
// but it is no longer a dynamic template:
return {
restrict: 'AEC',
link: linker,
scope: {
dynamicTemplate: '='
},
template: '<input ng-model="$parent.myValue"></input>'
}
I don't understand why this doesn't work for the dynamic template?
I am using AngularJS 1.3.0.
Maybe you should pass that value into your directives scope, instead of only dynamicTemplate, i think it should work.
You have a good answer about directives scope here: How to access parent scope from within a custom directive *with own scope* in AngularJS?
Hope I was of any help.
js directive :
angular.module('directive')
.directive('myDirective', function ($compile) {
var tpl1 ='<div class="template1">{{data.title}}</div>';
var tpl2 ='<div class="template2">Hi</div>';
var getTemplate = function (data) {
if (data.title == 'hello') {
template = tpl1;
}
else {
template = tpl2;
}
return template;
};
var linker = function (scope, element, attrs, ngModelCtrl) {
ngModelCtrl.$render = function () {
// wait for data from the ng-model, particulary if you are loading the data from an http request
if (scope.data != null) {
element.html(getTemplate(scope.data));
$compile(element.contents())(scope);
}
};
};
return {
restrict: "E",
require: 'ngModel',
link: linker,
scope: {
data: '=ngModel'
}
};
});
html :
<my-directive
ng-model="myData">
</my-directive>
js controller:
$scope.myData = {title:'hello'};

$compile nested directive from within another directive

TL;DR; jsFiddle here.
I want to use two directives (kmOuter and kmInner) as nested directives:
<div km-outer>
<div km-inner></div>
<div km-inner></div>
<!-- ... -->
</div>
So I declared them as follows. Please note that inner directive requires outer's controller:
app.directive('kmOuter', function () {
return {
restrict: 'AC',
scope: null,
controller: function ($scope) {
this.childAdded = function () {
console.log('Child added.');
};
}
};
});
app.directive('kmInner', function () {
return {
restrict: 'AC',
require: '^kmOuter',
template: '<div>Lorem ipsum</div>',
link: function (scope, elem, attrs, kmOuterController) {
kmOuterController.childAdded();
}
};
});
That works just fine (.childAdded() is being called, among others). Now, I want to dynamically insert new instances of inner directive. This action is being triggered from some third, unrelated directive:
app.directive('kmChildAdder', function ($compile) {
return {
restrict: 'AC',
scope: {
target: '#kmChildAdder'
},
link: function (scope, elem) {
console.log(scope);
var target = document.querySelector(scope.target);
angular.element(elem[0]).bind('click', function () {
// Error is here
var newInner = $compile('<div km-inner></div>')(scope)[0];
target.appendChild(newInner);
});
}
};
});
Used like this:
<button km-child-adder="[km-outer]">Add child</button>
But it breaks with the following message:
Error: [$compile:ctreq] Controller 'kmOuter', required by directive 'kmInner',
can't be found!
.childAdded() isn't called anymore.
How can I fix this? Or maybe this design is itself broken and I should reorganise my code?
I think I made it, borrowing from #Mobin Skariya's answer.
Key was to $compile only the inserted element, not all elements:
link: function (scope, elem) {
var target = angular.element(document.querySelector(scope.target));
angular.element(elem[0]).bind('click', function () {
var newInner = angular.element('<div km-inner="param"/>');
target.append(newInner);
scope.$apply(function () {
$compile(newInner)(scope);
});
});
}
I've prepared jsFiddle with example where third, unrelated directive inserts ad compiles inner directive with working, two-way data bindings - you will find it here. Nice thing about it is that third directive (kmChildAdder) can insert inner directives taking bindings from multiple, separate controllers.
Made some edits in your code. Code given in jsFiddle link
link: function (scope, elem) {
console.log(scope);
var target = document.querySelector(scope.target);
angular.element(elem[0]).bind('click', function () {
var newInner = '<div km-inner></div>';
angular.element(target).append(newInner);
$compile(target)(scope)
});
}
Check whether this is what you expect.

How can I get my directive to access the controllers scope

I have a setup like this:
<controller>
<directive>
in my controller that has a function that returns an html string. How can I get my directive to render this by accessing the controllers scope?
Or maybe I should just put the controller in the directive?
app.controller('controller', ['$scope', 'DataService', function ($scope, DataService) {
$scope.parseJson = function () {
//returns the html
};
}]);
directive
app.directive('Output', function () {
return {
restrict: 'A',
replace: true,
template: '<need html from controller>',
link: function(scope, element, attr) {
//render
//scope.parseJson();
}
};
});
You should use the isolated scope: '&' option
app.directive('output', ['$sce', function ($sce) {
return {
restrict: 'A',
replace: true,
template: "<div ng-bind-html='parsed'></div>",
scope:{
output: "&"
},
link: function(scope){
scope.parsed = $sce.trustAsHtml(scope.output());
}
};
}]);
Template:
<div output="parseJson()"></div>
The directive and the controller should be sharing the scope already. Don't bother using a template for the directive, just get the HTML string in you linking function (you already have the method call in there) and modify the element directly using element.html(). Take a look at the element docs for more info.
app.directive('Output', function ($compile) {
return {
restrict: 'A',
link: function(scope, element, attr) {
var templateString = scope.parseJson();
var compiledTemplate = $compile(templateString)(scope);
compiledTemplate.appendTo("TheElementYouWishtoAppendYourDirectiveTo");
}
};
});

Resources