Tutoriel · Salesforce · LWC

LWC et Apex : choisir entre @wire et appel impératif

Deux composants sur une page de compte pour comparer lecture réactive et chargement au clic, puis comprendre les droits, les erreurs et l’actualisation du cache.

Choisir le bon accès aux données

Une liste dépend du compte affiché ; un bouton lance une recherche ; un formulaire enregistre une modification. Ces trois interactions ne demandent pas le même mode d’appel. Ce tutoriel utilise une organisation de démonstration fictive avec les objets standards Account et Contact.

Prérequis : un projet Salesforce DX, une sandbox de démonstration, une page de compte Lightning et un utilisateur autorisé à lire les contacts. Les exemples ciblent l’API 66.0. Commencez par Lightning Data Service ou les composants de formulaire standards lorsque leur contrat suffit ; Apex sert ici à illustrer une lecture métier spécifique.

BesoinChoixConséquence
Lecture dépendant d’un paramètre réactif@wireLe framework fournit les résultats ; le code traite data et error.
Lecture au clic ou à la soumissionAppel impératifLe composant déclenche une promesse et gère son état de chargement.
Création, modification ou suppression via ApexAppel impératifUne méthode qui écrit ne doit pas être déclarée cacheable=true.

Une lecture Apex avec les droits utilisateur

La méthode attend un identifiant typé et renvoie au plus 50 contacts. Le paramètre SOQL est lié ; les permissions sont explicitement prises en compte dans la requête. Le composant ne reçoit ni secret ni jeton d’intégration.

public with sharing class AccountContactsController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts(Id accountId) {
        if (accountId == null) {
            return new List<Contact>();
        }
        return [
            SELECT Id, Name, Email
            FROM Contact
            WHERE AccountId = :accountId
            WITH USER_MODE
            ORDER BY LastName, Id
            LIMIT 50
        ];
    }
}

with sharing exprime la politique de partage de la classe ; WITH USER_MODE rend explicite le contrôle des droits sur les objets et champs. Les valeurs par défaut évoluent selon les versions d’API : les déclarations explicites facilitent la maintenance. Donnez aussi l’accès à la classe Apex par permission set. Un refus de permission n’est pas une liste vide : il doit apparaître comme une erreur dans l’interface.

La limite de 50 rend l’exemple lisible ; elle ne constitue pas une pagination. Pour parcourir davantage de lignes, utilisez le tutoriel de pagination LWC côté serveur.

Lire avec @wire

Le préfixe $ relie l’appel à recordId. Une valeur undefined suspend l’appel ; null est une valeur transmise. Gardez l’objet complet reçu par le wire : c’est celui que refreshApex() attend, et non le tableau de contacts.

// lwc/wiredAccountContacts/wiredAccountContacts.js
import { LightningElement, api, wire } from 'lwc';
import getContacts from '@salesforce/apex/AccountContactsController.getContacts';
import { refreshApex } from '@salesforce/apex';

export default class WiredAccountContacts extends LightningElement {
    @api recordId;
    contacts = [];
    message = '';
    busy = false;
    wiredResult;

    @wire(getContacts, { accountId: '$recordId' })
    receiveContacts(result) {
        this.wiredResult = result;
        if (result.data) {
            this.contacts = result.data;
            this.message = result.data.length ? '' : 'Aucun contact accessible.';
        } else if (result.error) {
            this.contacts = [];
            this.message = 'Lecture impossible. Vérifiez vos accès ou réessayez.';
        }
    }

    async handleReload() {
        if (!this.recordId || !this.wiredResult || this.busy) return;
        this.busy = true;
        try {
            await refreshApex(this.wiredResult);
        } catch {
            this.message = 'Actualisation impossible. Réessayez.';
        } finally {
            this.busy = false;
        }
    }
}
<!-- lwc/wiredAccountContacts/wiredAccountContacts.html -->
<template>
    <lightning-card title="Contacts du compte">
        <div class="slds-p-around_medium">
            <lightning-button label="Actualiser" onclick={handleReload}
                disabled={busy}></lightning-button>
            <template lwc:if={busy}>
                <lightning-spinner alternative-text="Actualisation"
                    size="small"></lightning-spinner>
            </template>
            <p role="status">{message}</p>
            <ul>
                <template for:each={contacts} for:item="contact">
                    <li key={contact.Id}>
                        {contact.Name} — {contact.Email}
                    </li>
                </template>
            </ul>
        </div>
    </lightning-card>
</template>

La donnée reçue est traitée en lecture seule. Pour préparer une édition locale, copiez les objets dans un état distinct plutôt que de modifier les enregistrements fournis par le wire.

Déclencher une lecture impérative

Le second composant utilise le même contrat Apex, mais ne charge rien avant le clic. Copiez le template précédent dans imperativeAccountContacts.html et remplacez le libellé du bouton par « Charger les contacts ».

// lwc/imperativeAccountContacts/imperativeAccountContacts.js
import { LightningElement, api } from 'lwc';
import getContacts from '@salesforce/apex/AccountContactsController.getContacts';

export default class ImperativeAccountContacts extends LightningElement {
    _recordId;
    contacts = [];
    message = '';
    busy = false;

    @api
    get recordId() { return this._recordId; }
    set recordId(value) {
        if (value !== this._recordId) {
            this._recordId = value;
            this.contacts = [];
            this.message = '';
        }
    }

    async handleReload() {
        if (!this.recordId || this.busy) return;
        const requestedAccount = this.recordId;
        this.busy = true;
        this.message = '';
        try {
            const data = await getContacts({ accountId: requestedAccount });
            // La navigation peut changer le compte pendant la requête.
            if (requestedAccount !== this.recordId) return;
            this.contacts = data;
            this.message = data.length ? '' : 'Aucun contact accessible.';
        } catch {
            if (requestedAccount === this.recordId) {
                this.contacts = [];
                this.message = 'Lecture impossible. Réessayez.';
            }
        } finally {
            this.busy = false;
        }
    }
}

La méthode importée reçoit un objet dont les propriétés portent les noms des paramètres Apex : { accountId: requestedAccount }. Le try/catch englobe l’await ; il intercepte ainsi le rejet de la promesse. La garde sur le compte demandé évite d’afficher une réponse appartenant à une ancienne page.

Le setter de recordId efface l’ancienne liste lors d’un changement de compte. L’utilisateur peut lancer une nouvelle lecture après la fin de l’appel précédent ; la garde empêche une réponse périmée de repeupler la liste.

Actualiser le bon cache

Un appel impératif à une méthode cacheable peut être satisfait depuis le cache ; « impératif » ne signifie donc pas « ignorer le cache ». Ne supposez pas une durée de conservation fixe.

  • Après une écriture, une liste fournie par un wire Apex s’actualise avec refreshApex(wiredResult).
  • Après une modification effectuée hors de Lightning Data Service, notifyRecordUpdateAvailable([{ recordId }]) informe LDS des enregistrements concernés. Ce mécanisme ne remplace pas la réexécution d’une liste Apex impérative.
  • Une lecture impérative se relance par son propre parcours de chargement ; n’envoyez pas son tableau de résultats à refreshApex().

Attendez la réussite de l’écriture avant l’actualisation. Si l’écriture réussit mais que la relecture échoue, affichez « enregistré, actualisation impossible » : annoncer un échec d’enregistrement pourrait inciter l’utilisateur à rejouer inutilement l’action.

Déployer et vérifier les deux parcours

<?xml version="1.0" encoding="UTF-8"?>
<!-- wiredAccountContacts.js-meta.xml ; même contenu pour l’autre composant -->
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>66.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <objects><object>Account</object></objects>
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Ajoutez également un fichier AccountContactsController.cls-meta.xml en API 66.0, de statut Active. Déployez la classe et les deux bundles dans la sandbox, attribuez les droits requis, puis placez les composants dans Lightning App Builder. Un compte et ses contacts doivent être créés uniquement pour la démonstration.

  1. Ouvrir un compte avec contacts : la liste réactive se charge ; la liste impérative attend le clic.
  2. Changer de compte : la lecture réactive suit le nouvel identifiant ; une ancienne réponse impérative ne doit pas remplacer les données du compte courant.
  3. Ouvrir un compte sans contact : afficher l’état vide, sans erreur.
  4. Vérifier un utilisateur privé d’accès à un champ demandé ou à la classe : le refus doit rester visible et aucune donnée ne doit être exposée.
  5. Modifier un contact puis actualiser : vérifier séparément la liste Apex et les composants LDS de la page.

Les exemples exposent le contrat et les états de l’interface ; leur validation finale dépend de votre org, des permission sets et des règles de validation. Pour les traitements longs, consultez le guide des traitements Apex asynchrones.

Sources et suite du parcours

Documentation officielle Salesforce : wire Apex, appels impératifs, cache et refreshApex, notification de LDS et sécurité des classes Apex.

Étape suivante : construire un tableau LWC paginé. Pour des appels vers un système externe, distinguez ce parcours utilisateur de l’authentification OAuth d’une intégration Salesforce.

🍪 Gestion des cookies
Ce site utilise des cookies pour mesurer l'audience (Google Analytics) et lire des vidéos (YouTube). Vous pouvez personnaliser votre choix.
Mentions légales
Réglages des cookies
Essentiels Nécessaires au fonctionnement du site — ne peuvent pas être désactivés.
Google Analytics Analyse d'audience (pages vues, sources de trafic). Aucune donnée personnelle identifiante.
YouTube Permet de lire les vidéos YouTube intégrées sur cette page.