Accessing customField properties?
Opened this issue · 1 comments
Hi, i'm rather new to node & angular. I'm trying to create custom fields for a Product, but having trouble accessing the custom fields i've created.
Custom fields are defined in my vendure-config.ts below.
customFields: {
Product: [
{
name: 'metaTitle',
type: 'string',
label: [ { languageCode: LanguageCode.en, value: 'Meta Title' } ]
},
{
name: 'metaDescription',
type: 'string',
label: [ { languageCode: LanguageCode.en, value: 'Meta Description' } ]
},
{
name: 'metaKeywords',
type: 'string',
label: [ { languageCode: LanguageCode.en, value: 'Meta Keywords' } ]
}
]
},
product-detail.component.ts
import { Component, OnDestroy, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { filter, map, switchMap, withLatestFrom } from 'rxjs/operators';
import { AddToCart, GetProductDetail } from '../../../common/generated-types';
import { notNullOrUndefined } from '../../../common/utils/not-null-or-undefined';
import { DataService } from '../../providers/data/data.service';
import { NotificationService } from '../../providers/notification/notification.service';
import { StateService } from '../../providers/state/state.service';
import { ADD_TO_CART, GET_PRODUCT_DETAIL } from './product-detail.graphql';
import { Title, Meta } from '@angular/platform-browser';
@Component({
selector: 'vsf-product-detail',
templateUrl: './product-detail.component.html',
styleUrls: ['./product-detail.component.scss'],
})
export class ProductDetailComponent implements OnInit, OnDestroy {
product: GetProductDetail.Product;
selectedAsset: { id: string; preview: string; };
selectedVariant: GetProductDetail.Variants;
qty = 1;
breadcrumbs: GetProductDetail.Breadcrumbs[] | null = null;
@ViewChild('addedToCartTemplate', {static: true})
private addToCartTemplate: TemplateRef<any>;
private sub: Subscription;
constructor(private dataService: DataService,
private stateService: StateService,
private notificationService: NotificationService,
private route: ActivatedRoute,
private titleService: Title,
private metaService: Meta) {
}
ngOnInit() {
const lastCollectionSlug$ = this.stateService.select(state => state.lastCollectionSlug);
const productSlug$ = this.route.paramMap.pipe(
map(paramMap => paramMap.get('slug')),
filter(notNullOrUndefined),
);
this.sub = productSlug$.pipe(
switchMap(slug => {
return this.dataService.query<GetProductDetail.Query, GetProductDetail.Variables>(GET_PRODUCT_DETAIL, {
slug,
},
);
}),
map(data => data.product),
filter(notNullOrUndefined),
withLatestFrom(lastCollectionSlug$),
).subscribe(([product, lastCollectionSlug]) => {
this.product = product;
// trying to assign customFields here throws error
let customFieldsData = this.product.customFields;
if (this.product.featuredAsset) {
this.selectedAsset = this.product.featuredAsset;
}
// return this.product.customFields.meta_title/;
// if(this.product.customFields){
// console.log('product', this.product.customFields.meta_title);
// }
// this.titleService.setTitle(custom.metaTitle);
// this.metaService.addTags([
// {name: 'keywords', content: custom.metaKeywords},
// {name: 'description', content: custom.metaDescription},
// {name: 'robots', content: 'index, follow'}
// ]);
this.selectedVariant = product.variants[0];
const collection = this.getMostRelevantCollection(product.collections, lastCollectionSlug);
this.breadcrumbs = collection ? collection.breadcrumbs : [];
});
}
ngOnDestroy() {
if (this.sub) {
this.sub.unsubscribe();
}
}
addToCart(variant: GetProductDetail.Variants, qty: number) {
this.dataService.mutate<AddToCart.Mutation, AddToCart.Variables>(ADD_TO_CART, {
variantId: variant.id,
qty,
}).subscribe(({addItemToOrder}) => {
switch (addItemToOrder.__typename) {
case 'Order':
this.stateService.setState('activeOrderId', addItemToOrder ? addItemToOrder.id : null);
if (variant) {
this.notificationService.notify({
title: 'Added to cart',
type: 'info',
duration: 3000,
templateRef: this.addToCartTemplate,
templateContext: {
variant,
quantity: qty,
},
}).subscribe();
}
break;
case 'OrderModificationError':
case 'OrderLimitError':
case 'NegativeQuantityError':
case 'InsufficientStockError':
this.notificationService.error(addItemToOrder.message).subscribe();
break;
}
});
}
viewCartFromNotification(closeFn: () => void) {
this.stateService.setState('cartDrawerOpen', true);
closeFn();
}
/**
* If there is a collection matching the `lastCollectionId`, return that. Otherwise return the collection
* with the longest `breadcrumbs` array, which corresponds to the most specific collection.
*/
private getMostRelevantCollection(collections: GetProductDetail.Collections[], lastCollectionSlug: string | null) {
const lastCollection = collections.find(c => c.slug === lastCollectionSlug);
if (lastCollection) {
return lastCollection;
}
return collections.slice().sort((a, b) => {
if (a.breadcrumbs.length < b.breadcrumbs.length) {
return 1;
}
if (a.breadcrumbs.length > b.breadcrumbs.length) {
return -1;
}
return 0;
})[0];
}
}
`
I have also added the customFields to the graphql query in product-detail.graphql.ts
export const GET_PRODUCT_DETAIL = gql`
query GetProductDetail($slug: String!) {
product(slug: $slug) {
id
name
description
variants {
id
name
options {
code
name
}
price
priceWithTax
sku
}
featuredAsset {
...Asset
}
assets {
...Asset
}
collections {
id
slug
breadcrumbs {
id
name
slug
}
}
customFields {
metaTitle
metaDescription
metaKeywords
}
}
}
${ASSET_FRAGMENT}
`;
However it keeps throwing the error:
error TS2339: Property 'customFields' does not exist on type '{ __typename?: "Product" | undefined; } & Pick<Product, "id" | "name" | "description"> & { variants: ({ __typename?: "ProductVariant" | undefined; } & Pick<ProductVariant, "id" | ... 3 more ... | "sku"> & { ...; })[]; featuredAsset?: ({ ...; } & ... 2 more ... & { ...; }) | undefined; assets: ({ ...; } & ... 2 more ...'.
I have referred to the docs on the customFields, i have tried adding
declare module '@vendure/core' {
interface CustomProductFields {
metaTitle: string;
metaDescription: string;
metaKeywords: string;
}
}
to vendure-config.ts file before the export. Maybe this is incorrect, i'm not to sure as i said i'm rather new and trying to get my head around this. It feels like i need to add something else to my angular frontend config to get it to work, or define something, but i'm not sure.
The full vendure config, only thing different from the original github version is the custom fields,
import {
dummyPaymentHandler,
DefaultJobQueuePlugin,
DefaultSearchPlugin,
VendureConfig,
LanguageCode
} from '@vendure/core';
import { defaultEmailHandlers, EmailPlugin } from '@vendure/email-plugin';
import { AssetServerPlugin } from '@vendure/asset-server-plugin';
import { AdminUiPlugin } from '@vendure/admin-ui-plugin';
import path from 'path';
export const config: VendureConfig = {
apiOptions: {
port: 3000,
adminApiPath: 'admin-api',
adminApiPlayground: {
settings: {
'request.credentials': 'include',
} as any,
},// turn this off for production
adminApiDebug: true, // turn this off for production
shopApiPath: 'shop-api',
shopApiPlayground: {
settings: {
'request.credentials': 'include',
} as any,
},// turn this off for production
shopApiDebug: true,// turn this off for production
},
authOptions: {
superadminCredentials: {
identifier: 'superadmin',
password: 'superadmin',
},
},
dbConnectionOptions: {
type: 'mysql',
synchronize: true, // turn this off for production
logging: false,
database: 'vendure',
host: 'localhost',
port: 3306,
username: 'root',
password: '',
migrations: [path.join(__dirname, '../migrations/*.ts')],
},
paymentOptions: {
paymentMethodHandlers: [dummyPaymentHandler],
},
customFields: {
Product: [
{
name: 'metaTitle',
type: 'string',
label: [ { languageCode: LanguageCode.en, value: 'Meta Title' } ]
},
{
name: 'metaDescription',
type: 'string',
label: [ { languageCode: LanguageCode.en, value: 'Meta Description' } ]
},
{
name: 'metaKeywords',
type: 'string',
label: [ { languageCode: LanguageCode.en, value: 'Meta Keywords' } ]
}
]
},
plugins: [
AssetServerPlugin.init({
route: 'assets',
assetUploadDir: path.join(__dirname, '../static/assets'),
}),
DefaultJobQueuePlugin,
DefaultSearchPlugin,
EmailPlugin.init({
devMode: true,
outputPath: path.join(__dirname, '../static/email/test-emails'),
route: 'mailbox',
handlers: defaultEmailHandlers,
templatePath: path.join(__dirname, '../static/email/templates'),
globalTemplateVars: {
// The following variables will change depending on your storefront implementation
fromAddress: '"example" <noreply@example.com>',
verifyEmailAddressUrl: 'http://localhost:8080/verify',
passwordResetUrl: 'http://localhost:8080/password-reset',
changeEmailAddressUrl: 'http://localhost:8080/verify-email-address-change'
},
}),
AdminUiPlugin.init({
route: 'admin',
port: 3002,
}),
],
};
any help would be appreciated, using mac Mojave, node v14.17.0 if that makes any difference.
Hi,
There are 2 aspects to the typings of the custom fields:
- The typings of the server-side entities. This is covered by the
declare module '@vendure/core' { ...
code above. This is only needed when you are developing plugins for your Vendure server and need to access custom fields in a type-safe way. - The typings for the storefront. In this repo, these typings are generated by graphql-code-generator. There is some documentation about it here: https://github.com/vendure-ecommerce/storefront#code-generation
So based on what you wrote above, you are interested in number 2 above. Since you have already added the custom field field selection to your graphql document, you should be able to npm run generate-types
and then have the generated typings automatically updated with your custom fields.