Can attribute directives transclude? - angularjs

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>

Related

Access form during component initialization in angularjs

I have a component as following :
.component('myLink', {
bindings: {
linkEntity: '=',
constraints: '=?',
fieldName: '#',
standalone: '#',
adherence: '#',
searchMinLength: '<',
searchHandler: '&',
viewItemHandler: '&',
onSelectItemHandler: '&',
hiddenFields: '#',
formName: '#',
date: '<?',
constraintsJson: '#'
},
require: {
parent: '?^form'
},
templateUrl: 'my-link-component.html',
controller: 'MyLinkController',
controllerAs: 'slink'
});
The template url :
<ng-form name="{{slink.linkFormName}}">
<fieldset class="link-fieldset">
<legend>
<md-icon>link</md-icon>
</legend>
<div layout="row" flex ng-if="slink.isFormLoaded">
<md-button print-remove ng-if="!slink.constraints.readOnly" ng-click="slink.querySearch(' ')" class="md-icon-button md-primary">
<md-icon aria-label="Tout voir">view_headline</md-icon>
<md-tooltip md-direction="right">Afficher toute la liste de : {{slink.uiName | lowercase}}</md-tooltip>
</md-button>
<md-autocomplete flex ng-disabled="slink.constraints.readOnly" md-selected-item="slink.selectedItem" md-search-text="slink.searchText" md-selected-item-change="slink.changeItem(item)" md-items="item in slink.querySearch(slink.searchText)" md-min-length="1" md-no-cache="slink.noCache" md-select-on-match=true md-autoselect=true md-item-text="slink.itemToText(item)" md-input-name="{{slink.property}}" md-input-id="simple-link-component-{{slink.property}}-{{slink.autoId}}-input" md-floating-label="{{slink.floatingLabel}}" md-select-on-focus style="background: none;">
<md-item-template>
<span md-highlight-text="slink.searchText === ' ' ? slink.itemToText(slink.selectedItemStore) : slink.searchText" md-highlight-flags="^i">
{{item.domainObjectDescription}}
</span>
</md-item-template>
<md-not-found>
<div style="width: 100%;">{{slink.notFoundMsg}}
</div>
</md-not-found>
<div ng-messages="slink.errorMessage" ng-if="!slink.constraints.readOnly">
<div ng-messages-include='messages/messages.html'></div>
</div>
</md-autocomplete>
<md-button print-remove ng-if="slink.standalone !== 'list' && !slink.constraints.readOnly" ng-disabled="!slink.selectedItem" class="md-icon-button md-primary" ng-click="slink.viewItem()">
<md-icon aria-label="Voir le détail">visibility</md-icon>
<md-tooltip md-direction="left">Voir le détail de : {{slink.uiName | lowercase}}</md-tooltip>
</md-button>
</div>
</fieldset>
</ng-form>
In the controller I'm trying to access the form as following :
function onInitComponent() {
//...
console.log($scope[slink.linkFormName]);
//...
}
But I always get undefined.
In the other hand, from the changeItem function, which triggers after I made a change on the component it's defined.
How can I access my form when I initilize my component ?
As discussed in this thread you can use $postLink() lifecycle hook to initially access your form in your components controller, since it is called after this controller's element and its children have been linked so we can be sure the form was initialized. See the example below:
(function (angular) {
'use strict';
angular.module('myApp', [])
.controller('MyCtrl', ['$scope', function MyCtrl($scope) {
this.id = 1;
}]);
})(angular);
(function (window, angular) {
'use strict';
var myLink = {
bindings: {
id: '<',
linkFormName: '#'
},
templateUrl: 'my-link-component.tmpl.html',
controller: 'MyLinkController',
controllerAs: 'slink'
};
angular.module('myApp')
.component('myLink', myLink);
})(window, angular);
(function (angular) {
'use strict';
angular
.module('myApp')
.controller('MyLinkController', MyLinkController);
MyLinkController.$inject = ['$scope'];
function MyLinkController($scope) {
var slink = this;
//component lifecycle hooks
slink.$onInit = onInit;
slink.$postLink = postLink;
function onInit() {
console.log('onInit: ', $scope[slink.linkFormName]);
}
function postLink() {
console.log('postLink: ',$scope[slink.linkFormName]);
}
}
})(angular);
<script src="//code.angularjs.org/1.6.2/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyCtrl as myCtrl">
<my-link id="myCtrl.id" link-form-name='myLinkForm'></my-link>
</div>
<script type="text/ng-template" id="my-link-component.tmpl.html">
<ng-form name="{{slink.linkFormName}}">
<fieldset class="link-fieldset">
<legend>
<md-icon>link</md-icon>
</legend>
</fieldset>
</ng-form>
</script>
</div>

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.

User Input won't duplicate/display in directive template

Why are the user inputted values not duplicating when a user types into an input?
The user input works and duplicates when the HTML is separate from a custom directive template as shown below and in this fiddle: http://jsfiddle.net/Lvc0u55v/7069/ .
<div ng-controller="LeaseTemplateController">
<div class="leasespecial">
<div class="firstsec">
<div class="percNumber">
<h1 id="perId" ng-repeat="bb in percent_id">{{bb.value}}</h1>
</div>
</div>
<h2>Lease Special Template</h2>
<form>
<div class="form-group" ng-repeat="cc in percent_id">
<div class="input-group">
<input class="form-control input" type="text" placeholder="Enter Percent" ng-model="cc.value">
</div>
</div>
</form>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('LeaseTemplateController', ['$scope', function($scope) {
//Lease Special Template
$scope.percent_id = [{
value: '20'
}];
}]);
</script>
However, instead I'm trying to insert it using two different directive templates as shown in this fiddle: http://jsfiddle.net/Lvc0u55v/7068/
<div lease-text-directive>
</div>
<div lease-input-directive>
</div>
<script>
var myApp = angular.module('myApp', []);
myApp.controller('LeaseTemplateController', ['$scope', function($scope) {
//Lease Special Template
$scope.percent_id = [{
value: '20'
}];
}]);
myApp.directive('leaseTextDirective', function() {
return {
restrict: 'A',
template: '<div class="leasespecial" ng-controller="LeaseTemplateController">\
<div class="firstsec">\
<div class="percNumber">\
<h1 id="perId" ng-repeat="bb in percent_id">{{bb.value}}</h1>\
</div>\
</div>'
};
});
myApp.directive('leaseInputDirective', function() {
return {
restrict: 'A',
template: '<h2>Lease Special Template</h2>\
<form ng-controller="LeaseTemplateController">\
<div class="form-group" ng-repeat="cc in percent_id">\
<div class="input-group">\
<input class="form-control input" type="text" placeholder="Enter Percent" ng-model="cc.value">\
</div>\
</div>\
</form>'
};
});
</script>
Why are the values not duplicating over in the second example and would you suggest a better practice than this?
I believe your experiencing the separation of scopes. Your directives have a different scope than your controller so it knows nothing. Try injecting your rootscope or scope like myApp.directive('leaseInputDirective', function($rootScope, $scope)
got it working now
var myApp = angular.module('myApp', []);
myApp.controller('LeaseTemplateController', function($scope,$rootScope) {
//Lease Special Template
$rootScope.percent_id = [{
value: '20'
}];
});
myApp.directive('leaseTextDirective', function() {
return {
restrict: 'E',
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div class="leasespecial" ng-controller="LeaseTemplateController">\
<div class="firstsec">\
<div class="percNumber">\
<h1 id="perId" ng-repeat="bb in percent_id">{{bb.value}}</h1>\
</div>\
</div>'
};
});
myApp.directive('leaseInputDirective', function() {
return {
restrict: 'E',
replace: true, // Replace with the template below
transclude: true, // we want to insert custom content inside the directive
template: '<div><h2>Lease Special Template</h2>\
<form ng-controller="LeaseTemplateController">\
<div class="form-group" ng-repeat="cc in percent_id">\
<div class="input-group">\
<input class="form-control input" type="text" placeholder="Enter Percent" ng-model="cc.value">\
</div>\
</div>\
</form></div>'
};
});
<lease-text-directive>
</lease-text-directive>
<!-- leaseTextDirective -->
<lease-input-directive>
</lease-input-directive>

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

Ng-transclude with nested directives

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>'

Resources