LWC superbadge step 14 challenge - salesforce

I am on step 14 of LWC superbadge and getting the following error : We can't find the wire service provisioned function similarBoats() with the data property used correctly in the component similarBoats JavaScript file. Make sure the component was created according to the requirements, including the proper values for relatedBoats, boatId, and similarBy, using the proper case-sensitivity and consistent quotation. Below is the JS code for my file. Can someone tell me what is wrong in my code?? I have been stuck on this for more than 24 hours now.
import { LightningElement, api, wire } from 'lwc';
import getSimilarBoats from '#salesforce/apex/BoatDataService.getSimilarBoats';
import { NavigationMixin } from 'lightning/navigation'
export default class SimilarBoats extends NavigationMixin(LightningElement) {
#api similarBy;
relatedBoats;
boatId;
error;
// public
#api
get recordId() {
return this.boatId;
}
set recordId(value) {
// sets the boatId value
this.boatId = value;
// sets the boatId attribute
}
#wire(getSimilarBoats, { boatId: this.boatId, similarBy: '$similarBy' })
similarBoats({ error, data }) {
if (data) {
this.relatedBoats = data;
this.error = undefined;
} else if (error) {
this.relatedBoats = undefined;
this.error = error;
}
}
get getTitle() {
return 'Similar boats by ' + this.similarBy;
}
get noBoats() {
return !(this.relatedBoats && this.relatedBoats.length > 0);
}
// Navigate to record page
openBoatDetailPage(event) {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: {
recordId: this.boatId,
objectApiName: BOAT_OBJECT,
actionName: 'view'
},
});
}
}

Your problem is that you're using { boatId: this.boatId, ... , when you need to use { boatId: '$boatId', ....

In addition to what Joe noted, you'll need to add the following line to your recordId setter in order to set the correct value for the attribute (as the comment suggests).
// sets the boatId attribute
this.setAttribute('boatId', value);

Related

The operand of a 'delete' operator must be optional.ts(2790) While creating a form using Typescript

I don't understand why this error happening.
I'm creating a form to submit user email
export const register = createAsyncThunk<
User,
RegisterProps,
{
rejectValue: ValidationErrors;
}
>("auth/registerStatus", async (credentials, { rejectWithValue }) => {
try {
// Don't POST blank email
if (!credentials["email"]) {
delete credentials["email"]; //editor marking in this line there is error.
}
const response = await api.post(API_REGISTER, credentials);
return response.data;
} catch (err) {
const error: AxiosError<ValidationErrors> = err;
if (!error.response) {
throw err;
}
return rejectWithValue(error.response.data);
}
});
but I am facing this error:
The operand of a 'delete' operator must be optional.ts(2790)
I guess there is some logic error but I need your help to solve this.
In your credentials interface or class declaration object, you must mark email as option field using ? mark.
ex:
interface Credentials {
email?: string,
...
}
This error happens because you created a class with the mandatory attribute you can work around creating a mirror class with the optional attributes.
class Foo {
aa: string
bb: number
cc: boolean
}
type FooMirror = {aa: string, bb?: number, cc?: boolean}
const foo = new Foo()
const test: FooMirror = foo
delete test.aa // error operator must by optional
delete test.bb // true
delete test.cc // true
Another solution, we could use the Partial utility type to construct a new type with the properties set to optional before using the delete operator.
credentials: Partial<Credentials> = { ... };
delete credentials["email"];

class-validator doesn't validate arrays

I can't get the class-validator to work. It seems like I am not using it: everything works as if I didn't use class-validator. When sending a request with an incorrectly formatted body, I don't have any validation error, although I should.
My DTO:
import { IsInt, Min, Max } from 'class-validator';
export class PatchForecastDTO {
#IsInt()
#Min(0)
#Max(9)
score1: number;
#IsInt()
#Min(0)
#Max(9)
score2: number;
gameId: string;
}
My controller:
#Patch('/:encid/forecasts/updateAll')
async updateForecast(
#Body() patchForecastDTO: PatchForecastDTO[],
#Param('encid') encid: string,
#Query('userId') userId: string
): Promise<ForecastDTO[]> {
return await this.instanceService.updateForecasts(userId, encid, patchForecastDTO);
}
My bootstrap:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(PORT);
Logger.log(`Application is running on http://localhost:${PORT}`, 'Bootstrap');
}
bootstrap();
I can't find what's wrong. What did I miss?
In the current version of NestJS (7.6.14), validating a request body that is a JSON array is supported using the built in ParseArrayPipe.
#Post()
createBulk(
#Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
See the official docs or the source code for more info.
NestJS actually does not support array validation out of the box. In order to validate an array, it must be wrapped in an object.
This way, I would not use a DTO corresponding to a list of items, but a DTO corresponding to an object that contains a list of items:
import { PatchForecastDTO } from './patch.forecast.dto';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class PatchForecastsDTO {
#IsArray()
#ValidateNested() // perform validation on children too
#Type(() => PatchForecastDTO) // cast the payload to the correct DTO type
forecasts: PatchForecastDTO[];
}
And I would use that DTO in my controller:
#Patch('/:encid/forecasts/updateAll')
async updateForecast(
#Body() patchForecastsDTO: PatchForecastsDTO,
#Param('encid') encid: string,
#Query('userId') userId: string
): Promise<ForecastDTO[]> {
return await this.instanceService.updateForecasts(userId, encid, patchForecastsDTO);
}

Nativescript Angular ActivityIndicator

in my Nativescript Angular app i am using an ActivityIndicator, setup as i've seen in the Nativescript Angular docs (the GroceryList example):
<ActivityIndicator width="30" height="30" [busy]="refreshing" [visibility]="refreshing ? 'visible' : 'collapsed'" horizontalAlignment="center" verticalAlignment="center"></ActivityIndicator>
if the Component using it i have:
export class MyComponent {
public refreshing = false;
........
}
Then i fetch some data from my backend:
public onRefreshTap() {
console.log("onrefreshtap");
this.refreshing = true;
this.backend.getData(function (data) { //this.backend is my Service
this.refreshing = false;
})
}
The problem is that when i put this.refreshing to true, the ActivityIndicator correctly shows. But when bakend request completes (and so, i put this.refreshing=false) the ActivityIndicator does not hides... (and also it seems that its busy property is not updated, it stays in spinning state)..
What am i doing wrong ?
Thanks in advance
You could also try to access the refreshing property as it has been shown in the sample codes below. It could be a problem of accessing the property inside the callback method of your service.
public onRefreshTap() {
var that = this;
this.refreshing = true;
this.backend.getData(function (data) { //this.backend is my Service
that.refreshing = false;
})
}
or
public onRefreshTap() {
this.refreshing = true;
this.backend.getData((data) => {
that.refreshing = false;
})
}
It may be many things:
1) The change to false, on the Observable, is not being "seen" by the component.
------ The solution is run the code in a Zone (see https://angular.io/docs/ts/latest/api/core/index/NgZone-class.html )
2) The backend is returning an error (I don't see it dealing with that in the code).
------ The solution is put a function to deal with the error.
3) The callback is not being called. In your code, you're SENDING a function as a parameter to the backendService, so maybe the service is not executing it.
------ Try using a Promisses or Observables to deal with returned values (you'll have to Google about it, since I'm still learning them my explanation would be the worst). :)
Here's some code that might work:
my-component.html
<ActivityIndicator [busy]="isWorking" [visibility]="isWorking?'visible':'collapse'"></ActivityIndicator>
my-component.ts
import { Component, NgZone } from "#angular/core";
...
export class MyComponent {
isWorking:boolean = false;
constructor(private backendService: BackendService,
private _ngZone: NgZone)
{
this.isWorking = false;
}
public onRefreshTap() {
console.log("onrefreshtap");
this.isWorking = true;
this.backendService.getData()
.then(
// data is what your BackendService returned after some seconds
(data) => {
this._ngZone.run(
() => {
this.isWorking = false;
// I use to return null when some Server Error occured, but there are smarter ways to deal with that
if (!data || data == null || typeof(data)!=='undefined') return;
// here you deal with your data
}
)
}
);
}
}

Meteor Collection find()/fetch() working sometimes

I started working with meteor which seems to be good for my use, a problem occurred where I get my documents only 9/10 times. I think I implemented something wrong.
I use Angular 1.5 and Typescript
My collection gets created in the lib folder at /
import { Mongo } from 'meteor/mongo';
export const Locations= new Mongo.Collection('locations');
then the collection gets imported to my service
import {app} from '../../js/lib/app';
import {Locations} from '../../../lib/collections';
export class LocationsService {
locations: any;
constructor(){
console.log('Constructor called');
this.locations = Locations
.find({})
.fetch();
console.log('documents loaded');
console.log(this.locations);
}
public createLocation(any:any){
Locations.insert(any);
}
public updateLocation(identity:any, modifier:any){
Locations.update(identity,modifier);
}
}
app.service('locationsService', LocationsService);
Here are the console.logs from 3 different page refreshes:
It looks like the amount of docs I get is totally random.
Here is some code that will help you. It uses the "resolve" feature of ui-router to hold up loading of the page until data is loaded. In this case there are two things being resolved:
User record
Elders record
The second one needs an "elderid" from users.profile in order to find an elder record.
function config($locationProvider, $urlRouterProvider, $stateProvider) {
'ngInject';
$stateProvider
.state('member.calendar', {
url: '/calendar',
template: "<membercalendar></membercalendar>",
resolve: {
currentUser: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function () {
if (!Meteor.loggingIn()) {
if (Meteor.user() == null) {
deferred.reject('AUTH_REQUIRED');
} else {
deferred.resolve(Meteor.user());
}
}
});
return deferred.promise;
},
elder: ($q) => {
var deferred = $q.defer();
Meteor.autorun(function () {
if (!Meteor.loggingIn()) {
if (Meteor.user() == null) {
deferred.reject('AUTH_REQUIRED');
} else {
deferred.resolve(Elders.find({_id: Meteor.user().profile.elderid}));
}
}
});
return deferred.promise;
}
}
});
}
This works well if you want the data to be loaded fully before the page loads. If you don't mind an asynchronous update to the page, you can use getReactively to make a helper run once the data has resolved. I can give you example code for that too if you like.
My new Service simply subscribes
export class LocationsService {
locations:any;
constructor(){
console.log('Constructor called');
//Subscribe to a collection//localStorage.getItem('ID')
Meteor.subscribe('locations', 2223 );
this.locations = Locations;
console.log('documents loaded');
}
public createLocation(any:any){
Locations.insert(any);
}
public updateLocation(identity:any, modifier:any){
Locations.update(identity,modifier);
}
}
app.service('locationsService', LocationsService);
In my controller i simply add the fetching of my documents in the Tracker.
import {app} from '../../js/lib/app';
import {LocationsService} from './LocationsService';
import {Tracker} from 'meteor/tracker';
export class LocationsController {
static $inject = ['locationsService','$reactive','$scope'];
public $reactive: any;
public $scope: any;
public locations: any[];
constructor(private locationsService: LocationsService, $reactive:any, $scope:any){
this.locationsService = locationsService;
this.$reactive = $reactive;
this.$scope = $scope;
$reactive(this).attach(this.$scope);
Tracker.autorun(() => {
//console.log('autorun');
this.locations = locationsService.locations.find({}).fetch();
console.log(this.locations)
});
}
public createLocation(location:any){
console.log('Locations does what it should');
console.log(location);
this.locationsService.createLocation(location);
}
public updateLocation(location:any, modifier:any){
this.locationsService.updateLocation(location._id,modifier)
}
}
app.controller('locationsController', LocationsController);
The only problem I have now is that the modell updates like a charm but not the view when I create new locations. The autorun works and the new location gets saved in my collection but I see it only if I reload. But that one is low priority for me.

Angular service and pouchdb

How do you use angularjs service to call pouchdb and return the data to the controller? I have been working on a ionic app with pouchdb for local storage. I have a simple crud app built in a controller. Now I want to start to move the pouchdb calls into a service. I haven’t been able to get back data from the service. How would I use a service to call pouchdb to get all docs and return it to the controller?
One strategy that I think could work very well for Angular services is this one. It describes a method for keeping an in-memory array synced with the result of PouchDB's allDocs().
Since it's an array that automatically stays synced with PouchDB, you can just do an ng-repeat on it, and you're done. :)
Although your question is a year old, it deserves an answer.
You might want more than one service i.e. one to use in the controller and another for the backend database storage. For example, in the controller:
(function () {
'use strict';
angular
.module('app.services')
.factory('db',db);
db.$inject = ['$db'];
function db($db) {
var data = {}; // set up a data object to receive document(s)
return {
getDoc: getDoc,
getList: getList,
save: save,
saveBatch: saveBatch
};
// get a single document using the id
function getDoc(id) {
$db.getDoc(id)
.then(
function onSuccess(doc) {
// success so update the view model
angular.extend(data,doc); // use angular.extend to shallow copy object so that it can be returned in full
},
function onError() {
// failure to get document
}
);
return data;
}
// retrieve a group of documents where key is the prefix of the data you want
function getList(key) {
$db.getList(key).then(
function onSuccess(docs) {
// success so update the view model details
angular.forEach(docs.rows, function (value) {
this.push(value.doc);
}, data);
// now you can sort data or anything else you want to do with it
},
function onError() {
// no data found
}
);
return data;
}
// save a single viewItem
function save(viewItem) {
$db.update(viewItem).then(
function onSuccess() {
// success so update view model if required
},
function onError(e) {
console.log(e); // unable to save
}
);
}
// save an array of viewItems
function saveBatch(viewItems) {
$db.updateBatch(viewItems).then(
function onSuccess() {
// success so update the view model if required
},
function onError(e) {
console.log(e); // unable to save
}
);
}
}
})();
For the backend, something like this:
(function () {
'use strict';
angular
.module('app.services')
.factory('$db',$db);
$db.$inject = ['$q'];
function $db($q) {
var db;
return {
setLocalDB: setLocalDB,
update: update,
updateBatch: updateBatch,
getDoc: getDoc,
getAllDocs: getAllDocs,
getList: getList
};
// ------ DATABASE OPENING HANDLER(S) ------
// set to any named database
function setLocalDB(dbName) {
db = new PouchDB(dbName);
return db.info()
.catch(failedCheck()); // returns a promise to either work or fail
}
// return a rejection for a failure
function failedCheck() {
return $q.reject();
}
// ------ DOCUMENT HANDLING ------
// update document but if errors occur recurse qUpdate until either complete or retries exhausted
function update(doc) {
var counter = 0;
return $q.when(qUpdate(doc,counter));
}
// this routine works for both new and existing documents
function qUpdate(doc,counter) {
return db.put(doc)
.then(function() {
console.log('success - new document');
})
.catch(function(e) {
console.log(e); // not a new document so try as a revision of existing document using _id to find
return db.get(doc._id)
.then(function(origDoc) {
doc._rev = origDoc._rev; // get document revision _rev
return db.put(doc,doc._id,doc._rev)
.then(function() {
console.log('success - revision of document');
})
.catch(function(e){
console.log(e); // log error for failure
});
})
.catch(function(e){
console.log(e); // log error before we take any other action
counter ++; // increment counter, so we can limit retries (5 by default)
if (counter< 5) {
switch (e.status) {
case 404:
delete doc._rev; // remove revision information so we can see if this works
return qUpdate(doc); // might be deleted so return revised document for retry
case 409:
return qUpdate(doc); // in conflict so try again
default:
try {
throw new Error("cannot save: " + doc._id); // cannot go any further so throw new error
} catch(err) {
console.log(err); // log error for failure
}
}
} else {
try {
throw new Error("cannot save" + doc._id); // cannot go any further so throw new error
} catch(err) {
console.log(err); // log error for failure
}
}
});
});
}
// update a document batch stored in an array
function updateBatch(docs) {
return $q.when(qUpdateBatch(docs));
}
// do the actual update of a batch
function qUpdateBatch(docs) {
db.bulkDocs(docs).then(function(res) {
for (var i=0; i < res.length; i++) {
if (res[i].status === 409) {
update(docs[i]); // in conflict so try this document separately
}
}
}).catch(function(e){
console.log(e); // log error
});
}
// get the document as an angular promise and deal with it in host routine
function getDoc(id) {
return $q.when(db.get(id));
}
// get all documents
function getAllDocs() {
return $q.when(db.allDocs({include_docs: true, attachments: false}));
}
// get a batch of documents between a start and end key
function getList(key) {
return $q.when(db.allDocs({startkey: key, endkey: key + '\uffff', include_docs: true, attachments: false}));
}
}
})();
In your main controller you would want to set the database:
$db.setLocalDB('yourDB');
Hope this is what you were looking for?
In my own data services module I have other functions for remote database, event listeners, remove, sync, compact, destroy and so on.

Resources