Skip to content
Learni
Voir tous les tutoriels
Power Platform

Comment créer un contrôle PCF personnalisé pour Power Apps en 2026

Read in English

Introduction

La Power Platform révolutionne le low-code, mais pour des besoins avancés, les contrôles PCF (Power Apps Component Framework) permettent d'injecter du code TypeScript natif dans vos Power Apps Canvas ou Model-Driven. Imaginez un compteur interactif : un label dynamique affichant une entrée utilisateur, un bouton incrémentant un compteur, et une sortie Power Fx pour binder à d'autres contrôles. Ce tutoriel avancé vous guide pas à pas pour créer, tester et déployer un tel contrôle en 2026.

Pourquoi c'est crucial ? Les PCF étendent les limites du low-code : performances optimales, intégration JS libraries, dataset-aware pour grids custom. Avec PAC CLI, l'automatisation est pro. À la fin, vous bookmarkederez ce guide pour vos projets enterprise. Durée estimée : 45 min. Préparez votre environnement dev.

Prérequis

  • Compte Power Apps avec environnement développeur (gratuit sur make.powerapps.com).
  • Node.js 18+ et npm installés.
  • Visual Studio Code avec extensions Power Platform Tools.
  • Power Platform CLI (PAC CLI) v1.12+.
  • Connaissances avancées en TypeScript, Power Fx et Dataverse.

Installer PAC CLI

install-pac-cli.ps1
npm install -g @microsoft/powerplatform-cli --unsafe-perm=true
pac --version
pac auth create --url https://<votre-environnement>.crm.dynamics.com

Ce script PowerShell installe globalement PAC CLI, vérifie la version et crée un profil d'authentification pour votre environnement Power Apps. Remplacez par votre instance Dataverse. Le flag --unsafe-perm évite les erreurs de permissions npm ; authentifiez-vous via browser.

Initialiser le projet PCF

Une fois PAC CLI prêt, initialisez un projet PCF vide. Cela génère la structure boilerplate : index.ts, manifest XML, package.json. Nous customiserons pour un contrôle CounterLabel : entrée texte, bouton cliquable, sortie numérique (compteur).

Initialiser et configurer package.json

terminal-init.sh
mkdir pcf-counterlabel && cd pcf-counterlabel
pac pcf init --namespace Contoso --name CounterLabel --template field
npm install
npm install --save-dev typescript@5.5.4
cat > package.json << 'EOF'
{
  "name": "pcf-counterlabel",
  "version": "0.0.1",
  "description": "Advanced Counter Label PCF",
  "private": true,
  "main": "CounterLabel.js",
  "scripts": {
    "build": "gulp build",
    "watch": "gulp watch",
    "bundle": "gulp bundle",
    "clean": "gulp clean",
    "extension": "gulp extension"
  },
  "dependencies": {
    "@microsoft/powerapps-component-framework": "~2.0.10"
  },
  "devDependencies": {
    "@types/node": "^20.0.0",
    "@types/powerapps-component-framework": "~2.0.10",
    "del": "^7.1.0",
    "eslint": "^8.57.0",
    "gulp": "^4.0.2",
    "gulp-eslint": "^6.0.0",
    "gulp-sourcemaps": "3.0.0",
    "gulp-typedoc": "^2.2.1",
    "gulp-typescript": "^6.0.0-alpha.1",
    "typescript": "5.5.4",
    "webpack": "^5.94.0",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^5.1.0"
  }
}
EOF

Ce bash init le projet avec template field, installe deps et remplace package.json pour compat 2026 (TypeScript 5.5, PCF 2.0). Le template génère les fichiers de base ; nous les overriderons ensuite. Lancez npm run build pour vérifier.

Définir le manifest XML

ControlManifest.Input.xml
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
  <control namespace="Contoso" constructor="CounterLabel" version="0.0.1" display-name-key="CounterLabel" description-key="Dynamic counter label control" control-type="standard" preview-image="~/assets/images/Control.png">
    <resources>
      <code path="index.ts" order="1"/>
      <css path="CounterLabel.css" order="1" />
      <resx path="strings/CounterLabel.1033.resx" version="1.0" />
    </resources>
    <data-set name="sampleDataSet" display-name-key="DataSet" />
    <property name="inputText" display-name-key="Text_Input" description-key="The text to display" of-type="SingleLine.Text" usage="input" required="true" />
    <property name="counterValue" display-name-key="Counter_Value" description-key="Output counter" of-type="Whole.Number" usage="output" />
    <feature-usage>
      <uses-feature name="BoardingAvailable" required="true" />
    </feature-usage>
    <control-manifest-schemas>
      <schema version="1.0" name="ControlManifestSchema" />
    </control-manifest-schemas>
  </control>
</manifest>

Ce manifest définit le contrôle avec entrée inputText (string), sortie counterValue (number), et dataset optionnel. usage="output" permet d'assigner via Power Fx comme CounterLabel_1.counterValue. Ajoutez preview PNG dans assets/images. Versionnez pour ALM.

Implémenter le code TypeScript core

Le cœur du PCF est index.ts : lifecycle hooks (init, updateView, destroy), context pour properties, notifications pour outputs. Notre contrôle affiche inputText + ' (' + count + ')', bouton incrémente count et notifie sortie.

Écrire index.ts complet

index.ts
import { IInputs, IOutputs } from './generated/ManifestTypes';
import * as React from 'react';
import * as ReactDOM from 'react-dom';

export class CounterLabel implements ComponentFramework.StandardControl<IInputs, IOutputs> {
  private _container: HTMLDivElement;
  private _count: number = 0;
  private _notifyOutputChanged: () => void;
  private _rootElement: React.ComponentElement<any, any>;

  public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: any, container: HTMLDivElement): void {
    this._container = document.createElement('div');
    this._container.style.width = '100%';
    this._container.style.height = '100%';
    this._container.className = 'counter-container';
    this._notifyOutputChanged = notifyOutputChanged;
    container.appendChild(this._container);
    this.renderReact();
  }

  public updateView(context: ComponentFramework.Context<IInputs>): void {
    this._rootElement = React.createElement(CounterLabelComponent, {
      inputText: context.parameters.inputText.formatted ? context.parameters.inputText.formatted : '',
      count: this._count,
      onIncrement: () => {
        this._count++;
        this._notifyOutputChanged();
      }
    });
    ReactDOM.render(this._rootElement, this._container);
  }

  public getOutputs(): IOutputs {
    return {
      counterValue: this._count
    };
  }

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

  private renderReact(): void {
    this.updateView({} as any);
  }
}

interface CounterLabelProps {
  inputText: string;
  count: number;
  onIncrement: () => void;
}

const CounterLabelComponent: React.FC<CounterLabelProps> = ({ inputText, count, onIncrement }) => (
  <div style={{ padding: '10px', fontFamily: 'Segoe UI', border: '1px solid #ccc', borderRadius: '4px' }}>
    <label>{inputText} ({count})</label>
    <button onClick={onIncrement} style={{ marginLeft: '10px', padding: '5px 10px' }}>
      +1
    </button>
  </div>
);

Ce code utilise React pour render (optionnel mais pro), gère updateView pour reactivity, getOutputs() expose le compteur. notifyOutputChanged() trigger Power Fx updates. Lifecycle complet évite memory leaks. Ajoutez CSS pour styles avancés.

Ajouter les styles CSS

CounterLabel.css
.counter-container {
  display: flex;
  align-items: center;
  font-size: 14px;
}

.counter-container button {
  background-color: #0078d4;
  color: white;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.counter-container button:hover {
  background-color: #106ebe;
}

@media (max-width: 480px) {
  .counter-container {
    flex-direction: column;
  }
}

CSS responsive pour mobile-first, hover effects. Chargé via manifest, scope au container. Utilisez Tailwind si besoin via CDN, mais vanilla pour perf. Testez en F12 devtools.

Build et test local

build-test.sh
npm run build
pac solution init --publisher-name "Contoso" --publisher-prefix "contoso"
pac solution add-reference --path %CD%
npm run start
# Ouvrez https://localhost:8181 pour test harness

Build compile TS→JS, init solution unmanaged. npm run start lance webpack dev server avec harness pour tester properties live. Debuggez JS en browser. Une fois OK, pac solution clone pour managed.

Intégrer dans une Power App Canvas

Étapes : 1. pac pcf push pour importer en prod. 2. Créez une app Canvas, ajoutez Custom Control > CounterLabel. 3. Bind inputText à TextInput1.Text, counterValue à Label2 via Power Fx : CounterLabel1_1.counterValue. Testez clics !

Power Fx pour binding dans l'app

app-formulas.pfx
// Label2.Text
CounterLabel1.counterValue

// Update inputText
UpdateContext({
  varInput: TextInput1.Text
});
CounterLabel1.inputText = varInput

// Flow trigger on count > 5
If(CounterLabel1.counterValue > 5, Notify("High count!"))

Ces formules Power Fx bindent entrée/sortie. UpdateContext refresh input. Intégrez à flows Power Automate via trigger sur variable. Scalable pour datasets : itérez sur collection.

Déployer la solution

deploy.sh
pac solution build --enhanced
mkdir bin/for%20package
copy %CD%/out/* bin/for%20package/
pac solution pack --zip-file bin/Contoso.CounterLabel.zip
pac solution import --path bin/Contoso.CounterLabel.zip

Build enhanced pour prod, pack en ZIP managed. Import auto publie le contrôle. Utilisez CI/CD Azure DevOps pour ALM. Versionnez ZIP en artifacts.

Bonnes pratiques

  • Perf : Utilisez context.mode.isOffline pour cache IndexedDB ; évitez heavy libs.
  • Accessibilité : Ajoutez ARIA labels, keyboard nav (Enter pour increment).
  • Dataset-aware : Étendez à react template pour grids ; paginez avec context.parameters.sampleDataSet.
  • Sécurité : Validez inputs client-side ; pas de secrets en code.
  • Tests : Ajoutez Jest ; mock context pour unit tests.

Erreurs courantes à éviter

  • Oublier notifyOutputChanged() : outputs ne sync pas avec Power Fx.
  • Ignorer destroy() : leaks mémoire en app multi-écrans.
  • Manifest malformé : validez XML avec VS Code schema.
  • Build sans enhanced : debug symbols manquants en prod.

Pour aller plus loin

Plongez dans PCF datasets pour custom grids (docs.microsoft.com/power-apps/developer). Intégrez D3.js pour charts. Découvrez nos formations Power Platform Learni : certification PL-400 avancée. Repo GitHub exemple : github.com/learni-dev/pcf-counterlabel.