Ng-transclude with nested directives - angularjs

I am working with 2 directives where one directives template contains the second directive. I would like to use ng-transclude inside the second (nested) directive. How can I accomplish this? Here is a plnkr.
<body ng-app="transcludeExample">
<script>
angular.module('transcludeExample', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: true,
scope: { title:'#' },
template: '<div style="border: 1px solid black;">' +
'<div style="background-color: yellow">{{title}}</div>' +
'<test></test>' +
//'<ng-transclude></ng-transclude>' +
'</div>'
};
})
.directive('test', function(){
return {
restrict: 'E',
transclude: true,
template: '<div><ng-transclude></ng-transclude></div>'
};
})
.controller('ExampleController', ['$scope', function($scope) {
$scope.title = 'Lorem Ipsuma';
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
}]);
</script>
<div ng-controller="ExampleController">
<input ng-model="title"> <br/>
<textarea ng-model="text"></textarea> <br/><br/><br/><br/><br/><br/>
<pane title="{{title}}">{{text}}</pane>
</div>
</body>

Don't know if I'm missing something, but this seems to work:
template: '<div style="border: 1px solid black;">' +
'<div style="background-color: yellow">{{title}}</div>' +
'<test><ng-transclude></ng-transclude></test>' +
'</div>'

Related

Using template from 1 directive into another directives template

Basically I have 2 custom directives, each having it's own template. What I need is to insert one of the templates into the other. I have also read about transclusion, but can't wrap my head around it. Any ideas would be of great help!
From the AngularJS Website, an example:
<script>
angular.module('transcludeExample', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: true,
scope: { title:'#' },
template: '<div style="border: 1px solid black;">' +
'<div style="background-color: gray">{{title}}</div>' +
'<ng-transclude></ng-transclude>' +
'</div>'
};
})
.controller('ExampleController', ['$scope', function($scope) {
$scope.title = 'Lorem Ipsum';
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
}]);
</script>
<div ng-controller="ExampleController">
<input ng-model="title" aria-label="title"> <br/>
<textarea ng-model="text" aria-label="text"></textarea> <br/>
<pane title="{{title}}"><span>{{text}}</span></pane>
</div>
You have to create the custom directive, in this case "pane", from inside angular.module. When you have done that, the directive exists from within the module which should be your application and you can use it freely as it is returned from the directive example. In this case the example uses the "pane" directive and it associates the transcluded template to it.

Can attribute directives transclude?

I want slotted transclusion, and I've seen examples of element directives like this:
<my-directive>
<slot-a></slot-a>
<slot-b></slot-b>
</my-directive>
I want to know if it must be an element directive. I'd like to do something like this:
<div my-directive>
<slot-a></slot-a>
<slot-b></slot-b>
</div>
Is this possible? I can't find any documentation saying it can or can't be done.
Apparently you can—at least in recent versions of AngularJS. The snippet below is a variation of the element directive in the multi-slot transclusion section.
(function(angular) {
'use strict';
angular.module('multiSlotTranscludeExample', [])
.directive('pane', function() {
return {
restrict: 'A',
transclude: {
'title': '?paneTitle',
'body': 'paneBody',
'footer': '?paneFooter'
},
template: '<div style="border: 1px solid black;">' +
'<div class="title" ng-transclude="title">Fallback Title</div>' +
'<div ng-transclude="body"></div>' +
'<div class="footer" ng-transclude="footer">Fallback Footer</div>' +
'</div>'
};
})
.controller('ExampleController', ['$scope',
function($scope) {
$scope.title = 'Lorem Ipsum';
$scope.link = 'https://google.com';
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
}
]);
})(window.angular);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<body ng-app="multiSlotTranscludeExample">
<style>
.title,
.footer {
background-color: gray
}
</style>
<div ng-controller="ExampleController">
<input ng-model="title" aria-label="title">
<br/>
<textarea ng-model="text" aria-label="text"></textarea>
<br/>
<div pane>
<pane-title>
<a ng-href="{{link}}" ng-bind="title"></a>
</pane-title>
<pane-body>
<p ng-bind="text"></p>
</pane-body>
</div>
</div>
</body>

Understanding the contextual usage of scope in angular directive

I was just going through the documentation of Angular.js for ngTransclude, i came across the following example:
<script>
angular.module('transcludeExample', [])
.directive('pane', function(){
return {
restrict: 'E',
transclude: true,
scope: { title:'#' },
template: '<div style="border: 1px solid black;">' +
'<div style="background-color: gray">{{title}}</div>' +
'<ng-transclude></ng-transclude>' +
'</div>'
};
})
.controller('ExampleController', ['$scope', function($scope) {
$scope.title = 'Lorem Ipsum';
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
}]);
</script>
<div ng-controller="ExampleController">
<input ng-model="title" aria-label="title"> <br/>
<textarea ng-model="text" aria-label="text"></textarea> <br/>
<pane title="{{title}}">{{text}}</pane>
</div>
I am not quite sure why these two properties are used in the directive:
transclude: true,
scope: { title:'#' },
I beleive doing transclude: true, gives the directive access to
$scope.title
$scope.text
which is in the controller , i am not sure about this though , but why is scope being used here ? i mean in such a weird fashion that too, I.E. ,
scope: { title:'#' },
What is the # there for ? so to sum up my whole question, can somebody explain to me why the transclude and scope properties are used here in the directive ?
Thank you
The transclude property tells angular to replace the <ng-transclude> tag with the HTML code inside the directive. In your case, the {{text}} string.
The property:
scope: { title:'#' },
tells angular to include the attribute title passed to the directive in its scope.
More documentation here:
What is the difference between '#' and '=' in directive scope in AngularJS?
and of course here:
https://docs.angularjs.org/guide/directive

AngularJS form validation inside google places directive

I have an input field that is create by the following directive:
.directive('googlePlaces', function(){
return {
restrict:'E',
replace:true,
scope: {location:'='},
template: function (elem, attrs) {
return '<div><div class="form-group" ng-class="{ \'has-error\' : '+attrs.form+'.google_places_ac.$invalid }"><label>Address*</label><input id="google_places_ac" required name="google_places_ac" type="text" class="form-control" placeholder="Address" /><p class="help-block" ng-message="required" ng-show="'+attrs.form+'.google_places_ac.$invalid">Message</p></div></div>'
},
link: function($scope, elm, attrs){
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
$scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
$scope.$apply();
});
}
}
})
I have been trying exhaustively to add required validation to this field, but it doesn't work. I do the same for other input fields in my HTML form and it works fine.
This is the relevant HTML:
<form name="registrationForm" ng-submit="register()" novalidate>
...
<div class="col-xs-12">
<google-places location=location form="registrationForm"></google-places>
</div>
...
I've tried many different things, scope: {location:'=', form:'='}, $compile, just adding directly the name registrationForm, or simply form. None of it worked.
I would really appreciate if someone could help me with this :)
You could do this in many ways. Here are couple of them.
1) Isolate the validation and display of messages, accessing form etc from the googlePlaces directive and have the control of it given to the consumer as it really is a consumer's concern. They can have full control over how to display, what to display and where to display. This would avoid any more responsibilities to the directive who will just be responsible for providing the place selection. Have your directive require the ng-model and specify required attribute as well.
So a rough implementation would be something like this.
.directive('googlePlaces', function() {
return {
require:'ngModel',
restrict: 'E',
replace: true,
scope: {
location: '=ngModel'
},
template: function(elem, attrs) {
return '<div><div class="form-group"><label>Address*</label><input id="google_places_ac" required name="google_places_ac" type="text" ng-model="locSearch" class="form-control" placeholder="Address" /></div><button type="button" ng-click="clear()">clear</button></div>'
},
link: function($scope, elm, attrs, ctrl) {
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
$scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
$scope.$apply();
});
$scope.clear = function() {
$scope.location = null;
}
}
}
});
and
<google-places name="location" ng-model=location required
ng-class="{ 'has-error' : registrationForm.location.$invalid }">
</google-places>
<span class="help-block"
ng-show="registrationForm.location.$invalid">Please specify location</span>
angular.module('app', []).directive('googlePlaces', function() {
return {
require:'ngModel',
restrict: 'E',
replace: true,
scope: {
location: '=ngModel'
},
template: function(elem, attrs) {
return '<div><div class="form-group"><label>Address*</label><input id="google_places_ac" required name="google_places_ac" type="text" ng-model="location" class="form-control" placeholder="Address" /></div><button type="button" ng-click="clear()">clear</button></div>'
},
link: function($scope, elm, attrs, ctrl) {
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
$scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
$scope.$apply();
});
$scope.clear = function() {
$scope.location = null;
}
}
}
});
.has-error input {
border: 2px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
<div ng-app="app">
<form name="registrationForm" ng-submit="register()" novalidate>
<div class="col-xs-12">
<google-places name="location" ng-model=location required ng-class="{ 'has-error' : registrationForm.location.$invalid }"></google-places>
<span class="help-block" ng-show="registrationForm.location.$invalid">Please specify location</span>
</div>
</form>
</div>
2) You can 2 way bind the form object to the directive and control the validation and display message from there. You would need to place an ng-model on the input in order for validation to kick in properly.
.directive('googlePlaces', function() {
return {
restrict: 'E',
replace: true,
scope: {
location: '=',
form:'='
},
template: function(elem, attrs) {
return '<div><div class="form-group" ng-class="{ \'has-error\' :form.google_places_ac.$invalid }"><label>Address*</label><input ng-model="selectedLocation" id="google_places_ac" required name="google_places_ac" type="text" class="form-control" placeholder="Address" /><p class="help-block" ng-message="required" ng-show="form.google_places_ac.$invalid">Message</p></div></div>'
},
link: function($scope, elm, attrs) {
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
$scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
$scope.$apply();
});
}
}
});
angular.module('app', []).directive('googlePlaces', function() {
return {
restrict: 'E',
replace: true,
scope: {
location: '=',
form: '='
},
template: function(elem, attrs) {
return '<div><div class="form-group" ng-class="{ \'has-error\' :form.google_places_ac.$invalid }"><label>Address*</label><input ng-model="location" id="google_places_ac" required name="google_places_ac" type="text" class="form-control" placeholder="Address" /><p class="help-block" ng-message="required" ng-show="form.google_places_ac.$invalid">Message</p></div></div>'
},
link: function($scope, elm, attrs) {
var autocomplete = new google.maps.places.Autocomplete($("#google_places_ac")[0], {});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
$scope.location = place.name + ',' + place.geometry.location.lat() + ',' + place.geometry.location.lng() + "," + place.formatted_address;
$scope.$apply();
});
}
}
})
.has-error input {
border: 2px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?libraries=places"></script>
<div ng-app="app">
<form name="registrationForm" ng-submit="register()" novalidate>
<div class="col-xs-12">
<google-places location=location form="registrationForm"></google-places>
</div>
</form>
</div>

need to get template from ng-init function call of controller for the custom directive in Angularjs

As beginner to angularjs directives I am confused little bit. Can any one help me in the following. The following is my custom directive.
app.directive('customCharts', ['$compile', function($compile) {
return {
restrict: 'EA',
scope: {
dashboard1Data: '=',
title1Text: '=',
dashboard2Data: '=',
title2Text: '=',
},
link: function(scope, element, attrs) {
var template = '<div class="col1"> <p class="graphtitle"> {{title1Text}}</p> <c3-simple id="dashboard1Data" config="dashboard1Data"></c3-simple> </div>' + ' <div class="col2"> <p class="graphtitle"> {{title2Text}}</p> <c3-simple id="dashboard2Data" config="dashboard2Data"></c3-simple> </div> ';
var parent = angular.element(document.querySelectorAll('.customChartsDiv')) // DOM element where the compiled template can be appended
var linkFn = $compile(template);
var content = linkFn(scope);
parent.append(content);
}
}
}]);
I want my template to be as template = templateFromController. i.e., I don't want to hard code my template in the directive. Rather I would like to form the template in the controller during ng-init function call and I want my directive to use that template. How can I do it?
So in my controller, I would be having some thing like,
var templateFromController = '<div class="col1"> <p class="graphtitle"> {{title1Text}}</p> <c3-simple id="dashboard1Data" config="dashboard1Data"></c3-simple> </div>'
+' <div class="col2"> <p class="graphtitle"> {{title2Text}}</p> <c3-simple id="dashboard2Data" config="dashboard2Data"></c3-simple> </div> '
+ '<div class="col1"> <p class="graphtitle"> {{title3Text}}</p> <c3-simple id="dashboard3Data" config="dashboard3Data"></c3-simple> </div>'
+' <div class="col2"> <p class="graphtitle"> {{title3Text}}</p> <c3-simple id="dashboard4Data" config="dashboard4Data"></c3-simple> </div> ';
or
var templateFromController = '<div class="col1"> <p class="graphtitle"> {{title1Text}}</p> <c3-simple id="dashboard1Data" config="dashboard1Data"></c3-simple> </div>'
+' <div class="col2"> <p class="graphtitle"> {{title2Text}}</p> <c3-simple id="dashboard2Data" config="dashboard2Data"></c3-simple> </div> ';
Something like this, based on some other criteria in my ng-init function call of controller I will form my var templateFromController, and I want my custom directive to use this templateFromController for its template. Can any one help me to do it?
#Daniel, I made the following changes as per your suggestion :
app.directive('customCharts', ['$compile', function($compile) {
return {
restrict: 'EA',
scope: {
dashboard1Data: '=',
title1Text: '=',
dashboard2Data: '=',
title2Text: '=',
template: '='
},
link: function(scope, element, attrs) {
var parent = angular.element(document.querySelectorAll('.customChartsDiv')) // DOM element where the compiled template can be appended
var linkFn = $compile(template);
var content = linkFn(scope);
parent.append(content);
}
}
}]);
And in controller :
var template = '<div> </div>';
$scope.init = function() {
template = '<div class="col1"> <p class="graphtitle"> {{title1Text}}</p> <c3-simple id="dashboard1Data" config="dashboard1Data"></c3-simple> </div>';
}
And in my jsp, I have :
<div class="customChartsDiv">
<div custom-charts dashboard1-data="dashboard1Data" title1-text="title1Text" dashboard2-data="dashboard2Data" title2-text="title2Text" template="template"></div>
</div>
But it is giving error : template is not defined at line var linkFn = $compile(template); of my directive.
You could define it as an attribute on your directive and then pass in the object/string (that's created in your controller).
app.directive('customCharts', ['$compile', function($compile) {
return {
restrict: 'EA',
scope: {
dashboard1Data: '=',
title1Text: '=',
dashboard2Data: '=',
title2Text: '=',
template: '='
},
link: function(scope, element, attrs) {
var parent = angular.element(document.querySelectorAll('.customChartsDiv')) // DOM element where the compiled template can be appended
var linkFn = $compile(scope.template);
var content = linkFn(scope);
parent.html('').append(content);
}
}
}]);
Here is a plnkr that shows this should work: http://embed.plnkr.co/O6gNn1b6C7xJ3y2jJC05/preview

Resources