Wicked Smart Data
LearnArticlesAbout
Sign InSign Up
LearnArticlesAboutContact
Sign InSign Up
Wicked Smart Data

The go-to platform for professionals who want to master data, automation, and AI — from Excel fundamentals to cutting-edge machine learning.

Platform

  • Learning Paths
  • Articles
  • About
  • Contact

Connect

  • Contact Us
  • RSS Feed

© 2026 Wicked Smart Data. All rights reserved.

Privacy PolicyTerms of Service
All Articles
Power Apps Controls: Galleries, Forms, and Data Tables - Advanced Architecture and Performance

Power Apps Controls: Galleries, Forms, and Data Tables - Advanced Architecture and Performance

Power Apps🔥 Expert23 min readMar 27, 2026Updated Mar 27, 2026
Table of Contents
  • Prerequisites
  • The Data Control Trinity: Architecture and Trade-offs
  • Gallery Controls: The Performance Powerhouse
  • Form Controls: The User Experience Champion
  • Data Tables: The Familiar Workhorse
  • Gallery Mastery: Building High-Performance Data Interfaces
  • Delegation Strategies That Actually Work
  • Advanced Search and Performance Optimization
  • Memory Management and Virtualization Tuning
  • Building Responsive Galleries
  • Form Engineering: Beyond Basic CRUD
  • Advanced Validation Architectures

You're staring at a canvas app that needs to display 15,000 customer records, allow filtered searching, enable bulk edits, and maintain sub-second response times. Your stakeholders want Excel-like flexibility with database reliability, and they want it yesterday. Welcome to the world where Power Apps' data controls—galleries, forms, and data tables—either make you a hero or send you spiraling into performance hell.

Most developers treat these controls as simple UI widgets. That's a mistake. Each control represents a different philosophy for handling data: galleries prioritize customization and performance, forms focus on user experience and validation, and data tables emphasize bulk operations and familiar interfaces. Understanding when and how to leverage each control, along with their underlying delegation patterns, connection architectures, and memory management strategies, separates competent app builders from true Power Apps engineers.

By the end of this deep dive, you'll architect data interfaces that scale, debug performance bottlenecks like a detective, and choose the right control pattern before you write your first formula.

What you'll learn:

  • Master delegation strategies and connection pooling for large datasets across all three controls
  • Implement advanced filtering, sorting, and search patterns that maintain performance at scale
  • Design form validation architectures that handle complex business logic and async operations
  • Optimize memory usage and rendering performance for galleries displaying thousands of items
  • Build hybrid interfaces that intelligently combine multiple controls for maximum user experience
  • Debug and troubleshoot data binding issues, connection timeouts, and delegation warnings

Prerequisites

You should have solid experience building canvas apps, understand Power Fx fundamentals, and be comfortable with data source connections. Familiarity with delegation concepts and basic performance optimization will help you get the most from this lesson.

The Data Control Trinity: Architecture and Trade-offs

Before diving into implementation details, let's establish the fundamental architectural differences between these controls and why they exist.

Gallery Controls: The Performance Powerhouse

Galleries excel at displaying large datasets with maximum customization. They're built around virtualization—only rendering visible items plus a small buffer. This makes them capable of handling thousands of records without the memory bloat you'd see with other approaches.

The gallery's template-based architecture means every item uses the same layout and formulas, which Power Apps can optimize aggressively. Behind the scenes, galleries maintain a sophisticated caching layer that prefetches data as users scroll, manages connection pooling to avoid overwhelming your data source, and implements intelligent batching for network requests.

However, galleries require more setup for complex interactions. Want inline editing? You'll build it yourself. Need sortable columns? That's custom logic. Want row selection with bulk operations? Prepare for some formula engineering.

Form Controls: The User Experience Champion

Forms prioritize user experience and data integrity over raw performance. They're designed around the single-record paradigm—creating, editing, or viewing one item at a time. This focus enables rich validation, automatic save state management, and sophisticated error handling that galleries and data tables can't match.

Forms automatically handle optimistic UI updates, retry logic for failed saves, and conflict resolution when multiple users edit the same record. They also provide the richest validation framework, supporting field-level validation, cross-field validation, and async validation patterns.

The trade-off is performance at scale. Forms aren't designed for bulk operations or large dataset browsing. They shine in detail views, configuration screens, and anywhere data quality matters more than quantity.

Data Tables: The Familiar Workhorse

Data tables bridge the gap between galleries and forms, providing a spreadsheet-like interface that's immediately familiar to business users. They handle sorting, filtering, and basic editing without custom formulas, making them ideal for rapid prototyping and scenarios where development time is constrained.

Under the hood, data tables implement their own virtualization and caching strategies, but they're less configurable than galleries. They excel at scenarios where users need to quickly scan, sort, and edit tabular data without the complexity of custom interfaces.

The limitation is customization. Data tables look like data tables. You can't embed rich media, create custom layouts, or implement complex interaction patterns. They're powerful within their constraints but inflexible outside them.

Gallery Mastery: Building High-Performance Data Interfaces

Let's start with galleries because they're the most versatile and, when properly implemented, the highest-performing option for large datasets.

Delegation Strategies That Actually Work

Delegation is where most Power Apps developers fail. They understand the concept—push filtering and sorting to the data source instead of pulling everything locally—but they don't understand the nuances that make delegation work in real applications.

Consider this common scenario: you're building a customer service app that needs to display support tickets. Users need to filter by status, priority, customer, and date range. Here's how most developers approach it:

// Wrong: This breaks delegation and limits to 2,000 records
Filter(
    SupportTickets,
    Status = StatusDropdown.Selected.Value &&
    Priority = PriorityDropdown.Selected.Value &&
    Created >= DateRangeStart.SelectedDate &&
    Created <= DateRangeEnd.SelectedDate
)

This looks reasonable, but it has several delegation-breaking issues. Complex boolean logic, especially with multiple && operators, often doesn't delegate properly. Date comparisons with local variables can break delegation depending on your data source. And multiple filter conditions in a single Filter() function can overwhelm the delegation engine.

Here's the engineered approach:

// Right: Build delegatable filters progressively
With({
    _baseFilter: Filter(SupportTickets, Status = StatusDropdown.Selected.Value),
    _priorityFilter: If(
        IsBlank(PriorityDropdown.Selected),
        _baseFilter,
        Filter(_baseFilter, Priority = PriorityDropdown.Selected.Value)
    ),
    _dateFilter: If(
        IsBlank(DateRangeStart.SelectedDate),
        _priorityFilter,
        Filter(_priorityFilter, Created >= DateRangeStart.SelectedDate)
    )
},
    If(
        IsBlank(DateRangeEnd.SelectedDate),
        _dateFilter,
        Filter(_dateFilter, Created <= DateRangeEnd.SelectedDate)
    )
)

This approach chains filters instead of combining them, which delegates more reliably. Each filter operation is simple and single-purpose. The With() function prevents repeated evaluation of complex expressions, improving both performance and readability.

But we can do better. For high-performance applications, implement a filter state management pattern:

// In App.OnStart
Set(FilterState, {
    Status: Blank(),
    Priority: Blank(),
    DateStart: Blank(),
    DateEnd: Blank(),
    SearchTerm: ""
});

// Create a reusable filter function
Set(BuildTicketFilter, 
    Filter(
        SupportTickets,
        (IsBlank(FilterState.Status) || Status = FilterState.Status) &&
        (IsBlank(FilterState.Priority) || Priority = FilterState.Priority) &&
        (IsBlank(FilterState.DateStart) || Created >= FilterState.DateStart) &&
        (IsBlank(FilterState.DateEnd) || Created <= FilterState.DateEnd) &&
        (IsEmpty(FilterState.SearchTerm) || 
         StartsWith(Title, FilterState.SearchTerm) ||
         StartsWith(CustomerName, FilterState.SearchTerm))
    )
);

// Gallery Items property
BuildTicketFilter

This pattern centralizes filter logic, makes filters easily testable, and provides a foundation for advanced features like saved filter presets and filter history.

Advanced Search and Performance Optimization

Search functionality often becomes the performance bottleneck in gallery applications. Users expect instant results, but naive search implementations can bring apps to their knees.

The key insight is that different types of search require different strategies. Exact match searches can delegate efficiently. Fuzzy searches cannot. Text searches work well with indexed columns but poorly with computed columns. Understanding these constraints lets you build search that feels instant while staying within delegation limits.

// Implement tiered search strategy
With({
    _exactMatches: If(
        Len(SearchBox.Text) >= 3,
        Filter(
            SupportTickets,
            Title = SearchBox.Text ||
            TicketNumber = SearchBox.Text ||
            CustomerEmail = SearchBox.Text
        ),
        Table()
    ),
    _startsWithMatches: If(
        Len(SearchBox.Text) >= 2,
        Filter(
            SupportTickets,
            StartsWith(Title, SearchBox.Text) ||
            StartsWith(CustomerName, SearchBox.Text) ||
            StartsWith(CustomerEmail, SearchBox.Text)
        ),
        Table()
    ),
    _containsMatches: If(
        Len(SearchBox.Text) >= 4,
        Filter(
            SupportTickets,
            SearchBox.Text in Title ||
            SearchBox.Text in Description
        ),
        Table()
    )
},
    Distinct(
        _exactMatches,
        _startsWithMatches,
        _containsMatches,
        ID
    )
)

This tiered approach prioritizes exact matches (which are fastest), falls back to prefix matches (which delegate well), and only attempts contains searches (which are expensive) for longer search terms. The Distinct() function removes duplicates across tiers.

For even better performance, implement search debouncing to avoid overwhelming your data source:

// In SearchBox.OnChange
Set(SearchStartTime, Now());
Set(SearchTerm, Self.Text);

// Create a timer control with Duration = 500, AutoStart = false
// In SearchTimer.OnTimerEnd
If(
    DateDiff(SearchStartTime, Now(), Milliseconds) >= 450,
    Set(ActiveSearchTerm, SearchTerm)
);

// In SearchBox.OnChange (add this line)
Reset(SearchTimer); Start(SearchTimer);

// Gallery Items uses ActiveSearchTerm instead of SearchBox.Text

This debouncing pattern waits 500ms after the user stops typing before executing the search, dramatically reducing unnecessary API calls.

Memory Management and Virtualization Tuning

Galleries implement virtualization, but you can tune their behavior for specific use cases. The key properties are LoadingSpinner, LoadingSpinnerColor, and the often-overlooked TemplatePadding and TemplateSize.

For galleries with complex templates, memory usage can balloon quickly. Each template instance creates its own formula context, and complex formulas get evaluated for every visible item. Here's how to optimize:

// Instead of complex expressions in template controls
Label1.Text = If(
    ThisItem.Priority = "High",
    "🔴 " & ThisItem.Title,
    If(
        ThisItem.Priority = "Medium", 
        "🟡 " & ThisItem.Title,
        "🟢 " & ThisItem.Title
    )
)

// Create computed columns at the gallery level
Gallery1.Items = AddColumns(
    FilteredTickets,
    "DisplayTitle", 
    Switch(
        Priority,
        "High", "🔴 " & Title,
        "Medium", "🟡 " & Title,
        "🟢 " & Title
    ),
    "StatusColor",
    Switch(
        Status,
        "Open", RGBA(255, 0, 0, 1),
        "In Progress", RGBA(255, 165, 0, 1),
        "Closed", RGBA(0, 128, 0, 1),
        RGBA(128, 128, 128, 1)
    )
)

// Template controls use simple references
Label1.Text = ThisItem.DisplayTitle
Rectangle1.Fill = ThisItem.StatusColor

This pattern moves complex calculations out of the template and into the gallery's Items property, where they're calculated once per item rather than once per visible template instance.

Building Responsive Galleries

Modern applications need to work across devices and screen sizes. Galleries provide several mechanisms for responsive behavior, but they require careful orchestration.

// Create responsive column calculations
With({
    _screenWidth: App.Width,
    _minItemWidth: 300,
    _maxItemWidth: 500,
    _padding: 20
},
    {
        ItemsPerRow: Max(1, Int((_screenWidth - _padding) / _minItemWidth)),
        ItemWidth: Min(
            _maxItemWidth,
            (_screenWidth - _padding) / Max(1, Int((_screenWidth - _padding) / _minItemWidth)) - 10
        )
    }
)

Use this calculation in your gallery's TemplateSize and template control positioning:

// Gallery.TemplateSize
App.ResponsiveLayout.ItemWidth + 10

// Gallery.WrapCount  
App.ResponsiveLayout.ItemsPerRow

// Template controls use relative positioning
Container1.Width = Parent.TemplateWidth - 20
Container1.X = 10

This creates a responsive grid that adapts to screen size while maintaining usable item dimensions.

Form Engineering: Beyond Basic CRUD

Forms might seem straightforward—they edit records, right?—but sophisticated applications demand sophisticated form architectures. Let's explore patterns that handle complex validation, async operations, and integration with other systems.

Advanced Validation Architectures

Basic field validation is trivial in Power Apps. Real applications need cross-field validation, async validation (checking unique constraints against databases), conditional validation that changes based on user roles or data state, and validation that integrates with external systems.

Here's a validation framework that handles these scenarios:

// Create validation state management
Set(ValidationState, {
    IsValidating: false,
    Errors: Table(),
    Warnings: Table(),
    LastValidated: Blank()
});

// Validation function for email uniqueness (async)
Set(ValidateEmailAsync, 
    UpdateContext({_validatingEmail: true});
    With({
        _existingUser: LookUp(Users, Email = EmailInput.Text && ID <> Form1.LastSubmit.ID)
    },
        UpdateContext({_validatingEmail: false});
        If(
            IsBlank(_existingUser),
            true,
            // Add error to validation state
            Set(ValidationState, Patch(ValidationState, {
                Errors: Patch(ValidationState.Errors, {
                    Field: "Email",
                    Message: "Email address already exists",
                    Severity: "Error"
                })
            }));
            false
        )
    )
);

// Cross-field validation for business rules
Set(ValidateBusinessRules,
    // Manager approval required for high-value requests
    If(
        RequestAmount.Text > 10000 && IsBlank(ManagerApproval.Selected),
        Set(ValidationState, Patch(ValidationState, {
            Errors: Patch(ValidationState.Errors, {
                Field: "ManagerApproval", 
                Message: "Manager approval required for requests over $10,000",
                Severity: "Error"
            })
        }))
    );
    
    // Warn if deadline is less than standard processing time
    If(
        DateDiff(Today(), RequestDeadline.SelectedDate, Days) < 5,
        Set(ValidationState, Patch(ValidationState, {
            Warnings: Patch(ValidationState.Warnings, {
                Field: "RequestDeadline",
                Message: "Short deadline may require expedited processing",
                Severity: "Warning"
            })
        }))
    )
);

Implement validation triggers that respect user experience:

// Field-level validation on focus loss
EmailInput.OnChange = If(
    Len(Self.Text) > 0 && IsMatch(Self.Text, Match.Email),
    ValidateEmailAsync,
    // Clear previous email validation errors
    Set(ValidationState, Patch(ValidationState, {
        Errors: Filter(ValidationState.Errors, Field <> "Email")
    }))
);

// Form-level validation before save
SaveButton.OnSelect = 
    // Clear previous validation
    Set(ValidationState, {
        IsValidating: true,
        Errors: Table(),
        Warnings: Table(),
        LastValidated: Now()
    });
    
    ValidateBusinessRules;
    
    Set(ValidationState, Patch(ValidationState, {IsValidating: false}));
    
    If(
        CountRows(ValidationState.Errors) = 0,
        SubmitForm(Form1),
        // Show validation summary
        UpdateContext({ShowValidationSummary: true})
    );

Handling Complex Form State

Enterprise forms often need to track changes, implement auto-save, handle optimistic updates, and manage concurrent editing scenarios. Here's an architecture that handles these requirements:

// Initialize form state management
Set(FormState, {
    OriginalRecord: Blank(),
    CurrentRecord: Blank(),
    HasUnsavedChanges: false,
    LastAutoSave: Blank(),
    IsAutoSaving: false,
    ConflictDetected: false
});

// Track changes automatically
Form1.OnChange = 
    With({
        _currentValues: {
            Title: TitleInput.Text,
            Description: DescriptionInput.Text,
            Priority: PriorityDropdown.Selected,
            DueDate: DueDatePicker.SelectedDate
        }
    },
        Set(FormState, Patch(FormState, {
            CurrentRecord: _currentValues,
            HasUnsavedChanges: !Equals(_currentValues, FormState.OriginalRecord)
        }));
        
        // Trigger auto-save after 30 seconds of inactivity
        Reset(AutoSaveTimer);
        Start(AutoSaveTimer)
    );

// Auto-save implementation
AutoSaveTimer.OnTimerEnd = 
    If(
        FormState.HasUnsavedChanges && !FormState.IsAutoSaving,
        Set(FormState, Patch(FormState, {IsAutoSaving: true}));
        
        With({
            _saveResult: Patch(
                SupportTickets,
                LookUp(SupportTickets, ID = Form1.LastSubmit.ID),
                FormState.CurrentRecord
            )
        },
            If(
                IsError(_saveResult),
                // Handle auto-save failure gracefully
                UpdateContext({AutoSaveError: "Auto-save failed. Please save manually."}),
                // Update tracking state
                Set(FormState, {
                    OriginalRecord: FormState.CurrentRecord,
                    HasUnsavedChanges: false,
                    LastAutoSave: Now(),
                    IsAutoSaving: false
                })
            )
        )
    );

Optimistic UI Updates and Conflict Resolution

For responsive user experiences, implement optimistic updates that assume operations will succeed, then handle failures gracefully:

SaveButton.OnSelect = 
    // Show optimistic success state immediately
    UpdateContext({
        ShowSuccessMessage: true,
        IsSaving: false
    });
    
    // Navigate away optimistically
    Navigate(TicketListScreen);
    
    // Perform actual save in background
    With({
        _saveOperation: Patch(
            SupportTickets,
            LookUp(SupportTickets, ID = Form1.LastSubmit.ID),
            {
                Title: TitleInput.Text,
                Description: DescriptionInput.Text,
                LastModified: Now(),
                ModifiedBy: User()
            }
        )
    },
        If(
            IsError(_saveOperation),
            // Revert optimistic state and show error
            UpdateContext({ShowSuccessMessage: false});
            Navigate(EditTicketScreen, ScreenTransition.None);
            UpdateContext({
                SaveError: "Save failed: " & FirstError.Message,
                ShowErrorDialog: true
            }),
            // Confirm successful save
            Set(LastSuccessfulSave, Now())
        )
    );

Data Table Optimization: Excel-Like Performance

Data tables seem simple, but achieving Excel-like performance with large datasets requires understanding their internals and optimization strategies.

Delegation-Friendly Data Table Patterns

Data tables have their own delegation quirks. Unlike galleries, they delegate sorting and basic filtering automatically, but they struggle with complex filters and computed columns. Here's how to work with their strengths:

// Instead of complex computed columns in the data source
DataTable1.Items = SupportTickets

// Use the built-in filtering instead of custom Filter() functions
// Configure column filters in the data table properties

// For complex filtering, pre-process the data
DataTable1.Items = 
    AddColumns(
        Filter(
            SupportTickets,
            Status in ["Open", "In Progress"] // Simple, delegatable filter
        ),
        "DaysOpen", DateDiff(Created, Now(), Days),
        "IsOverdue", DueDate < Now(),
        "PriorityScore", Switch(
            Priority,
            "High", 3,
            "Medium", 2,
            "Low", 1,
            0
        )
    )

Performance Tuning for Large Datasets

Data tables can handle large datasets, but they need tuning for optimal performance:

// Implement progressive loading for very large datasets
With({
    _pageSize: 100,
    _currentPage: DataTablePageState.Page
},
    FirstN(
        SortByColumns(
            FilteredDataSource,
            DataTablePageState.SortColumn,
            If(DataTablePageState.SortDirection = "Ascending", Ascending, Descending)
        ),
        _pageSize * _currentPage
    )
)

Configure the data table for optimal rendering:

  • Set appropriate column widths to avoid horizontal scrolling
  • Use fixed heights for consistent virtualization
  • Limit the number of visible columns (hide less important data in expandable details)
  • Implement smart column ordering (put most important data first)

Custom Column Types and Formatting

Data tables support custom column formatting that can dramatically improve usability:

// For date columns, use relative formatting
ThisItem.CreatedDate = DateAdd(Today(), -DateDiff(ThisItem.Created, Today(), Days), Days)

// For status columns, use color coding
ThisItem.StatusColor = Switch(
    ThisItem.Status,
    "Open", RGBA(220, 53, 69, 1),        // Red
    "In Progress", RGBA(255, 193, 7, 1), // Yellow  
    "Completed", RGBA(40, 167, 69, 1),   // Green
    RGBA(108, 117, 125, 1)               // Gray
)

// For numeric columns, use conditional formatting
ThisItem.AmountColor = If(
    ThisItem.Amount > ThisItem.Budget,
    RGBA(220, 53, 69, 1),  // Over budget - red
    RGBA(40, 167, 69, 1)   // Under budget - green
)

Advanced Integration Patterns

Real applications rarely use just one control type. The most powerful Power Apps combine galleries, forms, and data tables strategically. Here are patterns that create seamless user experiences.

Master-Detail Architectures

The classic master-detail pattern uses different controls for browsing versus editing:

// Master view (Gallery for browsing with search and filters)
MasterGallery.Items = 
    SortByColumns(
        Filter(
            SupportTickets,
            (IsBlank(StatusFilter.Selected) || Status = StatusFilter.Selected.Value) &&
            (IsEmpty(SearchTerm) || SearchTerm in Title || SearchTerm in Description)
        ),
        "Created",
        Descending
    )

// Selection management
MasterGallery.OnSelect = 
    Set(SelectedTicket, ThisItem);
    Navigate(DetailScreen)

// Detail view (Form for editing selected record)
DetailForm.Item = SelectedTicket
DetailForm.DataSource = SupportTickets

Inline Editing Patterns

For scenarios requiring quick edits without navigation:

// Gallery with inline edit capability
EditableGallery.Items = 
    AddColumns(
        BaseDataSource,
        "IsEditing", ThisItem.ID = EditingItemID,
        "EditableTitle", If(ThisItem.ID = EditingItemID, EditTitleInput.Text, ThisItem.Title)
    )

// Toggle edit mode
EditButton.OnSelect = 
    If(
        EditingItemID = ThisItem.ID,
        // Save and exit edit mode
        Patch(
            SupportTickets,
            LookUp(SupportTickets, ID = ThisItem.ID),
            {Title: EditTitleInput.Text}
        );
        Set(EditingItemID, Blank()),
        // Enter edit mode
        Set(EditingItemID, ThisItem.ID);
        Set(EditTitleInput.Text, ThisItem.Title)
    )

// Template shows edit controls conditionally
EditTitleInput.Visible = ThisItem.IsEditing
TitleLabel.Visible = !ThisItem.IsEditing

Bulk Operations Architecture

For scenarios requiring bulk operations across multiple records:

// Selection state management
Gallery1.OnSelect = 
    If(
        ThisItem.ID in SelectedItems,
        // Remove from selection
        Set(SelectedItems, Filter(SelectedItems, Value <> ThisItem.ID)),
        // Add to selection
        Set(SelectedItems, Collect(SelectedItems, {Value: ThisItem.ID}))
    )

// Bulk update implementation
BulkUpdateButton.OnSelect = 
    With({
        _selectedRecords: Filter(SupportTickets, ID in SelectedItems),
        _updateData: {
            Status: BulkStatusDropdown.Selected.Value,
            LastModified: Now(),
            ModifiedBy: User()
        }
    },
        ForAll(
            _selectedRecords,
            Patch(SupportTickets, ThisRecord, _updateData)
        );
        
        // Clear selection after update
        Clear(SelectedItems);
        
        UpdateContext({
            BulkUpdateMessage: CountRows(_selectedRecords) & " records updated successfully"
        })
    )

Hands-On Exercise

Let's build a comprehensive customer service ticketing system that demonstrates all the concepts we've covered. This exercise will create a real-world application with advanced filtering, inline editing, bulk operations, and performance optimization.

Exercise Setup

Create a new canvas app and set up a SharePoint list called "ServiceTickets" with these columns:

  • Title (Single line of text)
  • Description (Multiple lines of text)
  • Status (Choice: Open, In Progress, Waiting, Resolved, Closed)
  • Priority (Choice: Low, Medium, High, Critical)
  • Customer (Single line of text)
  • AssignedTo (Person or Group)
  • Created (Date and Time) - default column
  • DueDate (Date and Time)
  • EstimatedHours (Number)
  • ActualHours (Number)

Part 1: Build the Master Gallery

Create a gallery that implements advanced filtering and search:

// App.OnStart - Initialize application state
Set(FilterState, {
    Status: Blank(),
    Priority: Blank(),
    AssignedTo: Blank(),
    DateRange: "All",
    SearchTerm: ""
});

Set(ViewState, {
    CurrentView: "Gallery",
    SelectedItem: Blank(),
    EditingItem: Blank(),
    SelectedItems: Table()
});

// Main gallery Items property
With({
    _baseFilter: ServiceTickets,
    _statusFilter: If(
        IsBlank(FilterState.Status),
        _baseFilter,
        Filter(_baseFilter, Status.Value = FilterState.Status)
    ),
    _priorityFilter: If(
        IsBlank(FilterState.Priority),
        _statusFilter,
        Filter(_statusFilter, Priority.Value = FilterState.Priority)
    ),
    _dateFilter: Switch(
        FilterState.DateRange,
        "Today", Filter(_priorityFilter, DateValue(Text(Created)) = Today()),
        "This Week", Filter(_priorityFilter, 
            Created >= DateAdd(Today(), -(Weekday(Today()) - 1), Days) &&
            Created < DateAdd(Today(), 8 - Weekday(Today()), Days)
        ),
        "This Month", Filter(_priorityFilter,
            Month(Created) = Month(Today()) && Year(Created) = Year(Today())
        ),
        _priorityFilter
    ),
    _searchFilter: If(
        IsEmpty(FilterState.SearchTerm),
        _dateFilter,
        Filter(
            _dateFilter,
            FilterState.SearchTerm in Title ||
            FilterState.SearchTerm in Customer ||
            FilterState.SearchTerm in Description
        )
    )
},
    AddColumns(
        _searchFilter,
        "IsSelected", ID in ViewState.SelectedItems,
        "IsOverdue", DueDate < Now() && !(Status.Value in ["Resolved", "Closed"]),
        "DaysUntilDue", DateDiff(Now(), DueDate, Days),
        "StatusColor", Switch(
            Status.Value,
            "Open", RGBA(220, 53, 69, 1),
            "In Progress", RGBA(255, 193, 7, 1),
            "Waiting", RGBA(108, 117, 125, 1),
            "Resolved", RGBA(40, 167, 69, 1),
            "Closed", RGBA(40, 167, 69, 0.5),
            RGBA(108, 117, 125, 1)
        )
    )
)

Part 2: Implement Advanced Search with Debouncing

Create the search interface:

// Search input OnChange
Set(SearchInputTime, Now());
Reset(SearchTimer);
Start(SearchTimer);

// Search timer (Duration: 500ms, AutoStart: false) OnTimerEnd
If(
    DateDiff(SearchInputTime, Now(), Milliseconds) >= 450,
    Set(FilterState, Patch(FilterState, {SearchTerm: SearchInput.Text}))
);

Part 3: Build Inline Editing Capability

Add inline editing to your gallery template:

// Edit button in gallery template OnSelect
If(
    ViewState.EditingItem = ThisItem.ID,
    // Save changes
    With({
        _updateResult: Patch(
            ServiceTickets,
            LookUp(ServiceTickets, ID = ThisItem.ID),
            {
                Title: EditTitleInput.Text,
                Priority: EditPriorityDropdown.Selected,
                Status: EditStatusDropdown.Selected,
                DueDate: EditDueDatePicker.SelectedDate
            }
        )
    },
        If(
            IsError(_updateResult),
            UpdateContext({ErrorMessage: "Save failed: " & FirstError.Message}),
            Set(ViewState, Patch(ViewState, {EditingItem: Blank()}))
        )
    ),
    // Enter edit mode
    Set(ViewState, Patch(ViewState, {EditingItem: ThisItem.ID}));
    Set(EditTitleInput.Text, ThisItem.Title);
    Set(EditPriorityDropdown.Selected, ThisItem.Priority);
    Set(EditStatusDropdown.Selected, ThisItem.Status);
    Set(EditDueDatePicker.SelectedDate, ThisItem.DueDate)
);

// Checkbox for bulk selection OnCheck
Set(ViewState, Patch(ViewState, {
    SelectedItems: Collect(ViewState.SelectedItems, ThisItem.ID)
}));

// Checkbox OnUncheck
Set(ViewState, Patch(ViewState, {
    SelectedItems: Filter(ViewState.SelectedItems, Value <> ThisItem.ID)
}));

Part 4: Implement Bulk Operations

Create bulk operation controls:

// Bulk status update OnSelect
With({
    _selectedRecords: Filter(ServiceTickets, ID in ViewState.SelectedItems),
    _updateData: {
        Status: BulkStatusDropdown.Selected,
        LastModified: Now()
    }
},
    ForAll(_selectedRecords, 
        Patch(ServiceTickets, ThisRecord, _updateData)
    );
    
    Set(ViewState, Patch(ViewState, {SelectedItems: Table()}));
    
    UpdateContext({
        BulkMessage: CountRows(_selectedRecords) & " tickets updated to " & 
                    BulkStatusDropdown.Selected.Value
    })
);

Part 5: Add Performance Monitoring

Implement performance tracking:

// Performance monitoring
Set(PerfCounters, {
    LastFilterTime: Blank(),
    FilterExecutions: 0,
    AverageFilterTime: 0
});

// In gallery Items (wrap the existing formula)
With({
    _startTime: Now()
},
    With({
        _result: /* Your existing gallery Items formula */,
        _endTime: Now(),
        _duration: DateDiff(_startTime, _endTime, Milliseconds)
    },
        Set(PerfCounters, {
            LastFilterTime: _duration,
            FilterExecutions: PerfCounters.FilterExecutions + 1,
            AverageFilterTime: (PerfCounters.AverageFilterTime * PerfCounters.FilterExecutions + _duration) / (PerfCounters.FilterExecutions + 1)
        });
        _result
    )
)

Test your application with various filter combinations, search terms, and bulk operations. Monitor the performance counters to understand how different operations affect responsiveness.

Common Mistakes & Troubleshooting

After building hundreds of data-driven Power Apps, certain mistakes appear repeatedly. Understanding these patterns will save you hours of debugging and performance tuning.

Delegation Pitfalls That Kill Performance

Mistake: Complex filters that break delegation Most developers understand that delegation pushes operations to the data source, but they don't recognize when their formulas break delegation.

// This breaks delegation and limits you to 2,000 records
Filter(
    Orders,
    Year(OrderDate) = 2023 && 
    Status = "Pending" && 
    Customer.Name = "Acme Corp" &&
    Total > 1000
)

The Year() function doesn't delegate to most data sources. Lookup columns (like Customer.Name) often don't delegate. Complex boolean logic can break delegation even when individual conditions would delegate.

Solution: Test delegation systematically

// Test each condition individually
Set(TestFilter1, Filter(Orders, Status = "Pending")); // Check count
Set(TestFilter2, Filter(Orders, OrderDate >= Date(2023,1,1))); // Check count  
Set(TestFilter3, Filter(Orders, Total > 1000)); // Check count

// Build delegatable version
Filter(
    Orders,
    Status = "Pending" &&
    OrderDate >= Date(2023,1,1) &&
    OrderDate < Date(2024,1,1) &&
    Total > 1000
)

Always test your filters with datasets larger than 2,000 records. If you get exactly 2,000 results, delegation is failing.

Mistake: Computed columns in gallery templates

// This recalculates for every visible item, every time
Label1.Text = If(
    DateDiff(ThisItem.Created, Now(), Days) > 30,
    "Overdue: " & Text(ThisItem.Created, "mm/dd/yyyy"),
    If(
        DateDiff(ThisItem.Created, Now(), Days) > 14,
        "Due Soon: " & Text(ThisItem.Created, "mm/dd/yyyy"), 
        Text(ThisItem.Created, "mm/dd/yyyy")
    )
)

Solution: Compute columns at the gallery level

Gallery1.Items = AddColumns(
    Orders,
    "DisplayText", 
    With({_daysDiff: DateDiff(Created, Now(), Days)},
        If(
            _daysDiff > 30,
            "Overdue: " & Text(Created, "mm/dd/yyyy"),
            If(
                _daysDiff > 14,
                "Due Soon: " & Text(Created, "mm/dd/yyyy"),
                Text(Created, "mm/dd/yyyy")
            )
        )
    )
)

// Template just references the computed column
Label1.Text = ThisItem.DisplayText

Form Validation Anti-Patterns

Mistake: Validation that blocks user experience

// This validates on every keystroke, creating a terrible UX
EmailInput.OnChange = If(
    !IsMatch(Self.Text, Match.Email),
    UpdateContext({EmailError: "Invalid email format"}),
    UpdateContext({EmailError: ""})
)

Users see error messages while they're still typing. This creates frustration and cognitive load.

Solution: Validate on meaningful events

// Validate on focus loss, not on every change
EmailInput.OnChange = UpdateContext({EmailTouched: true})

// Separate component for validation trigger
Timer_ValidationDelay.Duration = 1000
Timer_ValidationDelay.OnTimerEnd = If(
    EmailTouched && Len(EmailInput.Text) > 0,
    If(
        !IsMatch(EmailInput.Text, Match.Email),
        UpdateContext({EmailError: "Invalid email format"}),
        UpdateContext({EmailError: ""})
    )
)

EmailInput.OnChange = Reset(Timer_ValidationDelay); Start(Timer_ValidationDelay)

Mistake: Synchronous validation that blocks UI

// This freezes the UI while checking uniqueness
SaveButton.OnSelect = 
    If(
        !IsEmpty(Filter(Users, Email = EmailInput.Text)),
        UpdateContext({SaveError: "Email already exists"}),
        SubmitForm(UserForm)
    )

The Filter() operation blocks the UI thread until it completes, creating a poor user experience.

Solution: Async validation patterns

SaveButton.OnSelect = 
    UpdateContext({IsSaving: true});
    
    // Use concurrent operations
    ConcurrentTable(
        {
            ValidationResult: If(
                !IsEmpty(Filter(Users, Email = EmailInput.Text)),
                {Valid: false, Message: "Email already exists"},
                {Valid: true, Message: ""}
            )
        }
    );
    
    If(
        ValidationResult.Valid,
        SubmitForm(UserForm),
        UpdateContext({SaveError: ValidationResult.Message})
    );
    
    UpdateContext({IsSaving: false})

Data Table Performance Traps

Mistake: Overloading data tables with computed columns

// This kills performance with complex calculations
DataTable1.Items = AddColumns(
    AddColumns(
        AddColumns(
            LargeDataset,
            "ComputedValue1", /* complex calculation */
        ),
        "ComputedValue2", /* another complex calculation */
    ),
    "ComputedValue3", /* yet another calculation */
)

Each AddColumns() creates a new iteration over the dataset. With large datasets, this becomes exponentially expensive.

Solution: Single AddColumns with multiple computed values

DataTable1.Items = AddColumns(
    LargeDataset,
    "ComputedValue1", /* calculation 1 */,
    "ComputedValue2", /* calculation 2 */,  
    "ComputedValue3", /* calculation 3 */
)

Or better yet, compute values in your data source if possible (calculated columns in SharePoint, computed columns in SQL, etc.).

Memory Management Issues

Mistake: Creating memory leaks with global variables

// This accumulates data in global collections
Button1.OnSelect = Collect(GlobalData, LargeDataset)

Global collections persist for the entire app session. Repeatedly collecting large datasets causes memory bloat.

Solution: Manage collection lifecycle

// Clear before collecting
Button1.OnSelect = 
    Clear(GlobalData);
    Collect(GlobalData, FirstN(LargeDataset, 100)) // Limit data volume

Mistake: Nested With() statements that duplicate data

// This creates multiple copies of large datasets in memory
With({_data1: LargeDataset},
    With({_data2: Filter(_data1, Condition1)},
        With({_data3: AddColumns(_data2, "NewColumn", Calculation)},
            /* use _data3 */
        )
    )
)

Solution: Chain operations efficiently

With({
    _filteredAndComputed: AddColumns(
        Filter(LargeDataset, Condition1),
        "NewColumn", Calculation
    )
},
    /* use _filteredAndComputed */
)

Debugging Techniques for Data Controls

When things go wrong, systematic debugging saves time:

Create debug information displays:

// Debug label showing delegation status
DebugLabel.Text = "Gallery Items: " & CountRows(Gallery1.AllItems) & 
                  " | Visible: " & CountRows(Gallery1.Items) &
                  " | Delegation: " & If(CountRows(Gallery1.AllItems) = 2000, "BROKEN", "OK")

Log performance metrics:

// Performance tracking in App.OnStart
Set(PerfLog, Table({Timestamp: Now(), Operation: "App Start", Duration: 0}));

// In expensive operations
With({_start: Now()},
    /* your operation */;
    Collect(PerfLog, {
        Timestamp: Now(),
        Operation: "Gallery Filter",
        Duration: DateDiff(_start, Now(), Milliseconds)
    })
)

Create test data generators:

// Generate test data for performance testing
ForAll(
    Sequence(1000),
    Collect(TestTickets, {
        ID: Value,
        Title: "Test Ticket " & Text(Value),
        Status: Index(["Open", "In Progress", "Closed"], Mod(Value, 3) + 1),
        Created: DateAdd(Now(), -Rand() * 365, Days),
        Priority: Index(["Low", "Medium", "High"], Mod(Value, 3) + 1)
    })
)

Pro Tip: Always test with realistic data volumes. An app that works with 50 test records might fail catastrophically with 5,000 production records.

Summary & Next Steps

You've now mastered the deep architecture of Power Apps data controls—galleries, forms, and data tables. You understand their performance characteristics, delegation strategies, and integration patterns. More importantly, you can diagnose performance issues, implement advanced features like inline editing and bulk operations, and choose the right control for each scenario.

The key insights to remember:

  • Galleries excel at performance and customization but require more development effort
  • Forms prioritize user experience and data integrity for single-record operations
  • Data tables provide familiar interfaces with minimal development but limited customization
  • Delegation is critical for performance, but it requires understanding each data source's capabilities
  • Validation should be async and user-friendly, not blocking or intrusive
  • Performance optimization requires understanding memory management, virtualization, and network efficiency

Your next steps depend on your specific use cases:

For large-scale enterprise applications, focus on advanced delegation patterns, performance monitoring, and error handling strategies. Study how to integrate with Azure services, implement offline capabilities, and design for governance at scale.

For complex business processes, dive deeper into form validation frameworks, workflow integration, and approval patterns. Learn how to build reusable validation components and integrate with Power Automate for complex business logic.

For data-intensive scenarios, explore advanced data modeling techniques, caching strategies, and integration with Power BI for analytics. Understand how to optimize for different data sources and implement effective data synchronization patterns.

The foundation you've built here will support whatever direction you choose. These aren't just controls—they're the building blocks of sophisticated business applications that scale, perform, and delight users.

Learning Path: Canvas Apps 101

Previous

Your First Power App: Build a Data Entry Form in 30 Minutes

Next

Master Power Apps Formulas: Navigate, Filter, Lookup, and Patch for Professional Apps

Related Articles

Power Apps⚡ Practitioner

Model-Driven Apps vs Canvas Apps: When to Use Which Platform

15 min
Power Apps🌱 Foundation

Power Apps Security: Roles, Sharing, and Data Permissions

16 min
Power Apps🔥 Expert

Power Apps Components: Build Reusable UI Elements for Enterprise Scale

20 min

On this page

  • Prerequisites
  • The Data Control Trinity: Architecture and Trade-offs
  • Gallery Controls: The Performance Powerhouse
  • Form Controls: The User Experience Champion
  • Data Tables: The Familiar Workhorse
  • Gallery Mastery: Building High-Performance Data Interfaces
  • Delegation Strategies That Actually Work
  • Advanced Search and Performance Optimization
  • Memory Management and Virtualization Tuning
  • Building Responsive Galleries
  • Handling Complex Form State
  • Optimistic UI Updates and Conflict Resolution
  • Data Table Optimization: Excel-Like Performance
  • Delegation-Friendly Data Table Patterns
  • Performance Tuning for Large Datasets
  • Custom Column Types and Formatting
  • Advanced Integration Patterns
  • Master-Detail Architectures
  • Inline Editing Patterns
  • Bulk Operations Architecture
  • Hands-On Exercise
  • Exercise Setup
  • Part 1: Build the Master Gallery
  • Part 2: Implement Advanced Search with Debouncing
  • Part 3: Build Inline Editing Capability
  • Part 4: Implement Bulk Operations
  • Part 5: Add Performance Monitoring
  • Common Mistakes & Troubleshooting
  • Delegation Pitfalls That Kill Performance
  • Form Validation Anti-Patterns
  • Data Table Performance Traps
  • Memory Management Issues
  • Debugging Techniques for Data Controls
  • Summary & Next Steps
  • Form Engineering: Beyond Basic CRUD
  • Advanced Validation Architectures
  • Handling Complex Form State
  • Optimistic UI Updates and Conflict Resolution
  • Data Table Optimization: Excel-Like Performance
  • Delegation-Friendly Data Table Patterns
  • Performance Tuning for Large Datasets
  • Custom Column Types and Formatting
  • Advanced Integration Patterns
  • Master-Detail Architectures
  • Inline Editing Patterns
  • Bulk Operations Architecture
  • Hands-On Exercise
  • Exercise Setup
  • Part 1: Build the Master Gallery
  • Part 2: Implement Advanced Search with Debouncing
  • Part 3: Build Inline Editing Capability
  • Part 4: Implement Bulk Operations
  • Part 5: Add Performance Monitoring
  • Common Mistakes & Troubleshooting
  • Delegation Pitfalls That Kill Performance
  • Form Validation Anti-Patterns
  • Data Table Performance Traps
  • Memory Management Issues
  • Debugging Techniques for Data Controls
  • Summary & Next Steps