Настал день, когда начинаю пилить Лайтнинг компоненты для боевого проекта, а не самообразования.
Встретил в Аура компоненте решение, которое не видел в обучающих материалах, и глядя на него, мне искренно не понятно, это лайфхак, паттерн, анти-паттерн, да и вообще "норма, ли это"?
В общем, в АПЕКС контроллере объявляются проперти доступные для Ауры.
В самом Аура компоненте объявляется проперти с типом этого самого АПЕКС контроллера.
В вызываемом из Аура контроллера методе АПЕКС контроллера выполняется какая-то работа как обычно, и создается объект нашего контроллера, в нужные проперти передаются значения, и это объект возвращается в Аура контроллер с всеми данными, где set-ается в Аура проперти.
И далее в Аура компоненте спокойно используется данные из этого самого объекта в соответсвии с нужной логикой.
Обратно, объект не возвращается, возвращаются более простые и конкретно-необходимые для вызываемого метода аргументы, но вероятно можно вернуть и весь объект, тогда он фактически может хранить state между вызовами АПЕКС методов.
Что думаете?
Есть такое дело. Но учитывая что это уже умирающая технология, я бы не заморачивался
сделал свой первый "большой" Аура компонент для боевого проекта. Это wizard страница, где юзер проходит через серию скринов, каждый скрин имеет какую то логику.
использовал самый прямолинейный подход, чтобы и выполнить работу вовремя, и не рисковать результатом, что было вполне себе challenge, так как это мой первый проект полностью в лайтнинге.
План действий был такой:
(1) На базе ВФ страницы и ее контролера я сделал полнофункциональный макет будущего wizardа. Задача была написать и протестировать логику выполняемую в контроллере.
(2) После этого началась "перешивочка" с ВФ интерфейса на Аура:
- создавался новый UI в Аура и
- войдовые методы контроллера клонировались и переделывались под Аура: теперь они принимают объект класса этого самого контроллера и возвращают его, используя объект как хранилице State wizardа. Сама логика осталась прежней
- Аура UI подключается к логике апекс контроллера через простые методы Аура контроллера, которые отправляют на сервер объект класса контроллера и принимают его.
Вот так вот просто. Не сказать что идеально, но "пойдет" для начала ![]()
Для wizard мне кажется лучше использовать flow
вот и я о том же:
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 значение не получается: код тихо-тихо падает, без всяких логов в веб консоли.
Почему не получается?
Вешаешь 2 callback. Один на change, второй на clear. И все то же самое получаешь
а можно подробнее
задача передать в con.accName текущее значение из соответствующего инпута
На сколько я понял ты не можешь присвоить значение поскольку потерял контекст, используй в 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 нет обратного связывания между инпутом и проперти, и новое значение нужно "вручную" кодом получать из инпута и передавать в проперти. Ну разве это дело?
Это сделано только ради увеличения производительности и для того чтобы юзер сам решал что и как ему связывать. Ровно как нельзя вытянуть в lwc сразу все лейблы, а только по одно, только то что конкретно тебе сейчас нуно