├── .editorconfig
├── .firebaserc
├── .gitignore
├── LICENSE
├── README.md
├── angular.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── firebase.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── protractor.conf.js
├── public
└── index.html
├── scripts
└── set-env.js
├── src
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.routes.ts
│ ├── components
│ │ ├── checkout
│ │ │ ├── checkout.component.html
│ │ │ ├── checkout.component.scss
│ │ │ ├── checkout.component.spec.ts
│ │ │ └── checkout.component.ts
│ │ ├── components.module.ts
│ │ ├── footer
│ │ │ ├── footer.component.html
│ │ │ ├── footer.component.scss
│ │ │ ├── footer.component.spec.ts
│ │ │ └── footer.component.ts
│ │ └── navbar
│ │ │ ├── navbar.component.html
│ │ │ ├── navbar.component.scss
│ │ │ ├── navbar.component.spec.ts
│ │ │ └── navbar.component.ts
│ ├── pipes
│ │ └── category.pipe.ts
│ ├── services
│ │ ├── cart.service.ts
│ │ ├── category.service.ts
│ │ ├── product.service.ts
│ │ └── services.module.ts
│ ├── shop
│ │ ├── cart
│ │ │ ├── cart-item-count.component.html
│ │ │ ├── cart-item-count.component.ts
│ │ │ ├── cart-menu.component.html
│ │ │ ├── cart-menu.component.ts
│ │ │ ├── cart-view.component.html
│ │ │ └── cart-view.component.ts
│ │ ├── category
│ │ │ ├── category-card.component.html
│ │ │ ├── category-card.component.ts
│ │ │ ├── category-list.component.html
│ │ │ ├── category-list.component.ts
│ │ │ ├── category-slide.component.html
│ │ │ └── category-slide.component.ts
│ │ ├── product
│ │ │ ├── product-card.component.html
│ │ │ ├── product-card.component.ts
│ │ │ ├── product-grid.component.html
│ │ │ ├── product-grid.component.ts
│ │ │ ├── product-list.component.html
│ │ │ ├── product-list.component.ts
│ │ │ ├── product-search.component.html
│ │ │ ├── product-search.component.ts
│ │ │ ├── product-view.component.html
│ │ │ └── product-view.component.ts
│ │ └── shop.module.ts
│ └── welcome
│ │ ├── welcome.component.html
│ │ ├── welcome.component.scss
│ │ ├── welcome.component.spec.ts
│ │ └── welcome.component.ts
├── assets
│ ├── .gitkeep
│ └── ecommerce.scss
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── index.html
├── main.ts
├── polyfills.ts
├── styles.scss
├── test.ts
├── tsconfig.app.json
├── tsconfig.spec.json
└── typings.d.ts
├── tsconfig.json
└── tslint.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "shopping-1578f"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /dist-server
6 | /tmp
7 | /out-tsc
8 |
9 | # dependencies
10 | /node_modules
11 |
12 | # IDEs and editors
13 | /.idea
14 | .project
15 | .classpath
16 | .c9/
17 | *.launch
18 | .settings/
19 | *.sublime-workspace
20 |
21 | # IDE - VSCode
22 | .vscode/*
23 | !.vscode/settings.json
24 | !.vscode/tasks.json
25 | !.vscode/launch.json
26 | !.vscode/extensions.json
27 |
28 | # misc
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | testem.log
35 | /typings
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 | .env
45 |
46 | /src/environments
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Prosper DOKO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # angular6-bootstrap4-firebase-ecommerce-application
2 | E-commerce web app based on angular 6, bootstrap 4 and firebase
3 |
4 | ## Development server
5 |
6 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
7 |
8 | ## Code scaffolding
9 |
10 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
11 |
12 | ## Build
13 |
14 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `-prod` flag for a production build.
15 |
16 | ## Running unit tests
17 |
18 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
19 |
20 | ## Running end-to-end tests
21 |
22 | Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
23 |
24 | ## Further help
25 |
26 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
27 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "shop": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "targets": {
11 | "build": {
12 | "builder": "@angular-devkit/build-angular:browser",
13 | "options": {
14 | "outputPath": "dist",
15 | "index": "src/index.html",
16 | "main": "src/main.ts",
17 | "tsConfig": "src/tsconfig.app.json",
18 | "polyfills": "src/polyfills.ts",
19 | "assets": [
20 | "src/assets",
21 | "src/favicon.ico"
22 | ],
23 | "styles": [
24 | "src/styles.scss"
25 | ],
26 | "scripts": []
27 | },
28 | "configurations": {
29 | "production": {
30 | "optimization": true,
31 | "outputHashing": "all",
32 | "sourceMap": false,
33 | "extractCss": true,
34 | "namedChunks": false,
35 | "aot": true,
36 | "extractLicenses": true,
37 | "vendorChunk": false,
38 | "buildOptimizer": true,
39 | "fileReplacements": [
40 | {
41 | "replace": "src/environments/environment.ts",
42 | "with": "src/environments/environment.prod.ts"
43 | }
44 | ]
45 | }
46 | }
47 | },
48 | "serve": {
49 | "builder": "@angular-devkit/build-angular:dev-server",
50 | "options": {
51 | "browserTarget": "shop:build"
52 | },
53 | "configurations": {
54 | "production": {
55 | "browserTarget": "shop:build:production"
56 | }
57 | }
58 | },
59 | "extract-i18n": {
60 | "builder": "@angular-devkit/build-angular:extract-i18n",
61 | "options": {
62 | "browserTarget": "shop:build"
63 | }
64 | },
65 | "test": {
66 | "builder": "@angular-devkit/build-angular:karma",
67 | "options": {
68 | "main": "src/test.ts",
69 | "karmaConfig": "./karma.conf.js",
70 | "polyfills": "src/polyfills.ts",
71 | "tsConfig": "src/tsconfig.spec.json",
72 | "scripts": [],
73 | "styles": [
74 | "src/styles.scss"
75 | ],
76 | "assets": [
77 | "src/assets",
78 | "src/favicon.ico"
79 | ]
80 | }
81 | },
82 | "lint": {
83 | "builder": "@angular-devkit/build-angular:tslint",
84 | "options": {
85 | "tsConfig": [
86 | "src/tsconfig.app.json",
87 | "src/tsconfig.spec.json"
88 | ],
89 | "exclude": [
90 | "**/node_modules/**"
91 | ]
92 | }
93 | }
94 | }
95 | },
96 | "shop-e2e": {
97 | "root": "e2e",
98 | "sourceRoot": "e2e",
99 | "projectType": "application",
100 | "targets": {
101 | "e2e": {
102 | "builder": "@angular-devkit/build-angular:protractor",
103 | "options": {
104 | "protractorConfig": "./protractor.conf.js",
105 | "devServerTarget": "shop:serve"
106 | }
107 | },
108 | "lint": {
109 | "builder": "@angular-devkit/build-angular:tslint",
110 | "options": {
111 | "tsConfig": [
112 | "e2e/tsconfig.e2e.json"
113 | ],
114 | "exclude": [
115 | "**/node_modules/**"
116 | ]
117 | }
118 | }
119 | }
120 | }
121 | },
122 | "defaultProject": "shop",
123 | "schematics": {
124 | "@schematics/angular:component": {
125 | "prefix": "app",
126 | "styleext": "scss"
127 | },
128 | "@schematics/angular:directive": {
129 | "prefix": "app"
130 | }
131 | }
132 | }
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('shop App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "public",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | },
16 | "dependencies": {
17 | "firebase-admin": "^5.9.0",
18 | "firebase-functions": "^0.8.1",
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 |
23 | reporters: ['progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | singleRun: false
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shop",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "node server.js",
8 | "config": "node ./scripts/set-env.js",
9 | "build": "npm run config -- --environment=prod && HOME=/tmp/build ng build --prod --buildOptimizer",
10 | "test": "ng test",
11 | "lint": "ng lint",
12 | "e2e": "ng e2e",
13 | "postinstall": "npm run build",
14 | "serve": "npm run config -- --environment= && ng serve",
15 | "deploy": "git push origin master && git push heroku master"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "^6.1.7",
20 | "@angular/common": "^6.1.7",
21 | "@angular/compiler": "^6.1.7",
22 | "@angular/core": "^6.1.7",
23 | "@angular/fire": "^5.0.0",
24 | "@angular/forms": "^6.1.7",
25 | "@angular/http": "^6.1.7",
26 | "@angular/platform-browser": "^6.1.7",
27 | "@angular/platform-browser-dynamic": "^6.1.7",
28 | "@angular/router": "^6.1.7",
29 | "angularfire2": "^5.0.0-rc.2",
30 | "bootstrap": "^4.0.0",
31 | "core-js": "^2.4.1",
32 | "dotenv": "^6.0.0",
33 | "firebase": "^5.4.2",
34 | "font-awesome": "^4.7.0",
35 | "grpc": "^1.14.2",
36 | "jquery": "^3.2.1",
37 | "ngx-bootstrap": "^2.0.2",
38 | "node-sass": "^4.9.3",
39 | "popper.js": "^1.12.5",
40 | "rxjs": "^6.3.2",
41 | "rxjs-compat": "^6.3.2",
42 | "zone.js": "^0.8.19"
43 | },
44 | "devDependencies": {
45 | "@angular-devkit/build-angular": "~0.8.0",
46 | "@angular/cli": "^6.2.1",
47 | "@angular/compiler-cli": "^6.1.7",
48 | "@angular/language-service": "^6.1.7",
49 | "@types/jasmine": "~2.8.3",
50 | "@types/jasminewd2": "~2.0.2",
51 | "@types/node": "~6.0.60",
52 | "codelyzer": "^4.0.1",
53 | "jasmine-core": "~2.8.0",
54 | "jasmine-spec-reporter": "~4.2.1",
55 | "karma": "^3.0.0",
56 | "karma-chrome-launcher": "~2.2.0",
57 | "karma-coverage-istanbul-reporter": "^1.2.1",
58 | "karma-jasmine": "~1.1.0",
59 | "karma-jasmine-html-reporter": "^0.2.2",
60 | "protractor": "^5.4.0",
61 | "ts-node": "~4.1.0",
62 | "tslint": "~5.9.1",
63 | "typescript": "~2.9.2"
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: [
9 | './e2e/**/*.e2e-spec.ts'
10 | ],
11 | capabilities: {
12 | 'browserName': 'chrome'
13 | },
14 | directConnect: true,
15 | baseUrl: 'http://localhost:4200/',
16 | framework: 'jasmine',
17 | jasmineNodeOpts: {
18 | showColors: true,
19 | defaultTimeoutInterval: 30000,
20 | print: function() {}
21 | },
22 | onPrepare() {
23 | require('ts-node').register({
24 | project: 'e2e/tsconfig.e2e.json'
25 | });
26 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Welcome to Firebase Hosting
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
32 |
33 |
34 |
35 |
Welcome
36 |
Firebase Hosting Setup Complete
37 |
You're seeing this because you've successfully setup Firebase Hosting. Now it's time to go build something extraordinary!
38 |
Open Hosting Documentation
39 |
40 | Firebase SDK Loading…
41 |
42 |
64 |
65 |
66 |
--------------------------------------------------------------------------------
/scripts/set-env.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const yargs = require('yargs');
3 |
4 | // This is good for local dev environments, when it's better to
5 | // store a projects environment variables in a .gitignore'd file
6 | require('dotenv').config();
7 |
8 | // Would be passed to script like this:
9 | // `ts-node set-env.ts --environment=dev`
10 | // we get it from yargs's argv object
11 | const environment = yargs.argv.environment;
12 | const isProd = environment === 'prod';
13 |
14 | const targetPath = `./src/environments/environment${environment ? '.' + environment : ''}.ts`;
15 | const envConfigFile = `
16 | export const environment = {
17 | production: ${isProd},
18 | config: {
19 | apiKey: '${process.env.API_KEY}',
20 | authDomain: '${process.env.AUTH_DOMAIN}',
21 | databaseURL: '${process.env.DATABASE_URL}',
22 | projectId: '${process.env.PROJECT_ID}',
23 | storageBucket: '${process.env.STORAGE_BUCKET}',
24 | messagingSenderId: '${process.env.MESSAGING_SENDER_ID}',
25 | }
26 | };
27 | `
28 | fs.writeFile(targetPath, envConfigFile, function(err) {
29 | if (err) {
30 | console.log(err);
31 | }
32 |
33 | console.log(`Output generated at ${targetPath} \n`);
34 | });
35 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .product-menu {
2 | height: 50px;
3 | margin-top: 22px;
4 | }
5 |
6 | .product-slide-item {
7 | margin-bottom: 25px;
8 | margin-top: 25px;
9 | }
10 |
11 | .product-item {
12 | height: 171px;
13 | width: 100%;
14 | display: block;
15 | }
16 |
17 | body {
18 | padding-top: 50px;
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, async } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 |
4 | describe('AppComponent', () => {
5 | beforeEach(async(() => {
6 | TestBed.configureTestingModule({
7 | declarations: [
8 | AppComponent
9 | ],
10 | }).compileComponents();
11 | }));
12 |
13 | it('should create the app', async(() => {
14 | const fixture = TestBed.createComponent(AppComponent);
15 | const app = fixture.debugElement.componentInstance;
16 | expect(app).toBeTruthy();
17 | }));
18 | it(`should have as title 'app'`, async(() => {
19 | const fixture = TestBed.createComponent(AppComponent);
20 | const app = fixture.debugElement.componentInstance;
21 | expect(app.title).toEqual('app');
22 | }));
23 | it('should render title in a h1 tag', async(() => {
24 | const fixture = TestBed.createComponent(AppComponent);
25 | fixture.detectChanges();
26 | const compiled = fixture.debugElement.nativeElement;
27 | expect(compiled.querySelector('h1').textContent).toContain('Welcome to app!');
28 | }));
29 | });
30 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.scss']
7 | })
8 | export class AppComponent {
9 | title = 'Shop app';
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { BrowserModule } from '@angular/platform-browser';
2 | import { NgModule } from '@angular/core';
3 | import { CommonModule } from "@angular/common";
4 |
5 | import { AngularFireModule } from 'angularfire2';
6 | import { AngularFireDatabaseModule } from 'angularfire2/database';
7 | import { AngularFireAuthModule } from 'angularfire2/auth';
8 | import { CarouselModule } from 'ngx-bootstrap';
9 |
10 | /*
11 | * Components
12 | */
13 | import { AppComponent } from './app.component';
14 | import { WelcomeComponent } from './welcome/welcome.component';
15 | /**
16 | * Modules
17 | */
18 | import { ServicesModule } from './services/services.module';
19 | import { ShopModule } from './shop/shop.module';
20 | import { ComponentsModule } from "./components/components.module";
21 |
22 | import { environment } from './../environments/environment';
23 | /*
24 | * Routing
25 | */
26 | import { routing } from "./app.routes";
27 |
28 | @NgModule({
29 | declarations: [
30 | AppComponent,
31 | WelcomeComponent,
32 | ],
33 | imports: [
34 | BrowserModule,
35 | AngularFireModule.initializeApp(environment.config),
36 | AngularFireDatabaseModule,
37 | AngularFireAuthModule,
38 | CommonModule,
39 | routing,
40 | //App Modules
41 | CarouselModule.forRoot(),
42 | ComponentsModule,
43 | ShopModule,
44 | ServicesModule
45 | ],
46 | providers: [],
47 | bootstrap: [AppComponent]
48 | })
49 | export class AppModule { }
50 |
--------------------------------------------------------------------------------
/src/app/app.routes.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import { Routes, RouterModule } from "@angular/router";
5 |
6 | /*
7 | * Guards
8 | */
9 | //import { AuthGuard } from "./auth/auth.module";
10 |
11 | /*
12 | * Components
13 | */
14 | import { WelcomeComponent } from "./welcome/welcome.component";
15 | // import { SignInComponent} from "./auth/sign-in.component";
16 | import { ProductListComponent } from "./shop/product/product-list.component";
17 | import { ProductViewComponent } from "./shop/product/product-view.component";
18 | import { CartViewComponent } from "./shop/cart/cart-view.component";
19 | //import { CheckoutViewComponent } from "./checkout/checkout-view.component";
20 |
21 | /*
22 | * Routes
23 | */
24 | const routes: Routes = [
25 | { path: "", redirectTo: "welcome", pathMatch: "full" },
26 | //{ path: "login", component: SignInComponent },
27 | { path: "welcome", component: WelcomeComponent },
28 | { path: "products", component: ProductListComponent},
29 | { path: "products/:id", component: ProductViewComponent},
30 | { path: "cart", component: CartViewComponent},
31 | // { path: "checkout", component: CheckoutViewComponent, canActivate: [AuthGuard] },
32 | ];
33 |
34 | /*
35 | * Routes Provider
36 | */
37 | export const routing = RouterModule.forRoot(routes);
38 |
--------------------------------------------------------------------------------
/src/app/components/checkout/checkout.component.html:
--------------------------------------------------------------------------------
1 |
2 | checkout works!
3 |
4 |
--------------------------------------------------------------------------------
/src/app/components/checkout/checkout.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadonet/angular6-bootstrap4-firebase-ecommerce-application/86cd12ec4d1f34a5fc421c24d2b977d65022711a/src/app/components/checkout/checkout.component.scss
--------------------------------------------------------------------------------
/src/app/components/checkout/checkout.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { CheckoutComponent } from './checkout.component';
4 |
5 | describe('CheckoutComponent', () => {
6 | let component: CheckoutComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ CheckoutComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(CheckoutComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/checkout/checkout.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-checkout',
5 | templateUrl: './checkout.component.html',
6 | styleUrls: ['./checkout.component.scss']
7 | })
8 | export class CheckoutComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/components/components.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from "@angular/core";
2 | import { BrowserModule } from "@angular/platform-browser";
3 | import { CommonModule } from "@angular/common";
4 | /*
5 | * Components
6 | */
7 | import { NavbarComponent } from "./navbar/navbar.component";
8 | import { FooterComponent } from "./footer/footer.component";
9 | import { CheckoutComponent } from "./checkout/checkout.component";
10 |
11 | @NgModule({
12 | imports: [
13 | BrowserModule, CommonModule
14 | ],
15 | declarations: [
16 | NavbarComponent,
17 | FooterComponent,
18 | CheckoutComponent
19 | ],
20 | exports: [
21 | NavbarComponent,
22 | FooterComponent,
23 | CheckoutComponent
24 | ]
25 | })
26 | export class ComponentsModule { }
27 |
--------------------------------------------------------------------------------
/src/app/components/footer/footer.component.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/app/components/footer/footer.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadonet/angular6-bootstrap4-firebase-ecommerce-application/86cd12ec4d1f34a5fc421c24d2b977d65022711a/src/app/components/footer/footer.component.scss
--------------------------------------------------------------------------------
/src/app/components/footer/footer.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { FooterComponent } from './footer.component';
4 |
5 | describe('FooterComponent', () => {
6 | let component: FooterComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ FooterComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(FooterComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/footer/footer.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-footer',
5 | templateUrl: './footer.component.html',
6 | styleUrls: ['./footer.component.scss']
7 | })
8 | export class FooterComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/components/navbar/navbar.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/navbar/navbar.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadonet/angular6-bootstrap4-firebase-ecommerce-application/86cd12ec4d1f34a5fc421c24d2b977d65022711a/src/app/components/navbar/navbar.component.scss
--------------------------------------------------------------------------------
/src/app/components/navbar/navbar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { NavbarComponent } from './navbar.component';
4 |
5 | describe('NavbarComponent', () => {
6 | let component: NavbarComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ NavbarComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(NavbarComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/components/navbar/navbar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-navbar',
5 | templateUrl: './navbar.component.html',
6 | styleUrls: ['./navbar.component.scss']
7 | })
8 | export class NavbarComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/pipes/category.pipe.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Pipe, PipeTransform} from "@angular/core";
5 |
6 | /*
7 | * Components Imports
8 | */
9 | import {Category, CategoryService} from "../services/category.service";
10 |
11 | /*
12 | * Return category title of the value
13 | * Usage:
14 | * value | categoryTitle
15 | * Example:
16 | * {{ categoryId | categoryTitle }}
17 | * presume categoryId="1"
18 | * result formats to "Bread & Bakery"
19 | */
20 | @Pipe({ name: "categoryTitle" })
21 | export class CategoryTitlePipe implements PipeTransform {
22 |
23 | constructor(private categoryService: CategoryService) { }
24 |
25 | transform(value: string): string {
26 | let category: Category = this.categoryService.getCategory(value);
27 | return category ? category.title : "";
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/services/cart.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from "@angular/core";
2 |
3 | import {Product} from "./product.service";
4 |
5 | export interface CartItem {
6 | product: Product;
7 | count: number;
8 | amount: number;
9 | }
10 |
11 | export class Cart {
12 | count: number = 0;
13 | amount: number = 0;
14 | items: CartItem[] = [];
15 | }
16 |
17 | @Injectable()
18 | export class CartService {
19 |
20 | cart: Cart = new Cart();
21 |
22 | /**
23 | * This method adds the new product or increases the number
24 | * of the same products in the cart.
25 | * It updates the amount and count of items in the cart.
26 | */
27 | addProduct(product: Product): CartItem {
28 | // Find CartItem in items
29 | let item: CartItem = this.findItem(product.id);
30 | // Check was it found?
31 | if (item) {
32 | // Item was found.
33 | // Increase the count of the same products
34 | item.count++;
35 | // Increase amount of the same products
36 | item.amount += product.price;
37 | } else {
38 | // Item was not found.
39 | // Create the cart item
40 | item = {
41 | product: product,
42 | count: 1,
43 | amount: product.price
44 | };
45 | // Add item to items
46 | this.cart.items.push(item);
47 | }
48 | // Increase count in the cart
49 | this.cart.count++;
50 | // Increase amount in the cart
51 | this.cart.amount += product.price;
52 | return item;
53 | }
54 |
55 | /**
56 | * This method decreases the number of the same products
57 | * in the cart or removes the last product.
58 | * It updates the amount and count of items in the cart.
59 | */
60 | removeProduct(product: Product): CartItem {
61 | // Find CartItem in items
62 | let item: CartItem = this.findItem(product.id);
63 | // Check is item found?
64 | if (item) {
65 | // Decrease the count
66 | item.count--;
67 | // Substract price
68 | item.amount -= product.price;
69 | // Check was that the last product?
70 | if (!item.count) {
71 | // It was last product
72 | // Delete item from items
73 | this.remove(item);
74 | // We must return null
75 | item = null;
76 | }
77 | // Decrease count in the cart
78 | this.cart.count--;
79 | // Decrease amount in the cart
80 | this.cart.amount -= product.price;
81 | }
82 | return item;
83 | }
84 |
85 | /**
86 | * Remove item from the cart.
87 | * It updates the amount and count of items in the cart.
88 | */
89 | removeItem(item: CartItem) {
90 | // Delete item from items
91 | this.remove(item);
92 | // Decrease count in the cart
93 | this.cart.count -= item.count;
94 | // Decrease amount in the cart
95 | this.cart.amount -= item.amount;
96 | }
97 |
98 | /**
99 | * This method returns cart item by product id or null.
100 | */
101 | findItem(id: string): CartItem {
102 | for (let i = 0; i < this.cart.items.length; i++) {
103 | if (this.cart.items[i].product.id === id) {
104 | return this.cart.items[i];
105 | }
106 | }
107 | return null;
108 | }
109 |
110 | /**
111 | * This method remove all products and clean ammount and items.
112 | */
113 | clearCart() {
114 | this.cart.items = [];
115 | this.cart.amount = 0;
116 | this.cart.count = 0;
117 | }
118 |
119 | /**
120 | * This method removes existing cart item.
121 | */
122 | private remove(item: CartItem) {
123 | // Find the index of cart item
124 | let indx: number = this.cart.items.indexOf(item);
125 | // Check was item found
126 | if (indx !== -1) {
127 | // Remove element from array
128 | this.cart.items.splice(indx, 1);
129 | }
130 | }
131 | }
--------------------------------------------------------------------------------
/src/app/services/category.service.ts:
--------------------------------------------------------------------------------
1 |
2 | import {throwError as observableThrowError, Observable} from 'rxjs';
3 | import {Injectable} from "@angular/core";
4 | import { AngularFireDatabase } from 'angularfire2/database';
5 | import { catchError } from 'rxjs/operators';
6 |
7 | export class Category {
8 | // Unique Id
9 | id: string;
10 | // The title
11 | title: string;
12 | // Description
13 | desc: string;
14 | // Path to small image
15 | imageS: string;
16 | // Path to large image
17 | imageL: string;
18 | }
19 |
20 | @Injectable()
21 | export class CategoryService {
22 |
23 | // URL to Categories web api
24 | private categoriesUrl = "categories";
25 | // We keep categories in cache variable
26 | private categories: Category[] = [];
27 |
28 | constructor(private db: AngularFireDatabase) {}
29 |
30 | getCategories(): Observable {
31 | return this.db
32 | .list(this.categoriesUrl).valueChanges();
33 | }
34 |
35 | getCategory(id: string): Category {
36 | for (let i = 0; i < this.categories.length; i++) {
37 | if (this.categories[i].id === id) {
38 | return this.categories[i];
39 | }
40 | }
41 | return null;
42 | }
43 |
44 | private handleError(error: any): Observable {
45 | let errMsg = (error.message) ? error.message : error.status ?
46 | `${error.status} - ${error.statusText}` : "Server error";
47 | window.alert(`An error occurred: ${errMsg}`);
48 | return observableThrowError(errMsg);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/app/services/product.service.ts:
--------------------------------------------------------------------------------
1 |
2 | import {throwError as observableThrowError, empty as observableEmpty, Observable} from 'rxjs';
3 | import {Injectable} from "@angular/core";
4 | import { AngularFireDatabase } from 'angularfire2/database';
5 | import { empty, of } from 'rxjs';
6 |
7 | export interface Product {
8 | // Unique Id
9 | id: string;
10 | // Ref on category belongs to
11 | categoryId: string;
12 | // The title
13 | title: string;
14 | // Price
15 | price: number;
16 | // Mark product with specialprice
17 | isSpecial: boolean;
18 | // Description
19 | desc: string;
20 | // Path to small image
21 | imageS: string;
22 | // Path to large image
23 | imageL: string;
24 | }
25 |
26 | @Injectable()
27 | export class ProductService {
28 |
29 | // URL to Products web api
30 | private productsUrl = "products";
31 |
32 | constructor(private db: AngularFireDatabase) {}
33 |
34 | getProducts(category?: string, search?: string): Observable {
35 | if (category || search) {
36 | let query = {};
37 | if (category) {
38 | return this.db
39 | .list(this.productsUrl,
40 | ref => ref.orderByChild('categoryId').equalTo(category)).valueChanges();
41 |
42 | } else {
43 | return this.db
44 | .list(this.productsUrl,
45 | ref => ref.orderByChild('title')
46 | .startAt(search.toUpperCase())).valueChanges();
47 | }
48 |
49 | } else {
50 | return observableEmpty();
51 | }
52 | }
53 |
54 | getProduct(id: string): Observable {
55 | return this.db
56 | .object(this.productsUrl + `/${id}`).valueChanges();
57 | }
58 |
59 | private handleError(error: any): Observable {
60 | let errMsg = (error.message) ? error.message : error.status ?
61 | `${error.status} - ${error.statusText}` : "Server error";
62 | window.alert(`An error occurred: ${errMsg}`);
63 | return observableThrowError(errMsg);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/app/services/services.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 |
3 | import { ProductService } from './product.service';
4 | import { CategoryService } from './category.service';
5 | import { CartService } from './cart.service';
6 |
7 | @NgModule({
8 | imports: [
9 | ],
10 | providers: [
11 | ProductService,
12 | CategoryService,
13 | CartService
14 | ]
15 | })
16 | export class ServicesModule { }
17 |
--------------------------------------------------------------------------------
/src/app/shop/cart/cart-item-count.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/shop/cart/cart-item-count.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, Input, Output, EventEmitter} from "@angular/core";
5 |
6 |
7 | @Component({
8 | selector: "db-cart-item-count",
9 | templateUrl: "./cart-item-count.component.html"
10 | })
11 | export class CartItemCountComponent {
12 |
13 | @Input() name: string;
14 | @Input() count: string;
15 | @Output() countChanged: EventEmitter = new EventEmitter();
16 |
17 | update(value: string) {
18 | this.count = value;
19 | this.countChanged.emit(value);
20 | }
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/src/app/shop/cart/cart-menu.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/shop/cart/cart-menu.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import { Component, Input } from "@angular/core";
5 |
6 | /*
7 | * Components
8 | */
9 | import { Cart, CartService } from "../../services/cart.service";
10 |
11 | @Component({
12 | selector: "db-cart-menu",
13 | templateUrl: "./cart-menu.component.html"
14 | })
15 | export class CartMenuComponent {
16 |
17 | private cart: Cart;
18 |
19 | constructor(private cartService: CartService) {
20 | this.cart = this.cartService.cart;
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/src/app/shop/cart/cart-view.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
30 |
The cart is empty!
31 |
32 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/src/app/shop/cart/cart-view.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, Input} from "@angular/core";
5 |
6 | /*
7 | * Components
8 | */
9 | import { Cart, CartItem, CartService } from "../../services/cart.service";
10 | import {CartItemCountComponent} from "./cart-item-count.component";
11 |
12 | @Component({
13 | selector: "db-cart-view",
14 | templateUrl: "./cart-view.component.html"
15 | })
16 | export class CartViewComponent {
17 |
18 | private cart: Cart;
19 |
20 | constructor(private cartService: CartService) {
21 | this.cart = this.cartService.cart;
22 | }
23 |
24 | clearCart() {
25 | this.cartService.clearCart();
26 | }
27 |
28 | update(value, item: CartItem) {
29 | let res = value - item.count;
30 | if (res > 0) {
31 | for (let i = 0; i < res; i++) {
32 | this.cartService.addProduct(item.product);
33 | }
34 | } else if (res < 0) {
35 | for (let i = 0; i < -res; i++) {
36 | this.cartService.removeProduct(item.product);
37 | }
38 | }
39 | return value;
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/src/app/shop/category/category-card.component.html:
--------------------------------------------------------------------------------
1 |
2 |

3 |
4 |
{{category.title}}
5 |
{{category.desc}}
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/app/shop/category/category-card.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, Input} from "@angular/core";
5 | import {Router} from "@angular/router";
6 |
7 | /*
8 | * Components
9 | */
10 | import {Category} from "../../services/category.service";
11 |
12 | @Component({
13 | selector: "db-category-card",
14 | templateUrl: "./category-card.component.html"
15 | })
16 | export class CategoryCardComponent {
17 | @Input() category: Category;
18 |
19 | constructor(private router: Router) {}
20 |
21 | filterProducts(category: Category) {
22 | this.router.navigate(["/products"], { queryParams: { category: category.id} });
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/shop/category/category-list.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/shop/category/category-list.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, OnInit} from "@angular/core";
5 | import {Router} from "@angular/router";
6 | import {Observable} from "rxjs";
7 |
8 | /*
9 | * Components
10 | */
11 | import {Category, CategoryService} from "../../services/category.service";
12 |
13 | @Component({
14 | selector: "db-category-list",
15 | templateUrl: "./category-list.component.html"
16 | })
17 | export class CategoryListComponent implements OnInit {
18 |
19 | categories: Observable;
20 |
21 | constructor(private router: Router, private categoryService: CategoryService) { }
22 |
23 | ngOnInit(): void {
24 | this.categories = this.categoryService.getCategories();
25 | console.log(this.categories);
26 | }
27 |
28 | filterProducts(category: Category) {
29 | this.router.navigate(["/products"], { queryParams: { category: category.id} });
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/shop/category/category-slide.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{category.title}}
4 |
--------------------------------------------------------------------------------
/src/app/shop/category/category-slide.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, Input, Output, EventEmitter} from "@angular/core";
5 |
6 | /*
7 | * Components
8 | */
9 | import {Category} from "../../services/category.service";
10 |
11 | @Component({
12 | selector: "db-category-slide",
13 | templateUrl: "./category-slide.component.html"
14 | })
15 | export class CategorySlideComponent {
16 | @Input() category: Category;
17 | @Output() select: EventEmitter = new EventEmitter();
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-card.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |

8 |
9 |
Price: ${{product.price}}
10 |
11 |
15 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-card.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, Input, Output, EventEmitter} from "@angular/core";
5 |
6 | /*
7 | * Components
8 | */
9 | import { Product } from "../../services/product.service";
10 |
11 |
12 | @Component({
13 | selector: "db-product-card",
14 | templateUrl: "./product-card.component.html"
15 | })
16 | export class ProductCardComponent {
17 | @Input() products: Product[];
18 | @Output() addToCart: EventEmitter = new EventEmitter();
19 |
20 | setClasses(product: Product) {
21 | return {
22 | "card-danger": product.isSpecial,
23 | "card-inverse": product.isSpecial
24 | };
25 | }
26 |
27 | buy(product: Product) {
28 | this.addToCart.emit(product);
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-grid.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-grid.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, OnInit} from "@angular/core";
5 | import {ActivatedRoute} from "@angular/router";
6 |
7 | import {Observable} from "rxjs/Observable";
8 | import "rxjs/add/operator/debounceTime";
9 | import "rxjs/add/operator/distinctUntilChanged";
10 | import "rxjs/add/operator/map";
11 |
12 | /*
13 | * Components
14 | */
15 | import { Product, ProductService } from "../../services/product.service";
16 | import {CartService} from "../../services/cart.service";
17 |
18 | @Component({
19 | selector: "db-product-grid",
20 | templateUrl: "./product-grid.component.html"
21 | })
22 | export class ProductGridComponent implements OnInit {
23 | products: Observable;
24 |
25 | constructor(private route: ActivatedRoute, private productService: ProductService, private cartService: CartService) {}
26 |
27 | ngOnInit(): void {
28 | this.route
29 | .queryParams
30 | .debounceTime(300) // wait for 300 ms pause in events
31 | .distinctUntilChanged() // only changed values pass
32 | .subscribe(params => {
33 | let category: string = params["category"];
34 | let search: string = params["search"];
35 | this.products = this.productService.getProducts(category, search).map(this.transform);
36 | });
37 | }
38 |
39 | transform(source: Product[]) {
40 | let index = 0;
41 | let length = source.length;
42 |
43 | let products = [];
44 |
45 | while (length) {
46 | let row: Product[] = [];
47 | if (length >= 3) {
48 | for (let i = 0; i < 3; i++) {
49 | row.push(source[index++]);
50 | }
51 | products.push(row);
52 | length -= 3;
53 | } else {
54 | for (; length > 0; length--) {
55 | row.push(source[index++]);
56 | }
57 | products.push(row);
58 | }
59 | }
60 |
61 | return products;
62 | }
63 |
64 | addToCart(product: Product) {
65 | this.cartService.addProduct(product);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-list.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-list.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component} from "@angular/core";
5 |
6 | @Component({
7 | selector: "db-products",
8 | templateUrl: "./product-list.component.html"
9 | })
10 | export class ProductListComponent {}
11 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-search.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
5 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-search.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component} from "@angular/core";
5 | import {Router} from "@angular/router";
6 |
7 | @Component({
8 | selector: "db-product-search",
9 | templateUrl: "./product-search.component.html"
10 | })
11 | export class ProductSearchComponent {
12 |
13 | constructor(private router: Router) {}
14 |
15 | searchProduct(value: string) {
16 | this.router.navigate(["/products"], { queryParams: { search: value} });
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-view.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |

5 |
6 |
7 |
8 |
9 |
{{product?.title}}
10 |
{{product?.desc}}
11 |
12 |
13 | - ID: {{product?.id}}
14 | - Category: {{product?.categoryId | categoryTitle}}
15 |
16 |
19 |
20 |
21 |
22 |
Price: {{product?.price | currency:'USD':true:'1.2-2'}}
23 |
24 |
25 |
26 |
27 |
Price: {{product?.price | currency:'USD':true:'1.2-2'}}
28 |
29 |
30 |
31 |
62 |
63 |
--------------------------------------------------------------------------------
/src/app/shop/product/product-view.component.ts:
--------------------------------------------------------------------------------
1 | /*
2 | * Angular Imports
3 | */
4 | import {Component, OnInit} from "@angular/core";
5 | import {ActivatedRoute} from "@angular/router";
6 |
7 | /*
8 | * Components Imports
9 | */
10 | import { Product, ProductService } from "../../services/product.service";
11 | import { Cart, CartItem, CartService } from "../../services/cart.service";
12 |
13 | @Component({
14 | selector: "db-product-view",
15 | templateUrl: "./product-view.component.html"
16 | })
17 | export class ProductViewComponent implements OnInit {
18 |
19 | product: Product;
20 | cartItem: CartItem;
21 |
22 | get quantity(): number {
23 | return this.cartItem ? this.cartItem.count : 0;
24 | }
25 |
26 | get amount(): number {
27 | return this.cartItem ? this.cartItem.amount : 0;
28 | }
29 |
30 | constructor(private route: ActivatedRoute,
31 | private productService: ProductService,
32 | private cartService: CartService) { }
33 |
34 | ngOnInit(): void {
35 | this.route
36 | .params
37 | .subscribe(params => {
38 | // Get the product id
39 | let id: string = params["id"];
40 | // Return the product from ProductService
41 | this.productService.getProduct(id).subscribe((product: Product) => this.product = product);
42 | // Return the cart item
43 | this.cartItem = this.cartService.findItem(id);
44 | });
45 | }
46 |
47 | addToCart() {
48 | this.cartItem = this.cartService.addProduct(this.product);
49 | }
50 |
51 | removeFromCart() {
52 | this.cartItem = this.cartService.removeProduct(this.product);
53 | }
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/src/app/shop/shop.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from "@angular/core";
2 | import { BrowserModule } from "@angular/platform-browser";
3 | import { CommonModule } from "@angular/common";
4 | import {FormsModule, ReactiveFormsModule} from "@angular/forms";
5 | import {RouterModule} from "@angular/router";
6 |
7 | import {CategoryListComponent} from "./category/category-list.component";
8 | import {CategoryTitlePipe} from "../pipes/category.pipe";
9 | import {CategoryCardComponent} from "./category/category-card.component";
10 | import {CategorySlideComponent} from "./category/category-slide.component";
11 |
12 | import { ProductListComponent } from "./product/product-list.component";
13 | import { ProductViewComponent } from "./product/product-view.component";
14 | import { ProductCardComponent } from "./product/product-card.component";
15 | import { ProductSearchComponent } from "./product/product-search.component";
16 | import { ProductGridComponent } from "./product/product-grid.component";
17 |
18 | import { CartItemCountComponent } from "./cart/cart-item-count.component";
19 | import { CartMenuComponent } from "./cart/cart-menu.component";
20 | import { CartViewComponent } from "./cart/cart-view.component";
21 |
22 | import { BsDropdownModule } from 'ngx-bootstrap';
23 |
24 | @NgModule({
25 | imports: [
26 | BrowserModule, CommonModule,
27 | FormsModule, ReactiveFormsModule,
28 | RouterModule, BsDropdownModule
29 | ],
30 | declarations: [
31 | CategoryListComponent,
32 | CategoryTitlePipe,
33 | CategoryCardComponent,
34 | CategorySlideComponent,
35 | ProductListComponent,
36 | ProductViewComponent,
37 | ProductCardComponent,
38 | ProductSearchComponent,
39 | ProductGridComponent,
40 | CartItemCountComponent,
41 | CartMenuComponent,
42 | CartViewComponent
43 | ],
44 | exports: [
45 | CategoryListComponent,
46 | CategoryTitlePipe,
47 | CategoryCardComponent,
48 | CategorySlideComponent,
49 | ProductListComponent,
50 | ProductViewComponent,
51 | ProductCardComponent,
52 | ProductSearchComponent,
53 | ProductGridComponent,
54 | CartItemCountComponent,
55 | CartMenuComponent,
56 | CartViewComponent
57 | ]
58 | })
59 | export class ShopModule { }
60 |
--------------------------------------------------------------------------------
/src/app/welcome/welcome.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/app/welcome/welcome.component.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadonet/angular6-bootstrap4-firebase-ecommerce-application/86cd12ec4d1f34a5fc421c24d2b977d65022711a/src/app/welcome/welcome.component.scss
--------------------------------------------------------------------------------
/src/app/welcome/welcome.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { WelcomeComponent } from './welcome.component';
4 |
5 | describe('WelcomeComponent', () => {
6 | let component: WelcomeComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async(() => {
10 | TestBed.configureTestingModule({
11 | declarations: [ WelcomeComponent ]
12 | })
13 | .compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(WelcomeComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/src/app/welcome/welcome.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import {Observable} from "rxjs";
3 |
4 | /*
5 | * Components
6 | */
7 | import {Category, CategoryService} from "../services/category.service";
8 |
9 |
10 | @Component({
11 | selector: 'app-welcome',
12 | templateUrl: './welcome.component.html',
13 | styleUrls: ['./welcome.component.scss']
14 | })
15 | export class WelcomeComponent implements OnInit {
16 |
17 | // Slide Categories
18 | slideCategories: Category[] = [];
19 |
20 | // Card categories
21 | cardCategories: Observable;
22 |
23 | constructor(private categoryService: CategoryService) {}
24 |
25 | ngOnInit(): void {
26 | this.cardCategories = this.categoryService.getCategories();
27 | //console.log(this.cardCategories);
28 | this.cardCategories.subscribe((categories: Category[]) => {
29 | this.slideCategories = [
30 | categories[0],
31 | categories[1],
32 | categories[2]
33 | ];
34 | }, errors => {
35 | console.log(errors);
36 | });
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadonet/angular6-bootstrap4-firebase-ecommerce-application/86cd12ec4d1f34a5fc421c24d2b977d65022711a/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/ecommerce.scss:
--------------------------------------------------------------------------------
1 | body {
2 | min-height: 100vh;
3 | position: relative;
4 | margin: 0;
5 | padding-bottom: 70px;
6 | }
7 |
8 | a {
9 | cursor: pointer;
10 | }
11 |
12 | .product-menu {
13 | height: 50px;
14 | margin-top: 22px;
15 | }
16 |
17 | .product-slide-item {
18 | margin-bottom: 25px;
19 | }
20 |
21 | .product-item {
22 | height: 171px;
23 | width: 100%;
24 | display: block;
25 | }
26 |
27 | .product-img {
28 | height: 300px;
29 | width: 100%;
30 | display: block;
31 | }
32 |
33 | .marketing {
34 | background-color: #f6eee3;
35 | }
36 |
37 | .carousel {
38 | margin-bottom: 12px;
39 | }
40 |
41 | .carousel-item {
42 | margin-top: 12px;
43 | }
44 |
45 | .footer {
46 | position: absolute;
47 | bottom: 0;
48 | width: 100%;
49 | background-color: #f5f5f5;
50 | text-align: center;
51 | }
52 |
53 | .card-deck-wrapper > .card-deck > .card {
54 | width: 270px;
55 | }
56 |
57 | .card-deck-wrapper {
58 | padding-bottom: 1rem;
59 | }
60 |
61 | .table-cart {
62 | text-align: center;
63 | width: 100%;
64 | }
65 |
66 | .table-cart tr {
67 | border-bottom:1px solid gray;
68 | }
69 |
70 | .table-cart tr td {
71 | padding: 4px;
72 | vertical-align: middle !important;
73 | }
74 |
75 | .total-cart {
76 | margin: 5px;
77 | }
78 |
79 | .btn-cart {
80 | margin: 0 5px 5px 0;
81 | }
82 |
83 | .emty-cart {
84 | text-align: center;
85 | }
86 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 |
2 | export const environment = {
3 | production: true,
4 | config: {
5 | apiKey: 'AIzaSyDWJ7baKjt-qRjyGvrR-ItZ7ihh3NAVRUs',
6 | authDomain: 'shopping-1578f.firebaseapp.com',
7 | databaseURL: 'https://shopping-1578f.firebaseio.com',
8 | projectId: 'shopping-1578f',
9 | storageBucket: 'shopping-1578f.appspot.com',
10 | messagingSenderId: '883731239859',
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 |
2 | export const environment = {
3 | production: false,
4 | config: {
5 | apiKey: 'AIzaSyDWJ7baKjt-qRjyGvrR-ItZ7ihh3NAVRUs',
6 | authDomain: 'shopping-1578f.firebaseapp.com',
7 | databaseURL: 'https://shopping-1578f.firebaseio.com',
8 | projectId: 'shopping-1578f',
9 | storageBucket: 'shopping-1578f.appspot.com',
10 | messagingSenderId: '883731239859',
11 | }
12 | };
13 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shadonet/angular6-bootstrap4-firebase-ecommerce-application/86cd12ec4d1f34a5fc421c24d2b977d65022711a/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Shop
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import { AppModule } from './app/app.module';
4 | import { environment } from './environments/environment';
5 |
6 | if (environment.production) {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic().bootstrapModule(AppModule)
11 | .catch(err => console.log(err));
12 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | // import 'core-js/es6/symbol';
23 | // import 'core-js/es6/object';
24 | // import 'core-js/es6/function';
25 | // import 'core-js/es6/parse-int';
26 | // import 'core-js/es6/parse-float';
27 | // import 'core-js/es6/number';
28 | // import 'core-js/es6/math';
29 | // import 'core-js/es6/string';
30 | // import 'core-js/es6/date';
31 | // import 'core-js/es6/array';
32 | // import 'core-js/es6/regexp';
33 | // import 'core-js/es6/map';
34 | // import 'core-js/es6/weak-map';
35 | // import 'core-js/es6/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | // import 'core-js/es6/reflect';
42 |
43 | /** Evergreen browsers require these. **/
44 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
45 | import 'core-js/es7/reflect';
46 |
47 | /**
48 | * Required to support Web Animations `@angular/platform-browser/animations`.
49 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
50 | **/
51 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
52 |
53 | /***************************************************************************************************
54 | * Zone JS is required by default for Angular itself.
55 | */
56 | import 'zone.js/dist/zone'; // Included with Angular CLI.
57 |
58 | /***************************************************************************************************
59 | * APPLICATION IMPORTS
60 | */
61 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | $fa-font-path: '../node_modules/font-awesome/fonts';
3 |
4 | @import '~bootstrap/scss/bootstrap';
5 | @import './assets/ecommerce.scss';
6 | @import '~font-awesome/scss/font-awesome';
7 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/dist/zone-testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | declare const require: any;
11 |
12 | // First, initialize the Angular testing environment.
13 | getTestBed().initTestEnvironment(
14 | BrowserDynamicTestingModule,
15 | platformBrowserDynamicTesting()
16 | );
17 | // Then we find all the tests.
18 | const context = require.context('./', true, /\.spec\.ts$/);
19 | // And load the modules.
20 | context.keys().map(context);
21 |
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015",
7 | "types": []
8 | },
9 | "exclude": [
10 | "test.ts",
11 | "**/*.spec.ts"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "types": [
8 | "jasmine",
9 | "node"
10 | ]
11 | },
12 | "files": [
13 | "test.ts",
14 | "polyfills.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "target": "es5",
11 | "typeRoots": [
12 | "node_modules/@types"
13 | ],
14 | "lib": [
15 | "es2017",
16 | "dom"
17 | ],
18 | "module": "es2015",
19 | "baseUrl": "./"
20 | }
21 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": [
3 | "node_modules/codelyzer"
4 | ],
5 | "rules": {
6 | "arrow-return-shorthand": true,
7 | "callable-types": true,
8 | "class-name": true,
9 | "comment-format": [
10 | true,
11 | "check-space"
12 | ],
13 | "curly": true,
14 | "deprecation": {
15 | "severity": "warn"
16 | },
17 | "eofline": true,
18 | "forin": true,
19 | "import-blacklist": [
20 | true,
21 | "rxjs/Rx"
22 | ],
23 | "import-spacing": true,
24 | "indent": [
25 | true,
26 | "spaces"
27 | ],
28 | "interface-over-type-literal": true,
29 | "label-position": true,
30 | "max-line-length": [
31 | true,
32 | 140
33 | ],
34 | "member-access": false,
35 | "member-ordering": [
36 | true,
37 | {
38 | "order": [
39 | "static-field",
40 | "instance-field",
41 | "static-method",
42 | "instance-method"
43 | ]
44 | }
45 | ],
46 | "no-arg": true,
47 | "no-bitwise": true,
48 | "no-console": [
49 | true,
50 | "debug",
51 | "info",
52 | "time",
53 | "timeEnd",
54 | "trace"
55 | ],
56 | "no-construct": true,
57 | "no-debugger": true,
58 | "no-duplicate-super": true,
59 | "no-empty": false,
60 | "no-empty-interface": true,
61 | "no-eval": true,
62 | "no-inferrable-types": [
63 | true,
64 | "ignore-params"
65 | ],
66 | "no-misused-new": true,
67 | "no-non-null-assertion": true,
68 | "no-shadowed-variable": true,
69 | "no-string-literal": false,
70 | "no-string-throw": true,
71 | "no-switch-case-fall-through": true,
72 | "no-trailing-whitespace": true,
73 | "no-unnecessary-initializer": true,
74 | "no-unused-expression": true,
75 | "no-use-before-declare": true,
76 | "no-var-keyword": true,
77 | "object-literal-sort-keys": false,
78 | "one-line": [
79 | true,
80 | "check-open-brace",
81 | "check-catch",
82 | "check-else",
83 | "check-whitespace"
84 | ],
85 | "prefer-const": true,
86 | "quotemark": [
87 | true,
88 | "single"
89 | ],
90 | "radix": true,
91 | "semicolon": [
92 | true,
93 | "always"
94 | ],
95 | "triple-equals": [
96 | true,
97 | "allow-null-check"
98 | ],
99 | "typedef-whitespace": [
100 | true,
101 | {
102 | "call-signature": "nospace",
103 | "index-signature": "nospace",
104 | "parameter": "nospace",
105 | "property-declaration": "nospace",
106 | "variable-declaration": "nospace"
107 | }
108 | ],
109 | "unified-signatures": true,
110 | "variable-name": false,
111 | "whitespace": [
112 | true,
113 | "check-branch",
114 | "check-decl",
115 | "check-operator",
116 | "check-separator",
117 | "check-type"
118 | ],
119 | "directive-selector": [
120 | true,
121 | "attribute",
122 | "app",
123 | "camelCase"
124 | ],
125 | "component-selector": [
126 | true,
127 | "element",
128 | "app",
129 | "kebab-case"
130 | ],
131 | "no-output-on-prefix": true,
132 | "use-input-property-decorator": true,
133 | "use-output-property-decorator": true,
134 | "use-host-property-decorator": true,
135 | "no-input-rename": true,
136 | "no-output-rename": true,
137 | "use-life-cycle-interface": true,
138 | "use-pipe-transform-interface": true,
139 | "component-class-suffix": true,
140 | "directive-class-suffix": true
141 | }
142 | }
143 |
--------------------------------------------------------------------------------