Регистрация  |  Вход

Использование АПЕКС контроллер объекта непосредственно в Аура компоненте

Настал день, когда начинаю пилить Лайтнинг компоненты для боевого проекта, а не самообразования.

Встретил в Аура компоненте решение, которое не видел в обучающих материалах, и глядя на него, мне искренно не понятно, это лайфхак, паттерн, анти-паттерн, да и вообще "норма, ли это"?

В общем, в АПЕКС контроллере объявляются проперти доступные для Ауры.

В самом Аура компоненте объявляется проперти с типом этого самого АПЕКС контроллера.

В вызываемом из Аура контроллера методе АПЕКС контроллера выполняется какая-то работа как обычно, и создается объект нашего контроллера, в нужные проперти передаются значения, и это объект возвращается в Аура контроллер с всеми данными, где set-ается в Аура проперти.

И далее в Аура компоненте спокойно используется данные из этого самого объекта в соответсвии с нужной логикой.

Обратно, объект не возвращается, возвращаются более простые и конкретно-необходимые для вызываемого метода аргументы, но вероятно можно вернуть и весь объект, тогда он фактически может хранить state между вызовами АПЕКС методов.

Что думаете?

Настал день, когда начинаю пилить Лайтнинг компоненты для боевого проекта, а не самообразования.

Встретил в Аура компоненте решение, которое не видел в обучающих материалах, и глядя на него, мне искренно не понятно, это лайфхак, паттерн, анти-паттерн, да и вообще "норма, ли это"?

В общем, в АПЕКС контроллере объявляются проперти доступные для Ауры.

В самом Аура компоненте объявляется проперти с типом этого самого АПЕКС контроллера.

В вызываемом из Аура контроллера методе АПЕКС контроллера выполняется какая-то работа как обычно, и создается объект нашего контроллера, в нужные проперти передаются значения, и это объект возвращается в Аура контроллер с всеми данными, где set-ается в Аура проперти. 

И далее в Аура компоненте спокойно используется данные из этого самого объекта в соответсвии с нужной логикой.

Обратно, объект не возвращается, возвращаются более простые и конкретно-необходимые для вызываемого метода аргументы, но вероятно можно вернуть и весь объект, тогда он фактически может хранить state между вызовами АПЕКС методов.

Что думаете?

Есть такое дело. Но учитывая что это уже умирающая технология, я бы не заморачивался

Есть такое дело. Но учитывая что это уже умирающая технология, я бы не заморачивался

сделал свой первый "большой" Аура компонент для боевого проекта. Это wizard страница, где юзер проходит через серию скринов, каждый скрин имеет какую то логику.

использовал самый прямолинейный подход, чтобы и выполнить работу вовремя, и не рисковать результатом, что было вполне себе challenge, так как это мой первый проект полностью в лайтнинге.

План действий был такой:
(1) На базе ВФ страницы и ее контролера я сделал полнофункциональный макет будущего wizardа. Задача была написать и протестировать логику выполняемую в контроллере.

(2) После этого началась "перешивочка" с ВФ интерфейса на Аура:
- создавался новый UI в Аура и
- войдовые методы контроллера клонировались и переделывались под Аура: теперь они принимают объект класса этого самого контроллера и возвращают его, используя объект как хранилице State wizardа. Сама логика осталась прежней
- Аура UI подключается к логике апекс контроллера через простые методы Аура контроллера, которые отправляют на сервер объект класса контроллера и принимают его.

Вот так вот просто. Не сказать что идеально, но "пойдет" для начала

сделал свой первый "большой" Аура компонент для боевого проекта. Это wizard страница, где юзер проходит через серию скринов, каждый скрин имеет какую то логику.

использовал самый прямолинейный подход, чтобы и выполнить работу вовремя, и не рисковать результатом, что было вполне себе challenge, так как это мой первый проект полностью в лайтнинге.

План действий был такой:
(1) На базе ВФ страницы и ее контролера я сделал полнофункциональный макет будущего wizardа. Задача была написать и протестировать логику выполняемую в контроллере.

(2) После этого началась "перешивочка" с ВФ интерфейса на Аура:
-  создавался новый UI в Аура и
- войдовые методы контроллера клонировались и переделывались под Аура: теперь они принимают объект класса этого самого контроллера и возвращают его, используя объект как хранилице State wizardа. Сама логика осталась прежней
-  Аура UI подключается к логике апекс контроллера через простые методы Аура контроллера, которые отправляют на сервер объект класса контроллера и принимают его.

Вот так вот просто. Не сказать что идеально, но "пойдет" для начала :)




Для wizard мне кажется лучше использовать flow

Для wizard мне кажется лучше использовать flow

wilder
Для wizard мне кажется лучше использовать flow

вот и я о том же:
Flow wizard with Next/Previous/Pause уже встроен в Flow а для UI в компоненту Screen, можно вставлять LWC или Aura

[quote="wilder"]Для wizard мне кажется лучше использовать flow[/quote]

вот и я о том же:
Flow wizard with Next/Previous/Pause уже встроен в Flow а для UI в компоненту Screen,  можно вставлять LWC или Aura 

Идея использования объекта контроллера как "носителя" данных между фронтом и сервером работает и с LWC.

Но, как говорится, есть одно НО. И я думаю, что я просто что-то делаю неправильно.

Вот например, если в VF page я использую <apex:inputField value="{!object.field}" /> или в Аура <lightning:formattedText value="{!object.field}" />, то значение object.field связано с инпутом в обоих направлениях: инпут ренедрит пришедшее значение, и отправляет на сервер (или передается в object.field и отправляет на сервер) текущее, возможно измененное пользователем значение. Не получается добиться того же в LWC.

Вот код:

Апекс контроллер:

public with sharing class ContactController {

@AuraEnabled Public List<Contact> myContacts {set; get;}
@AuraEnabled Public String accName {set; get;}

@AuraEnabled(cacheable=true)
public static ContactController initController() {
ContactController con = new ContactController();

con.myContacts = [SELECT Id, Name, FirstName, LastName, Title, Phone, Email FROM Contact LIMIT 10];
con.accName ='Account 1';

return con;
}

// актуальный метод, но хотелось бы без второго аргумента!
@AuraEnabled(cacheable=true)
public static ContactController getControllerWithArgument( ContactController contrl, String accName) {

contrl.accName = accName;

contrl.myContacts = [SELECT Id, Name, FirstName, LastName, Title, Phone, Email FROM Contact Where Account.Name =:contrl.accName];

return contrl;
}
}

LWC:

import { LightningElement, track, api } from 'lwc';

import getControllerWithArgument from '@salesforce/apex/ContactController.getControllerWithArgument';
import initController from '@salesforce/apex/ContactController.initController';

export default class LWCwithControllerObject extends LightningElement {
@track contacts;
@track error;

@track con = {}; // или @api результат один

connectedCallback() {

initController()
.then(result => {
console.log('result');
console.log(result);
this.con = result;
})
.catch(error => {
console.log('error');
console.log(error);
this.error = error;
});

}

handleLoad() {

console.log('in handleLoad');

var myInput = this.template.querySelector(".inputCmp");
console.log('1: '+myInput.value);
console.log('2: '+this.con.accName);

/* БОЛЬ и РАЗОЧАРОВАНИЕ! ЧТО НЕ ПРОБУЮ, НЕ МОГУ ПЕРЕДАТЬ ЗНАЧЕНИЕ В this.con.accName
приходится отправлять это myInput.value вторым аргументом

myInput.forEach(function(element){

this.con.accName=element.value;

},this);
*/

//this.con.accName = Object.assign({}, String(myInput.value));
// this.con.accName = String(myInput.value);
//Object.assign({}, data);

console.log('3: '+this.con.accName);

getControllerWithArgument({ contrl : this.con, accName : String(myInput.value) })
.then(result => {
console.log('result');
console.log(result);
this.con = result;
})
.catch(error => {
console.log('error');
console.log(error);
this.error = error;
});

}
}

с фронтом все просто:

<template>
<lightning-card title="LCW with controller object" icon-name="custom:custom63">
<div class="slds-m-around_medium">
<p class="slds-m-bottom_small">

<lightning-input name="input1" aura:id="myinput" type="text" class="inputCmp slds-m-bottom_small" label="Account name: " value={con.accName}></lightning-input>

<lightning-button label="Load Contacts" onclick={handleLoad}></lightning-button>
</p>
<template if:true={con.myContacts}>
myContacts:<br/>
<template for:each={con.myContacts} for:item="contact">
<p key={contact.Id}>{contact.Name}</p>
</template>
</template>

<template if:true={error}>
<p>Error: {error}</p>
</template>
</div>
</lightning-card>
</template>

мало того что значение con.accName не связано в обратном направлении, так просто передать в con.accName значение не получается: код тихо-тихо падает, без всяких логов в веб консоли.

Идея использования объекта контроллера как "носителя" данных между фронтом и сервером работает и с LWC.

Но, как говорится, есть одно НО. И я думаю, что я просто что-то делаю неправильно.

Вот например, если в VF page я использую <apex:inputField value="{!object.field}" /> или в Аура <lightning:formattedText value="{!object.field}" />, то значение object.field связано с инпутом в обоих направлениях: инпут ренедрит пришедшее значение, и отправляет на сервер (или передается в object.field и отправляет на сервер) текущее, возможно измененное пользователем значение. Не получается добиться того же в LWC.

Вот код:

Апекс контроллер:

[code]public with sharing class ContactController {

    @AuraEnabled Public List<Contact> myContacts {set; get;}
    @AuraEnabled Public String accName {set; get;}

    @AuraEnabled(cacheable=true)
    public static ContactController initController() {
        ContactController con = new ContactController();

            con.myContacts = [SELECT Id, Name, FirstName, LastName, Title, Phone, Email FROM Contact LIMIT 10]; 
            con.accName ='Account 1';
 
       return con;
    }

// актуальный метод, но хотелось бы без второго аргумента!
    @AuraEnabled(cacheable=true)
    public static ContactController getControllerWithArgument( ContactController contrl, String accName) {

        contrl.accName = accName;

        contrl.myContacts = [SELECT Id, Name, FirstName, LastName, Title, Phone, Email FROM Contact Where Account.Name =:contrl.accName]; 

       return contrl;
    }
}[/code]

LWC:
[code]
import { LightningElement, track, api } from 'lwc';

import getControllerWithArgument from '@salesforce/apex/ContactController.getControllerWithArgument';
import initController from '@salesforce/apex/ContactController.initController';

export default class LWCwithControllerObject extends LightningElement {
    @track contacts;
    @track error;

    @track  con = {}; // или @api результат один
 
    connectedCallback() {

        initController()
        .then(result => {
            console.log('result');
            console.log(result);
            this.con = result;
        })
        .catch(error => {
            console.log('error');
            console.log(error);
            this.error = error;
        });

    }

    handleLoad() {

        console.log('in handleLoad');

        var myInput = this.template.querySelector(".inputCmp");
        console.log('1: '+myInput.value);
        console.log('2: '+this.con.accName);

        /* БОЛЬ и РАЗОЧАРОВАНИЕ! ЧТО НЕ ПРОБУЮ, НЕ МОГУ ПЕРЕДАТЬ ЗНАЧЕНИЕ В this.con.accName
           приходится отправлять это myInput.value вторым аргументом

            myInput.forEach(function(element){

                    this.con.accName=element.value;
    
            },this);
        */

        //this.con.accName = Object.assign({}, String(myInput.value));
        // this.con.accName = String(myInput.value);
        //Object.assign({}, data);

        console.log('3: '+this.con.accName);

        getControllerWithArgument({ contrl : this.con, accName : String(myInput.value) })
        .then(result => {
            console.log('result');
            console.log(result);
            this.con = result;
        })
        .catch(error => {
            console.log('error');
            console.log(error);
            this.error = error;
        });

    }
}[/code]

с фронтом все просто:
[code]<template>
    <lightning-card title="LCW with controller object" icon-name="custom:custom63">
        <div class="slds-m-around_medium">
            <p class="slds-m-bottom_small">

                <lightning-input  name="input1" aura:id="myinput" type="text" class="inputCmp slds-m-bottom_small" label="Account name: " value={con.accName}></lightning-input>
               
                <lightning-button label="Load Contacts" onclick={handleLoad}></lightning-button>
            </p>
            <template if:true={con.myContacts}>
                myContacts:<br/>
                <template for:each={con.myContacts} for:item="contact">
                    <p key={contact.Id}>{contact.Name}</p>
                </template>
            </template>

            <template if:true={error}>
                <p>Error: {error}</p>
            </template>
        </div>
    </lightning-card>
</template>[/code]

мало того что значение [b][i]con.accName[/i][/b] не связано в обратном направлении, так просто передать в [b][i]con.accName[/i][/b] значение не получается: код тихо-тихо падает, без всяких логов в веб консоли.

Почему не получается?

Вешаешь 2 callback. Один на change, второй на clear. И все то же самое получаешь

Почему не получается?

Вешаешь 2 callback. Один на change, второй на clear. И все то же самое получаешь

wilder
Вешаешь 2 callback. Один на change, второй на clear.

а можно подробнее

задача передать в con.accName текущее значение из соответствующего инпута

[quote="wilder"]Вешаешь 2 callback. Один на change, второй на clear. [/quote]

а можно подробнее

задача передать в [b][i]con.accName[/i][/b] текущее значение из соответствующего инпута

На сколько я понял ты не можешь присвоить значение поскольку потерял контекст, используй в forEach стрелочную функцию, должно помочь.

На сколько я понял ты не можешь присвоить значение поскольку потерял контекст, используй в forEach стрелочную функцию, должно помочь.

Разобрался!

код падает вот здесь:

this.con.accName = myInput.value;

потому что метод объявлен (cacheable=true) и переменная this.con - read-only.

не знаю зачем мне нужен (cacheable=true), убрал. Также можно было клонировать объект и работать с клоном.

нашел ответы вот здесь:
https://salesforce.stackexchange.com/questions/256761/uncaught-typeerror-set-on-proxy-trap-returned-falsish-for-property-name

Но в целом пока неприятно удивляюсь что в LWC нет обратного связывания между инпутом и проперти, и новое значение нужно "вручную" кодом получать из инпута и передавать в проперти. Ну разве это дело?

Разобрался!

код падает вот здесь:

[code]this.con.accName = myInput.value;[/code]

потому что метод объявлен (cacheable=true) и переменная this.con - read-only.

не знаю зачем мне нужен (cacheable=true), убрал. Также можно было клонировать объект и работать с клоном.

нашел ответы вот здесь:
https://salesforce.stackexchange.com/questions/256761/uncaught-typeerror-set-on-proxy-trap-returned-falsish-for-property-name

Но в целом пока неприятно удивляюсь что в LWC нет обратного связывания между инпутом и проперти, и новое значение нужно "вручную" кодом получать из инпута и передавать в проперти. Ну разве это дело?

Den Brown
Но в целом пока неприятно удивляюсь что в LWC нет обратного связывания между инпутом и проперти, и новое значение нужно "вручную" кодом получать из инпута и передавать в проперти. Ну разве это дело?

Это сделано только ради увеличения производительности и для того чтобы юзер сам решал что и как ему связывать. Ровно как нельзя вытянуть в lwc сразу все лейблы, а только по одно, только то что конкретно тебе сейчас нуно

[quote="Den Brown"]Но в целом пока неприятно удивляюсь что в LWC нет обратного связывания между инпутом и проперти, и новое значение нужно "вручную" кодом получать из инпута и передавать в проперти. Ну разве это дело?[/quote]
Это сделано только ради увеличения производительности и для того чтобы юзер сам решал что и как ему связывать. Ровно как нельзя вытянуть в lwc сразу все лейблы, а только по одно, только то что конкретно тебе сейчас нуно