简体   繁体   中英

Using different controllers with custom directive in AngularJS?

I have created a search box which is being used on two different views, one is for searching jobs and the other is for searching companies. I have made two separate controllers for both and separate services as well.

Here is the html for the searchbox -

<span class="searchButton"><i class="fa fa-search fa-2x"></i></span>
<input ng-change="companies.search()" 
       ng-model="companies.searchTerm" 
       ng-keydown="companies.deleteTerm($event)" 
       type="text" id="search-box" 
       style="width: 0px; visibility:hidden;"/>

Here is a script i am using for styling it -

<script type="text/javascript"> 
var toggleVar = true;
    $('.searchButton').on('click', function() {
        if(toggleVar) {
            $('.searchButton').animate({right: '210px'}, 400);
            $('#search-box').css("visibility", "visible");
            setTimeout(function() {
                $('.searchButton').css("color", "#444444");
            }, 200);
            $('#search-box').animate({ width: 185 }, 400).focus();
            toggleVar = false;
        }
        else {
            $('#search-box').animate({ width: 0 }, 400);
            $('.searchButton').animate({right: '25px'}, 400);
            setTimeout(function() {
                $('.searchButton').css("color", "#eeeeee");
            }, 300);
            toggleVar = true;
        }
    });

    $('#search-box').focusout(function() {
        if(!toggleVar) {
            $('#search-box').animate({ width: 0 }, 400);
            $('.searchButton').animate({right: '25px'}, 400);
            setTimeout(function() {
                $('.searchButton').css("color", "#eeeeee");
            }, 300);
            toggleVar = true;
        }
    });
</script>

Controller -

angular.module('jobSeekerApp')
  .controller('CompaniesallCtrl', ['getAllCompanies', function (companiesService) {
    var ctrl = this;
    var count;
    ctrl.pageNumber = 1;
    ctrl.searchPageNumber = 1;
    ctrl.isSearching = false;
    ctrl.searchTerm = "";

    // Initial page load
    companiesService.getCompanies(ctrl.pageNumber)
      .then(function(response) {
        ctrl.companiesList = response.data.results;
        count = response.data.count;
        checkCount();
      }, function(error) {
        console.log(error);
      });

    // User clicks next button
    ctrl.getNext = function() {
      // If search is not being used
      if(ctrl.searchTerm === "" && ctrl.isSearching === false) {
        ctrl.pageNumber = ctrl.pageNumber + 1;
        companiesService.getCompanies(ctrl.pageNumber)
          .then(function(response) {
            ctrl.companiesList = ctrl.companiesList.concat(response.data.results);
            checkCount(); 
          }, function(error) {
            console.log(error);
          });
      }
      // If search is being used
      else {
        ctrl.searchPageNumber = ctrl.searchPageNumber + 1;
        companiesService.searchCompany(ctrl.searchPageNumber, ctrl.searchTerm)
          .then(function(response) {
            ctrl.companiesList = ctrl.companiesList.concat(response.data.results);
            checkCount();
          }, function(error) {
            console.log(error);
          });
      } 
    };

    // User backspaces to delete search term
    ctrl.deleteTerm = function (event) {
      if(event.keyCode === 8) {
        ctrl.searchTermLen = ctrl.searchTermLen - 1;
      }
      // If search box is empty
      ctrl.isSearching = ctrl.searchTermLen !== 0;
    };

    // User clicks search button
    ctrl.search = function() {
      ctrl.searchTermLen = ctrl.searchTerm.length;
      // If search box is empty, show normal results
      if(ctrl.searchTerm === "" && ctrl.isSearching === false) {
        ctrl.pageNumber = 1;
        companiesService.getCompanies(ctrl.pageNumber)
          .then(function(response) {
            ctrl.companiesList = response.data.results;
            count = response.data.count;
            checkCount();
          }, function(error) {
            console.log(error);
          });
      }
      // If search box is not empty, search the input
      else {
        ctrl.isSearching = true;
        ctrl.searchPageNumber = 1;
        companiesService.searchCompany(ctrl.searchPageNumber, ctrl.searchTerm)
          .then(function(response) {
            ctrl.companiesList = response.data.results;
            count = response.data.count;
            checkCount();
          }, function(error) {
            console.log(error);
          });
      }
    };

    // Function to hide and show next button
    function checkCount() {
      console.log(count);
      $(".nextButton").toggle(count > 10);
      count = count - 10;
    }
  }]);

I am trying to make a directive for this, since all this code is being repeated for the both the views. But how do I make the directive interact with different controllers. And how do i make this part ng-change="companies.search()" ng-model="companies.searchTerm" ng-keydown="companies.deleteTerm($event)" not dependent on the controllers. I am new to angular and am not sure if this is the right approach or should i let the keep the code separate? Please help.

Server-Side search logic makes it simple

If it is possible that your search logic resides on the server and searching jobs or companies could be distinguished by simply setting a query variable in the URL, then it easy. You could use 1 search directive with an attribute to say which module to search and include this in your HTTP request.

Client-Side search logic slightly more angularjs

If you need different client-side logic for each type of search, consider this approach where there is 1 common search directive, plus 1 directive for each customized search.

  1. the common search directive controls view + common search functionality

  2. a search-companies directive that is restrict: 'A' and require: 'search' and performs functions specific to the company search

  3. a search-jobs directive that is also restrict: 'A' and require: 'search' and performs functions specific to the job search

The concept is that the custom search directives will provide their controller/api object to the common search directive. The common search directive handles the view-controller interaction and calls the provided API functions for customized search functionality.

In Code , this could look something like:

angular.module('SearchDemo', [])
.directive('search', function(){
    return {
        restrict: 'E',
        templateUrl: '/templates/search.tpl.html',
        controller: ['$scope', function($scope){
            $scope.results = [];

            this.setSearchAPI = function(searchAPI){
                this.api = searchAPI;
            };

            $scope.doSearch = function(query){
                $scope.results.length = 0;

                // here we call one of the custom controller functions
                if(this.api && angular.isFunction(this.api.getResults)){
                    var results = this.api.getResults(query);

                    // append the results onto $scope.results
                    // without creating a new array
                    $scope.results.push.apply($scope.results, results);
                }
            };
        }]
    };
})
.directive('searchCompanies', function(){
    return {
        restrict: 'A',
        require: ['search', 'searchCompanies'],
        link: function(scope, elem, attr, Ctrl){
            // here we pass the custom search-companies controller
            // to the common search controller
            Ctrl[0].setSearchAPI(Ctrl[1]);
        },
        controller: ['$scope', function($scope){
            // you need to design your common search API and 
            // implement the custom versions of those functions here

            // example:
            this.getResults = function(query){
                // TODO: load the results for company search
            };
        }]
    };
})
.directive('searchJobs', function(){
    return {
        restrict: 'A',
        require: ['search', 'searchJobs'],
        link: function(scope, elem, attr, Ctrl){
            // here we pass the custom search-jobs controller
            // to the common search controller
            Ctrl[0].setSearchAPI(Ctrl[1]);
        },
        controller: ['$scope', function($scope){
            // you need to design your common search API and 
            // implement the custom versions of those functions here

            // example:
            this.getResults = function(query){
                // TODO: load the results for job search
            };
        }]
    };
});

And using it in template would look like:

<search search-companies></search>

and

<search search-jobs></search>

Multiple searches on one directive

This concept could be easily expanded if you need to have one search directive that searches both companies and jobs.

The change would be to turn the search controller's this.api into an array.

angular.module('SearchDemo', [])
.directive('search', function(){
    return {
        restrict: 'E',
        templateUrl: '/templates/search.tpl.html',
        controller: ['$scope', function($scope){
            $scope.results = [];

            // this.api is now an array and can support 
            // multiple custom search controllers
            this.api = [];
            this.addSearchAPI = function(searchAPI){
                if(this.api.indexOf(searchAPI) == -1){
                    this.api.push(searchAPI);
                }
            };

            $scope.doSearch = function(query){
                $scope.results.length = 0;

                // here we call each of the custom controller functions
                for(var i=0; i < this.api.length; i++){
                    var api = this.api[i];
                    if(angular.isFunction(api.getResults)){
                        var results = api.getResults(query);

                        $scope.results.push.apply($scope.results, results);
                    }
                }
            };
        }]
    };
})
.directive('searchCompanies', function(){
    return {
        restrict: 'A',
        require: ['search', 'searchCompanies'],
        link: function(scope, elem, attr, Ctrl){
            // here we pass the custom search-companies controller
            // to the common search controller
            Ctrl[0].addSearchAPI(Ctrl[1]);
        },
        controller: ['$scope', function($scope){
            // you need to design your common search API and 
            // implement the custom versions of those functions here

            // example:
            this.getResults = function(query){
                // TODO: load the results for company search
            };
        }]
    };
})
.directive('searchJobs', function(){
    return {
        restrict: 'A',
        require: ['search', 'searchJobs'],
        link: function(scope, elem, attr, Ctrl){
            // here we pass the custom search-jobs controller
            // to the common search controller
            Ctrl[0].addSearchAPI(Ctrl[1]);
        },
        controller: ['$scope', function($scope){
            // you need to design your common search API and 
            // implement the custom versions of those functions here

            // example:
            this.getResults = function(query){
                // TODO: load the results for job search
            };
        }]
    };
});

And using it in template would look like:

<search search-companies search-jobs></search>

You will have to pass your data source or service to the directive and bind the events from there.

<body ng-app="customSearchDirective">
  <div ng-controller="Controller">

  <input type="text" placeholder="Search a Company" data-custom-search data-source="companies" />
  <input type="text" placeholder="Search for People" data-custom-search data-source="people" />

  <hr>
  Searching In: {{ searchSource }}
  <br/>
  Search Result is At: {{ results }}
</div>
</body>

In this sample I am using data-source to pass an array but you can use a service of course.

Then your directive should use the scope attribute to assign what you passed as parameter in source to the scope of the directive.

You will have the input that is using the directive in the elem parameter to bind all the parameters your desire.

(function(angular) {
  'use strict';

  angular.module('customSearchDirective', [])


  .controller('Controller', ['$scope', function($scope) {
    $scope.companies = ['Microsoft', 'ID Software', 'Tesla'];
    $scope.people = ['Gill Bates', 'Cohn Jarmack', 'Melon Musk'];
    $scope.results = [];
    $scope.searchSource = [];
  }])


  .directive('customSearch', [function() {
    function link(scope, element, attrs) {
      element.on("change", function(e) {
        var searchTerm = e.target.value;
        scope.$parent.$apply(function() {
          scope.$parent.searchSource = scope.source;
          scope.$parent.results = scope.source.indexOf(searchTerm);
        });
      });
    }

    return {
      scope: {
        source: '='
      },
      link: link
    };
  }]);
})(window.angular);

Using scope.$parent feels a bit hacky I know and limits the use of this directive to be a direct child of the controller but I think it's a good way to get you started.

You can try it out: https://plnkr.co/edit/A3jzjek6hyjK4Btk34Vc?p=preview

Just a couple of notes from the example.

  • The change event work after you remove focus from the text box (not while you're typing
  • You will have to search the exact string to get a match

Hope it helps.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM