Wrapping Angular.js ui-bootstrap or ui-select directives in own directive
angularjs, javascript
Solution
One solution would be to add a directive which extends the existing directive.
I created a Plunker with an example: http://plnkr.co/edit/9IZ0aW?p=preview
Following Code:
HTML:
<ui-select ng-model="address.selected" theme="bootstrap" ng-disabled="disabled" reset-search-input="false" style="width: 300px;">
<ui-select-match placeholder="Enter an address...">{{$select.selected.formatted_address}}</ui-select-match>
<ui-select-choices repeat="address in addresses track by $index" refresh="refreshAddresses($select.search)" refresh-delay="0">
<div ng-bind-html="address.formatted_address | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
Controller:
$scope.address = {};
$scope.refreshAddresses = function(address) {
var params = {
address: address,
sensor: false
};
return $http.get(
'http://maps.googleapis.com/maps/api/geocode/json', {
params: params
}
).then(function(response) {
$scope.addresses = response.data.results
});
};
can be simplified by using a configuration directive:
<ui-select ng-model="adress.selected" tq-select></ui-select>
Controller is now empty!
Directive:
app.directive("tqSelect", function($http) {
return {
restrict: "A", // Attribute
require: ["uiSelect", "ngModel"],
compile: function compile(tElement, tAttrs, transclude) {
// Add the inner content to the element
tElement.append('<ui-select-match placeholder="Enter an address...">{{$select.selected.formatted_address}}</ui-select-match>\
<ui-select-choices repeat="address in addresses track by $index" refresh="refreshAddresses($select.search)" refresh-delay="0">\
<div ng-bind-html="address.formatted_address | highlight: $select.search"></div>\
</ui-select-choices>');
return {
pre: function preLink(scope, iElement, iAttrs, controller) {},
post: function postLink(scope, iElement, iAttrs, controller) {
// logic from controller
scope.address = {};
scope.refreshAddresses = function(address) {
var params = {
address: address,
sensor: false
};
return $http.get(
'http://maps.googleapis.com/maps/api/geocode/json', {
params: params
}
).then(function(response) {
scope.addresses = response.data.results
});
};
}
}
}
}
});
The directive is the actual tricky part. I am using a not trivial logic in the compile function. First I add the required markup for the ui-select directive.
Then in the post-link function I added the logic which is normally in the controller (or in the link()-function).
Problem
I am creating a big Angular.JS application which uses some third party modules like ui-select and ui-bootstrap. To avoid repeating myself I started to create directives which are wrapping for example ui-select code and the logic to retrieve / search for data. Goal: The goal was to create a directive which can be used in the template like that, without duplicating code in controllers: ``` <tq-car-select ng-model="model.car"></tq-car-select> ``` What I try to avoid: ``` <select ng-options="car.id as car.name for car in cars"></select> ``` and duplicate following code in all controllers which are using the select: ``` $scope.cars = carResource.query(); $scope.$watch('model'), function (newValue, oldValue) { $scope.cars = carResource.query({model: $scope.model}); }); ``` I created directives for that kind of select fields. Actual Example with ui-select: tq-lead-select.html: ``` <ui-select ng-model="$parent.tqModel" style="display: block"> <ui-select-match placeholder="tippen ...">{{$select.selected.bezeichnung}}</ui-select-match> <ui-select-choices repeat="lead in leads | filter:{bezeichnung: $select.search}"> <div ng-bind-html="lead.bezeichnung | highlight: $select.search"></div> </ui-select-choices> </ui-select> ``` tqLeadSelect.ts (TypeScript): ``` export function tqLeadSelect(tqLeadSelects): ng.IDirective { var dir: ng.IDirective = {}; dir.scope = { tqModel: '=', tqCompany: '=' }; dir.restrict = 'E'; dir.templateUrl = '/js/templates/leadApp/tq-lead-select.html'; dir.replace = false; dir.controller = function ($scope: any) { if (tqLeadSelects != null && $scope.tqCompany != null) { $scope.leads = tqLeadSelects.getLeadsFromFirma({ id: $scope.tqCompany }); } $scope.$watch('tqCompany', (newValue, oldValue) => { if (newValue === oldValue) return; $scope.leads = tqLeadSelects.getLeadsFromFirma({ id: $scope.tqCompany }); }, true); } return dir; } tqLeadSelect.$inject = ['tqLeadSelects']; ``` Problems: - I need isolated scope, because some views use multiple instances of one field. - I am using the isolated scope variable tqModel which is set by the ngModel of the ui-select directive - I would like to use ng-required without creating a tq-required scope variable on tqLeadSelect directive Questions: - Am I doing it right? Are there better ways achieving my goals? - how are you defining select fields with supporting controller code for retrieving data and additional functions?