ng-shopping-cart
Version:
An Angular component library to create shopping carts
2,204 lines • 71.2 kB
JavaScript
import { EventEmitter, Component, Input, Output, Inject, LOCALE_ID, ComponentFactoryResolver, Directive, NgModuleRef, ViewContainerRef, InjectionToken, Injectable, Pipe, NgModule } from '@angular/core';
import { CurrencyPipe, getLocaleCurrencyName, CommonModule } from '@angular/common';
import { HttpClient, HttpParams, HttpRequest, HttpClientModule } from '@angular/common/http';
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Converts a string into an object with all currency display options as properties.
* @param {?} format
* @return {?}
*/
function parseLocaleFormat(format) {
if (!format) {
throw new Error('Invalid format for currency. Expected a non empty string');
}
const /** @type {?} */ res = { currencyCode: undefined, display: 'symbol', digitsInfo: undefined, locale: undefined };
const /** @type {?} */ props = ['currencyCode', 'display', 'digitsInfo', 'locale'];
const /** @type {?} */ parts = format.split(':');
const /** @type {?} */ partsLength = parts.length;
if (partsLength > 4) {
throw new Error(`Invalid format for currency. Expected a value in the form ${props.join(':')} and got ${format}`);
}
parts.forEach((p, idx) => {
let /** @type {?} */ value = p;
if (value === 'auto') {
value = idx === 1 ? 'symbol' : undefined;
}
res[props[idx]] = value;
});
return res;
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
/**
* The base class for storing items in your cart
*
* \@service
* \@note {warning} Do not modify the items `id` after they are added to the cart. Doing so could result in duplicates which can cause
* undefined behaviour
* \@order 1
* @abstract
* @template T
*/
class CartService {
constructor() {
this.localeFormat = parseLocaleFormat('auto');
this.format = 'auto';
/**
* Emits an event every time items, tax, shipping cost or currency formats are changed in the cart.
*/
this.onChange = new EventEmitter();
/**
* Emits an event every time an item is added to the cart.
*/
this.onItemAdded = new EventEmitter();
/**
* Emits an event every time an item is removed from the cart.
*
* > This event only fires when a single item is removed by id. If you want to be notified of any removal (eg: clearing the cart) listen
* to the `onChange` or the `onItemsChanged` event instead.
*/
this.onItemRemoved = new EventEmitter();
/**
* Emits an event every time an item is added or removed from the cart.
*/
this.onItemsChanged = new EventEmitter();
/**
* Emits an event every time taxes for the cart are changed.
*/
this.onTaxChange = new EventEmitter();
/**
* Emits an event every time shipping costs for the cart are changed.
*/
this.onShippingChange = new EventEmitter();
}
/**
* Returns the tax computation of the shopping cart.
* @return {?}
*/
getTax() {
return this.cost() * (this.getTaxRate() / 100);
}
/**
* Returns the total cost of the shopping cart including shipping and taxes.
* @return {?}
*/
totalCost() {
return this.cost() + this.getTax() + this.getShipping();
}
/**
* Changes the currency symbol and number format for all components associated to this service instance.
*
* Check the Angular `CurrencyPipe` and the Internationalization guide for more info.
* @param {?} format
* @return {?}
*/
setLocaleFormat(format) {
this.localeFormat = parseLocaleFormat(format);
this.format = format;
this.onChange.emit({ change: 'format', value: this.format });
}
/**
* Returns the currency format as set with `setCurrencyFormat` or `'auto'` if no value is set.
*
* Passing true as parameter will return an object instead of a string.
* @param {?=} object
* @return {?}
*/
getLocaleFormat(object = false) {
return object ? this.localeFormat : this.format;
}
/**
* Returns an object with all the cart information in it, useful for serialization of the cart.
* @return {?}
*/
toObject() {
return {
taxRate: this.getTaxRate(),
shipping: this.getShipping(),
items: this.getItems()
};
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* The base class for every unit of information stored in the cart service
* \@order 1
* @abstract
*/
class CartItem {
/**
* Return the total cost of the item, that is the price multiplied by the quantity
* @return {?}
*/
total() {
return this.getPrice() * this.getQuantity();
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* A component to add items to the cart by pressing a button. Has different built-in editors to select quantity.
*
* \@order 1
* \@howToUse "With a custom button or projected content"
* ```html
* <add-to-cart [item]="item" [custom]="true">
* <button type="button" class="my-custom-class">Add item</button>
* </add-to-cart>
* ```
*
* \@howToUse "With different text and classes"
* ```html
* <add-to-cart [item]="item" [buttonText]="'Add item'" [buttonClass]="'my-custom-class'">
* </add-to-cart>
* ```
*
* \@howToUse "With a html number input positioned on top"
* ```html
* <add-to-cart [item]="item" [type]="'number'" [position]="'top'">
* </add-to-cart>
* ```
*
* \@howToUse "With a select for selecting quantity"
* ```html
* <add-to-cart [item]="item" [type]="'dropdown'" [dropdown]="[{ label: 'One item', value: 1 }, { label: 'Two items', value: 2 }]">
* </add-to-cart>
* ```
*
* \@howToUse "With the default button and different quantity"
* ```html
* <add-to-cart [item]="item" [quantity]="5">
* </add-to-cart>
* ```
*
* \@note {warning} This component captures click events that bubble from its projected content if you are using `[custom]=true` therefore if
* you have html content other than buttons inside you must stop the event propagation unless the click originated in the button that
* add the items to the cart.
*/
class AddToCartComponent {
/**
* @param {?} cartService
*/
constructor(cartService) {
this.cartService = cartService;
this._editorQuantity = 1;
this.hasEditor = false;
this.horizontalEditor = true;
this.editorPrecedence = 'before';
/**
* If `false` displays a default button provided by the component, otherwise projects the contents of the component to be used as a
* button.
*/
this.custom = false;
/**
* Changes the default text of the component's button.
*/
this.buttonText = 'Add to cart';
/**
* Changes the default CSS class of the component's button.
*/
this.buttonClass = 'add-to-cart-button';
/**
* Renders a button or a button with an editor to select the quantity of the item that will be added in the cart. When it has a value
* other than `'button'` an editor is displayed depending on the selected `[type]`; it can be a `select`, or a text or a number `input`.
*
* > Do not confuse this input with the html attribute `type`. The default button is always generated with this attribute set to
* `button` to prevent accidental form submissions.
*/
this.type = 'button';
/**
* Sets the position where the editor will be placed. If the `[type]` is set to `'button'` no editor is displayed and this setting has
* no effect.
*/
this.position = 'left';
/**
* If `[type]` is set to `'dropdown'` it can be used to set the options of the rendered `select` editor. Is an array of objects with
* label and a value properties used to populate the select's `option` elements.
*/
this.dropdown = [
{ label: '1 item', value: 1 },
{ label: '2 items', value: 2 },
{ label: '5 items', value: 5 }
];
/**
* This event is fired when the component uses an editor and its value is changed by the user.
*/
this.change = new EventEmitter();
/**
* This event is fired when the item is added to the cart.
*/
this.added = new EventEmitter();
}
/**
* @return {?}
*/
get editorQuantity() {
return this._editorQuantity;
}
/**
* @param {?} value
* @return {?}
*/
set editorQuantity(value) {
this._editorQuantity = value;
this.change.emit(value);
}
/**
* @return {?}
*/
ngOnInit() {
this.computeClass();
}
/**
* @return {?}
*/
itemQuantity() {
if (this.type === 'button') {
if (this.quantity) {
return this.quantity;
}
return this.item.getQuantity();
}
else {
return this._editorQuantity;
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (changes['type']) {
this.hasEditor = changes['type'].currentValue !== 'button';
if (changes['type'].currentValue === 'dropdown' && this.dropdown.length) {
const /** @type {?} */ quantity = this.itemQuantity();
const /** @type {?} */ match = this.dropdown.find(i => i.value === quantity);
if (!match) {
this._editorQuantity = this.dropdown[0].value;
}
}
}
if (changes['position']) {
const /** @type {?} */ pos = changes['position'].currentValue;
this.horizontalEditor = pos === 'left' || pos === 'right';
this.editorPrecedence = pos === 'left' || pos === 'top' ? 'before' : 'after';
}
this.computeClass();
}
/**
* @param {?} evt
* @return {?}
*/
addToCart(evt) {
evt.stopPropagation();
if (this.item) {
const /** @type {?} */ quantity = this.itemQuantity();
this.item.setQuantity(quantity);
this.cartService.addItem(this.item);
this.added.emit(this.item);
}
}
/**
* @return {?}
*/
computeClass() {
this.containerClass = [
'add-to-cart-' + this.type,
this.horizontalEditor ?
'editor-position-horizontal' :
'editor-position-vertical'
];
}
}
AddToCartComponent.decorators = [
{ type: Component, args: [{
selector: 'add-to-cart',
// tslint:disable-line component-selector
template: `<div class="add-to-cart" [ngClass]="containerClass">
<div class="add-to-cart-component" [ngClass]="position"
*ngIf="editorPrecedence === 'before' && hasEditor">
<add-to-cart-editor [type]="type" [dropdown]="dropdown" [(value)]="editorQuantity"></add-to-cart-editor>
</div>
<div class="cart-button-container" (click)="addToCart($event)">
<ng-container *ngIf="!custom">
<button type="button" [ngClass]="buttonClass" [disabled]="!item">
{{buttonText}}
</button>
</ng-container>
<ng-content *ngIf="custom">
</ng-content>
</div>
<div class="add-to-cart-component" [ngClass]="position"
*ngIf="editorPrecedence === 'after' && hasEditor">
<add-to-cart-editor [type]="type" [dropdown]="dropdown" [(value)]="editorQuantity"></add-to-cart-editor>
</div>
</div>
`,
},] },
];
/** @nocollapse */
AddToCartComponent.ctorParameters = () => [
{ type: CartService, },
];
AddToCartComponent.propDecorators = {
"custom": [{ type: Input },],
"item": [{ type: Input },],
"buttonText": [{ type: Input },],
"buttonClass": [{ type: Input },],
"type": [{ type: Input },],
"position": [{ type: Input },],
"dropdown": [{ type: Input },],
"quantity": [{ type: Input },],
"change": [{ type: Output },],
"added": [{ type: Output },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Renders a button to initiate checkout of the cart.
*
* \@order 6
* \@howToUse "With a custom button or projected content"
* ```html
* <cart-checkout [custom]="true">
* <button type="button" class="my-custom-class">Do checkout</button>
* </cart-checkout>
* ```
*
* \@howToUse "With different text and classes"
* ```html
* <cart-checkout [buttonText]="'Add item'" [buttonClass]="'my-custom-class'">
* </cart-checkout>
* ```
*
* \@howToUse "Using http in a protected endpoint"
* ```html
* <cart-checkout [service]="'http'" settings="settings">
* </cart-checkout>
* ```
* ```typescript
* export class MyComponent {
* settings: CheckoutHttpSettings = {
* method: 'POST',
* url: 'http://myapi.com/',
* options: { headers: { Authorization: 'Bearer my-auth-token' } }
* };
* }
* ```
*
* \@howToUse "Using the PayPal service"
* ```html
* <cart-checkout [service]="'paypal'" settings="settings">
* </cart-checkout>
* ```
* ```typescript
* export class MyComponent {
* settings: CheckoutPaypalSettings = {
* business: 'myaccount\@paypal.com',
* itemName: 'myMarketplaceAppCart',
* itemNumber: '1234',
* serviceName: 'MyBusiness',
* country: 'US'
* };
* }
* ```
*
* \@note {warning} This component captures clicks events bubbling from its projected content. Make sure the event keeps bubbling only when
* you want the checkout operation to start.
*
* \@note {warning} When the `[service]` is set to `paypal` an actual PayPal button is rendered. None of the inputs `custom`, `buttonText`
* or `buttonClass` have any effect.
*/
class CartCheckoutComponent {
/**
* @param {?} cartService
* @param {?} httpClient
* @param {?} locale
*/
constructor(cartService, httpClient, locale) {
this.cartService = cartService;
this.httpClient = httpClient;
this.locale = locale;
this.empty = true;
this.cost = 0;
this.taxRate = 0;
this.shipping = 0;
this.currency = 'USD';
this.paypalLocale = 'en';
/**
* If `false` displays a default button provided by the component. When set to `true` projects the contents of the component.
*/
this.custom = false;
/**
* Changes the default text of the component's button.
*/
this.buttonText = 'Checkout';
/**
* Changes the default text of the component's button.
*/
this.buttonClass = 'cart-checkout-button';
/**
* Sets the type of service to be used when initiating the checkout.
*/
this.service = 'log';
/**
* Depending on the type of the service you might need to add some configuration to it. This input allows you to change that
* configuration.
*/
this.settings = null;
/**
* Emits the result of the checkout operation. If the service is set to `'log'` it emits the entire cart object including tax rates and
* shipping info. If is set to `'http'` it emits an `HttpResponse` object with body, headers, etc as it was received by the remote server.
*
* > When `[service]` is set to `'paypal'` this event is never emitted.
*/
this.checkout = new EventEmitter();
/**
* When the `[service]` is set to `'http'` and the checkout operation fails the thrown error can be captured using this output.
*
* The emitted value is the complete `HttpErrorResponse` object returned by `HttpClient` so you can inspect other properties like status
* codes, headers, messages, etc.
*/
this.error = new EventEmitter();
this.getLocaleCurrencyName = getLocaleCurrencyName;
}
/**
* @return {?}
*/
ngOnInit() {
this.updateCart(true);
this._serviceSubscription = this.cartService
.onChange
.subscribe((evt) => this.updateCart(evt.change === 'format'));
}
/**
* @param {?} formatChange
* @return {?}
*/
updateCart(formatChange) {
this.empty = this.cartService.isEmpty();
this.cost = this.cartService.cost();
this.taxRate = this.cartService.getTaxRate();
this.shipping = this.cartService.getShipping();
if (formatChange) {
this.updateLocale();
}
}
/**
* @return {?}
*/
updateLocale() {
this.format = this.localeFormat ?
parseLocaleFormat(this.localeFormat) : /** @type {?} */ (this.cartService.getLocaleFormat(true));
const /** @type {?} */ loc = this.format.locale || this.locale;
this.paypalLocale = loc.substring(0, 2);
this.currency = this.format.currencyCode || this.getCurrency(loc);
}
/**
* @param {?} locale
* @return {?}
*/
getCurrency(locale) {
const /** @type {?} */ currencyCode = this.getLocaleCurrencyName(locale);
if (!currencyCode) {
return 'USD';
}
if (currencyCode.length === 3) {
return currencyCode;
}
// Angular < 6 return "US Dollar" instead of "USD" so we have to hack the code using the currency pipe
// You will also get USD on locales where you should get EUR so for those versions currencyCode must be used
const /** @type {?} */ fmt = new CurrencyPipe(locale);
const /** @type {?} */ val = fmt.transform(0, undefined, 'code', '1.0-0', locale);
const /** @type {?} */ pre = val.startsWith('0');
return val.substr(pre ? -3 : 0, 3);
}
/**
* @return {?}
*/
doCheckout() {
let /** @type {?} */ cart = this.cartService.toObject();
switch (this.service) {
case 'log':
console.log(cart);
this.checkout.emit(cart);
break;
case 'http':
if (!this.settings) {
throw new Error('Missing settings configuration');
}
const /** @type {?} */ verbs = ['POST', 'PUT', 'PATCH'];
const { url, method = 'POST', options, body } = this.httpSettings;
const /** @type {?} */ methodUpper = method.toUpperCase();
if (verbs.indexOf(methodUpper) === -1) {
throw new Error(`Invalid http verb found in method setting. Expected one of ${verbs.join(' ')} and got ${method}`);
}
if (body) {
cart = typeof body === 'function' ? body(cart) : Object.assign({}, cart, body);
}
if (options && options.headers && options.headers.has('Content-Type')) {
const /** @type {?} */ contentType = options.headers.get('Content-Type');
if (contentType.startsWith('application/x-www-form-urlencoded')) {
cart = new HttpParams({ fromObject: cart });
}
}
this.httpClient
.request(new HttpRequest(methodUpper, url, cart, options))
.toPromise()
.then(response => {
this.checkout.emit(response);
})
.catch(err => {
this.error.emit(err);
});
break;
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (changes['settings'] && changes['settings'].currentValue) {
const /** @type {?} */ hasOwn = Object.prototype.hasOwnProperty;
const /** @type {?} */ value = changes['settings'].currentValue;
if (hasOwn.call(value, 'business')) {
this.paypalSettings = changes['settings'].currentValue;
}
if (hasOwn.call(value, 'url')) {
this.httpSettings = changes['settings'].currentValue;
}
}
if (changes['localeFormat']) {
this.updateLocale();
}
}
/**
* @return {?}
*/
ngOnDestroy() {
this._serviceSubscription.unsubscribe();
}
}
CartCheckoutComponent.decorators = [
{ type: Component, args: [{
selector: 'cart-checkout',
template: `<ng-container *ngIf="service === 'log' || service === 'http'">
<button [ngClass]="buttonClass" [disabled]="empty" *ngIf="!custom" (click)="doCheckout()">
{{buttonText}}
</button>
<span (click)="doCheckout()" *ngIf="custom">
<ng-content>
</ng-content>
</span>
</ng-container>
<ng-container *ngIf="service === 'paypal'">
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_xclick"/>
<input type="hidden" name="business" value="{{paypalSettings.business}}"/>
<input type="hidden" name="lc" value="{{paypalLocale}}"/>
<input type="hidden" name="item_name" value="{{paypalSettings.itemName}}" *ngIf="paypalSettings.itemName"/>
<input type="hidden" name="item_number" value="{{paypalSettings.itemNumber}}" *ngIf="paypalSettings.itemNumber"/>
<input type="hidden" name="amount" value="{{cost}}"/>
<input type="hidden" name="currency_code" value="{{currency}}"/>
<input type="hidden" name="tax_rate" value="{{taxRate}}"/>
<input type="hidden" name="shipping" value="{{shipping}}"/>
<input type="hidden" name="bn" value="{{paypalSettings.serviceName + '_BuyNow_WPS_' + paypalSettings.country}}"
*ngIf="paypalSettings.serviceName && paypalSettings.country"/>
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_buynowCC_LG.gif" border="0" name="submit"
alt="PayPal - The safer, easier way to pay online!"/>
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1"/>
</form>
</ng-container>
`,
},] },
];
/** @nocollapse */
CartCheckoutComponent.ctorParameters = () => [
{ type: CartService, },
{ type: HttpClient, },
{ type: undefined, decorators: [{ type: Inject, args: [LOCALE_ID,] },] },
];
CartCheckoutComponent.propDecorators = {
"custom": [{ type: Input },],
"buttonText": [{ type: Input },],
"buttonClass": [{ type: Input },],
"service": [{ type: Input },],
"settings": [{ type: Input },],
"localeFormat": [{ type: Input },],
"checkout": [{ type: Output },],
"error": [{ type: Output },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Renders a summary of the contents of the cart.
*
* \@order 3
* \@howToUse "With a different icon"
* ```html
* <cart-summary [icon]="'http://myapi/assets/icon.svg'"></cart-summary>
* ```
*
* \@howToUse "Display different words when the cart changes"
* ```html
* <cart-summary [noItemsText]="'Zero items'" [oneItemText]="'Single item'" [manyItemsText]="'Exactly # items'"></cart-summary>
* ```
*
* \@howToUse "Using always a number to display item count"
* ```html
* <cart-summary [noItemsText]="'# items'" [oneItemText]="'# items'" [manyItemsText]="'# items'"></cart-summary>
* ```
*
* \@note {info} Inputs that allows you to customize text also accept the special character `#` to use numbers instead of words to
* specify quantity, for example `'# bla'` will update to `'0 bla'` or `'1 bla'` when the number of items in the cart change.
*/
class CartSummaryComponent {
/**
* @param {?} cartService
*/
constructor(cartService) {
this.cartService = cartService;
/**
* The text to display when there are no items in the cart.
*/
this.noItemsText = 'No items';
/**
* The text to display when there is only one item in the cart.
*/
this.oneItemText = 'One item';
/**
* The text to display when there are several items in the cart.
*/
this.manyItemsText = '# items';
this.totalItems = 0;
this.totalCost = 0;
}
/**
* @return {?}
*/
updateItemsText() {
let /** @type {?} */ text = this.noItemsText;
if (this.totalItems > 0) {
text = this.totalItems === 1 ? this.oneItemText : this.manyItemsText;
}
this.itemsText = text.replace('#', this.totalItems.toString());
}
/**
* @return {?}
*/
updateComponent() {
this.totalItems = this.cartService.itemCount();
this.totalCost = !this.cartService.isEmpty() ? this.cartService.totalCost() : 0;
if (!this.localeFormat) {
this.format = /** @type {?} */ (this.cartService.getLocaleFormat(true));
}
this.updateItemsText();
}
/**
* @return {?}
*/
ngOnInit() {
this.updateComponent();
this._serviceSubscription = this.cartService.onChange.subscribe(() => {
this.updateComponent();
});
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (changes['localeFormat']) {
this.format = this.localeFormat ?
parseLocaleFormat(this.localeFormat) : /** @type {?} */ (this.cartService.getLocaleFormat(true));
}
if (changes['noItemsText'] || changes['oneItemText'] || changes['manyItemsText']) {
this.updateItemsText();
}
}
/**
* @return {?}
*/
ngOnDestroy() {
this._serviceSubscription.unsubscribe();
}
}
CartSummaryComponent.decorators = [
{ type: Component, args: [{
selector: 'cart-summary',
template: `<div class="cart-summary">
<div class="cart-summary-icon">
<svg *ngIf="!icon" version="1.1" class="summary-icon" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 32 32">
<path stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none" stroke-linejoin="round"
d="M2,4 L6,4 L10,20 L21,20 L25,10 L8,10 M13,10 L13,20 M18,20 L18,10 M10,15 L23,15"/>
<circle fill="currentColor" cx="11" cy="24" r="2"/>
<circle fill="currentColor" cx="21" cy="24" r="2"/>
</svg>
<img *ngIf="icon" [src]="icon" class="summary-icon" alt="cart-summary-icon">
</div>
<div class="cart-summary-contents">
<div class="cart-summary-items">
{{itemsText}}
</div>
<div class="cart-summary-cost">
{{totalCost | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</div>
</div>
</div>
`,
},] },
];
/** @nocollapse */
CartSummaryComponent.ctorParameters = () => [
{ type: CartService, },
];
CartSummaryComponent.propDecorators = {
"icon": [{ type: Input },],
"noItemsText": [{ type: Input },],
"oneItemText": [{ type: Input },],
"manyItemsText": [{ type: Input },],
"localeFormat": [{ type: Input },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* An editor to change the quantity of an item to add to the cart.
* @ignore
*/
class AddToCartEditorComponent {
constructor() {
/**
* The type of editor to display.
*/
this.type = 'text';
/**
* The value to display in the editor
*/
this.value = 1;
/**
* Emits a the value of the editor when the user changes it
*/
this.valueChange = new EventEmitter();
}
/**
* @param {?} value
* @return {?}
*/
changeValue(value) {
const /** @type {?} */ val = parseFloat(value);
this.valueChange.emit(Number.isNaN(val) ? 1 : val);
}
}
AddToCartEditorComponent.decorators = [
{ type: Component, args: [{
selector: 'add-to-cart-editor',
// tslint:disable-line component-selector
template: `<ng-container *ngIf="type === 'dropdown'">
<select class="add-to-cart-input" #selectAmount (change)="changeValue(selectAmount.value)">
<option [selected]="item.value === value" *ngFor="let item of dropdown" [value]="item.value">{{item.label}}</option>
</select>
</ng-container>
<ng-container *ngIf="type === 'number' || type === 'text'">
<input class="add-to-cart-input" #inputAmount [type]="type" (change)="changeValue(inputAmount.value)" [value]="value">
</ng-container>
`,
},] },
];
/** @nocollapse */
AddToCartEditorComponent.ctorParameters = () => [];
AddToCartEditorComponent.propDecorators = {
"type": [{ type: Input },],
"dropdown": [{ type: Input },],
"value": [{ type: Input },],
"valueChange": [{ type: Output },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Renders a view of the cart.
*
* \@order 2
* \@howToUse "Using responsive layout"
* ```html
* <cart-view [display]="'responsive'">
* </cart-view>
* ```
*
* \@howToUse "No images and using scrollbars on small screens"
* ```html
* <cart-view [images]="false" [display]="'responsive-table'">
* </cart-view>
* ```
*
* \@howToUse "Using different text for headers"
* ```html
* <cart-view [emptyText]="headers.empty" [nameHeaderText]="headers.name" [quantityHeaderText]="headers.quantity"
* [priceHeaderText]="headers.quantity" [totalHeaderText]="headers.total" [taxFooterText]="footers.tax"
* [shippingFooterText]="footers.shipping" [totalFooterText]="footers.total"
* >
* </cart-view>
* ```
* ```typescript
* export class MyComponent {
* headers = {
* empty: 'No items. Add some to the cart',
* name: 'Description',
* quantity: 'Amount',
* price: 'Cost',
* total: 'Total x item',
* }
* footers = {
* tax: 'Tax rate',
* shipping: 'Shipping cost',
* total: 'Total cost'
* }
* }
* ```
*
* \@howToUse "Change the default empty cart content"
* ```html
* <cart-view [customEmptyContent]="true">
* <div class="my-empty-cart-view">
* <span style="font-size: 36px;" class="glyphicon glyphicon-shopping-cart" aria-hidden="true"></span>
* Your cart is empty
* </div>
* </cart-view>
* ```
*/
class CartViewComponent {
/**
* @param {?} cartService
*/
constructor(cartService) {
this.cartService = cartService;
/**
* Changes the appearance how the cart view displays in different screen sizes
*/
this.display = 'responsive-table';
/**
* Whether to include images in the cart or not.
*/
this.images = true;
/**
* The text to show when the cart has no items in it.
*/
this.emptyText = 'Your cart is empty';
/**
* When set to `true` and the cart is empty displays the projected content of the component as the empty content.
*/
this.customEmptyContent = false;
/**
* The text to display in the header of the name column.
*/
this.nameHeaderText = 'Name';
/**
* The text to display in the header of the quantity column.
*/
this.quantityHeaderText = 'Quantity';
/**
* The text to display in the header of the price column.
*/
this.priceHeaderText = 'Price';
/**
* The text to display in the header of the total per item column.
*/
this.totalHeaderText = 'Total';
/**
* The text to display in the tax section of the footer.
*/
this.taxFooterText = 'Tax';
/**
* The text to display in the shipping section of the footer.
*/
this.shippingFooterText = 'Shipping';
/**
* The text to display in the total section of the footer.
*/
this.totalFooterText = 'Total';
this.empty = true;
this.taxRate = 0;
this.tax = 0;
this.shipping = 0;
this.cost = 0;
}
/**
* @return {?}
*/
update() {
this.empty = this.cartService.isEmpty();
this.items = this.cartService.getItems();
this.taxRate = this.cartService.getTaxRate() / 100;
this.tax = this.cartService.getTax();
this.shipping = this.cartService.getShipping();
this.cost = this.cartService.totalCost();
if (!this.localeFormat) {
this.format = /** @type {?} */ (this.cartService.getLocaleFormat(true));
}
}
/**
* @param {?} item
* @return {?}
*/
increase(item) {
item.setQuantity(item.getQuantity() + 1);
this.cartService.addItem(item);
}
/**
* @param {?} item
* @return {?}
*/
decrease(item) {
if (item.getQuantity() > 1) {
item.setQuantity(item.getQuantity() - 1);
this.cartService.addItem(item);
}
else {
this.cartService.removeItem(item.getId());
}
}
/**
* @return {?}
*/
ngOnInit() {
this.update();
this._serviceSubscription = this.cartService.onChange.subscribe(() => {
this.update();
});
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
if (changes['localeFormat']) {
this.format = this.localeFormat ?
parseLocaleFormat(this.localeFormat) : /** @type {?} */ (this.cartService.getLocaleFormat(true));
}
}
/**
* @return {?}
*/
ngOnDestroy() {
this._serviceSubscription.unsubscribe();
}
}
CartViewComponent.decorators = [
{ type: Component, args: [{
selector: 'cart-view',
template: `<ng-container *ngIf="empty">
<div *ngIf="!customEmptyContent" class="cart-view-empty">
{{emptyText}}
</div>
<ng-content *ngIf="customEmptyContent"></ng-content>
</ng-container>
<ng-container *ngIf="!empty">
<ng-container *ngIf="display !== 'table'">
<div *ngIf="!empty" class="cart-view" [ngClass]="display + '-display'">
<div class="cart-list-view" [ngClass]="{'no-images': !images}">
<div class="cart-list-header">
<div class="cart-list-header-value cart-list-image-header">
</div>
<div class="cart-list-header-value cart-list-name-header">
{{nameHeaderText}}
</div>
<div class="cart-list-header-value cart-list-quantity-header">
{{quantityHeaderText}}
</div>
<div class="cart-list-header-value cart-list-price-header">
{{priceHeaderText}}
</div>
<div class="cart-list-header-value cart-list-total-header">
{{totalHeaderText}}
</div>
</div>
<div *ngFor="let cartItem of items" class="cart-list-item">
<div class="cart-list-value cart-list-image-value"
[ngStyle]="{'background-image': 'url(' + cartItem.getImage() + ')'}">
</div>
<div class="cart-list-value cart-list-name-value">
{{cartItem.getName()}}
</div>
<div class="cart-list-value cart-list-quantity-value">
<button type="button" class="cart-increase-button" (click)="increase(cartItem)">
+
</button>
<span class="cart-list-quantity-content">{{cartItem.getQuantity()}}</span>
<button type="button" class="cart-decrease-button" (click)="decrease(cartItem)">
-
</button>
</div>
<div class="cart-list-value cart-list-price-value">
{{cartItem.getPrice() | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</div>
<div class="cart-list-value cart-list-total-value">
{{cartItem.total() | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</div>
</div>
<div class="cart-list-footer">
<div class="cart-list-summary">
<div class="cart-empty-summary" *ngIf="images"></div>
<div class="cart-empty-summary"></div>
<div class="cart-empty-summary"></div>
<div class="cart-empty-summary"></div>
<div class="cart-tax-summary">
{{taxFooterText}}: ({{taxRate | percent:format.digitsInfo:format.locale}})
{{tax | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</div>
</div>
<div class="cart-list-summary">
<div class="cart-empty-summary" *ngIf="images"></div>
<div class="cart-empty-summary"></div>
<div class="cart-empty-summary"></div>
<div class="cart-empty-summary"></div>
<div class="cart-shipping-summary">
{{shippingFooterText}}:
{{shipping | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</div>
</div>
<div class="cart-list-summary">
<div class="cart-empty-summary" *ngIf="images"></div>
<div class="cart-empty-summary"></div>
<div class="cart-empty-summary"></div>
<div class="cart-empty-summary"></div>
<div class="cart-total-summary">
{{totalFooterText}}:
{{cost | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</div>
</div>
</div>
</div>
</div>
</ng-container>
<ng-container *ngIf="display === 'table'">
<table class="cart-view table-display">
<thead class="cart-list-header">
<tr>
<th class="cart-list-header-value cart-list-image-header" *ngIf="images"></th>
<th class="cart-list-header-value cart-list-name-header">{{nameHeaderText}}</th>
<th class="cart-list-header-value cart-list-quantity-header">{{quantityHeaderText}}</th>
<th class="cart-list-header-value cart-list-price-header">{{priceHeaderText}}</th>
<th class="cart-list-header-value cart-list-total-header">{{totalHeaderText}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let cartItem of items">
<td class="cart-list-value cart-list-image-value" *ngIf="images"
[ngStyle]="{'background-image': 'url(' + cartItem.getImage() + ')'}">
</td>
<td class="cart-list-value cart-list-name-value">{{cartItem.getName()}}</td>
<td class="cart-list-value cart-list-quantity-value">
<button type="button" class="cart-increase-button" (click)="increase(cartItem)">
+
</button>
<span class="cart-list-quantity-content">{{cartItem.getQuantity()}}</span>
<button type="button" class="cart-decrease-button" (click)="decrease(cartItem)">
-
</button>
</td>
<td class="cart-list-value cart-list-price-value">
{{cartItem.getPrice() | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</td>
<td class="cart-list-value cart-list-total-value">
{{cartItem.total() | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</td>
</tr>
</tbody>
<tfoot class="cart-list-footer">
<tr class="cart-list-summary">
<td [attr.colspan]="images ? 4 : 3" class="cart-empty-summary"></td>
<td class="cart-tax-summary">
{{taxFooterText}}: ({{taxRate | percent:format.digitsInfo:format.locale}})
{{tax | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</td>
</tr>
<tr class="cart-list-summary">
<td [attr.colspan]="images ? 4 : 3" class="cart-empty-summary"></td>
<td class="cart-shipping-summary">
{{shippingFooterText}}:
{{shipping | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</td>
</tr>
<tr class="cart-list-summary">
<td [attr.colspan]="images ? 4 : 3" class="cart-empty-summary"></td>
<td class="cart-total-summary">
{{totalFooterText}}:
{{cost | currency:format.currencyCode:format.display:format.digitsInfo:format.locale}}
</td>
</tr>
</tfoot>
</table>
</ng-container>
</ng-container>
`,
},] },
];
/** @nocollapse */
CartViewComponent.ctorParameters = () => [
{ type: CartService, },
];
CartViewComponent.propDecorators = {
"display": [{ type: Input },],
"images": [{ type: Input },],
"emptyText": [{ type: Input },],
"customEmptyContent": [{ type: Input },],
"nameHeaderText": [{ type: Input },],
"quantityHeaderText": [{ type: Input },],
"priceHeaderText": [{ type: Input },],
"totalHeaderText": [{ type: Input },],
"taxFooterText": [{ type: Input },],
"shippingFooterText": [{ type: Input },],
"totalFooterText": [{ type: Input },],
"localeFormat": [{ type: Input },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* The default implementation of a showcase item.
*
* \@note {info} This component is only intended to be used as a template for you to create your own components either by pure css
* customization or by providing an actually working sample to serve as a guide for more complex cases.
*
* \@order 5
*/
class CartShowcaseItemComponent {
}
CartShowcaseItemComponent.decorators = [
{ type: Component, args: [{
selector: 'cart-showcase-item',
template: `<div class="showcase-item default-sc-item" [ngStyle]="{'background-image': 'url(' + item.getImage() + ')'}">
<div class="default-sc-description">
<div class="default-sc-name">
{{item.getName()}}
</div>
<div class="default-sc-price">
{{item.getPrice() | cartCurrency:format}}
</div>
</div>
</div>
`,
},] },
];
/** @nocollapse */
CartShowcaseItemComponent.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Renders items arranged in columns using a dynamic component for the item. Useful for getting started with e-commerce applications.
*
* \@order 4
* \@howToUse "Using wider items"
* ```html
* <cart-showcase [items]="items" [aspectRatio]="'2:1'">
* </cart-showcase>
* ```
*
* \@howToUse "Using four columns in all screen sizes bigger than 768px"
* ```html
* <cart-showcase [items]="items" [mCols]="4" [lCols]="4">
* </cart-showcase>
* ```
*
* \@howToUse "Using a different item component"
* ```html
* <!-- my-component.html -->
* <cart-showcase [items]="items" [itemComponent]="itemComponent">
* </cart-showcase>
* ```
* ```typescript
* // my-component.ts
* export class MyComponent {
* itemComponent = MyCustomItemComponent;
* }
*
* // my-custom-item-component.ts
* \@Component({
* selector: 'my-custom-item-component',
* template: '<div class="item-class">{{item.getName()}}</div>'
* })
* export class MyCustomItemComponent implements ShowcaseItem {
* item: CartItem;
* }
*
* // app.module.ts
* \@NgModule({
* // .....
* entryComponents: [MyCustomItemComponent],
* })
* export class AppModule {
* }
* ```
*
* \@note {warning} If you change the `[columns]` input you must also change the sass variable that controls the component grid and
* vice-versa. A similar procedure is required to create aspect ratios with values greater than four eg: `'1:5'`. Check the styling guide
* for more information.
*
* \@note {danger} The aspect ratio is the width/height proportion of the items therefore a ratio of `'2:2'` is equivalent to `'1:1'`.
* Redundant ratios like these are removed from the source so don't try to use them.
*
*/
class CartShowcaseComponent {
/**
* @param {?} cartService
*/
constructor(cartService) {
this.cartService = cartService;
this.xsClass = 'sc-container-xs-12';
this.sClass = 'sc-container-s-6';
this.mClass = 'sc-container-m-4';
this.lClass = 'sc-container-l-4';
this.xlClass = 'sc-container-xl-3';
this.ratioClass = 'sc-ratio-1-1';
/**
* The number of columns to display when the screen size matches phone devices.
*/
this.xsCols = 1;
/**
* The number of columns to display when the screen matches tablet devices.
*/
this.sCols = 2;
/**
* The number of columns to display when the screen matches desktop devices.
*/
this.mCols = 3;
/**
* The number of columns to display when the screen matches large desktop devices.
*/
this.lCols = 3;
/**
* The number of columns to display when the screen matches extra large desktop devices.
*/
this.xlCols = 4;
/**
* The number of columns in the grid.
* Only update this value if you changed the columns sass variable in the library styles following the Styling guide.
*/
this.columns = 12;
/**
* The component to render for each item. This type means any component that implements the interface `ShowcaseItem`.
*/
this.itemComponent = CartShowcaseItemComponent;
/**
* The aspect ratio of the container of the items. A value of `1:1` means square items, `2:1` means two times wider, `1:2` two times
* taller and so on.
*/
this.aspectRatio = '1:1';
}
/**
* @param {?} value
* @return {?}
*/
getColumnSize(value) {
return Math.floor(this.columns / value);
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
const /** @type {?} */ columnProps = ['xsCols', 'sCols', 'mCols', 'lCols', 'xlCols'];
const /** @type {?} */ classPrefix = ['xs', 's', 'm', 'l', 'xl'];
for (let /** @type {?} */ i = 0; i < columnProps.length; i++) {
const /** @type {?} */ prop = columnProps[i];
const /** @type {?} */ colChanges = changes[prop];
if (changes['columns'] || colChanges) {
const /** @type {?} */ prefix = classPrefix[i];
const /** @type {?} */ size = this.getColumnSize(this[prop]);
this[`${prefix}Class`] = `sc-container-${prefix}-${size}`;
}
}
if (changes['aspectRatio']) {
const /** @type {?} */ newRatio = changes['aspectRatio'].currentValue;
const /** @type {?} */ values = newRatio.split(':');
if (values.length === 2) {
this.ratioClass = `sc-ratio-${values[0]}-${values[1]}`;
}
}
if (changes['localeFormat']) {
this.format = this.localeFormat || /** @type {?} */ (this.cartService.getLocaleFormat());
}
}
/**
* @return {?}
*/
ngOnInit() {
this.format = this.localeFormat || /** @type {?} */ (this.cartService.getLocaleFormat());
this._serviceSubscription = this.cartService.onChange.subscribe((evt) => {
if (evt.change === 'format' && !this.localeFormat) {
this.format = /** @type {?} */ (this.cartService.getLocaleFormat());
}
});
}
/**
* @return {?}
*/
ngOnDestroy() {
this._serviceSubscription.unsubscribe();
}
}
CartShowcaseComponent.decorators = [
{ type: Component, args: [{
selector: 'cart-showcase',
template: `<div class="cart-showcase">
<div class="sc-item-container" *ngFor="let carItem of items"
[ngClass]="[xsClass, sClass, mClass, lClass, xlClass, ratioClass]">
<div class="sc-item-wrapper">
<ng-container
*cartShowcaseOutlet="itemComponent;item:carItem;format:format;injector:injector;ngModuleFactory:moduleFactory">
</ng-container>
</div>
</div>
</div>
`,
},] },
];
/** @nocollapse */
CartShowcaseComponent.ctorParameters = () => [
{ type: CartService, },
];
CartShowcaseComponent.propDecorators = {
"xsCols": [{ type: Input },],
"sCols": [{ type: Input },],
"mCols": [{ type: Input },],
"lCols": [{ type: Input },],
"xlCols": [{ type: Input },],
"columns": [{ type: Input },],
"items": [{ type: Input },],
"itemComponent": [{ type: Input },],
"injector": [{ type: Input },],
"moduleFactory": [{ type: Input },],
"aspectRatio": [{ type: Input },],
"localeFormat": [{ type: Input },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* A directive to create dynamic item components for the showcase component
* @ignore
*/
class ShowcaseOutletDirective {
/**
* @param {?} viewContainerRef
*/
constructor(viewContainerRef) {
this.viewContainerRef = viewContainerRef;
this._componentRef = null;
this._moduleRef = null;
}
/**
* @return {?}
*/
cleanModule() {
if (this._moduleRef) {
this._moduleRef.destroy();
}
}
/**
* @param {?} changes
* @return {?}
*/
ngOnChanges(changes) {
const /** @type {?} */ templateChange = Object.keys(changes).length !== 1 || !changes['cartShowcaseOutletFormat'];
if (templateChange) {
this.viewContainerRef.clear();
this._componentRef = null;
if (this.cartShowcaseOutlet) {
const /** @type {?} */ elInjector = this.cartShowcaseOutletInjector || this.viewContainerRef.parentInjector;
if (changes['cartShowcaseOutletNgModuleFactory']) {
this.cleanModule();
if (this.cartShowcaseOutletNgModuleFactory) {
const /** @type {?} */ parentModule = elInjector.get(NgModuleRef);
this._moduleRef = this.cartShowcaseOutletNgModuleFactory.create(parentModule.injector);
}
else {
this._moduleRef = null;
}
}
const /** @type {?} */ componentFactoryResolver = this._moduleRef ? this._moduleRef.componentFactoryResolver :
elInjector.get(ComponentFactoryResolver);
const /** @type {?} */ componentFactory = componentFactoryResolver.resolveComponentFactory(this.cartShowcaseOutlet);
this._componentRef = this.viewContainerRef.createComponent(componentFactory, this.viewContainerRef.length, elInjector);
const /** @type {?} */ instance = this._componentRef.instance;
instance.item = this.cartShowcaseOutletItem;
instance.format = this.cartShowcaseOutletFormat;
}
}
else {
if (this._componentRef) {
this._componentRef.instance.format = this.cartShowcaseOutletFormat;
}
}
}
/**
* @return {?}
*/
ngOnDestroy() {
this.cleanModule();
}
}
ShowcaseOutletDirective.decorators = [
{ type: Directive, args: [{
selector: '[cartShowcaseOutlet]',
},] },
];
/** @nocollapse */
ShowcaseOutletDirective.ctorParameters = () => [
{ type: ViewContainerRef, },
];
ShowcaseOutletDirective.propDecorators = {
"cartShowcaseOutlet": [{ type: Input },],
"cartShowcaseOutletInjector": [{ type: Input },],
"cartShowcaseOutletNgModuleFactory": [{ type: Input },],
"cartShowcaseOutletItem": [{ type: Input },],
"cartShowcaseOutletFormat": [{ type: Input },],
};
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* An injection token to resolve the class used to create CartItem instances
*/
const CART_ITEM_CLASS = new InjectionToken('CartItemClass');
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* An injection token to resolve the configuration of the cart service
*/
const CART_SERVICE_CONFIGURATION = new InjectionToken('CartServiceConfiguration');
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* An injection token to store the service type entered in the `forRoot` static function. Is used to prevent errors when compiling with AOT.
* @ignore
*
* \@note {info} You can safely ignore this token if you are using custom cart services.
*/
const CART_SERVICE_TYPE = new InjectionToken('CartServiceType');
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
/**
* An implementation of the CartService using an in-memory array to store items
* \@order 2
* @template T
*/
class MemoryCartService extends CartService {
constructor() {
super(...arguments);
this._items = [];
this._taxRate = 0;
this._shipping = 0;
}
/**
* @param {?} item
* @return {?}
*/
_addItem(item) {
const /** @type {?} */ foundIdx = this._items.findIndex(i => i.getId() === item.getId());
if (foundIdx === -1) {
this._items.push(item);
}
else {
this._items[foundIdx] = item;
}
this.onItemAdded.emit(item);
this.onItemsChanged.emit(this._items.length);
this.onChange.emit({ change: 'items', value: this.getItems() });
}
/**
* @param {?} id
* @return {?}
*/
_removeItem(id) {
const /** @type {?} */ idx = this._items.findIndex(i => i.getId() === id);
if (idx !== -1) {
const /** @type {?} */ removed = this._items.splice(idx, 1);
this.onItemRemoved.emit(removed[0]);
this.onItemsChanged.emit(this._items.length);
this.onChange.emit({ change: 'items', value: this.getItems() });
}
}
/**
* @param {?} id
* @return {?}
*/
getItem(id) {
return this._items.find(i => i.getId() === id);
}
/**
* @return {?}
*/
getItems() {
return this._items.slice();
}
/**
* @return {?}
*/
itemCount() {
return this._items.length;
}
/**
* @return {?}
*/
entries() {
return this._items.reduce((curr, i) => (curr + i.getQuantity()), 0);
}
/**
* @param {?} item
* @return {?}
*/
addItem(item) {
this._addItem(item);
}
/**
* @param {?} id
* @return {?}
*/
removeItem(id) {
this._removeItem(id);
}
/**
* @return {?}
*/
cost() {
return this._items.reduce((curr, i) => (curr + i.getPrice() * i.getQuantity()), 0);
}
/**
* @return {?}
*/
clear() {
this._items = [];
this.onItemsChanged.emit(this._items.length);
this.onChange.emit({ change: 'items', value: this.getItems() });
}
/**
* @return {?}
*/
getShipping() {
return this._shipping;
}
/**
* @param {?} shipping
* @return {?}
*/
setShipping(shipping) {
this._shipping = shipping;
this.onShippingChange.emit(this._shipping);
this.onChange.emit({ change: 'shipping', value: this._shipping });
}
/**
* @return {?}
*/
getTaxRate() {
return this._taxRate;
}
/**
* @param {?} taxRate
* @return {?}
*/
setTaxRate(taxRate) {
this._taxRate = taxRate;
this.onTaxChange.emit(this._taxRate);
this.onChange.emit({ change: 'taxRate', value: this._taxRate });
}
/**
* @return {?}
*/
isEmpty() {
return this._items.length === 0;
}
}
MemoryCartService.decorators = [
{ type: Injectable },
];
/** @nocollapse */
MemoryCartService.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
/**
* The base class for all CartService implementations that use the Storage interface of the Web Storage API like LocalStorage and
* SessionStorage.
* \@service
*
* \@order 3
* @abstract
* @template T
*/
class BrowserStorageCartService extends MemoryCartService {
/**
* @param {?} itemClass
* @param {?} configuration
*/
constructor(itemClass, configuration) {
super() /* istanbul ignore next */;
this.storageKey = configuration && configuration.storageKey ? configuration.storageKey : 'NgShoppingCart';
this.clearOnError = configuration && configuration.clearOnError !== undefined ? configuration.clearOnError : true;
this.itemClass = itemClass;
}
/**
* @param {?} error
* @return {?}
*/
resetStorage(error) {
if (this.clearOnError || !error) {
this.setTaxRate(0);
this.setShipping(0);
this.clear();
this.save();
}
else {
if (typeof error === 'string') {
throw new Error(error);
}
throw error;
}
}
/**
* @return {?}
*/
save() {
this.storage.setItem(this.storageKey, JSON.stringify(this.toObject()));
}
/**
* @return {?}
*/
restore() {
if (!this.storage.getItem(this.storageKey)) {
this.resetStorage(false);
return;
}
try {
const /** @type {?} */ sc = JSON.parse(this.storage.getItem(this.storageKey));
if (!(sc.hasOwnProperty('items') && Array.isArray(sc.items) && sc.hasOwnProperty('taxRate') && sc.hasOwnProperty('shipping'))) {
this.resetStorage('The object found under the key ' + this.storageKey + ' is not a valid cart object');
return;
}
this._items = sc.items.map(i => {
if (this.itemClass.fromJSON) {
return this.itemClass.fromJSON(i);
}
return new this.itemClass(i);
});
this.setTaxRate(parseFloat(sc.taxRate));
this.setShipping(parseFloat(sc.shipping));
}
catch (/** @type {?} */ e) {
this.resetStorage(e);
}
}
/**
* @param {?} item
* @return {?}
*/
addItem(item) {
super.addItem(item);
this.save();
}
/**
* @param {?} id
* @return {?}
*/
removeItem(id) {
super.removeItem(id);
this.save();
}
/**
* @return {?}
*/
clear() {
super.clear();
this.save();
}
/**
* @param {?} shipping
* @return {?}
*/
setShipping(shipping) {
super.setShipping(shipping);
this.save();
}
/**
* @param {?} tax
* @return {?}
*/
setTaxRate(tax) {
super.setTaxRate(tax);
this.save();
}
}
/** @nocollapse */
BrowserStorageCartService.ctorParameters = () => [
{ type: CartItem, decorators: [{ type: Inject, args: [CART_ITEM_CLASS,] },] },
{ type: undefined, decorators: [{ type: Inject, args: [CART_SERVICE_CONFIGURATION,] },] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
/**
* An implementation of the cart service using localStorage to store items
* \@order 5
* @template T
*/
class LocalStorageCartService extends BrowserStorageCartService {
/**
* @param {?} itemClass
* @param {?} configuration
*/
constructor(itemClass, configuration) {
super(itemClass, configuration) /* istanbul ignore next */;
this.storage = window.localStorage;
this.restore();
}
}
LocalStorageCartService.decorators = [
{ type: Injectable },
];
/** @nocollapse */
LocalStorageCartService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [CART_ITEM_CLASS,] },] },
{ type: undefined, decorators: [{ type: Inject, args: [CART_SERVICE_CONFIGURATION,] },] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
// unsupported: template constraints.
/**
* An implementation of the cart service using sessionStorage to store items
* \@order 4
* @template T
*/
class SessionStorageCartService extends BrowserStorageCartService {
/**
* @param {?} itemClass
* @param {?} configuration
*/
constructor(itemClass, configuration) {
super(itemClass, configuration) /* istanbul ignore next */;
this.storage = window.sessionStorage;
this.restore();
}
}
SessionStorageCartService.decorators = [
{ type: Injectable },
];
/** @nocollapse */
SessionStorageCartService.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [CART_ITEM_CLASS,] },] },
{ type: undefined, decorators: [{ type: Inject, args: [CART_SERVICE_CONFIGURATION,] },] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* A default implementation for CartItem
*
* \@order 2
*
* \@howToUse "Using properties and methods"
* ```typescript
* const item = new BaseCartItem({id: 1, name: 'Demo'});
* item.quantity = 10;
* item.setQuantity(50);
* console.log(item.quantity) // prints 50
* ```
*
* \@note {info} You can access item information either with direct property access or method calls, eg. `item.id === item.getId()`
*/
class BaseCartItem extends CartItem {
/**
* @param {?=} itemData
*/
constructor(itemData = {}) {
super();
this.id = itemData.id || 0;
this.name = itemData.name || '';
this.price = itemData.price || 0;
this.image = itemData.image || '';
this.quantity = itemData.quantity || 1;
this.data = itemData.data || {};
}
/**
* Abstract base method implementation to obtain the item id
* @return {?}
*/
getId() {
return this.id;
}
/**
* Sets the current id for the item
* @param {?} id {any}: The id value
* @return {?}
*/
setId(id) {
this.id = id;
}
/**
* Abstract base method implementation to return the name, a small text describing the item
* @return {?}
*/
getName() {
return this.name;
}
/**
* Sets the name of the item
* @param {?} name
* @return {?}
*/
setName(name) {
this.name = name;
}
/**
* Abstract base method implementation to know how much the item cost
* @return {?}
*/
getPrice() {
return this.price;
}
/**
* Set the price of the item
* @param {?} price
* @return {?}
*/
setPrice(price) {
this.price = price;
}
/**
* Abstract base method implementation to return how much of the item is ordered
* @return {?}
*/
getQuantity() {
return this.quantity;
}
/**
* Abstract base method implementation to set how much of the item is ordered
* @param {?} quantity
* @return {?}
*/
setQuantity(quantity) {
this.quantity = quantity;
}
/**
* Abstract base method implementation to get the url of an image for the item
* @return {?}
*/
getImage() {
return this.image;
}
/**
* Sets the url of the item's image
* @param {?} image
* @return {?}
*/
setImage(image) {
this.image = image;
}
/**
* Gets any additional data added to the item
* @return {?}
*/
getData() {
return this.data;
}
/**
* Sets any additional data to the item
* @param {?} data
* @return {?}
*/
setData(data) {
this.data = data;
}
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @template T
* @param {?} serviceType
* @param {?} itemClass
* @param {?} configuration
* @return {?}
*/
function serviceFactory(serviceType, itemClass, configuration) {
switch (serviceType) {
case 'localStorage':
return new LocalStorageCartService(itemClass, configuration);
case 'sessionStorage':
return new SessionStorageCartService(itemClass, configuration);
default:
return new MemoryCartService();
}
}
/**
* @param {?} serviceType
* @return {?}
*/
function setupService(serviceType) {
return {
provide: CART_SERVICE_TYPE,
useValue: serviceType || 'localStorage'
};
}
/**
* @param {?} itemClass
* @return {?}
*/
function setItemClass(itemClass) {
return {
provide: CART_ITEM_CLASS,
useValue: itemClass || BaseCartItem
};
}
/**
* @param {?} serviceType
* @param {?} serviceOptions
* @return {?}
*/
function setServiceConfiguration(serviceType, serviceOptions) {
return {
provide: CART_SERVICE_CONFIGURATION,
useValue: serviceType !== 'memory' ? (!serviceOptions ? {
storageKey: 'NgShoppingCart',
clearOnError: true
} : serviceOptions) : null
};
}
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* A pipe that wraps the `CurrencyPipe` to set currency value display using a string rather than several arguments for easy configuration.
*
* \@summary
* It takes a string as a single parameter in the format `'currencyCode:symbol:digitsInfo:locale'`. You can also use the special
* value `'auto'` which will set the default used by Angular in that specific configuration.
*
* Every configuration is optional and not using any arguments or an argument of `'auto'`is equivalent to how the `CurrencyPipe` works by
* default. If no locale is specified uses the current locale to format numbers.
*
* \@note {info} A value of `'auto:auto:auto:auto'` is equivalent to simply using `'auto'`.
*
* \@note {danger} In Angular versions lower than 6 the `CurrencyPipe` does not change the currency symbol if you don't specify a different
* `currencyCode`
*
* \@howToUse "With a different currency symbol"
* ```html
* <span>
* {{ value | cartCurrency:format }}
* </span>
* ```
* ```typescript
* export class MyComponent {
* value = 10;
* format = 'EUR';
* }
* ```
*
* \@howToUse "With a five digits after the decimal point"
* ```html
* <span>
* {{ value | cartCurrency:format }}
* </span>
* ```
* ```typescript
* export class MyComponent {
* value = 10.56;
* format = 'auto:auto:1.5-5';
* }
* ```
*
* \@howToUse "With a different locale"
* ```html
* <span>
* {{ value | cartCurrency:format }}
* </span>
* ```
* ```typescript
* export class MyComponent {
* value = 10;
* format = 'auto:auto:auto:en-GB';
* }
* ```
*/
class CartCurrencyPipe {
/**
* @param {?} _locale
*/
constructor(_locale) {
this._locale = _locale;
this.currencyFormatter = new CurrencyPipe(this._locale);
}
/**
* @param {?} value
* @param {?=} format
* @return {?}
*/
transform(value, format = 'auto') {
if (!value && value !== 0) {
return null;
}
const { currencyCode, display, digitsInfo, locale } = parseLocaleFormat(format);
return this.currencyFormatter.transform(value, currencyCode, display, digitsInfo, locale);
}
}
CartCurrencyPipe.decorators = [
{ type: Pipe, args: [{ name: 'cartCurrency' },] },
];
/** @nocollapse */
CartCurrencyPipe.ctorParameters = () => [
{ type: undefined, decorators: [{ type: Inject, args: [LOCALE_ID,] },] },
];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* The main exported library module. It includes `forRoot` and `forChild` static methods to support angular feature modules and singleton
* services.
*
* \@note {danger} Only the `forRoot` method will configure providers for you. If you use the module without it you must configure the
* library providers yourself.
*/
class ShoppingCartModule {
/**
* @param {?=} options
* @return {?}
*/
static forRoot(options = {}) {
return {
ngModule: ShoppingCartModule,
providers: [
setItemClass(options.itemType),
setupService(options.serviceType),
setServiceConfiguration(options.serviceType, options.serviceOptions),
{
provide: CartService,
useFactory: serviceFactory,
deps: [CART_SERVICE_TYPE, CART_ITEM_CLASS, CART_SERVICE_CONFIGURATION]
}
],
};
}
/**
* @return {?}
*/
static forChild() {
return {
ngModule: ShoppingCartModule
};
}
}
ShoppingCartModule.decorators = [
{ type: NgModule, args: [{
declarations: [
AddToCartEditorComponent,
AddToCartComponent,
CartCheckoutComponent,
CartSummaryComponent,
CartShowcaseComponent,
CartViewComponent,
ShowcaseOutletDirective,
CartShowcaseItemComponent,
CartCurrencyPipe,
],
imports: [
CommonModule,
HttpClientModule,
],
exports: [
AddToCartEditorComponent,
AddToCartComponent,
CartCheckoutComponent,
CartSummaryComponent,
CartShowcaseComponent,
CartViewComponent,
CartShowcaseItemComponent,
CartCurrencyPipe,
CommonModule,
HttpClientModule
],
entryComponents: [CartShowcaseItemComponent],
},] },
];
/** @nocollapse */
ShoppingCartModule.ctorParameters = () => [];
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* @fileoverview added by tsickle
* @suppress {checkTypes} checked by tsc
*/
/**
* Generated bundle index. Do not edit.
*/
export { AddToCartComponent, CartCheckoutComponent, CartSummaryComponent, CartShowcaseComponent, CartShowcaseItemComponent, CartViewComponent, CartCurrencyPipe, ShoppingCartModule, CART_ITEM_CLASS, CART_SERVICE_CONFIGURATION, CART_SERVICE_TYPE, CartService, MemoryCartService, LocalStorageCartService, SessionStorageCartService, CartItem, BaseCartItem, parseLocaleFormat, AddToCartEditorComponent as ɵa, ShowcaseOutletDirective as ɵb, serviceFactory as ɵc, setItemClass as ɵe, setServiceConfiguration as ɵf, setupService as ɵd, BrowserStorageCartService as ɵg };
//# sourceMappingURL=ng-shopping-cart.js.map