UNPKG

ng-shopping-cart

Version:

An Angular component library to create shopping carts

2,204 lines 71.2 kB
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