How to replace the element with ng-transclude - angularjs

Is it possible to replace the element with ng-transclude on it rather than the entire template element?
HTML:
<div my-transcluded-directive>
<div>{{someData}}</div>
</div>
Directive:
return {
restrict:'A',
templateUrl:'templates/my-transcluded-directive.html',
transclude:true,
link:function(scope,element,attrs)
{
}
};
my-transcluded-directive.html:
<div>
<div ng-transclude></div>
<div>I will not be touched.</div>
</div>
What I am looking for is a way to have <div>{{someData}}</div> replace <div ng-transclude></div>. What currently happens is the transcluded HTML is placed inside the ng-transclude div element.
Is that possible?

I think the best solution would probably be to create your own transclude-replace directive that would handle this. But for a quick and dirty solution to your example you could essentially manually place the result of the transclusion where you want:
my-transcluded-directive.html:
<div>
<span>I WILL BE REPLACED</span>
<div>I will not be touched.</div>
</div>
Directive:
return {
restrict:'A',
templateUrl:'templates/my-transcluded-directive.html',
transclude:true,
link:function(scope,element,attrs,ctrl, transclude)
{
element.find('span').replaceWith(transclude());
}
};

It's easy to create a ng-transclude-replace directive, here is a copycat of the original ng-transclude.
directive('ngTranscludeReplace', ['$log', function ($log) {
return {
terminal: true,
restrict: 'EA',
link: function ($scope, $element, $attr, ctrl, transclude) {
if (!transclude) {
$log.error('orphan',
'Illegal use of ngTranscludeReplace directive in the template! ' +
'No parent directive that requires a transclusion found. ');
return;
}
transclude(function (clone) {
if (clone.length) {
$element.replaceWith(clone);
}
else {
$element.remove();
}
});
}
};
}]);
PS:you can also check this link to see the difference between the ng-transclude

this works in Angular 1.4.9 (and prob earlier too)
return {
restrict: 'E',
replace: true,
template: '<span data-ng-transclude></span>',
transclude: true,
link: function (scope, el, attrs) .........
}

If you don't have to support IE and Edge you can use display:contents in your css. That will destroy the wrapper on the level of css.
You can read more about this new display propertie here:
https://css-tricks.com/get-ready-for-display-contents/
Current browser support (hope to see Edge support in the future):
https://caniuse.com/#feat=css-display-contents

Related

Why my directives don't work in Plunker?

I was going to use Plunker to assist me in testing a directive, but first I just wanted to create one to test plunker was working, so I put in some sample code. Guess what, basic directives are not working and I have no idea why.
My directives:
app.directive('attributeDirective', function() {
return {
restrict: 'A',
link: function(scope, iElement, iAttrs) {
iElement.bind('click', function() {
console.log('clicked attributeDirective');
});
iElement.bind('mouseover', function() {
iElement.css('cursor', 'pointer');
});
}
};
});
app.directive('elementDirective', function() {
return {
restrict: 'E',
replace: true,
template: '<h2>this is elementDirective</h2>',
link: function(scope, iElement, iAttrs) {
iElement.bind('click', function() {
console.log('clicked elementDirective');
});
iElement.bind('mouseover', function() {
iElement.css('cursor', 'pointer');
});
}
};
});
My html:
<body ng-controller="MainCtrl">
<p>Hello {{name}}!</p>
<h2 attributeDirective>Here is my attribute directive</h2>
<elementDirective></elementDirective>
</body>
http://plnkr.co/edit/H9vPhV
while calling a directive in html you should replacecamelcase in directives name like this,
<element-directive></element-directive> and not as it is,
<elementDirective></elementDirective>
like you did.
Hope this helps!!!
PLUNKER
see through the custom directives here
You should use
<h2 attribute-directive>Here is my attribute directive</h2>
See http://plnkr.co/edit/2aGGDRw6SdYNc1joSVI1?p=preview
Common problem - you can't use camel case in your HTML element declaration.
Try <element-directive></element-directive>
Use restrict: 'A' in your directive to refer to attribute.
Use restrict: 'E' in your directive to refer to element.
Find the plunkr: "http://plnkr.co/edit/b1cf6l?p=preview"
Also call your directive using:
<h2 attribute-directive>Here is my attribute directive</h2>
<element-directive></element-directive>

AngularJs optional transclude

I've written a directive without the transclude option.
But now it would be nice when I could activate the transclude function/option when calling the directive with another attribute or something else if possible.
If that's not possible the only Way I see is, to copy the directive and add the Transclude in the second one, but then I've doubled my code whtat I'm not willing to do.
any Ideas how to optionally activate the transclude in Angular 1.2.x
Edit:
alternate problem is also that I need to set the ng-transclude in my directive Template because its a big one and only a few rows can be replaced by the transclusion content.
You could conditionally modify a template to include ng-transclude in the compile: function.
.directive('foo', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
templateUrl: 'foo.html',
compile: function (element, attrs) {
if (attrs.bar !== undefined) {
element.find('.may-transclude-here')
.attr('ng-transclude', '');
}
return function postLink(scope, element, attrs, controllers) {
scope.listEntries = ['apple', 'banana', 'tomato'];
};
}
}
})
and a html template:
<div class="foo">
<h4>Directive title</h4>
<div class="may-transclude-here" ng-repeat="item in listEntries">
Original content: {{item}}
</div>
<span>blah blah blah</span>
</div>
but contents that are transcluded via ng-transclude will not bind with a scope of each item created by ng-repeat. In case you also need the binding, here is the modified version of ng-transclude that do the correct scope binding.
.directive('myTransclude', function () {
return {
restrict: 'EAC',
link: function(scope, element, attrs, controllers, transcludeFn) {
transcludeFn(scope, function(nodes) {
element.empty();
element.append(nodes);
});
}
};
});
Plunker example: http://plnkr.co/edit/8lncowJ7jdbN0DEowdxP?p=preview
hope this helps.

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 {/* ... */};
});

AngularJS model not updating while typing

In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle

AngularJS How to access elements inside directive before they get replaced

How do I get the input element from within the directive before the template overwrites the contents?
html
<div xxx>
<input a="1" />
</div>
js
app.directive('xxx', function(){
return {
restrict: 'A',
template: '<p></p>',
replace: true, //if false, just leaves the parent div, still no input
compile: function(element, attrs) {
console.log(element);
return function (scope, iElement, iAttrs) {
}
}
};
});
i am on angular 1.0.x, I cannot pass in optional scope parameters with the '=?' syntax and i want to be able to override a portion of the default template of the directive in a very flexible way. instead of adding a scope variable or attribute everytime that I just plan on passing through the directive, I want to be able to supply the whole element to be used.
edit
the input must retain the scope of the directive, and not the parent.
edit
I am trying to include a partial template inside a directive that will overwrite a piece of the actual template. The piece I am including therefore needs to have access to the directive's scope and not the parent's.
Update
It seems if I do not provide a template or a template URL and instead replace the contents manually using the $templateCache I can have access to the inner elements. I want to let angular handle the template and the replacement though and just want to be able to access the contents in the directive naturally before they get replaced.
Solution
Plunkr
html
<body ng-controller="MainCtrl">
<div editable="obj.email">
<input validate-email="error message" ng-model="obj.email" name="contactEmail" type="text" />
</div>
</body>
js
app.controller('MainCtrl', function($scope) {
$scope.obj = {
email: 'xxx'
};
});
app.directive('editable', function($log){
return {
restrict: 'A',
transclude: true,
template: '<div ng-show="localScopeVar">{{value}}<div ng-transclude></div></div>',
scope: {
value: '=editable'
},
link: function(scope) {
scope.localScopeVar = true;
}
};
});
app.directive('validateEmail', function($log){
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function(scope, el, attrs, ctrl) {
console.log(attrs['validateEmail']);
}
};
});
I believe you're looking for the transclude function (link is to 1.0.8 docs). You can see what's going on with:
app.directive('xxx', function($log){
return {
restrict: 'A',
transclude: true,
compile: function(element, attrs, transclude) {
$log.info("every instance element:", element);
return function (scope, iElement, iAttrs) {
$log.info("this instance element:", element);
transclude(scope, function(clone){
$log.info("clone:", clone);
});
}
}
};
});

Resources