Power Platform

Custom Components in PowerApps with Component Framework


The PowerApps Component Framework (PCF) was first announced in April 2019 as a public preview feature for model-driven apps. Later that year in September the public preview for canvas apps has been announced. With this addition to the Power Platform, professional developers can further extend the capabilities of their applications with custom web components.

PowerApps Component Framework
PowerApps Component Framework

In contrary to web resources where create HTML pages that you can inject to a form or use it in a popup window, PCF offers the capability to replace the default look and behavior of form elements like fields and sub-grids.

If you are interested in seeing a large collection of open source components – that you can use right away -, head over to https://pcf.gallery/ where you can find lots of great examples!

Today we will dive into what we can expect from PowerApps Component Framework and we will create our own using PCF, React and Microsoft Fluent UI.

How to get started?

PowerApps Component Framework is meant to be used by IT and Professional Developers. Microsoft in its communication now differentiates the following groups of developers:

  • Citizen Developers: Those who doesn’t have the coding skills of a developer but feels themselves to be a techie enough to use low-code solutions like Power Automate and Power Apps with their drag and drop user interfaces.
  • IT Developers and Pro Developers: Developers and people who have actual coding knowledge.

This article is for IT & Pro Developers mostly, with actual knowledge in web development technologies. In order to get started, you will need at least the followings:

  • Have HTML, CSS and JavaScript knowledge (TypeScript is preferred)
  • Visual Studio 2017 or higher installed (for MSBuild)
  • .NET Framework 4.6 (SDK, Targeting Pack) installed
  • Power Apps CLI installed

Create your first component project

In this article we will be creating a number picker component field with minus and plus buttons on each side to increase or decrease the value of the field. Note that at the end of the article I have linked a GitHub repository where you can find the finished component and all the code we will be creating here!

Custom Number Picker Component
Custom Number Picker Component

Once you have everything installed on your machine, it is time to create your first component. This can be done by using Power Apps CLI which we can access from our command line.

1. Create a new directory and initialize your project using the following commands:

mkdir MyFirstComponent
cd MyFirstComponent
pac pcf init --namespace MyNamespace --name MyFirstComponent --template field

2. Install npm packages included inside the project using the npm install command.

3. Open the folder in your favorite code editor. I use Visual Studio Code for example.

Notes
The –template property of the command defines whether you would like to create a component for a dataset (sub-grid and views) or a field. In this tutorial, we will cover fields only.

Changing the manifest file

Manifest is an XML field inside your project that defines a number of details about your component. There is a manifest schema reference available that you should check out, but for now we will only be changing the input property of our control. Replace the existing <property> element with the following code:

<property name="numberValue" display-name-key="Value property" description-key="The property containing the number value" of-type="Whole.None" usage="bound" required="true" />

With this line we have defined that our control can be bound to a field containing a whole number. This will be our data input and output at the same time.

The component logic

When creating a new component, the most important file of all is index.ts. This file implements all the necessary methods for our component to work properly. Let’s see the key segments of this code:

export class MyFirstComponent implements ComponentFramework.StandardControl<IInputs, IOutputs> {
	/**
	 * The constructor is always empty by default.
	 */
	constructor() {	}

	/**
	 * The init method gives you a reference to the context which the component runs in, a notifyOutputChanged method that should be called when your output value changes, and a container HTMLDivElement that you can use to render your elements into.
	 */
	public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
	{
	}

	/**
	 * Define all your render logic here.
	 */
	public updateView(context: ComponentFramework.Context<IInputs>): void
	{
	}

	/** 
	 * We need to implement this call so others can get the output of the control outside the context.
	 */
	public getOutputs(): IOutputs
	{
	}

	/** 
	 * Called when the element is removed from the DOM. Perform cleanup here.
	 */
	public destroy(): void
	{
	}
}

We will be updating this index.ts file with some code to render our custom component:

import * as React from "react";
import * as ReactDOM from "react-dom";
import {IInputs, IOutputs} from "./generated/ManifestTypes";
import NumberPicker, { INumberPickerProps } from "./NumberPicker";

export class MyFirstComponent implements ComponentFramework.StandardControl<IInputs, IOutputs> {

	// Reference to notifyOutputChanged method
	private notifyOutputChanged: () => void;

	// Reference to container element
	private container: HTMLDivElement;

	// Properties that we will pass to our react component
	private props: INumberPickerProps = {
		notifyOnChange: this.onValueChanged.bind(this)
	}

	constructor() {	}

	public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container:HTMLDivElement)
	{
		this.notifyOutputChanged = notifyOutputChanged;
		this.container = container;
		this.props.value = context.parameters.numberValue.raw || 0;
	}

	public updateView(context: ComponentFramework.Context<IInputs>): void
	{
		// Update property if the input value has changed.
		if (context.parameters.numberValue.raw !== this.props.value)
			this.props.value = context.parameters.numberValue.raw || 0;

		ReactDOM.render(
			React.createElement(
				NumberPicker,
				this.props
			),
			this.container
		);
	}

	public getOutputs(): IOutputs
	{
		return {
			numberValue: this.props.value
		};
	}

	public destroy(): void
	{
		ReactDOM.unmountComponentAtNode(this.container);
	}

	/** Called when the child react component's value is updated. */
	onValueChanged(newValue: number): void {
		if (this.props.value !== newValue) {
			this.props.value = newValue;
			this.notifyOutputChanged();
		}
	}
}

As you can see, we are referencing another class called NumberPicker. Since we are using React, I find the tsx code much easier to maintain so I have created a separate React Component with state management.

For this component we will be using Fluent UI from Microsoft. It is a collection of styles and controls based on Microsoft’s own fluent design language.

This package is not part of the project by default, we need to install it first by using the following command:

npm install @fluentui/react

Now that we have it, create your own NumberPicker.tsx file next to your index.ts file with the following content:

import * as React from "react";
import { Stack, IconButton, TextField, Button, FontIcon } from '@fluentui/react';
import { initializeIcons } from '@uifabric/icons';

export interface INumberPickerProps {
    value?: number,
    notifyOnChange?: (newValue: number) => void;
}

export interface INumberPickerState {
    value: number
}

export default class NumberPicker extends React.Component<INumberPickerProps, INumberPickerState>
{
    //#region Constructor

    constructor(props: INumberPickerProps) {
        super(props);

        initializeIcons();

        // Set initial state
        this.state = {
            value: props.value || 0
        };

        // Bind event handlers
        this.onIconButtonAdd_Click = this.onIconButtonAdd_Click.bind(this);
        this.onIconButtonMinus_Click = this.onIconButtonMinus_Click.bind(this);
    }
    
    //#endregion Constructor

    //#region Component Methods

    /** Updates the component when input properties are changing. */
    public componentWillReceiveProps(newProps: INumberPickerProps): void {
        this.setState({
            value: newProps.value || this.state.value
        });
    }

    //#endregion Component Methods

    //#region Render Component

    render() {
        return (
            <Stack tokens={{ childrenGap: 4 }} horizontal verticalAlign="center">

                <Button onClick={this.onIconButtonMinus_Click} default>
                    <FontIcon iconName="Remove" />
                </Button>

                <TextField 
                    value={this.state.value.toString()} 
                    type="number"
                    borderless
                    style={{ textAlign: 'center' }}
                    readOnly />

                <Button onClick={this.onIconButtonAdd_Click} default>
                    <FontIcon iconName="Add" />
                </Button>

            </Stack>
        );
    }

    //#endregion Render Component

    //#region Event Handlers

    /** Decrease our number with 1 on click. */
    onIconButtonMinus_Click() {
        this.setState({
            value: this.state.value - 1
        }, 
        () => {
            // Notify on change after state has been updated
            if (this.props.notifyOnChange) {
                this.props.notifyOnChange(this.state.value);
            }
        });
    }

    /** Increments our number with 1 on click. */
    onIconButtonAdd_Click() {
        this.setState({
            value: this.state.value + 1
        }, 
        () => {
            // Notify on change after state has been updated
            if (this.props.notifyOnChange) {
                this.props.notifyOnChange(this.state.value);
            }
        });
    }

    //#endregion Render Component
}

The code contains all event handlers to change the value of our input field when we click on the plus or minus buttons and of course we notify our caller component through notifyOnChange property that the value has changed.

If all is fine, we should now be able to move forward to the debugging and packaging of our component.

Debugging your component

The Component Framework includes a test environment to try out your components. This test environment can be started by calling the npm run start command inside the terminal from the root folder of your component.

Component Framework Debugger
Component Framework Debugger

The test environment gives you access to some display configuration properties, a list of data inputs you can use and override and the data outputs – when you call the notifyOutputChanged method.

Tips
If you are actively working on your component and want to see changes on the go, you should use the npm start watch command.

Packaging the component

Once you are done building your component, it is time to package it. To use a component inside a Power App, you need to bundle it into a solution file that you can then import into your Common Data Service environment.

1. Create a new folder first where you wish to create your solution, navigate there from the terminal and run the following command:

pac solution init --publisher-name <enter your publisher name> --publisher-prefix <enter your publisher prefix>

2. Once the solution is created inside your folder, you need to reference your component inside it. To do so, run the following command and provide a path to the root folder of your component:

pac solution add-reference --path <path to your Power Apps component framework project>

3. Finally, you should build the project to create the solution. For this, I recommend you to use the Developer Command Prompt for VS to have access to msbuild. Navigate to the solution’s folder and issue the following command:

msbuild /t:build /restore

Notes
If you run the command with /p:configuration=Release, the solution package created will be a managed solution. Otherwise it will be unmanaged so changes can be made inside of it.

4. Import the generated solution file from \bin\debug\ folder to your Common Data Service environment.

5. To add the component as a control to a specific field, I would recommend you to use this guide since it gives you step by step info on that.

GitHub

The component I have made here can be viewed and downloaded from my GitHub Repository. You will find a release of the component too so you can install it in your Common Data Service environment without having to build it manually.

Summary

I know that this guide was pretty long and exhausting, but I really hope you have enjoyed it. With PowerApps Component Framework we are really reaching to a point where you can just do anything inside your Power Apps!

I encourage you to check out the sample components made by Microsoft to see the possibilities of PCF, or go to the PCF Gallery where you can find open sourced work of others. The sample components include codes using Angular and React+Fluent UI too, make sure you check them out!

Are you ready to build some awesome components? Need some help to get started? Send me a message if you have any questions, share what you have done or join the conversation and tell your opinions! And don’t forget to check my GitHub repository for the source code 😉

Power Platform
Extend Your Flows with C# Code in Common Data Service
Power Platform
Microsoft Power Platform: Approach with Low-Code
Power Platform
Power Series: Subscriptions Manager – EP 1
There are currently no comments.