I'm trying to extend the product model with a new attribute I created in the -items.xml, but I can't seem to get it in Spartacus front. I added a product.model.ts file with the following code:
import { Product as CxProduct } from '#spartacus/core';
export interface Product extends CxProduct {
myAttribute: string;
}
And I imported that file in the pipe where I use the product model, but it appears empty althoughs the product does have data in myAttribute field in backoffice. Is there something I may be missing?
I'm using this attribute in a product-images.component I created to override the default one.
This is the new product-images.component.ts
import { Component, OnInit } from '#angular/core';
import { CurrentProductService } from '#spartacus/storefront';
import { ProductImagesComponent as SpartacusProductImages } from '#spartacus/storefront'
#Component({
selector: 'cx-product-images',
templateUrl: './product-images.component.html',
styleUrls: ['./product-images.component.scss']
})
export class ProductImagesComponent extends SpartacusProductImages implements OnInit {
constructor(currentProductService: CurrentProductService) {
super(currentProductService)
}
ngOnInit() {
}
}
Thanks a lot
You need ask backend explicitly to return you the attribute. Please configure the product endpoint to contain ?fields= with your custom attribute. See docs: https://sap.github.io/spartacus-docs/connecting-to-other-systems/#configuring-endpoints
Please mind that you can specify various scoped endpoint configuraitons for product data - so you can load the custom attribute only when needed (i.e. it's useful when myAttribute is not displayed for some views like product carousel or product list). But since you're manipulating the image data, I guess you want to use this property everywhere (so please use scope called list).
For more, see docs for scoped product endpoint: https://sap.github.io/spartacus-docs/loading-scopes/#using-default-product-scopes. Note: When writing this post, the docs for Spartacus 2.0 version are not yet published. In 2.0 the product_scopes config property was renamed to product.
Related
I want to use a custom element, bind values to it, check and manipulate them in the view model and show them in the view.
This is the container's relevant statements:
<require from = "./../userAccount/userAccount"></require>
<div class="UserAccountWrapper" repeat.for="userAccount of
userAccountsData">
<div><user-account accountDetails.bind="userAccount"></user-account></div>
</div>
This is the custom element relevant code:
import { bindable } from 'aurelia-framework';
export class UserAccount{
#bindable account;
constructor() {}
activate(account) {
this.accountDetails.CompanyName = account.CompanyName == null ? "N/A" :
account.CompanyName;
....
}
The data doesn't get binded to the custom element.
You need to use the bind callback. activate is a callback for routed pages (and some other cases I mentioned in a comment to your question). Also, the bound value is not passed as a parameter to the bind callback, but it will be set at that point. You do have to use this before the property name though.
import { bindable } from 'aurelia-framework';
export class UserAccount{
#bindable account;
bind() {
this.accountDetails.CompanyName = this.account.CompanyName == null ? "N/A" :
this.account.CompanyName;
....
}
}
Another choice is to use the ${propertyName}Changed callback. This function will be called every time the property changes. Note that this is only when the property itself changes, and not when properties on the bound property change. So you the callback won't happen if only this.account.CompanyName changes. The changed callback will provide you with the new value and old value of the property as parameters to the callback, but you can ignore the parameters if you don't want them.
import { bindable } from 'aurelia-framework';
export class UserAccount{
#bindable account;
accountChanged(newValue, oldValue) {
this.accountDetails.CompanyName = account.CompanyName == null ? "N/A" :
this.account.CompanyName;
// OR
this.accountDetails.CompanyName = newValue || "N/A";
....
}
}
I have a code like below where a Project has a Timeline.
I have created a class named TimelineProperties which acts as a shared specification for a properties I want to pass from a project to a child Timeline.
I have planned to use TimelineProperties class in Project custom element like this:
import {TimelineProperties} from "./timeline";
#customElement('project')
export class Project {
timeline: TimelineProperties = new TimelineProperties();
}
<template>
...
<timeline list.bind="timeline.list" loading.bind="timeline.loading" error.bind="timeline.error" />
...
</template>
And also inside a Timeline custom element (to share code):
Either via inheritance like this:
export class Timeline extends TimelineProperties {}
Or via composition like this:
export class Timeline {
// TimelineProperties class has #bindable properties defined
timeline: TimelineProperties = new TimelineProperties();
}
// and then use bindings from project.html like this:
<timeline timeline.list.bind="timeline.list" timeline.loading.bind="timeline.loading" timeline.error.bind="timeline.error" />
The issue is that I cannot use a shared specification class TimelineProperties inside a Timeline custom element either via inheritance nor composition.
Issue with inheritance - https://github.com/aurelia/templating/pull/507
Issue with composition - Exception in runtime: "Error: Attempted to register an Element when one with the same name already exists. Name: project"
So now I have copied the TimelineProperties fields also into a Timeline Custom Element class (see 3 #bindable properties inside timeline.ts code below) just to make it work. But I would like to avoid that code duplication.
My question is, is there some way I could use TimelineProperties class inside a Timeline Custom Element to bind data from Project Element directly into a Timeline's TimelineProperties?
Here is my full code that works by code duplication and not using a shared TimelineProperties class:
project.ts - I have a parent Custom Element like this:
import {TimelineProperties} from "./timeline";
#customElement('project')
export class Project {
timeline: TimelineProperties = new TimelineProperties();
}
<template>
...
<timeline list.bind="timeline.list" loading.bind="timeline.loading" error.bind="timeline.error" />
...
</template>
timeline.ts - And child Custom Element like this:
import {DataLoading} from "./api";
export class TimelineProperties extends DataLoading {
#bindable list: Task[] = [];
}
#customElement('timeline')
export class Timeline {
#bindable list: Task[] = [];
#bindable loading: boolean = false;
#bindable error: any;
...
}
<template>
...
</template>
api.ts
export class DataLoading {
#bindable loading: boolean = false;
#bindable error: any;
}
UPDATE - Satisfied with this solution
Based on Ashley Grant's suggestions to use a decorator to initialize the bindings, I have modified my code based on that input and I am happy with it now. Here is my code now using the "extends" and a decorator to initialize the bindings in child classes:
import {DataLoading} from "./api";
export class TimelineProperties extends DataLoading {
list: Task[] = [];
}
// To ensure a compile time errors if property names are changed in TimelineProperties
function propertyName<T>(name: keyof T){
return name;
}
// Now define all bindings in a decorator instead of inside the classes
function timelineBindings() {
return function(target) {
bindable(propertyName<TimelineProperties>("loading"))(target);
bindable(propertyName<TimelineProperties>("error"))(target);
bindable(propertyName<TimelineProperties>("list"))(target);
}
}
#customElement('timeline')
#timelineBindings()
export class Timeline extends TimelineProperties {
...
}
I'm not sure why you are trying to create such a deep hierarchy of classes for this. These classes have one or two properties, so I'm not seeing what the benefit is of the inheritance tree. You're writing TypeScript, so you'd probably be better served by using interfaces for this stuff.
How many other places is TimelineProperties used in your codebase? If this is the only place then there isn't really code duplication in reality. The code duplication is caused by your insistence on using a bunch of classes to unnecessarily complicate the design.
I'm sorry if I'm misreading this from your example code, but I very often see developers going crazy with inheritance and such thinking it will help them not repeat themselves, but in the end the benefits of DRY are vastly outweighed by the increased complexity of the solution they produce.
So anyways, assuming there actually is a need to reuse these classes in multiple places, I would recommend using decorators to accomplish this.
For example: https://gist.run/?id=6ad573e051f53c8e163d36dc31dc36b6
timeline-props.js
import {bindable} from 'aurelia-framework';
export function timelineProps() {
return function(target) {
bindable('list')(target);
}
}
timeline.js
import {timelineProps} from 'timeline-props';
#timelineProps()
export class Timeline {
}
timeline.html
<template>
<ul>
<li repeat.for="item of list">
${item}
</li>
</ul>
</template>
app.html
<template>
<require from="./timeline"></require>
<timeline list.bind="items"></timeline>
</template>
This is the exact type of use-case that decorators were created to handle.
I just want to write a User module which has multiple classes i.e. UserDetail,UserResestPassword so on .
There are some common properties which these classes are going to share , one approach I can declare property on every class and Initialize it.
The second approach is I will use of inheritance this will need to declare in
interface
export interface Iuser {
UserID:string;
UserName:string
}
and implement it into classes
import {Iuser} from './IuserDetail'
class UserInfo implements Iuser {}
My question is, is't supported in typescript? if not what are the way around to sort out this
In TypeScript there is a feature called "type erasure" where all of the type information is removed during compilation so the JavaScript output contains no type annotations, interfaces, or ambient declarations - only real code.
This means that when you ask your module loader (at runtime) to get you the Iuser interface, it doesn't exist in the JavaScript file.
My recommendation would probably be to put your interfaces in files with their primary implementation. That means you don't end up attempting to load a module that is just a blank file.
For example:
export interface Iuser {
UserID:string;
USerName:string
}
export class UserInfo implements Iuser {
}
And in other files you can:
import * as User from './UserInfo'
export class Example implements User.Iuser {
}
Look at this example:
Interfaces don't actually exist after compilation but are strictly used for type checking.
// i-user.ts
export interface IUser {
UserID:string;
UserName:string
}
Just like interfaces in other languages you have to implement all members of an interface. TypeScript will check if you are missing any.
// user-info.ts
import { IUser } from './i-user'
export class UserInfo implements IUser {
UserID:string;
UserName:string;
OtherInfo:string;
}
Using extend all parent methods will be available and you don't need to implement them again.
// specific-user-info.ts
import { UserInfo } from './user-info'
class SpecificUserInfo extends UserInfo {
logInfo() {
console.log(this.UserID, this.UserName);
}
}
In aurelia, is it possible to have a custom decorator automatically inject dependencies into the classes it decorates or does each decorated class need to inject the dependencies itself (at least in its constructor)?
In other words, is this the best that can be done:
Custom Decorator (my-decorator.js)
import {inject} from 'aurelia-framework';
import {Dependency} from 'dependency';
export function MyDecorator() {
return function(target) {
inject(Dependency)(target);
}
}
Decorated Class
import {MyDecorator} from "my-decorator";
#MyDecorator()
export class DecoratedClass {
constructor(dependency) {
this.dependency = dependency;
}
}
The inject property of a class is typically static. When most of the resolvers are used they simply "augment" the static inject property so that the container can resolve them using the specified resolver and pass them to the constructor. I don't know that a class decorator would help in this instance as it doesn't decorate the instance in anyway that I can think of, though I may be wrong.
If you are trying to create a new instance and still use DI to resolve dependencies you should look at the Factory resolver which supports this.
If you are trying to completely leave the constructor alone another idea would be to decorate a property or function instead which uses a complete separate property static customInject for example that resolves dependencies on the instance when invoked at start.
I found this line of code in Aurelia Dialog
static inject = [DialogService];
This is the full class:
import {Prompt} from './prompt';
import {DialogService} from '../dialog-service';
export class CommonDialogs {
static inject = [DialogService];
constructor(dialogService){
this.dialogService = dialogService;
}
prompt(question){
return this.dialogService.open({viewModel:Prompt, model:question});
};
}
What is the static inject doing? I get that it is injecting the dialog service into the constructor. But why do it this way instead of the usual inject?
As the blog post you linked to mentions, static inject was the original way to do dependency injection. Once Babel started supporting decorators, we implemented the inject decorator to make Aurelia code look a little nicer. Under the covers, it simply adds the inject property to the class at runtime (https://github.com/aurelia/dependency-injection/blob/master/src/decorators.js#L13).