Domain 4 β€” Module 3 of 4 75%
13 of 26 overall
Domain 4: Extend the User Experience Free ⏱ ~14 min read

PCF Components: Build & Lifecycle

Build custom visual controls with TypeScript and React using the Power Apps Component Framework. Understand the component lifecycle, configure the manifest, and implement component interfaces.

Building custom controls

Simple explanation

Think of PCF components like LEGO Technic pieces.

Standard LEGO bricks (built-in controls) build most things. But sometimes you need a motor, a gear, or a custom piece that does not exist in the standard set. PCF (Power Apps Component Framework) lets you build those custom pieces using TypeScript and React.

Your custom component follows a lifecycle: it is initialised (born), updated (data changes), asked for output (what value to save), and eventually destroyed (form closes). Understanding this lifecycle is the key to building components that work reliably.

The PCF component lifecycle

Every component goes through four phases:

MethodWhen It RunsWhat You Do
initComponent first loadsSet up DOM elements, event listeners, initial state. Called ONCE.
updateViewData changes (from the platform)Re-render the component with new data. Called MANY times.
getOutputsPlatform requests current valueReturn the current value(s) to save to Dataverse. Called when user interacts.
destroyComponent is removedClean up event listeners, timers, memory. Called ONCE.
// Simplified PCF component structure
export class StarRating implements ComponentFramework.StandardControl<IInputs, IOutputs> {
    
    private container: HTMLDivElement;
    private currentValue: number;
    private notifyOutputChanged: () => void;
    
    // Called ONCE when the component loads
    public init(
        context: ComponentFramework.Context<IInputs>,
        notifyOutputChanged: () => void,
        state: ComponentFramework.Dictionary,
        container: HTMLDivElement
    ): void {
        this.container = container;
        this.notifyOutputChanged = notifyOutputChanged;
        this.currentValue = context.parameters.ratingValue.raw || 0;
        this.renderStars();
    }
    
    // Called when data changes from the platform
    public updateView(context: ComponentFramework.Context<IInputs>): void {
        this.currentValue = context.parameters.ratingValue.raw || 0;
        this.renderStars();
    }
    
    // Called when the platform needs the current value
    public getOutputs(): IOutputs {
        return { ratingValue: this.currentValue };
    }
    
    // Called when the component is removed
    public destroy(): void {
        // Clean up event listeners, timers, etc.
    }
    
    private renderStars(): void {
        // Render star HTML based on this.currentValue
        // When user clicks a star: this.currentValue = n;
        // Then call: this.notifyOutputChanged();
    }
}
The notifyOutputChanged pattern

The notifyOutputChanged callback is critical. When a user interacts with your component (clicks a star, types in a field), you:

  1. Update your internal state (this.currentValue = newValue)
  2. Call this.notifyOutputChanged() to tell the platform β€œmy value changed”
  3. The platform calls getOutputs() to retrieve the new value
  4. The platform saves the value to the bound field

If you forget to call notifyOutputChanged, the platform never knows the value changed and nothing saves. This is a common bug and exam topic.

The component manifest

The manifest (ControlManifest.Input.xml) defines your component’s contract with the platform:

<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="NovaSoft" 
           constructor="StarRating" 
           version="1.0.0" 
           display-name-key="Star Rating" 
           description-key="A star rating control"
           control-type="standard">
    
    <!-- Bound property (the value this component reads/writes) -->
    <property name="ratingValue" 
              display-name-key="Rating Value" 
              of-type="Whole.None" 
              usage="bound" 
              required="true" />
    
    <!-- Input property (configuration, not saved to Dataverse) -->
    <property name="maxStars" 
              display-name-key="Maximum Stars" 
              of-type="Whole.None" 
              usage="input" 
              required="false" 
              default-value="5" />
    
    <resources>
      <code path="index.ts" order="1" />
      <css path="css/StarRating.css" order="1" />
    </resources>
  </control>
</manifest>

Key manifest concepts

ElementPurposeExample
namespaceUnique identifier for your orgNovaSoft
constructorClass name in index.tsStarRating
property (bound)Data field the component reads/writesratingValue β€” saves to Dataverse
property (input)Configuration value (not saved)maxStars β€” set by the form designer
control-typestandard (field) or virtual (no bound property)standard
data-setFor dataset components (grids)Replaces property for collection binding
Field components bind to one value; dataset components bind to collections
FeatureField ComponentDataset Component
Binds toA single column (field)A view or collection of records
ManifestUses <property> with usage='bound'Uses <data-set> element
Use caseStar rating, colour picker, toggleCustom grid, chart, kanban board
Context datacontext.parameters.fieldNamecontext.parameters.dataSet
HostForm field, canvas controlGrid/subgrid, canvas gallery
Scenario: Marcus designs a PCF manifest

Marcus at NovaSoft is building a colour-coded priority badge component. His manifest decisions:

  • Bound property: priorityValue (Whole.None) β€” reads the priority number from Dataverse
  • Input property: colorScheme (SingleLine.Text) β€” lets admins configure β€œwarm” or β€œcool” colours
  • Input property: showLabel (TwoOptions) β€” toggle whether the priority name shows alongside the badge
  • No data-set β€” this is a field-level component (binds to one value)

The manifest defines the contract; the index.ts implements the visual rendering.

Question

What are the four lifecycle methods of a PCF component?

Click or press Enter to reveal answer

Answer

init (called once, set up DOM), updateView (called on data changes, re-render), getOutputs (return current value to platform), destroy (clean up resources). The platform controls when each is called β€” you implement the logic.

Click to flip back

Question

What is the purpose of notifyOutputChanged in a PCF component?

Click or press Enter to reveal answer

Answer

notifyOutputChanged is a callback you receive in init(). When the user changes the component's value (e.g., clicks a star), you call notifyOutputChanged() to tell the platform the value changed. The platform then calls getOutputs() to retrieve the new value. Without this call, changes are never saved.

Click to flip back

Question

What is the difference between a 'bound' and 'input' property in a PCF manifest?

Click or press Enter to reveal answer

Answer

A bound property connects to a Dataverse column β€” the component reads and writes this value. An input property is configuration that the form designer sets β€” like 'max stars' or 'colour scheme'. Input properties do not save to Dataverse.

Click to flip back

Knowledge Check

Marcus builds a PCF component for a progress bar. When the user drags the slider, the component should update the bound Dataverse field. The component renders correctly but the value never saves. What is the most likely cause?

Next up: PCF Components: Package, Deploy & Advanced Features β€” getting your component from code to production, plus Device and Web API features.