├── .angular-cli.json
├── .editorconfig
├── .gitignore
├── README.md
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── karma.conf.js
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── about
│ │ ├── about.component.html
│ │ ├── about.component.scss
│ │ └── about.component.ts
│ ├── app-header
│ │ ├── app-header.component.html
│ │ ├── app-header.component.scss
│ │ └── app-header.component.ts
│ ├── app.component.html
│ ├── app.component.model.ts
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── app.routing.module.ts
│ ├── application-state.service.ts
│ ├── core.module.ts
│ ├── frontpage
│ │ ├── desktop
│ │ │ ├── carousel.d.ts
│ │ │ ├── desktop-frontpage.component.html
│ │ │ ├── desktop-frontpage.component.model.ts
│ │ │ ├── desktop-frontpage.component.scss
│ │ │ └── desktop-frontpage.component.ts
│ │ ├── homepage-item.ts
│ │ └── mobile
│ │ │ ├── mobile-frontpage.component.html
│ │ │ ├── mobile-frontpage.component.model.ts
│ │ │ ├── mobile-frontpage.component.scss
│ │ │ └── mobile-frontpage.component.ts
│ ├── product
│ │ ├── product.component.desktop.html
│ │ ├── product.component.desktop.scss
│ │ ├── product.component.desktop.ts
│ │ ├── product.component.mobile.html
│ │ ├── product.component.mobile.scss
│ │ ├── product.component.mobile.ts
│ │ ├── product.component.model.ts
│ │ └── product.component.ts
│ └── user-profile
│ │ ├── user-profile.component.desktop.html
│ │ ├── user-profile.component.desktop.scss
│ │ ├── user-profile.component.desktop.ts
│ │ ├── user-profile.component.html
│ │ ├── user-profile.component.mobile.html
│ │ ├── user-profile.component.mobile.scss
│ │ ├── user-profile.component.mobile.ts
│ │ └── user-profile.component.ts
├── assets
│ ├── .gitkeep
│ ├── images
│ │ ├── front-page
│ │ │ ├── banana-guard.jpg
│ │ │ ├── hoverboard.jpg
│ │ │ ├── ipad.jpg
│ │ │ ├── iphone.jpg
│ │ │ └── xbox.jpg
│ │ ├── product-1.jpg
│ │ └── product-2.jpg
│ └── styles
│ │ └── constants.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
└── yarn.lock
/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "angular-multi-view"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico"
13 | ],
14 | "index": "index.html",
15 | "main": "main.ts",
16 | "polyfills": "polyfills.ts",
17 | "test": "test.ts",
18 | "tsconfig": "tsconfig.app.json",
19 | "testTsconfig": "tsconfig.spec.json",
20 | "prefix": "app",
21 | "styles": [
22 | "../node_modules/font-awesome/scss/font-awesome.scss",
23 | "styles.scss"
24 | ],
25 | "scripts": [
26 | "../node_modules/jquery/dist/jquery.js",
27 | "../node_modules/jquery.flipster/src/jquery.flipster.js"
28 | ],
29 | "environmentSource": "environments/environment.ts",
30 | "environments": {
31 | "dev": "environments/environment.ts",
32 | "prod": "environments/environment.prod.ts"
33 | }
34 | }
35 | ],
36 | "e2e": {
37 | "protractor": {
38 | "config": "./protractor.conf.js"
39 | }
40 | },
41 | "lint": [
42 | {
43 | "project": "src/tsconfig.app.json",
44 | "exclude": "**/node_modules/**"
45 | },
46 | {
47 | "project": "src/tsconfig.spec.json",
48 | "exclude": "**/node_modules/**"
49 | },
50 | {
51 | "project": "e2e/tsconfig.e2e.json",
52 | "exclude": "**/node_modules/**"
53 | }
54 | ],
55 | "test": {
56 | "karma": {
57 | "config": "./karma.conf.js"
58 | }
59 | },
60 | "defaults": {
61 | "styleExt": "scss",
62 | "component": {}
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | testem.log
34 | /typings
35 |
36 | # e2e
37 | /e2e/*.js
38 | /e2e/*.map
39 |
40 | # System Files
41 | .DS_Store
42 | Thumbs.db
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AngularMultiView
2 |
3 | This project is an example application for the article:
4 | it demonstrates an example for angular application with multiple views (for multiple screen sizes)
5 |
6 | ## Running
7 |
8 | clone the project
9 | run: npm install
10 | run: ng s
11 |
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('angular-multi-view 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 |
--------------------------------------------------------------------------------
/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/cli'],
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/cli/plugins/karma')
14 | ],
15 | client:{
16 | clearContext: false // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | reports: [ 'html', 'lcovonly' ],
20 | fixWebpackSourcePaths: true
21 | },
22 | angularCli: {
23 | environment: 'dev'
24 | },
25 | reporters: ['progress', 'kjhtml'],
26 | port: 9876,
27 | colors: true,
28 | logLevel: config.LOG_INFO,
29 | autoWatch: true,
30 | browsers: ['Chrome'],
31 | singleRun: false
32 | });
33 | };
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-multi-view",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e"
12 | },
13 | "private": true,
14 | "dependencies": {
15 | "@angular/animations": "^5.0.0",
16 | "@angular/cdk": "^5.0.0-rc0",
17 | "@angular/common": "^5.0.0",
18 | "@angular/compiler": "^5.0.0",
19 | "@angular/core": "^5.0.0",
20 | "@angular/forms": "^5.0.0",
21 | "@angular/http": "^5.0.0",
22 | "@angular/material": "^5.0.0-rc0",
23 | "@angular/platform-browser": "^5.0.0",
24 | "@angular/platform-browser-dynamic": "^5.0.0",
25 | "@angular/router": "^5.0.0",
26 | "bootstrap": "4.0.0-beta.2",
27 | "core-js": "^2.4.1",
28 | "font-awesome": "^4.7.0",
29 | "jquery": "^3.2.1",
30 | "jquery.flipster": "^1.1.2",
31 | "rxjs": "^5.5.2",
32 | "zone.js": "^0.8.14"
33 | },
34 | "devDependencies": {
35 | "@angular/cli": "1.7.3",
36 | "@angular/compiler-cli": "^5.0.0",
37 | "@angular/language-service": "^5.0.0",
38 | "@types/jasmine": "~2.5.53",
39 | "@types/jasminewd2": "~2.0.2",
40 | "@types/jquery": "^3.2.16",
41 | "@types/node": "~6.0.60",
42 | "codelyzer": "~3.2.0",
43 | "jasmine-core": "~2.6.2",
44 | "jasmine-spec-reporter": "~4.1.0",
45 | "karma": "~1.7.0",
46 | "karma-chrome-launcher": "~2.1.1",
47 | "karma-cli": "~1.0.1",
48 | "karma-coverage-istanbul-reporter": "^1.2.1",
49 | "karma-jasmine": "~1.1.0",
50 | "karma-jasmine-html-reporter": "^0.2.2",
51 | "protractor": "~5.1.2",
52 | "ts-node": "~3.2.0",
53 | "tslint": "~5.7.0",
54 | "typescript": "~2.4.2"
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/about/about.component.html:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
16 |
17 |
18 |
19 |
20 | Asaf Toker
21 |
22 |
23 |
24 | Founder, CEO
25 |
26 |
27 |
28 |
29 |
42 |
43 |
44 |
45 |
46 |
47 |
Dr. Toker is a certified medical doctor, who also holds extensive knowledge and
48 | experience in the fields of high-tech development and entrepreneurship. previously, Asaf was the VP of Business Development for
49 | Capital Point Ltd, an investment group that invests in early stage medical device and biotechnology companies. Asaf travel a lot –
50 | the number of destinations he visited is countless.
51 |
52 |
53 |
54 |
55 |
56 |
58 |
59 |
60 |
61 |
62 | Yosi Golan
63 |
64 |
65 |
66 | Founder, CTO
67 |
68 |
69 |
70 |
71 |
84 |
85 |
86 |
87 |
88 |
89 |
Yosi leads the development efforts at SeeVoov. He has 10 years of server side
90 | development and management experience on on his past, and passion for urban travel.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
99 |
100 |
101 |
102 |
103 | Assaf Meron
104 |
105 |
106 |
107 | Graphic Designer
108 |
109 |
110 |
111 |
112 |
125 |
126 |
127 |
128 |
129 |
Assaf specialize in brand identity, digital user interfaces and user experiences, mobile
130 | apps and website design. With over ten years of experience in New York’s and Israel’s leading advertising and design agencies,
131 | Assaf have created projects that run the gamut of the design industry for clients huge, large and small in almost every field of
132 | business.
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/src/app/about/about.component.scss:
--------------------------------------------------------------------------------
1 | .the-team-header {
2 | margin: 100px 0 90px;
3 |
4 | h2 {
5 | color: #00a4e9;
6 | margin-bottom: 20px;
7 | font-size: 36px;
8 | }
9 | }
10 |
11 | .team-content {
12 | margin: 0 auto;
13 | max-width: 580px;
14 | font-size: 18px;
15 | }
16 |
17 | .team-container {
18 | display: flex;
19 | flex-wrap: wrap;
20 | justify-content: space-between;
21 | }
22 |
23 | .member {
24 | width: 24%;
25 | margin: 0 0 150px;
26 | }
27 |
28 | @media screen and (max-width: 1000px) {
29 | .member {
30 | width: 32%;
31 | margin-bottom: 50px;
32 | }
33 | }
34 |
35 | @media screen and (max-width: 900px) {
36 | .member {
37 | width: 49%;
38 | }
39 | }
40 |
41 | @media screen and (max-width: 480px) {
42 | .member {
43 | width: 100%;
44 | }
45 | }
46 |
47 | .member-photo {
48 | max-width: 100%;
49 | margin-bottom: 25px;
50 | }
51 |
52 | h3 {
53 | margin: 0 0 5px;
54 | font-size: 20px;
55 | }
56 |
57 | h4 {
58 | font-size: 16px;
59 | margin: 0 0 20px;
60 | }
61 |
62 | .member-details {
63 | position: relative;
64 |
65 | .member-social {
66 | position: absolute;
67 | top: 5px;
68 | right: 0;
69 | max-width: 100px;
70 |
71 | i {
72 | padding-right: 15px;
73 | float: left;
74 | font-size: 16px;
75 | color: #353535;
76 | }
77 |
78 | a:last-of-type i {
79 | padding-right: 0;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/app/about/about.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-about',
5 | templateUrl: './about.component.html',
6 | styleUrls: ['./about.component.scss']
7 | })
8 | export class AboutComponent {
9 |
10 | constructor() {
11 |
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/app/app-header/app-header.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | About
9 |
10 |
11 |
12 | User Profile
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/src/app/app-header/app-header.component.scss:
--------------------------------------------------------------------------------
1 | .logo {
2 | height: 30px;
3 | }
4 |
5 |
--------------------------------------------------------------------------------
/src/app/app-header/app-header.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 | import {animate, style, transition, trigger} from '@angular/animations';
3 | import { ApplicationStateService } from '../application-state.service';
4 | import { Router } from '@angular/router';
5 |
6 | @Component({
7 | selector: 'app-header',
8 | templateUrl: './app-header.component.html',
9 | styleUrls: ['./app-header.component.scss'],
10 | animations: [
11 | trigger('userProfileAnimation', [
12 | transition('void => *', [
13 | style({opacity: 0, marginTop: 500}),
14 | animate('200ms ease-in', style({opacity: 1, marginTop: 50}))
15 | ]),
16 | transition('* => void', [
17 | animate('150ms ease-out', style({opacity: 0, marginTop: 500}))
18 | ])
19 | ])
20 | ]
21 | })
22 | export class AppHeaderComponent {
23 | public isMobileResolution: boolean;
24 | public isToShowUserProfile: boolean;
25 |
26 | constructor(private applicationStateService: ApplicationStateService,private router: Router) {
27 | this.isMobileResolution = this.applicationStateService.getIsMobileResolution();
28 | this.isToShowUserProfile = false;
29 | }
30 |
31 | public onUserProfileClick(): void {
32 | if (!this.isMobileResolution) {
33 | if (this.isToShowUserProfile) {
34 | this.isToShowUserProfile = false;
35 | } else {
36 | this.isToShowUserProfile = true;
37 | }
38 | } else {
39 | this.router.navigate(['/user-profile']);
40 | }
41 |
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/app/app.component.model.ts:
--------------------------------------------------------------------------------
1 | export class AppComponentModel {
2 |
3 | public isToShowUserProfile: boolean;
4 |
5 | constructor() {
6 | this.isToShowUserProfile = false;
7 | }
8 |
9 | public clone(): AppComponentModel {
10 | let model: AppComponentModel = new AppComponentModel();
11 |
12 | model.isToShowUserProfile = this.isToShowUserProfile;
13 |
14 | return model;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | .main-content {
2 | margin-top: 100px;
3 | margin-bottom: 50px;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import {Component} from '@angular/core';
2 | import {AppComponentModel} from './app.component.model';
3 | import {animate, style, transition, trigger} from '@angular/animations';
4 | import { ApplicationStateService } from './application-state.service';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.scss'],
10 | animations: [
11 | trigger('userProfileAnimation', [
12 | transition('void => *', [
13 | style({opacity: 0, marginTop: 500}),
14 | animate('200ms ease-in', style({opacity: 1, marginTop: 50}))
15 | ]),
16 | transition('* => void', [
17 | animate('150ms ease-out', style({opacity: 0, marginTop: 500}))
18 | ])
19 | ])
20 | ]
21 | })
22 | export class AppComponent {
23 |
24 | public myViewModel: AppComponentModel;
25 | public isMobileResolution: boolean;
26 | private model: AppComponentModel;
27 |
28 | constructor(private applicationStateService: ApplicationStateService) {
29 | this.model = new AppComponentModel();
30 | this.myViewModel = new AppComponentModel();
31 |
32 | this.isMobileResolution = this.applicationStateService.getIsMobileResolution();
33 | }
34 |
35 | public onUserProfileClick(): void {
36 | if (this.model.isToShowUserProfile) {
37 | this.model.isToShowUserProfile = false;
38 | } else {
39 | this.model.isToShowUserProfile = true;
40 | }
41 |
42 | this.updateView();
43 | }
44 |
45 | private updateView(): void {
46 | this.myViewModel = this.model.clone();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import {BrowserModule} from '@angular/platform-browser';
2 | import {NgModule} from '@angular/core';
3 | import {AppComponent} from './app.component';
4 | import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
5 | import {AppRoutingModule} from './app.routing.module';
6 | import {AboutComponent} from './about/about.component';
7 | import {UserProfileComponent} from './user-profile/user-profile.component';
8 | import {CoreModule} from './core.module';
9 | import {ProductComponent} from './product/product.component';
10 | import {DesktopFrontpageComponent} from './frontpage/desktop/desktop-frontpage.component';
11 | import {MobileFrontpageComponent} from './frontpage/mobile/mobile-frontpage.component';
12 | import {MatTabsModule} from '@angular/material';
13 | import { AppHeaderComponent } from './app-header/app-header.component';
14 | import { ProductComponentMobile } from './product/product.component.mobile';
15 | import { ProductComponentDesktop } from './product/product.component.desktop';
16 | import { UserProfileComponentDesktop } from './user-profile/user-profile.component.desktop';
17 | import { UserProfileComponentMobile } from './user-profile/user-profile.component.mobile';
18 |
19 | @NgModule({
20 | declarations: [
21 | AppComponent,
22 | AboutComponent,
23 | UserProfileComponent,
24 | UserProfileComponentDesktop,
25 | UserProfileComponentMobile,
26 | ProductComponentDesktop,
27 | ProductComponentMobile,
28 | DesktopFrontpageComponent,
29 | MobileFrontpageComponent,
30 | AppHeaderComponent
31 | ],
32 | imports: [
33 | BrowserModule,
34 | BrowserAnimationsModule,
35 | CoreModule,
36 | AppRoutingModule,
37 | MatTabsModule
38 | ],
39 | entryComponents: [
40 | UserProfileComponent,
41 | MobileFrontpageComponent,
42 | ProductComponentMobile
43 | ],
44 | bootstrap: [
45 | AppComponent
46 | ]
47 | })
48 | export class AppModule {
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/app.routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { PreloadAllModules, Router, RouterModule, Routes } from '@angular/router';
3 | import { DesktopFrontpageComponent } from './frontpage/desktop/desktop-frontpage.component';
4 | import { AboutComponent } from './about/about.component';
5 | import { MobileFrontpageComponent } from './frontpage/mobile/mobile-frontpage.component';
6 | import { UserProfileComponent } from './user-profile/user-profile.component';
7 | import { ProductComponent } from './product/product.component';
8 | import { ApplicationStateService } from './application-state.service';
9 | import { ProductComponentDesktop } from './product/product.component.desktop';
10 | import { ProductComponentMobile } from './product/product.component.mobile';
11 |
12 | const desktop_routes: Routes = [
13 | {
14 | path: '', component: DesktopFrontpageComponent, children:
15 | [
16 | {path: 'products/:productName', component: ProductComponentDesktop}
17 | ]
18 | },
19 | {path: 'about', component: AboutComponent},
20 | // directs all other routes to the main page
21 | {path: '**', redirectTo: ''}
22 | ];
23 |
24 | const mobile_routes: Routes = [
25 | {path: '', component: MobileFrontpageComponent},
26 | {path: 'products/:productName', component: ProductComponentMobile},
27 | {path: 'about', component: AboutComponent},
28 | {path: 'user-profile', component: UserProfileComponent},
29 | // directs all other routes to the main page
30 | {path: '**', redirectTo: ''}
31 | ];
32 |
33 | @NgModule({
34 | // as default we set the desktop routing configuration. if mobile will be started it will be replaced below.
35 | // note that we must specify some routes here (not an empty array) otherwise the trick below doesn't work...
36 | imports: [RouterModule.forRoot(desktop_routes, {preloadingStrategy: PreloadAllModules})],
37 | exports: [RouterModule]
38 | })
39 | export class AppRoutingModule {
40 |
41 | public constructor(private router: Router,
42 | private applicationStateService: ApplicationStateService) {
43 |
44 | if (applicationStateService.getIsMobileResolution()) {
45 | router.resetConfig(mobile_routes);
46 | }
47 | }
48 |
49 | /**
50 | * this function inject new routes for the given module instead the current routes. the operation happens on the given current routes object so after
51 | * this method a call to reset routes on router should be called with the the current routes object.
52 | * @param currentRoutes
53 | * @param routesToInject
54 | * @param childNameToReplaceRoutesUnder - the module name to replace its routes.
55 | */
56 | private injectModuleRoutes(currentRoutes: Routes, routesToInject: Routes, childNameToReplaceRoutesUnder: string): void {
57 | for (let i = 0; i < currentRoutes.length; i++) {
58 | if (currentRoutes[i].loadChildren != null &&
59 | currentRoutes[i].loadChildren.toString().indexOf(childNameToReplaceRoutesUnder) != -1) {
60 | // we found it. taking the route prefix
61 | let prefixRoute: string = currentRoutes[i].path;
62 | // first removing the module line
63 | currentRoutes.splice(i, 1);
64 | // now injecting the new routes
65 | // we need to add the prefix route first
66 | this.addPrefixToRoutes(routesToInject, prefixRoute);
67 | for (let route of routesToInject) {
68 | currentRoutes.push(route);
69 | }
70 | // since we found it we can break the injection
71 | return;
72 | }
73 |
74 | if (currentRoutes[i].children != null) {
75 | this.injectModuleRoutes(currentRoutes[i].children, routesToInject, childNameToReplaceRoutesUnder);
76 | }
77 | }
78 | }
79 |
80 | private addPrefixToRoutes(routes: Routes, prefix: string) {
81 | for (let i = 0; i < routes.length; i++) {
82 | routes[i].path = prefix + '/' + routes[i].path;
83 | }
84 | }
85 | }
86 |
87 |
88 |
--------------------------------------------------------------------------------
/src/app/application-state.service.ts:
--------------------------------------------------------------------------------
1 | import {Injectable} from '@angular/core';
2 |
3 | @Injectable()
4 | export class ApplicationStateService {
5 |
6 | private isMobileResolution: boolean;
7 |
8 | constructor() {
9 | if (window.innerWidth < 768) {
10 | this.isMobileResolution = true;
11 | } else {
12 | this.isMobileResolution = false;
13 | }
14 | }
15 |
16 | public getIsMobileResolution(): boolean {
17 | return this.isMobileResolution;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/core.module.ts:
--------------------------------------------------------------------------------
1 | import {NgModule} from '@angular/core';
2 | import { ApplicationStateService } from './application-state.service';
3 |
4 | /**
5 | * this module include services that must be singeltons. it should only be included in the app module
6 | * in order to work like that. see here for more info under core module:
7 | * https://angular.io/docs/ts/latest/guide/ngmodule.html#!#shared-module-for-root
8 | */
9 | @NgModule({
10 | imports: [],
11 | providers: [
12 | ApplicationStateService
13 | ]
14 | })
15 | export class CoreModule {
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/frontpage/desktop/carousel.d.ts:
--------------------------------------------------------------------------------
1 | // this is required to declare jquery references
2 | interface JQuery {
3 | flipster(params?: {}): void;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/frontpage/desktop/desktop-frontpage.component.html:
--------------------------------------------------------------------------------
1 |
2 | Cracking the Challenge - Multiple Views on Angular
3 |
4 |
5 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/app/frontpage/desktop/desktop-frontpage.component.model.ts:
--------------------------------------------------------------------------------
1 |
2 |
3 | import { HomepageItem } from '../homepage-item';
4 |
5 | export class DesktopFrontpageComponentModel {
6 |
7 | public sliderItems: HomepageItem[];
8 |
9 | constructor() {
10 | this.sliderItems = [];
11 | }
12 |
13 | public clone(): DesktopFrontpageComponentModel {
14 | let clonedModel: DesktopFrontpageComponentModel = new DesktopFrontpageComponentModel();
15 |
16 | clonedModel.sliderItems = Array.from(this.sliderItems);
17 |
18 | return clonedModel;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/frontpage/desktop/desktop-frontpage.component.scss:
--------------------------------------------------------------------------------
1 | :host::before {
2 | content: '';
3 | width: 100vw;
4 | height: 100vh;
5 | background: #353535;
6 | position: fixed;
7 | top: 0;
8 | left: 0;
9 | }
10 |
11 | h1 {
12 | color: #fff;
13 | text-align: center;
14 | }
15 |
16 | .slider {
17 | margin-bottom: 30px;
18 | }
19 |
20 | .flipster img {
21 | width: 600px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/app/frontpage/desktop/desktop-frontpage.component.ts:
--------------------------------------------------------------------------------
1 | import {AfterViewInit, Component, ElementRef, OnInit, ViewChild} from '@angular/core';
2 | import {DesktopFrontpageComponentModel} from './desktop-frontpage.component.model';
3 | import {DomSanitizer} from '@angular/platform-browser';
4 | import { HomepageItem } from '../homepage-item';
5 |
6 | @Component({
7 | selector: 'app-desktop-frontpage',
8 | templateUrl: './desktop-frontpage.component.html',
9 | styleUrls: ['./desktop-frontpage.component.scss']
10 | })
11 | export class DesktopFrontpageComponent implements OnInit, AfterViewInit {
12 |
13 | private model: DesktopFrontpageComponentModel;
14 | public myViewModel: DesktopFrontpageComponentModel;
15 |
16 | @ViewChild('slider') public slider: ElementRef;
17 |
18 | constructor(private sanitizer: DomSanitizer) {
19 | this.model = new DesktopFrontpageComponentModel();
20 | this.myViewModel = new DesktopFrontpageComponentModel();
21 | }
22 |
23 | public ngOnInit(): void {
24 | this.updateImageUrls();
25 |
26 | this.updateView();
27 | }
28 |
29 | public ngAfterViewInit(): void {
30 | this.initFlipster();
31 | }
32 |
33 | private updateView(): void {
34 | this.myViewModel = this.model.clone();
35 | }
36 |
37 | private updateImageUrls(): void {
38 | let imagesUrls: string[] = [];
39 |
40 | imagesUrls.push(
41 | '../../../assets/images/front-page/xbox.jpg',
42 | '../../../assets/images/front-page/iphone.jpg',
43 | '../../../assets/images/front-page/ipad.jpg',
44 | '../../../assets/images/front-page/hoverboard.jpg',
45 | '../../../assets/images/front-page/banana-guard.jpg'
46 | );
47 |
48 | imagesUrls.forEach((imageUrl: string, index: number) => {
49 | let homepageItem: HomepageItem = new HomepageItem();
50 | let productName: string = '';
51 |
52 | if (index % 2) {
53 | productName = 'chair';
54 | } else {
55 | productName = 'funnel';
56 | }
57 |
58 | homepageItem.imageSafeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(imageUrl);
59 | homepageItem.productName = productName;
60 |
61 | this.model.sliderItems.push(homepageItem);
62 | });
63 | }
64 |
65 | private initFlipster(): void {
66 | (window).jQuery(this.slider.nativeElement).flipster({
67 | style: 'flat',
68 | scrollwheel: true,
69 | loop: true,
70 | spacing: -0.35,
71 | buttons: true
72 | });
73 | }
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/src/app/frontpage/homepage-item.ts:
--------------------------------------------------------------------------------
1 | import {SafeResourceUrl} from '@angular/platform-browser';
2 |
3 | export class HomepageItem {
4 |
5 | public imageSafeUrl: SafeResourceUrl;
6 | public productName: string;
7 |
8 | constructor() {
9 | this.imageSafeUrl = '';
10 | this.productName = '';
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/frontpage/mobile/mobile-frontpage.component.html:
--------------------------------------------------------------------------------
1 |
2 | Cracking the Challenge - Multiple Views on Angular
3 |
4 |
5 |
12 |
--------------------------------------------------------------------------------
/src/app/frontpage/mobile/mobile-frontpage.component.model.ts:
--------------------------------------------------------------------------------
1 |
2 | import { HomepageItem } from '../homepage-item';
3 |
4 | export class MobileFrontpageComponentModel {
5 |
6 | public sliderItems: HomepageItem[];
7 |
8 | constructor() {
9 | this.sliderItems = [];
10 | }
11 |
12 | public clone(): MobileFrontpageComponentModel {
13 | let clonedModel: MobileFrontpageComponentModel = new MobileFrontpageComponentModel();
14 |
15 | clonedModel.sliderItems = Array.from(this.sliderItems);
16 |
17 | return clonedModel;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/frontpage/mobile/mobile-frontpage.component.scss:
--------------------------------------------------------------------------------
1 | h1 {
2 | margin-bottom: 25px;
3 | text-align: center;
4 | font-size: 20px;
5 | }
6 |
7 | .images-list {
8 | margin: 0;
9 | padding: 0;
10 | list-style: none;
11 |
12 | .image-item {
13 | margin-bottom: 30px;
14 |
15 | &:last-child {
16 | margin-bottom: 0;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/frontpage/mobile/mobile-frontpage.component.ts:
--------------------------------------------------------------------------------
1 | import {AfterViewInit, Component, OnInit} from '@angular/core';
2 | import {MobileFrontpageComponentModel} from './mobile-frontpage.component.model';
3 | import {DomSanitizer} from '@angular/platform-browser';
4 | import { HomepageItem } from '../homepage-item';
5 |
6 | @Component({
7 | selector: 'app-mobile-frontpage',
8 | templateUrl: './mobile-frontpage.component.html',
9 | styleUrls: ['./mobile-frontpage.component.scss']
10 | })
11 | export class MobileFrontpageComponent implements OnInit, AfterViewInit {
12 |
13 | public myViewModel: MobileFrontpageComponentModel;
14 | private model: MobileFrontpageComponentModel;
15 |
16 | constructor(private sanitizer: DomSanitizer) {
17 | this.model = new MobileFrontpageComponentModel();
18 | this.myViewModel = new MobileFrontpageComponentModel();
19 | }
20 |
21 | public ngOnInit(): void {
22 | this.updateImageUrls();
23 |
24 | this.updateView();
25 | }
26 |
27 | public ngAfterViewInit(): void {
28 | window.scrollTo(0, 0);
29 | }
30 |
31 | private updateView(): void {
32 | this.myViewModel = this.model.clone();
33 | }
34 |
35 | private updateImageUrls(): void {
36 | let imagesUrls: string[] = [];
37 |
38 | imagesUrls.push(
39 | '../../../assets/images/front-page/xbox.jpg',
40 | '../../../assets/images/front-page/iphone.jpg',
41 | '../../../assets/images/front-page/ipad.jpg',
42 | '../../../assets/images/front-page/hoverboard.jpg',
43 | '../../../assets/images/front-page/banana-guard.jpg'
44 | );
45 |
46 | imagesUrls.forEach((imageUrl: string, index: number) => {
47 | let homepageItem: HomepageItem = new HomepageItem();
48 | let productName: string = '';
49 |
50 | if (index % 2) {
51 | productName = 'chair';
52 | } else {
53 | productName = 'funnel';
54 | }
55 |
56 | homepageItem.imageSafeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(imageUrl);
57 | homepageItem.productName = productName;
58 |
59 | this.model.sliderItems.push(homepageItem);
60 | });
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/app/product/product.component.desktop.html:
--------------------------------------------------------------------------------
1 |
2 | {{myViewModel.name}}
3 |
4 |
5 |
6 |
7 |
8 | {{myViewModel.description}}
9 |
10 |
11 |
12 |
13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue.
15 | Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus.
16 | In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec,
17 | feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor,
18 | orci enim rutrum enim, vel tempor sapien arcu a tellus.
19 |
20 |
21 |
22 |
23 |
24 |
25 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue.
26 | Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus.
27 | In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec,
28 | feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor,
29 | orci enim rutrum enim, vel tempor sapien arcu a tellus.
30 |
31 |
32 |
33 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue.
34 | Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus.
35 | In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec,
36 | feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor,
37 | orci enim rutrum enim, vel tempor sapien arcu a tellus.
38 |
39 |
40 |
41 |
42 |
43 |
44 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue.
45 | Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus.
46 | In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec,
47 | feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor,
48 | orci enim rutrum enim, vel tempor sapien arcu a tellus.
49 |
50 |
51 |
52 |
53 |
54 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla venenatis ante augue.
55 | Phasellus volutpat neque ac dui mattis vulputate. Etiam consequat aliquam cursus.
56 | In sodales pretium ultrices. Maecenas lectus est, sollicitudin consectetur felis nec,
57 | feugiat ultricies mi. Aliquam erat volutpat. Nam placerat, tortor in ultrices porttitor,
58 | orci enim rutrum enim, vel tempor sapien arcu a tellus.
59 |
60 |
61 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/src/app/product/product.component.desktop.scss:
--------------------------------------------------------------------------------
1 | @import '../../assets/styles/constants';
2 |
3 | :host {
4 | position: fixed;
5 | top: 10vh;
6 | right: 10vw;
7 | bottom: 10vh;
8 | left: 10vw;
9 | padding: 25px;
10 | background: #fff;
11 | border-radius: 10px;
12 | z-index: 1000;
13 | }
14 |
15 | img {
16 | width: 25%;
17 | float: left;
18 | margin: 0 30px 30px 0;
19 | }
20 |
21 | :host ::ng-deep .mat-tab-label {
22 | text-transform: uppercase;
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/product/product.component.desktop.ts:
--------------------------------------------------------------------------------
1 | import { AfterViewInit, Component } from '@angular/core';
2 | import { ProductComponentModel } from './product.component.model';
3 | import { ActivatedRouteSnapshot, Router } from '@angular/router';
4 | import { DomSanitizer } from '@angular/platform-browser';
5 | import { ApplicationStateService } from '../application-state.service';
6 | import { ProductComponent } from './product.component';
7 |
8 | @Component({
9 | selector: 'app-product-desktop',
10 | templateUrl: './product.component.desktop.html',
11 | styleUrls: ['./product.component.desktop.scss']
12 | })
13 | export class ProductComponentDesktop extends ProductComponent {
14 |
15 | constructor(router: Router,
16 | sanitizer: DomSanitizer) {
17 | super(router, sanitizer);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/product/product.component.mobile.html:
--------------------------------------------------------------------------------
1 |
2 | {{myViewModel.name}}
3 |
4 |
5 |
6 |
7 |
8 | {{myViewModel.description}}
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/product/product.component.mobile.scss:
--------------------------------------------------------------------------------
1 | @import '../../assets/styles/constants';
2 |
3 | img {
4 | width: 100%;
5 | margin-bottom: 15px;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/product/product.component.mobile.ts:
--------------------------------------------------------------------------------
1 | import {AfterViewInit, Component} from '@angular/core';
2 | import {ProductComponentModel} from './product.component.model';
3 | import {ActivatedRouteSnapshot, Router} from '@angular/router';
4 | import {DomSanitizer} from '@angular/platform-browser';
5 | import { ApplicationStateService } from '../application-state.service';
6 | import { ProductComponent } from './product.component';
7 |
8 | @Component({
9 | selector: 'app-product-mobile',
10 | templateUrl: './product.component.mobile.html',
11 | styleUrls: ['./product.component.mobile.scss']
12 | })
13 | export class ProductComponentMobile extends ProductComponent {
14 |
15 | constructor(router: Router,
16 | sanitizer: DomSanitizer) {
17 | super(router,sanitizer);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/app/product/product.component.model.ts:
--------------------------------------------------------------------------------
1 | import {DomSanitizer, SafeResourceUrl} from '@angular/platform-browser';
2 |
3 | export class ProductComponentModel {
4 |
5 | public name: string;
6 | public description: string;
7 | public imageUrl: SafeResourceUrl;
8 |
9 | constructor(private sanitizer: DomSanitizer) {
10 | this.name = '';
11 | this.description = '';
12 | this.imageUrl = this.sanitizer.bypassSecurityTrustResourceUrl('');
13 | }
14 |
15 | public clone(): ProductComponentModel {
16 | let clonedModel: ProductComponentModel = new ProductComponentModel(this.sanitizer);
17 |
18 | clonedModel.name = this.name;
19 | clonedModel.description = this.description;
20 | clonedModel.imageUrl = this.imageUrl;
21 |
22 | return clonedModel;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/app/product/product.component.ts:
--------------------------------------------------------------------------------
1 | import {AfterViewInit, Component} from '@angular/core';
2 | import {ProductComponentModel} from './product.component.model';
3 | import {ActivatedRouteSnapshot, Router} from '@angular/router';
4 | import {DomSanitizer} from '@angular/platform-browser';
5 | import { ApplicationStateService } from '../application-state.service';
6 |
7 |
8 | export abstract class ProductComponent {
9 |
10 | private model: ProductComponentModel;
11 | public myViewModel: ProductComponentModel;
12 |
13 | constructor(private router: Router,
14 | private sanitizer: DomSanitizer) {
15 |
16 | this.model = new ProductComponentModel(sanitizer);
17 | this.myViewModel = new ProductComponentModel(sanitizer);
18 |
19 | this.loadProduct();
20 | this.updateView();
21 | }
22 |
23 | private getProductNameParam(): string {
24 | let routeSnapshot: ActivatedRouteSnapshot = this.router.routerState.snapshot.root;
25 | // the trip title in the last tree node so we will find it
26 | while (routeSnapshot.firstChild !== null) {
27 | routeSnapshot = routeSnapshot.firstChild;
28 | }
29 |
30 | let tripTitle: string = routeSnapshot.params['productName'];
31 |
32 | return tripTitle;
33 | }
34 |
35 |
36 | private updateView(): void {
37 | this.myViewModel = this.model.clone();
38 | }
39 |
40 | private loadProduct(): void {
41 | if (this.getProductNameParam() === 'funnel') {
42 | this.model.name = 'Funnel';
43 | this.model.description = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse metus sem, consectetur consequat hendrerit in, volutpat sit amet lectus. Nullam sed pretium dolor. Nam sit amet bibendum nibh, ac hendrerit elit. Curabitur scelerisque placerat suscipit. Fusce vitae interdum lacus. Aliquam et massa arcu. Duis eget aliquet erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut sagittis sed eros vehicula pulvinar. Nunc tempus nunc vitae magna sollicitudin tincidunt. Curabitur semper tincidunt lorem. Cras venenatis tincidunt arcu, id convallis urna aliquet quis. It putes the "fun" in funnel!';
44 | this.model.imageUrl = this.sanitizer.bypassSecurityTrustResourceUrl('../../../assets/images/product-1.jpg');
45 | } else {
46 | this.model.name = 'Chair';
47 | this.model.description = 'Suspendisse potenti. Mauris volutpat massa id erat congue, ac ornare nisl dictum. Duis tincidunt ipsum tellus, ac consectetur metus fringilla sed. Suspendisse massa eros, lobortis nec tortor id, sollicitudin auctor ipsum. Vestibulum laoreet, erat sed porttitor sagittis, ex velit pulvinar tellus, a rutrum elit lectus et nisl. Aenean pulvinar lacus tellus, ac finibus dui suscipit at. Nunc bibendum pharetra magna, et consequat urna vestibulum vitae. Vivamus in nulla pulvinar, tempor lorem vel, condimentum massa. Cras fermentum quam finibus dui fringilla, eget luctus sem vehicula. Fusce maximus massa sit amet diam tincidunt, vel feugiat quam venenatis. Cras bibendum turpis vel est ornare venenatis. Donec hendrerit rhoncus erat in vestibulum. Nam hendrerit, magna non imperdiet semper, nisi risus tincidunt ligula, ac euismod libero enim ut massa. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin placerat ante bibendum libero consequat pulvinar.';
48 | this.model.imageUrl = this.sanitizer.bypassSecurityTrustResourceUrl('../../../assets/images/product-2.jpg');
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.desktop.html:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
18 |
19 |
20 |
Account Info
21 |
22 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.desktop.scss:
--------------------------------------------------------------------------------
1 | @import '../../assets/styles/constants';
2 |
3 | :host {
4 | position: fixed;
5 | width: 100%;
6 | height: calc(100vh - 50px);
7 | top: 0;
8 | left: 0;
9 | margin-top: 56px;
10 | z-index: 1050;
11 | }
12 |
13 | .top-user-profile-wrapper {
14 | height: 450px;
15 | background: #fff;
16 | }
17 |
18 | .top-user-profile-overlay {
19 | height: calc(100vh - 500px);
20 | background: rgba(0, 0, 0, 0.5);
21 | }
22 |
23 | .user-profile-header-wrapper {
24 | border-bottom: 1px solid #ccc;
25 | }
26 |
27 | .user-profile-header {
28 | display: inline-block;
29 | width: 100%;
30 | min-height: 91px;
31 | padding: 30px 0;
32 | }
33 |
34 | .user-name {
35 | margin: 0;
36 | float: left;
37 | font-size: 24px;
38 | font-weight: 400;
39 | }
40 |
41 | .user-profile-content {
42 | padding-top: 40px;
43 |
44 | h2 {
45 | margin: 0 0 40px;
46 | color: #79c500;
47 | font-size: 18px;
48 | }
49 | }
50 |
51 | .form-group {
52 | display: inline-block;
53 | width: 100%;
54 | margin-bottom: 25px;
55 |
56 | input {
57 | padding-left: 10px;
58 | }
59 |
60 | .col-sm-9 {
61 | padding-left: 0;
62 | }
63 | }
64 |
65 | .control-label {
66 | margin-top: 5px;
67 | padding: 0;
68 | text-align: left;
69 | }
70 |
71 | .form-control {
72 | padding-left: 0;
73 | border: none;
74 | border-bottom: 1px solid #ccc;
75 | border-radius: 0;
76 | box-shadow: none;
77 | }
78 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.desktop.ts:
--------------------------------------------------------------------------------
1 | import { Component, EventEmitter, Output } from '@angular/core';
2 | import { ApplicationStateService } from '../application-state.service';
3 | import { UserProfileComponent } from './user-profile.component';
4 |
5 | @Component({
6 | selector: 'app-user-profile-desktop',
7 | templateUrl: './user-profile.component.desktop.html',
8 | styleUrls: ['./user-profile.component.desktop.scss']
9 | })
10 | export class UserProfileComponentDesktop extends UserProfileComponent {
11 |
12 | constructor(applicationStateService: ApplicationStateService) {
13 | super(applicationStateService);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.html:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.mobile.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 | Account info
11 |
12 |
13 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.mobile.scss:
--------------------------------------------------------------------------------
1 | @import '../../assets/styles/constants';
2 |
3 | :host {
4 | margin-top: 50px;
5 | background: #fff;
6 | }
7 |
8 | .user-profile-header {
9 | display: flex;
10 | align-items: center;
11 | width: 100%;
12 | height: 60px;
13 | background: #093e52;
14 | border-bottom: 1px solid #cdcdcd;
15 |
16 | h1 {
17 | flex: 1;
18 | margin: 0;
19 | padding: 0 4vw;
20 | font-size: 16px;
21 | font-weight: 500;
22 | color: #2cb9d7;
23 | }
24 |
25 | .close-user-profile-button {
26 | width: 40px;
27 | height: 60px;
28 | border: none;
29 | border-left: 1px solid rgba(220, 224, 224, 0.5);
30 | background: none;
31 |
32 | img {
33 | width: 15px;
34 | }
35 | }
36 | }
37 |
38 | section {
39 | padding: 50px 4vw;
40 |
41 | h2 {
42 | margin: 0 0 25px;
43 | font-size: 18px;
44 | font-weight: 500;
45 | color: #093e52;
46 | text-transform: capitalize;
47 | }
48 | }
49 |
50 | .account-info {
51 | border-bottom: 1px solid #dce0e0;
52 | }
53 |
54 | .form-group {
55 | display: flex;
56 | margin-bottom: 25px;
57 | font-size: 14px;
58 |
59 | &:last-child {
60 | margin-bottom: 0;
61 | }
62 |
63 | label {
64 | flex: 1;
65 | font-weight: 500;
66 | }
67 |
68 | input {
69 | flex: 2;
70 | border: none;
71 | box-shadow: none;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.mobile.ts:
--------------------------------------------------------------------------------
1 | import {Component, EventEmitter, Output} from '@angular/core';
2 | import { ApplicationStateService } from '../application-state.service';
3 | import { UserProfileComponent } from './user-profile.component';
4 |
5 | @Component({
6 | selector: 'app-user-profile-mobile',
7 | templateUrl: './user-profile.component.mobile.html',
8 | styleUrls: ['./user-profile.component.mobile.scss']
9 | })
10 | export class UserProfileComponentMobile extends UserProfileComponent {
11 |
12 | constructor(applicationStateService: ApplicationStateService) {
13 | super(applicationStateService);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/user-profile/user-profile.component.ts:
--------------------------------------------------------------------------------
1 | import {Component, EventEmitter, Output} from '@angular/core';
2 | import { ApplicationStateService } from '../application-state.service';
3 |
4 | @Component({
5 | selector: 'app-user-profile',
6 | templateUrl: './user-profile.component.html'
7 | })
8 | export class UserProfileComponent {
9 |
10 | public isMobileResolution: boolean;
11 |
12 | @Output() public closeUserProfileEvent: EventEmitter = new EventEmitter();
13 |
14 | constructor(private applicationStateService: ApplicationStateService) {
15 | this.isMobileResolution = this.applicationStateService.getIsMobileResolution();
16 | }
17 |
18 | public close(): void {
19 | this.closeUserProfileEvent.emit();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/images/front-page/banana-guard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/front-page/banana-guard.jpg
--------------------------------------------------------------------------------
/src/assets/images/front-page/hoverboard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/front-page/hoverboard.jpg
--------------------------------------------------------------------------------
/src/assets/images/front-page/ipad.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/front-page/ipad.jpg
--------------------------------------------------------------------------------
/src/assets/images/front-page/iphone.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/front-page/iphone.jpg
--------------------------------------------------------------------------------
/src/assets/images/front-page/xbox.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/front-page/xbox.jpg
--------------------------------------------------------------------------------
/src/assets/images/product-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/product-1.jpg
--------------------------------------------------------------------------------
/src/assets/images/product-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/assets/images/product-2.jpg
--------------------------------------------------------------------------------
/src/assets/styles/constants.scss:
--------------------------------------------------------------------------------
1 | $min-mobile-display-width: 320px;
2 | $max-mobile-display-width: 767px;
3 | $min-desktop-display-width: 768px;
4 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/yosigolan/angular-multi-view/7061eb8e727df79fdc7e61bb32bb33c5452b2092/src/favicon.ico
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Angular Multi View
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/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 | * BROWSER POLYFILLS
18 | */
19 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
20 | // import 'core-js/es6/symbol';
21 | // import 'core-js/es6/object';
22 | // import 'core-js/es6/function';
23 | // import 'core-js/es6/parse-int';
24 | // import 'core-js/es6/parse-float';
25 | // import 'core-js/es6/number';
26 | // import 'core-js/es6/math';
27 | // import 'core-js/es6/string';
28 | // import 'core-js/es6/date';
29 | // import 'core-js/es6/array';
30 | // import 'core-js/es6/regexp';
31 | // import 'core-js/es6/map';
32 | // import 'core-js/es6/weak-map';
33 | // import 'core-js/es6/set';
34 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
35 | // import 'classlist.js'; // Run `npm install --save classlist.js`.
36 | /** IE10 and IE11 requires the following for the Reflect API. */
37 | // import 'core-js/es6/reflect';
38 | /** Evergreen browsers require these. **/
39 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
40 | import 'core-js/es7/reflect';
41 | /**
42 | * Required to support Web Animations `@angular/platform-browser/animations`.
43 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
44 | **/
45 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
46 | /***************************************************************************************************
47 | * Zone JS is required by Angular itself.
48 | */
49 | import 'zone.js/dist/zone'; // Included with Angular CLI.
50 |
51 |
52 | /***************************************************************************************************
53 | * APPLICATION IMPORTS
54 | */
55 |
56 | /**
57 | * Date, currency, decimal and percent pipes.
58 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
59 | */
60 | // import 'intl'; // Run `npm install --save intl`.
61 | /**
62 | * Need to import at least one locale-data with intl.
63 | */
64 | // import 'intl/locale-data/jsonp/en';
65 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | @import "~bootstrap/scss/bootstrap";
2 | @import "~jquery.flipster/dist/jquery.flipster.min.css";
3 | @import '~@angular/material/prebuilt-themes/deeppurple-amber.css';
4 | @import './assets/styles/constants';
5 |
6 | a {
7 | transition: color 0.3s;
8 | }
9 |
10 | .nav-link {
11 | cursor: pointer;
12 | }
13 |
14 | img {
15 | max-width: 100%;
16 | }
17 |
--------------------------------------------------------------------------------
/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/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import {getTestBed} from '@angular/core/testing';
10 | import {BrowserDynamicTestingModule, platformBrowserDynamicTesting} from '@angular/platform-browser-dynamic/testing';
11 |
12 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
13 | declare const __karma__: any;
14 | declare const require: any;
15 |
16 | // Prevent Karma from running prematurely.
17 | __karma__.loaded = function () {
18 | };
19 |
20 | // First, initialize the Angular testing environment.
21 | getTestBed().initTestEnvironment(
22 | BrowserDynamicTestingModule,
23 | platformBrowserDynamicTesting()
24 | );
25 | // Then we find all the tests.
26 | const context = require.context('./', true, /\.spec\.ts$/);
27 | // And load the modules.
28 | context.keys().map(context);
29 | // Finally, start Karma to run the tests.
30 | __karma__.start();
31 |
--------------------------------------------------------------------------------
/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 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.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 |
4 | interface NodeModule {
5 | id: string;
6 | }
7 |
--------------------------------------------------------------------------------
/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 | }
19 | }
20 |
--------------------------------------------------------------------------------
/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 | "eofline": true,
15 | "forin": true,
16 | "import-blacklist": [
17 | true,
18 | "rxjs"
19 | ],
20 | "import-spacing": true,
21 | "indent": [
22 | true,
23 | "spaces"
24 | ],
25 | "interface-over-type-literal": true,
26 | "label-position": true,
27 | "max-line-length": [
28 | true,
29 | 140
30 | ],
31 | "member-access": true,
32 | "member-ordering": [
33 | true,
34 | "static-before-instance",
35 | "variables-before-functions"
36 | ],
37 | "no-arg": true,
38 | "no-bitwise": true,
39 | "no-console": [
40 | true,
41 | "debug",
42 | "info",
43 | "time",
44 | "timeEnd",
45 | "trace"
46 | ],
47 | "no-construct": true,
48 | "no-debugger": true,
49 | "no-empty": false,
50 | "no-empty-interface": true,
51 | "no-eval": true,
52 | "no-inferrable-types": false,
53 | "no-shadowed-variable": true,
54 | "no-string-literal": false,
55 | "no-string-throw": true,
56 | "no-switch-case-fall-through": true,
57 | "no-trailing-whitespace": true,
58 | "no-unused-expression": true,
59 | "no-use-before-declare": true,
60 | "no-var-keyword": true,
61 | "object-literal-sort-keys": false,
62 | "one-line": [
63 | true,
64 | "check-open-brace",
65 | "check-catch",
66 | "check-else",
67 | "check-whitespace"
68 | ],
69 | "prefer-const": false,
70 | "quotemark": [
71 | true,
72 | "single"
73 | ],
74 | "radix": false,
75 | "semicolon": [
76 | "always"
77 | ],
78 | "triple-equals": [
79 | true,
80 | "allow-null-check"
81 | ],
82 | "typedef": [
83 | true,
84 | "call-signature",
85 | "property-declaration"
86 | ],
87 | "typedef-whitespace": [
88 | true,
89 | {
90 | "call-signature": "nospace",
91 | "index-signature": "nospace",
92 | "parameter": "nospace",
93 | "property-declaration": "nospace",
94 | "variable-declaration": "nospace"
95 | }
96 | ],
97 | "typeof-compare": true,
98 | "unified-signatures": true,
99 | "variable-name": false,
100 | "whitespace": [
101 | true,
102 | "check-branch",
103 | "check-decl",
104 | "check-operator",
105 | "check-separator",
106 | "check-type"
107 | ],
108 | "directive-selector": [
109 | true,
110 | "attribute",
111 | "app",
112 | "camelCase"
113 | ],
114 | "component-selector": [
115 | true,
116 | "element",
117 | "app",
118 | "kebab-case"
119 | ],
120 | "use-input-property-decorator": true,
121 | "use-output-property-decorator": true,
122 | "use-host-property-decorator": true,
123 | "no-input-rename": true,
124 | "no-output-rename": true,
125 | "use-life-cycle-interface": true,
126 | "use-pipe-transform-interface": true,
127 | "component-class-suffix": true,
128 | "directive-class-suffix": true,
129 | "no-access-missing-member": true,
130 | "templates-use-public": true,
131 | "invoke-injectable": true,
132 | "newline-before-return": true
133 | }
134 | }
135 |
--------------------------------------------------------------------------------