3

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?

1 Answer 1

4

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).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.