PDP Custom Routes defined in a module not working - spartacus-storefront

I'm following the Spartacus bootcamp sample for routing https://github.com/SAP/spartacus-bootcamp/tree/77b7474c9538eaa1032062ad3c6d461fb1fc7517/src/app/features/routing
My problem is when I have configured the custom PDP
imports: [
CommonModule,
// dependent module for semantic URLs like cxUrl
UrlModule,
// standard non-spartacus routes
RouterModule.forChild(staticRoutes),
// configure product routes
ConfigModule.withConfig({
routing: {
routes: {
product: {
paths: [
'product/:manufacturer/:firstCategoryName/:productCode/:prettyName',
'product/:manufacturer/:productCode/:prettyName',
'product/:productCode/:name',
],
},
},
},
} as RoutingConfig),
//code mapping in the routes
ConfigModule.withConfig({
paramsMapping: {
productCode: 'code',
},
} as RouteConfig),
The new PDP routes are never used in the Storefront. I can see in the browser console that the custom product properties firstCategoryName and prettyName are properly settled from the normalizers/converters of the sample...
Any insight what can be going on?
Thanks!
Fernando

I think it will also depend on whether/not the manufacturer property is populated as well. By default the manufacturer field is not requested in the productSearch OCC call, so will not be populated on the product object (see default-occ-product-config.ts in the Spartacus code) - this means that on product listing pages (category & search) those two paths will not resolve, and are therefore ignored.

Related

How to use vue component across multiple node projects?

I'm trying to build a website builder within the drag-and-drop abilities via using Vue3. So, the user will be playing with the canvas and generate a config structure that going to post the backend. Furthermore, the server-side will generate static HTML according to this config.
Eventually, the config will be like the below and it works perfectly. The config only can have HTML tags and attributes currently. Backend uses h() function to generate dom tree.
My question is: can I use .vue component that will generate on the server side as well? For example, the client-side has a Container.vue file that includes some interactions, styles, etc. How can the backend recognize/resolve this vue file?
UPDATE:
Basically, I want to use the Vue component that exists on the Client side on the backend side to generate HTML strings same as exactly client side. (including styles, interactions etc).
Currently, I'm able to generate HTML string via the below config but want to extend/support Vue component itself.
Note: client and server are completely different projects. Currently, server takes config and runs createSSRApp, renderToString methods.
Here is the gist of how server would handle the API:
https://gist.github.com/yulafezmesi/162eafcf7f0dcb3cb83fb822568a6126
{
id: "1",
tagName: "main",
root: true,
type: "container",
properties: {
class: "h-full",
style: {
width: "800px",
transform: "translateZ(0)",
},
},
children: [
{
id: "9",
type: "image",
tagName: "figure",
interactive: true,
properties: {
class: "absolute w-28",
style: {
translate: "63px 132px",
},
},
},
],
}
This might get you started: https://vuejs.org/guide/scaling-up/ssr.html#rendering-an-app
From the docs:
// this runs in Node.js on the server.
import { createSSRApp } from 'vue'
// Vue's server-rendering API is exposed under `vue/server-renderer`.
import { renderToString } from 'vue/server-renderer'
const app = createSSRApp({
data: () => ({ count: 1 }),
template: `<button #click="count++">{{ count }}</button>`
})
renderToString(app).then((html) => {
console.log(html)
})
I guess extract the template from request or by reading the submitted Vue file and use that as the template parameter value

Custom PageLayoutComponent

To have specific layout for some pages at our project we create few custom PageLayoutComponent's. Some contfiguration example:
{
// #ts-ignore
path: null,
canActivate: [CmsPageGuard],
component: CartPageLayoutComponent,
data: {
cxRoute: 'cart',
cxContext: {
[ORDER_ENTRIES_CONTEXT]: ActiveCartOrderEntriesContextToken,
},
},
},
All work fine with storefront until you will not try to select specific page at smartedit. As result it not use our custom CartPageLayoutComponent, but will use PageLayoutComponent for rendering.
Probably this is because it's not a normal route navigation. Can somebody from spartacus team suggest how this bug can be fixed?
Probably this is because it's not a normal route navigation
I believe your Route should be recognized normally, there is nothing special in adding a custom Angular Route.
So I guess there is something special about the page or URL of Spartacus Storefront that runs in your SmartEdit.
It's hard to tell the reason of your problem without more debugging.
You said your app works as expected when run differently (locally?), but when used in SmartEdit, then there is a problem. Please identify factors that makes the run SmartEdit different from your (local?) run. And try to isolate them. Guesses from top of my head:
production vs dev mode of the build?
exact URL of the cart page?
any difference in configuration between a local version and deployed one to be used in SmartEdit?
I would also add some debugging code to better know which routes configs are available and which one is used for the current route. For debugging purposes please add the following constructor logic in your AppModule:
export class AppModule {
// on every page change, log:
// - active url
// - the Route object that was matched with the active URL
// - all the Route objects provided to Angular Router
constructor(router: Router) {
router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
console.log({
activeUrl: router.url,
activeRouteConfig:
router.routerState.snapshot.root.firstChild.routeConfig,
allRoutesConfigs: router.config,
});
}
});
}
}
The pages opened in SmartEdit have the same route of cx-preview (e.g. to open faq page in smartedit, request is https://localhost:4200/electronics-spa/en/USD/cx-preview?cmsTicketId=xxxxx. backend can get page id from cmsTicketId). If you want to change the page layout, you can consider use PageLayoutHandler. Spartacus has some PageLayoutHandlers, e.g.
{
provide: PAGE_LAYOUT_HANDLER,
useExisting: CartPageLayoutHandler,
multi: true,
},

How to slug an url using Vuejs

I'm building a website with several articles. I'm using Vue Router, and for the moment the urls of my articles look like /article/id, for example : http://localhost:8080/article/85.
How can I slug an URL with the article title so it can be http://localhost:8080/article/the-article-title for example ?
I have no idea what kind of code should I provide so here is my article route :
routes: [
{
path: '/article/:id',
component: require('../components/articlePage.vue').default,
name: 'article',
meta: {title: "article"}
},
]
First add a slug in your JSON object if it is not exist in your API or DB
{
id: 1,
title: 'Jungle Book',
slug: 'jungle-book',
showDate: 'Monday',
showTime: '19:10 - 20:55'
}
Change the path in your router index file according to your path and component.
And the path should have ":slug"
{
path: '/movies/:slug',
name: 'moviedetail',
component: MovieDetail
}
In the "router-link" add the slug after v-for
<router-link :to="'/movies/' + movie.slug">
Now get the data from your component
data() {
return {
movie: this.$store.state.data,
movieDetail: []
}
},
methods: {
getMovie(){
this.movie.forEach(e => {
if(e.slug == this.$route.params.slug){
this.movieDetail = e;
}
});
}
},
created() {
this.getMovie();
}
There are different approaches with increasing level of complexity and aspects taken care of.
So to start - in order to slug-ify articles, you have to introduce slugs to the application. Since it is mentioned in comments that all articles are fetched priorly, slugs can be added to each article data before saving them to store with custom function that converts words to kebab-case or one of helper libraries (e.g. dashify).
This will make possible to render list of article links using :slug as route param, instead of id and search in store for by param before rendering article page. Good thing is that it still possible to keep both options (slug and id) available by extending logic to search by 2 fields.
Depending on your target - that might be it. But the problem arises in case article title has been changed and user accesses article via externally saved link (shared in social media, indexed by search engines, etc). This defeats SEO.
In order to keep article accessible, it is a good practice to include slug as a required field on the back-end. Slug still can be generated with same approach, but in CMS - before article is stored in the database. In such case it can be double checked, manually edited (as slugs do not always represent full article title because of characters length, special symbols, etc) and be accessible for querying, thus removing hassle of string manipulation from the front-end application.
Also this creates options to configure 301 redirects to preserve indexation even after slug is changed by saving old slugs. But such problem is out of the scope of the current question, even though is a good practice.
Router:
{
path: '/:slug',
name: 'article',
component: articlePage
}
Component:
use beforeRouteEnter to show the slug
beforeRouteEnter(to, from, next) {
console.log(to.params.slug);
}

Angular 2 with typescript routing not working

I have an angular 2 .net core app I downloaded from:
https://github.com/chsakell/aspnet-core-signalr-angular
I attempted to add a new module and changed the routing to this:
const appRoutes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: '', component: HomeComponent },
{ path: 'another', component: AnotherComponent },
];
"another" being the new component. So I expect that entering: "http://localhost:5000/#/another" into the address bar should load "another" module. But this doesn't work. It loads "another" module if I link the default path which is '' to AnotherComponent. The only path that works in the default path which is ''.
Can anyone see what I am doing wrong? No errors display in the console.
Thanks
If any one else has an issue similar to this - if you're using MVC routing alongside angular routing. The urls need to be specified for MVC as well. So when you add a new route for angular it attempts to find a controller/action that probably doesn't exist. So what I did was this:
[Route("Home")]
[Route("Home/Another")]
[Route("Home/Start")]
Public IActionResult Index()
{
return View();
}
And the angular routes need to match.

Angular2 - SEO - how to manipulate the meta description

Search Results in google are displayed via TitleTag and the <meta name="description"..."/> Tag.
The <title>-Tag is editiable via Angular2 how to change page title in angular2 router
What's left is the description.
Is it possibile to write a directive in angular2, that manipulates the meta-tags in the <head> part of my page.
So depending on the selected route, the meta description changes like:
<meta name="description" content="**my description for this route**"/>
Since Angular4, you can use Angular Meta service.
import { Meta } from '#angular/platform-browser';
// [...]
constructor(private meta: Meta) {}
// [...]
this.meta.addTag({ name: 'robots', content: 'noindex' });
It is possible. I implemented it in my app and below I provide the description how it is made.
The basic idea is to use Meta from #angular/platform-browser
To dynamically change particular meta tag you have to:
Remove the old one using removeTag(attrSelector: string) : void
method.
Add the new one using addTag(tag: MetaDefinition, forceCreation?:
boolean) : HTMLMetaElement method.
And you have to do it when the router fires route change event.
Notice: In fact it is also necessary to have default <title>...</title> and <meta name="description"..." content="..."/> in head of index.html so before it is set dynamically there is already some static content.
My app-routing.module.ts content:
import 'rxjs/add/operator/filter';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
import { NgModule } from '#angular/core';
import { RouterModule, Routes, Router, NavigationEnd, ActivatedRoute } from '#angular/router';
import { StringComparisonComponent } from '../module-string-comparison/string-comparison.component';
import { ClockCalculatorComponent } from '../module-clock-calculator/clock-calculator.component';
import { Title, Meta } from '#angular/platform-browser';
const routes: Routes = [
{
path: '', redirectTo: '/string-comparison', pathMatch: 'full',
data: { title: 'String comparison title', metaDescription: 'String comparison meta description content' }
},
{
path: 'string-comparison', component: StringComparisonComponent,
data: { title: 'String comparison title', metaDescription: 'String comparison meta description content' }
},
{
path: 'clock-time-calculator', component: ClockCalculatorComponent,
data: { title: 'Clock time calculator title', metaDescription: 'Clock time calculator meta description content' }
}
];
#NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private titleService: Title,
private metaService: Meta
){
//Boilerplate code to filter out only important router events and to pull out data object field from each route
this.router.events
.filter(event => event instanceof NavigationEnd)
.map(() => this.activatedRoute)
.map(route => {
while (route.firstChild) route = route.firstChild;
return route;
})
.filter(route => route.outlet === 'primary')
//Data fields are merged so we can use them directly to take title and metaDescription for each route from them
.mergeMap(route => route.data)
//Real action starts there
.subscribe((event) => {
//Changing title
this.titleService.setTitle(event['title']);
//Changing meta with name="description"
var tag = { name: 'description', content: event['metaDescription'] };
let attributeSelector : string = 'name="description"';
this.metaService.removeTag(attributeSelector);
this.metaService.addTag(tag, false);
});
}
}
As it can be seen there is an additional data object field for
each route. It contains title and metaDescription strings
which will be used as title and meta tag content.
In constructor we filter out router events and we subscribe to filtered
router event. Rxjs is used there, but in fact it is not necessary to use it. Regular if statements and loops could be used insead of stream, filter and map.
We also merge our data object field with our event so we can easily
use info like title and metaDescription strings.
We dynamically change <title>...</title> and <meta name="description"..." content="..."/> tags.
Effects:
First component
Second component
In fact I currently use a little bit more sophisticated version of this solution which uses also ngx-translate to show different title and meta description for different languages.
Full code is available in angular2-bootstrap-translate-website-starter project.
The app-routing.module.ts file with ngx-translate solution is exactly there: app-routing.module.ts.
There is also the production app which uses the same solution: http://www.online-utils.com.
For sure it is not the only way and there might be better ways to do it. But I tested this solution and it works.
In fact the solution is very similar to this from corresponding post about changing title: How to change page title in angular2 router.
Angular Meta docs: https://angular.io/docs/ts/latest/api/platform-browser/index/Meta-class.html. In fact they aren't very informative and I had to experiment and look into real .js code to make this dynamic meta change working.
I have developed and just released #ngx-meta/core plugin, which manipulates the meta tags at the route level, and allows setting the meta tags programmatically within the component constructor.
You can find detailed instructions at #ngx-meta/core github repository. Also, source files might be helpful to introduce a custom logic.
There is currently no out-of-the-box solution only an open issue to implement it https://github.com/angular/angular/issues/7438.
You can of course implement something like the title service yourself, just use the TitleService as template
A Meta service similar to Title service is in the works (currently only a pull request).