coderain blog

How to Fix AngularJS ui-grid Filter Not Working with External Text Input Field: Troubleshooting Guide

AngularJS ui-grid (formerly ng-grid) is a powerful data grid library for AngularJS applications, offering features like sorting, pagination, and filtering out of the box. While ui-grid’s built-in column filters work seamlessly, many developers encounter issues when integrating external text input fields (e.g., a search box above the grid) to filter grid data. Common problems include the filter not updating as the user types, partial results, or no results at all.

This guide will walk you through diagnosing and fixing issues with external filters in ui-grid. We’ll cover common causes, step-by-step troubleshooting, advanced scenarios, and best practices to ensure your external filter works reliably.

2025-12

Table of Contents#

  1. Understanding the Problem
  2. Common Causes of External Filter Failures
  3. Step-by-Step Troubleshooting
  4. Advanced Scenarios
  5. Conclusion
  6. References

Understanding the Problem#

An "external filter" refers to a text input field (e.g., <input type="text">) placed outside the ui-grid that dynamically filters grid rows as the user types. The expected behavior is:

  • User types in the input field.
  • The grid immediately updates to show only rows matching the input text.

When this fails, common symptoms include:

  • No grid updates when typing.
  • Only partial rows are filtered.
  • Errors in the browser console (critical for diagnosis!).
  • The filter works intermittently.

Common Causes of External Filter Failures#

Before diving into fixes, let’s identify why external filters fail:

  1. Broken ng-model Binding: The input field’s ng-model is not properly linked to the controller scope.
  2. Incorrect Filter Logic: The function filtering grid data has bugs (e.g., checks the wrong property, case sensitivity).
  3. Scope Inheritance Issues: The input field and grid exist in different scopes (common with directives or nested controllers).
  4. Missing Grid Refresh Trigger: The grid isn’t notified to refresh after the filter changes.
  5. ui-grid Misconfiguration: Disabled filtering, missing gridApi, or incorrect columnDefs setup.
  6. Data Structure Mismatch: The grid data is not an array, or row objects lack the properties being filtered.

Step-by-Step Troubleshooting#

Step 1: Verify Basic Setup & Console Errors#

Start with the basics:

  • Check the browser console (F12 in Chrome/Firefox) for errors/warnings. ui-grid and AngularJS often log issues here (e.g., gridApi is undefined, ng-model not found).
  • Ensure ui-grid is properly loaded: Confirm ui-grid.js and ui-grid.css are included in your HTML.
  • Verify the grid renders with data (no filter applied). If the grid is empty, fix data binding first.

Step 2: Check ng-model Binding#

The external input relies on ng-model to sync the input value with the controller.

Example Input HTML:

<!-- External filter input -->  
<input type="text" ng-model="vm.externalFilter" placeholder="Filter grid...">  
 
<!-- ui-grid -->  
<div ui-grid="vm.gridOptions" class="grid" ui-grid-auto-resize></div>  

Troubleshooting:

  • In your controller, log the externalFilter value to confirm it updates when typing:
    // In your controller  
    $scope.$watch('vm.externalFilter', function(newVal) {  
      console.log('Filter value:', newVal); // Should log as you type  
    });  
  • If newVal is undefined, the ng-model binding is broken. Fix by:
    • Using controllerAs syntax (e.g., ng-model="vm.externalFilter" instead of $scope).
    • Ensuring the input is within the controller’s scope (not in a child directive without scope inheritance).

Step 3: Validate Filter Function Logic#

The filter function determines which rows to show. It takes grid rows and the filter value, returning true (show row) or false (hide row).

Example Filter Function:

// Controller code  
function filterGridRows(row, filterText) {  
  if (!filterText) return true; // Show all rows if filter is empty  
  const lowerFilter = filterText.toLowerCase();  
  // Check if row matches filter (adjust properties to match your data!)  
  return row.entity.name.toLowerCase().includes(lowerFilter) ||  
         row.entity.email.toLowerCase().includes(lowerFilter);  
}  

Troubleshooting:

  • Test the function in isolation with sample data:
    // Test with a sample row  
    const testRow = { entity: { name: "John Doe", email: "[email protected]" } };  
    console.log(filterGridRows(testRow, "john")); // Should return true  
    console.log(filterGridRows(testRow, "jane")); // Should return false  
  • Common mistakes:
    • Using === instead of includes() for partial matches.
    • Forgetting to normalize case (e.g., "John" vs "john").
    • Referencing non-existent properties (e.g., row.name instead of row.entity.name—ui-grid wraps data in entity).

Step 4: Ensure Scope Inheritance#

If the input and grid are in different scopes (e.g., input in a parent controller, grid in a child directive), ng-model may not sync.

Fixes:

  • Use controllerAs syntax (avoids scope inheritance issues):
    // In your controller definition  
    angular.module('app').controller('GridController', GridController);  
    function GridController() {  
      const vm = this; // Bind properties to "vm" instead of $scope  
      vm.externalFilter = '';  
      vm.gridOptions = { ... };  
    }  
    In HTML:
    <div ng-controller="GridController as vm">  
      <input ng-model="vm.externalFilter">  
      <div ui-grid="vm.gridOptions"></div>  
    </div>  
  • Avoid $scope.$parent (fragile, but a quick fix for nested scopes):
    <input ng-model="$parent.externalFilter"> <!-- Use cautiously -->  

Step 5: Trigger Grid Data Refresh#

ui-grid doesn’t auto-refresh when external data changes. You must explicitly notify it via gridApi.

Example: Watch Filter and Refresh Grid

// In your controller, after defining gridOptions  
vm.gridOptions.onRegisterApi = function(gridApi) {  
  vm.gridApi = gridApi; // Store gridApi for later use  
 
  // Watch for filter changes and refresh  
  $scope.$watch('vm.externalFilter', function(newVal) {  
    if (!vm.gridApi) return; // Exit if gridApi isn't ready  
 
    // Apply filter to grid rows  
    const filteredRows = vm.gridApi.core.getVisibleRows().filter(row =>  
      filterGridRows(row, newVal)  
    );  
 
    // Notify ui-grid to update visibility  
    vm.gridApi.core.setRowVisibleBatch(filteredRows.map(row => row.uid), true);  
    // Alternatively, refresh the entire grid (simpler but less efficient)  
    vm.gridApi.core.notifyDataChange(vm.gridApi.grid.CONSTANTS.CHANGE.ALL);  
  });  
};  

Key Notes:

  • getVisibleRows() returns all rows (use getRenderedRows() for pagination-aware filtering).
  • notifyDataChange(CHANGE.ALL) triggers a full refresh (use for simplicity; CHANGE.FILTER for performance).

Step 6: Confirm ui-grid Configuration#

Ensure gridOptions enables filtering and exposes gridApi:

Required gridOptions Settings:

vm.gridOptions = {  
  enableFiltering: true, // Critical: Enables filtering infrastructure  
  useExternalFiltering: true, // Optional: For custom external filters (advanced)  
  columnDefs: [  
    { name: 'name', field: 'name' }, // Columns to display  
    { name: 'email', field: 'email' }  
  ],  
  data: vm.gridData, // Your grid data array (e.g., [{name: "John", ...}, ...])  
  onRegisterApi: function(gridApi) {  
    vm.gridApi = gridApi; // Must register to access gridApi  
  }  
};  

Common Mistakes:

  • Omitting enableFiltering: true (disables all filtering logic).
  • Forgetting onRegisterApi (prevents access to gridApi for refreshing).

Advanced Scenarios#

Filtering Specific Columns#

To filter only certain columns (e.g., name but not email), modify the filter function:

function filterGridRows(row, filterText) {  
  if (!filterText) return true;  
  const lowerFilter = filterText.toLowerCase();  
  // Filter ONLY the "name" column  
  return row.entity.name.toLowerCase().includes(lowerFilter);  
}  

Debouncing the Input#

Prevent excessive filtering while the user types by adding a 300ms delay:

HTML:

<input type="text" ng-model="vm.externalFilter"  
       ng-model-options="{ debounce: 300 }" placeholder="Filter...">  

This reduces grid refreshes and improves performance.

Case-Insensitive Filtering#

Normalize both the filter text and row data to lowercase:

function filterGridRows(row, filterText) {  
  if (!filterText) return true;  
  const lowerFilter = filterText.toLowerCase();  
  return row.entity.name.toLowerCase().includes(lowerFilter);  
}  

Conclusion#

Fixing external ui-grid filters requires checking:

  1. ng-model binding (console logs help!).
  2. Filter function logic (test with sample data).
  3. Scope inheritance (use controllerAs to avoid issues).
  4. Grid refresh triggers via gridApi.

By following these steps, you’ll resolve 90% of external filter issues. Always start with the browser console—it’s your best debugging tool!

References#