├── .gitignore
├── LICENSE
├── README.md
├── angular.json
├── e2e
├── protractor.conf.js
├── src
│ ├── app.e2e-spec.ts
│ └── app.po.ts
└── tsconfig.e2e.json
├── ionic.config.json
├── package-lock.json
├── package.json
├── src
├── app
│ ├── app-routing.module.ts
│ ├── app.component.html
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── components
│ │ ├── components.module.ts
│ │ └── preload-image
│ │ │ ├── preload-image.component.html
│ │ │ ├── preload-image.component.scss
│ │ │ └── preload-image.component.ts
│ ├── home
│ │ ├── home.module.ts
│ │ ├── home.page.html
│ │ ├── home.page.scss
│ │ ├── home.page.spec.ts
│ │ └── home.page.ts
│ └── web-components
│ │ ├── app-initialize.ts
│ │ ├── coffee-icon
│ │ └── coffee-icon.component.ts
│ │ └── web-components.module.ts
├── assets
│ └── icon
│ │ └── favicon.png
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── global.scss
├── index.html
├── karma.conf.js
├── main.ts
├── polyfills.ts
├── test.ts
├── theme
│ └── variables.scss
├── tsconfig.app.json
└── tsconfig.spec.json
├── tsconfig.json
└── tslint.json
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specifies intentionally untracked files to ignore when using Git
2 | # http://git-scm.com/docs/gitignore
3 |
4 | *~
5 | *.sw[mnpcod]
6 | *.log
7 | *.tmp
8 | *.tmp.*
9 | log.txt
10 | *.sublime-project
11 | *.sublime-workspace
12 | .vscode/
13 | npm-debug.log*
14 |
15 | .idea/
16 | .ionic/
17 | .sourcemaps/
18 | .sass-cache/
19 | .tmp/
20 | .versions/
21 | coverage/
22 | www/
23 | node_modules/
24 | tmp/
25 | temp/
26 | platforms/
27 | plugins/
28 | plugins/android.json
29 | plugins/ios.json
30 | $RECYCLE.BIN/
31 |
32 | .DS_Store
33 | Thumbs.db
34 | UserInterfaceState.xcuserstate
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 IonicThemes
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 | # Web Components in Ionic Framework
2 | This project is an Ionic app showcasing Angular custom components and Stencil custom web components. This repo is part of the post: [Mastering Web Components in Ionic Framework](https://ionicthemes.com/tutorials/about/ionic-4-tutorial-mastering-web-components-in-ionic-4).
3 |
4 |
5 | ### Run the project
6 | Install the dependencies:
7 | `npm install`
8 |
9 | Run the project:
10 | `ionic serve`
11 |
12 | ### Demo
13 | 
14 |
15 |
16 | ### Web Components, Shadow DOM, CSS Variables. Understanding the new components architecture in Ionic.
17 | In our previous post [Ionic 4 vs Ionic 3 — What you need to know about Ionic 4](https://ionicthemes.com/tutorials/about/ionic-4-vs-ionic-3) we surfaced the main differences between Ionic 3 and Ionic 4.
18 |
19 | Ionic 4 introduced a lot of changes that you need understand because they changed the way Ionic Framework components work.
20 |
21 | Then, when Ionic 5 was released, we created the post [What's new in Ionic 5 - Migration and Free Starter](https://ionicthemes.com/tutorials/about/ionic5-tutorial-migration-and-starter) where we explain how to take advantage of the new benefits from Ionic 5.
22 |
23 | In this series of posts we are going to go deeper on the structure and core concepts of Ionic apps and explore more advanced topics, so if you are new to Ionic, I strongly recommend that you first read our [Introduction to Ionic tutorial](https://ionicthemes.com/tutorials/about/ionic-framework-introduction-and-key-components).
24 |
25 | Covering everything from styling and customizing Ionic components, to building custom web components with Stencil and then use them in your Ionic projects (or any other framework, as web components are framework agnostic!).
26 |
27 | ### Get a premium Ionic 5 Starter App
28 | Did you know that we recently released [Ionic 5 Full Starter App](https://ionicthemes.com/product/ionic5-full-starter-app-pro-version)? It's an ionic 5 template that you can use to jump start your Ionic app development and save yourself hundreds of hours of design and development.
29 |
30 | It is also a PWA and uses Ionic Web Components.
31 | Try it on your phone to see how it works!
32 |
33 |
34 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular-devkit/core/src/workspace/workspace-schema.json",
3 | "version": 1,
4 | "defaultProject": "app",
5 | "projects": {
6 | "app": {
7 | "root": "",
8 | "sourceRoot": "src",
9 | "projectType": "application",
10 | "prefix": "app",
11 | "schematics": {},
12 | "architect": {
13 | "build": {
14 | "builder": "@angular-devkit/build-angular:browser",
15 | "options": {
16 | "progress": false,
17 | "outputPath": "www",
18 | "index": "src/index.html",
19 | "main": "src/main.ts",
20 | "polyfills": "src/polyfills.ts",
21 | "tsConfig": "src/tsconfig.app.json",
22 | "assets": [
23 | {
24 | "glob": "**/*",
25 | "input": "src/assets",
26 | "output": "assets"
27 | },
28 | {
29 | "glob": "**/*.svg",
30 | "input": "node_modules/@ionic/angular/dist/ionic/svg",
31 | "output": "./svg"
32 | }
33 | ],
34 | "styles": [
35 | {
36 | "input": "src/theme/variables.scss"
37 | },
38 | {
39 | "input": "src/global.scss"
40 | }
41 | ],
42 | "scripts": []
43 | },
44 | "configurations": {
45 | "production": {
46 | "fileReplacements": [
47 | {
48 | "replace": "src/environments/environment.ts",
49 | "with": "src/environments/environment.prod.ts"
50 | }
51 | ],
52 | "optimization": true,
53 | "outputHashing": "all",
54 | "sourceMap": false,
55 | "extractCss": true,
56 | "namedChunks": false,
57 | "aot": true,
58 | "extractLicenses": true,
59 | "vendorChunk": false,
60 | "buildOptimizer": true
61 | }
62 | }
63 | },
64 | "serve": {
65 | "builder": "@angular-devkit/build-angular:dev-server",
66 | "options": {
67 | "browserTarget": "app:build"
68 | },
69 | "configurations": {
70 | "production": {
71 | "browserTarget": "app:build:production"
72 | }
73 | }
74 | },
75 | "extract-i18n": {
76 | "builder": "@angular-devkit/build-angular:extract-i18n",
77 | "options": {
78 | "browserTarget": "app:build"
79 | }
80 | },
81 | "test": {
82 | "builder": "@angular-devkit/build-angular:karma",
83 | "options": {
84 | "main": "src/test.ts",
85 | "polyfills": "src/polyfills.ts",
86 | "tsConfig": "src/tsconfig.spec.json",
87 | "karmaConfig": "src/karma.conf.js",
88 | "styles": [
89 | "styles.css"
90 | ],
91 | "scripts": [],
92 | "assets": [
93 | {
94 | "glob": "favicon.ico",
95 | "input": "src/",
96 | "output": "/"
97 | },
98 | {
99 | "glob": "**/*",
100 | "input": "src/assets",
101 | "output": "/assets"
102 | }
103 | ]
104 | }
105 | },
106 | "lint": {
107 | "builder": "@angular-devkit/build-angular:tslint",
108 | "options": {
109 | "tsConfig": [
110 | "src/tsconfig.app.json",
111 | "src/tsconfig.spec.json"
112 | ],
113 | "exclude": [
114 | "**/node_modules/**"
115 | ]
116 | }
117 | },
118 | "ionic-cordova-build": {
119 | "builder": "@ionic/ng-toolkit:cordova-build",
120 | "options": {
121 | "browserTarget": "app:build"
122 | },
123 | "configurations": {
124 | "production": {
125 | "browserTarget": "app:build:production"
126 | }
127 | }
128 | },
129 | "ionic-cordova-serve": {
130 | "builder": "@ionic/ng-toolkit:cordova-serve",
131 | "options": {
132 | "cordovaBuildTarget": "app:ionic-cordova-build",
133 | "devServerTarget": "app:serve"
134 | },
135 | "configurations": {
136 | "production": {
137 | "cordovaBuildTarget": "app:ionic-cordova-build:production",
138 | "devServerTarget": "app:serve:production"
139 | }
140 | }
141 | }
142 | }
143 | },
144 | "app-e2e": {
145 | "root": "e2e/",
146 | "projectType": "application",
147 | "architect": {
148 | "e2e": {
149 | "builder": "@angular-devkit/build-angular:protractor",
150 | "options": {
151 | "protractorConfig": "e2e/protractor.conf.js",
152 | "devServerTarget": "app:serve"
153 | }
154 | },
155 | "lint": {
156 | "builder": "@angular-devkit/build-angular:tslint",
157 | "options": {
158 | "tsConfig": "e2e/tsconfig.e2e.json",
159 | "exclude": [
160 | "**/node_modules/**"
161 | ]
162 | }
163 | }
164 | }
165 | }
166 | },
167 | "cli": {
168 | "defaultCollection": "@ionic/schematics-angular"
169 | },
170 | "schematics": {
171 | "@ionic/schematics-angular:component": {
172 | "styleext": "scss"
173 | },
174 | "@ionic/schematics-angular:page": {
175 | "styleext": "scss"
176 | }
177 | }
178 | }
179 |
--------------------------------------------------------------------------------
/e2e/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 | './src/**/*.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 |
--------------------------------------------------------------------------------
/e2e/src/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('new 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()).toContain('The world is your oyster.');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/e2e/src/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.deepCss('app-root ion-content')).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 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ionic.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic-4-custom-components",
3 | "integrations": {},
4 | "type": "angular"
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ionic-4-custom-components",
3 | "version": "0.0.1",
4 | "author": "https://ionicthemes.com",
5 | "contributors": [
6 | "Agustín "
7 | ],
8 | "homepage": "https://ionicthemes.com",
9 | "scripts": {
10 | "ng": "ng",
11 | "start": "ng serve",
12 | "build": "ng build",
13 | "test": "ng test",
14 | "lint": "ng lint",
15 | "e2e": "ng e2e"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/common": "~6.1.1",
20 | "@angular/core": "~6.1.1",
21 | "@angular/forms": "~6.1.1",
22 | "@angular/http": "~6.1.1",
23 | "@angular/platform-browser": "~6.1.1",
24 | "@angular/platform-browser-dynamic": "~6.1.1",
25 | "@angular/router": "~6.1.1",
26 | "@ionic-native/core": "5.0.0-beta.15",
27 | "@ionic-native/splash-screen": "5.0.0-beta.15",
28 | "@ionic-native/status-bar": "5.0.0-beta.15",
29 | "@ionic/angular": "4.0.0-beta.5",
30 | "c-coffee-icon": "0.0.2",
31 | "core-js": "^2.5.3",
32 | "rxjs": "6.2.2",
33 | "zone.js": "^0.8.26"
34 | },
35 | "devDependencies": {
36 | "@angular/cli": "~6.1.1",
37 | "@angular/compiler": "~6.1.1",
38 | "@angular/compiler-cli": "~6.1.1",
39 | "@angular/language-service": "~6.1.1",
40 | "@angular-devkit/architect": "~0.7.2",
41 | "@angular-devkit/build-angular": "~0.7.2",
42 | "@angular-devkit/core": "~0.7.2",
43 | "@angular-devkit/schematics": "~0.7.2",
44 | "@ionic/ng-toolkit": "^1.0.0",
45 | "@ionic/schematics-angular": "^1.0.0",
46 | "@types/jasmine": "~2.8.6",
47 | "@types/jasminewd2": "~2.0.3",
48 | "@types/node": "~10.9.2",
49 | "codelyzer": "~4.4.2",
50 | "jasmine-core": "~2.99.1",
51 | "jasmine-spec-reporter": "~4.2.1",
52 | "karma": "~3.0.0",
53 | "karma-chrome-launcher": "~2.2.0",
54 | "karma-coverage-istanbul-reporter": "~2.0.0",
55 | "karma-jasmine": "~1.1.1",
56 | "karma-jasmine-html-reporter": "^0.2.2",
57 | "protractor": "~5.4.0",
58 | "ts-node": "~7.0.0",
59 | "tslint": "~5.11.0",
60 | "typescript": "~2.9.2"
61 | },
62 | "description": "An Ionic project showcasing Angular custom components and Stencil custom web components"
63 | }
64 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | const routes: Routes = [
5 | { path: '', redirectTo: 'home', pathMatch: 'full' },
6 | { path: 'home', loadChildren: './home/home.module#HomePageModule' },
7 | ];
8 |
9 | @NgModule({
10 | imports: [RouterModule.forRoot(routes)],
11 | exports: [RouterModule]
12 | })
13 | export class AppRoutingModule { }
14 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { TestBed, async } from '@angular/core/testing';
3 |
4 | import { Platform } from '@ionic/angular';
5 | import { SplashScreen } from '@ionic-native/splash-screen/ngx';
6 | import { StatusBar } from '@ionic-native/status-bar/ngx';
7 |
8 | import { AppComponent } from './app.component';
9 |
10 | describe('AppComponent', () => {
11 |
12 | let statusBarSpy, splashScreenSpy, platformReadySpy, platformSpy;
13 |
14 | beforeEach(async(() => {
15 | statusBarSpy = jasmine.createSpyObj('StatusBar', ['styleDefault']);
16 | splashScreenSpy = jasmine.createSpyObj('SplashScreen', ['hide']);
17 | platformReadySpy = Promise.resolve();
18 | platformSpy = jasmine.createSpyObj('Platform', { ready: platformReadySpy });
19 |
20 | TestBed.configureTestingModule({
21 | declarations: [AppComponent],
22 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
23 | providers: [
24 | { provide: StatusBar, useValue: statusBarSpy },
25 | { provide: SplashScreen, useValue: splashScreenSpy },
26 | { provide: Platform, useValue: platformSpy },
27 | ],
28 | }).compileComponents();
29 | }));
30 |
31 | it('should create the app', () => {
32 | const fixture = TestBed.createComponent(AppComponent);
33 | const app = fixture.debugElement.componentInstance;
34 | expect(app).toBeTruthy();
35 | });
36 |
37 | it('should initialize the app', async () => {
38 | TestBed.createComponent(AppComponent);
39 | expect(platformSpy.ready).toHaveBeenCalled();
40 | await platformReadySpy;
41 | expect(statusBarSpy.styleDefault).toHaveBeenCalled();
42 | expect(splashScreenSpy.hide).toHaveBeenCalled();
43 | });
44 |
45 | // TODO: add more tests!
46 |
47 | });
48 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | import { Platform } from '@ionic/angular';
4 | import { SplashScreen } from '@ionic-native/splash-screen/ngx';
5 | import { StatusBar } from '@ionic-native/status-bar/ngx';
6 |
7 | @Component({
8 | selector: 'app-root',
9 | templateUrl: 'app.component.html'
10 | })
11 | export class AppComponent {
12 | constructor(
13 | private platform: Platform,
14 | private splashScreen: SplashScreen,
15 | private statusBar: StatusBar
16 | ) {
17 | this.initializeApp();
18 | }
19 |
20 | initializeApp() {
21 | this.platform.ready().then(() => {
22 | this.statusBar.styleDefault();
23 | this.splashScreen.hide();
24 | });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { RouterModule, RouteReuseStrategy, Routes } from '@angular/router';
4 |
5 | import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
6 | import { SplashScreen } from '@ionic-native/splash-screen/ngx';
7 | import { StatusBar } from '@ionic-native/status-bar/ngx';
8 |
9 | import { WebComponentsModule } from './web-components/web-components.module';
10 |
11 | import { AppComponent } from './app.component';
12 | import { AppRoutingModule } from './app-routing.module';
13 |
14 | @NgModule({
15 | declarations: [AppComponent],
16 | entryComponents: [],
17 | imports: [
18 | BrowserModule,
19 | IonicModule.forRoot(),
20 | WebComponentsModule.forRoot(),
21 | AppRoutingModule
22 | ],
23 | providers: [
24 | StatusBar,
25 | SplashScreen,
26 | { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
27 | ],
28 | bootstrap: [AppComponent]
29 | })
30 | export class AppModule {}
31 |
--------------------------------------------------------------------------------
/src/app/components/components.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { IonicModule } from '@ionic/angular';
4 |
5 | import { PreloadImageComponent } from './preload-image/preload-image.component';
6 |
7 | @NgModule({
8 | imports: [
9 | CommonModule,
10 | IonicModule.forRoot(),
11 | ],
12 | declarations: [
13 | PreloadImageComponent
14 | ],
15 | exports: [
16 | PreloadImageComponent
17 | ],
18 | entryComponents: [],
19 | })
20 | export class ComponentsModule {}
21 |
--------------------------------------------------------------------------------
/src/app/components/preload-image/preload-image.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/src/app/components/preload-image/preload-image.component.scss:
--------------------------------------------------------------------------------
1 | $white: #FFFFFF;
2 | $grey: #9e9e9e;
3 |
4 | $preload-image-bg: rgba(darken($white, 10), .25);
5 |
6 | $spinner-size: 28px;
7 | $spinner-color: $grey;
8 |
9 | preload-image {
10 | display: block;
11 | background-color: $preload-image-bg;
12 | overflow: hidden;
13 | position: relative;
14 | width: 100%;
15 |
16 | .spinner {
17 | position: absolute;
18 | top: calc(50% - #{ ($spinner-size/2) });
19 | left: calc(50% - #{ ($spinner-size/2) });
20 | width: $spinner-size;
21 | height: $spinner-size;
22 | font-size: $spinner-size;
23 | line-height: $spinner-size;
24 | color: $spinner-color;
25 | }
26 |
27 | ion-img {
28 | position: absolute;
29 | top: 0;
30 | left: 0;
31 | transition: visibility 0s linear, opacity .5s linear;
32 | opacity: 0;
33 | visibility: hidden;
34 | width: 100%;
35 | height: 100%;
36 | }
37 |
38 | &.img-loaded {
39 | background-color: transparent;
40 | border: 0;
41 |
42 | ion-img {
43 | opacity: 1;
44 | visibility: visible;
45 | }
46 |
47 | .spinner {
48 | display: none;
49 | visibility: hidden;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/app/components/preload-image/preload-image.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ElementRef, Renderer2, ViewEncapsulation, ViewChild, OnChanges, PLATFORM_ID, Inject } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'preload-image',
5 | templateUrl: './preload-image.component.html',
6 | styleUrls: [
7 | './preload-image.component.scss'
8 | ],
9 | encapsulation: ViewEncapsulation.None
10 | })
11 | export class PreloadImageComponent implements OnChanges {
12 | _src = '';
13 | _ratio: { w: number, h: number };
14 |
15 | constructor(
16 | private _elementRef: ElementRef,
17 | private _renderer: Renderer2
18 | ) {}
19 |
20 | @Input() set src(val: string) {
21 | this._src = (val !== undefined && val !== null) ? val : '';
22 | }
23 |
24 | @Input() set ratio(ratio: { w: number, h: number }) {
25 | this._ratio = ratio || {w: 1, h: 1};
26 | }
27 |
28 | ngOnChanges() {
29 | const ratio_height = (this._ratio.h / this._ratio.w * 100) + '%';
30 | // Conserve aspect ratio (see: http://stackoverflow.com/a/10441480/1116959)
31 | this._renderer.setStyle(this._elementRef.nativeElement, 'padding', '0px 0px ' + ratio_height + ' 0px');
32 | this._update();
33 | }
34 |
35 | _update() {
36 | this._loaded(false);
37 | }
38 |
39 | _loaded(isLoaded: boolean) {
40 | if (isLoaded) {
41 | setTimeout(() => {
42 | this._renderer.addClass(this._elementRef.nativeElement, 'img-loaded');
43 | }, 500);
44 | } else {
45 | this._renderer.removeClass(this._elementRef.nativeElement, 'img-loaded');
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/app/home/home.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 | import { IonicModule } from '@ionic/angular';
4 | import { FormsModule } from '@angular/forms';
5 | import { RouterModule } from '@angular/router';
6 |
7 | import { ComponentsModule } from '../components/components.module';
8 | import { WebComponentsModule } from '../web-components/web-components.module';
9 |
10 | import { HomePage } from './home.page';
11 |
12 | @NgModule({
13 | imports: [
14 | CommonModule,
15 | FormsModule,
16 | IonicModule,
17 | RouterModule.forChild([
18 | {
19 | path: '',
20 | component: HomePage
21 | }
22 | ]),
23 | ComponentsModule,
24 | WebComponentsModule
25 | ],
26 | declarations: [HomePage],
27 | schemas: [CUSTOM_ELEMENTS_SCHEMA]
28 | })
29 | export class HomePageModule {}
30 |
--------------------------------------------------------------------------------
/src/app/home/home.page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Ionic Custom Components
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/src/app/home/home.page.scss:
--------------------------------------------------------------------------------
1 | c-coffee-icon {
2 | --handle-color: #1f2bac;
3 | --cup-color: #2f3fff;
4 | --smoke-color: #a5acbd;
5 |
6 | margin: 20px;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/home/home.page.spec.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2 | import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3 |
4 | import { HomePage } from './home.page';
5 |
6 | describe('HomePage', () => {
7 | let component: HomePage;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(async(() => {
11 | TestBed.configureTestingModule({
12 | declarations: [ HomePage ],
13 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
14 | })
15 | .compileComponents();
16 | }));
17 |
18 | beforeEach(() => {
19 | fixture = TestBed.createComponent(HomePage);
20 | component = fixture.componentInstance;
21 | fixture.detectChanges();
22 | });
23 |
24 | it('should create', () => {
25 | expect(component).toBeTruthy();
26 | });
27 | });
28 |
--------------------------------------------------------------------------------
/src/app/home/home.page.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-home',
5 | templateUrl: 'home.page.html',
6 | styleUrls: ['home.page.scss'],
7 | })
8 | export class HomePage {
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/app/web-components/app-initialize.ts:
--------------------------------------------------------------------------------
1 | import { defineCustomElements } from 'c-coffee-icon';
2 |
3 | export function appInitialize() {
4 | return () => {
5 | const win = window as any;
6 | if (typeof win !== 'undefined') {
7 | // Define all of our custom elements
8 | defineCustomElements(win);
9 | }
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/web-components/coffee-icon/coffee-icon.component.ts:
--------------------------------------------------------------------------------
1 | import 'c-coffee-icon';
2 |
3 | import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/core';
4 |
5 | @Component({
6 | selector: 'c-coffee-icon',
7 | changeDetection: ChangeDetectionStrategy.OnPush,
8 | encapsulation: ViewEncapsulation.None,
9 | template: ''
10 | })
11 | export class CoffeeIconComponent {
12 | }
13 |
--------------------------------------------------------------------------------
/src/app/web-components/web-components.module.ts:
--------------------------------------------------------------------------------
1 | import { APP_INITIALIZER, ModuleWithProviders, CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
2 | import { CommonModule } from '@angular/common';
3 |
4 | import { appInitialize } from './app-initialize';
5 |
6 | import { CoffeeIconComponent } from './coffee-icon/coffee-icon.component';
7 |
8 | @NgModule({
9 | imports: [
10 | CommonModule
11 | ],
12 | declarations: [
13 | CoffeeIconComponent
14 | ],
15 | exports: [
16 | CoffeeIconComponent
17 | ],
18 | entryComponents: [],
19 | schemas: [CUSTOM_ELEMENTS_SCHEMA]
20 | })
21 | export class WebComponentsModule {
22 | static forRoot(): ModuleWithProviders {
23 | return {
24 | ngModule: WebComponentsModule,
25 | providers: [
26 | {
27 | provide: APP_INITIALIZER,
28 | useFactory: appInitialize,
29 | multi: true
30 | }
31 | ]
32 | };
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/assets/icon/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ionicthemes/ionic-custom-components/7b6f9d660200312be6a345203b327c65cb2fc263/src/assets/icon/favicon.png
--------------------------------------------------------------------------------
/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 | export const environment = {
6 | production: false
7 | };
8 |
9 | /*
10 | * In development mode, to ignore zone related error stack frames such as
11 | * `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
12 | * import the following file, but please comment it out in production mode
13 | * because it will have performance impact when throw error
14 | */
15 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
16 |
--------------------------------------------------------------------------------
/src/global.scss:
--------------------------------------------------------------------------------
1 | // http://ionicframework.com/docs/theming/
2 | @import "~@ionic/angular/css/normalize.css";
3 | @import "~@ionic/angular/css/structure.css";
4 | @import "~@ionic/angular/css/typography.css";
5 | @import "~@ionic/angular/css/colors.css";
6 |
7 | @import "~@ionic/angular/css/padding.css";
8 | @import "~@ionic/angular/css/float-elements.css";
9 | @import "~@ionic/angular/css/text-alignment.css";
10 | @import "~@ionic/angular/css/text-transformation.css";
11 | @import "~@ionic/angular/css/flex-utils.css";
12 |
13 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ionic App
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/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'),
20 | reports: ['html', 'lcovonly'],
21 | fixWebpackSourcePaths: true
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 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule)
12 | .catch(err => console.log(err));
13 |
--------------------------------------------------------------------------------
/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 |
44 | /** Evergreen browsers require these. **/
45 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
46 | import 'core-js/es7/reflect';
47 |
48 |
49 | /**
50 | * Required to support Web Animations `@angular/platform-browser/animations`.
51 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
52 | **/
53 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
54 |
55 |
56 |
57 | /***************************************************************************************************
58 | * Zone JS is required by Angular itself.
59 | */
60 | import 'zone.js/dist/zone'; // Included with Angular CLI.
61 |
62 |
63 |
64 | /***************************************************************************************************
65 | * APPLICATION IMPORTS
66 | */
67 |
68 | /**
69 | * Date, currency, decimal and percent pipes.
70 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
71 | */
72 | // import 'intl'; // Run `npm install --save intl`.
73 | /**
74 | * Need to import at least one locale-data with intl.
75 | */
76 | // import 'intl/locale-data/jsonp/en';
77 |
--------------------------------------------------------------------------------
/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/theme/variables.scss:
--------------------------------------------------------------------------------
1 | // Ionic Variables and Theming. For more info, please see:
2 | // http://ionicframework.com/docs/theming/
3 |
4 | /** Ionic CSS Variables **/
5 | :root {
6 | /** primary **/
7 | --ion-color-primary: #488aff;
8 | --ion-color-primary-rgb: 72,138,255;
9 | --ion-color-primary-contrast: #fff;
10 | --ion-color-primary-contrast-rgb: 255,255,255;
11 | --ion-color-primary-shade: #3f79e0;
12 | --ion-color-primary-tint: #5a96ff;
13 |
14 | /** secondary **/
15 | --ion-color-secondary: #32db64;
16 | --ion-color-secondary-rgb: 50,219,100;
17 | --ion-color-secondary-contrast: #fff;
18 | --ion-color-secondary-contrast-rgb: 255,255,255;
19 | --ion-color-secondary-shade: #2cc158;
20 | --ion-color-secondary-tint: #47df74;
21 |
22 | /** tertiary **/
23 | --ion-color-tertiary: #f4a942;
24 | --ion-color-tertiary-rgb: 244,169,66;
25 | --ion-color-tertiary-contrast: #fff;
26 | --ion-color-tertiary-contrast-rgb: 255,255,255;
27 | --ion-color-tertiary-shade: #d7953a;
28 | --ion-color-tertiary-tint: #f5b255;
29 |
30 | /** success **/
31 | --ion-color-success: #10dc60;
32 | --ion-color-success-rgb: 16,220,96;
33 | --ion-color-success-contrast: #fff;
34 | --ion-color-success-contrast-rgb: 255,255,255;
35 | --ion-color-success-shade: #0ec254;
36 | --ion-color-success-tint: #28e070;
37 |
38 | /** warning **/
39 | --ion-color-warning: #ffce00;
40 | --ion-color-warning-rgb: 255,206,0;
41 | --ion-color-warning-contrast: #000;
42 | --ion-color-warning-contrast-rgb: 0,0,0;
43 | --ion-color-warning-shade: #e0b500;
44 | --ion-color-warning-tint: #ffd31a;
45 |
46 | /** danger **/
47 | --ion-color-danger: #f53d3d;
48 | --ion-color-danger-rgb: 245,61,61;
49 | --ion-color-danger-contrast: #fff;
50 | --ion-color-danger-contrast-rgb: 255,255,255;
51 | --ion-color-danger-shade: #d83636;
52 | --ion-color-danger-tint: #f65050;
53 |
54 | /** light **/
55 | --ion-color-light: #f4f4f4;
56 | --ion-color-light-rgb: 244,244,244;
57 | --ion-color-light-contrast: #000;
58 | --ion-color-light-contrast-rgb: 0,0,0;
59 | --ion-color-light-shade: #d7d7d7;
60 | --ion-color-light-tint: #f5f5f5;
61 |
62 | /** medium **/
63 | --ion-color-medium: #989aa2;
64 | --ion-color-medium-rgb: 152,154,162;
65 | --ion-color-medium-contrast: #000;
66 | --ion-color-medium-contrast-rgb: 0,0,0;
67 | --ion-color-medium-shade: #86888f;
68 | --ion-color-medium-tint: #a2a4ab;
69 |
70 | /** dark **/
71 | --ion-color-dark: #222;
72 | --ion-color-dark-rgb: 34,34,34;
73 | --ion-color-dark-contrast: #fff;
74 | --ion-color-dark-contrast-rgb: 255,255,255;
75 | --ion-color-dark-shade: #1e1e1e;
76 | --ion-color-dark-tint: #383838;
77 | }
--------------------------------------------------------------------------------
/src/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/app",
5 | "baseUrl": "./",
6 | "module": "es2015"
7 | },
8 | "exclude": [
9 | "test.ts",
10 | "**/*.spec.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/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 | ],
15 | "include": [
16 | "polyfills.ts",
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/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 | "lib": [
12 | "es2017",
13 | "dom"
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/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-spacing": true,
20 | "indent": [
21 | true,
22 | "spaces"
23 | ],
24 | "interface-over-type-literal": true,
25 | "label-position": true,
26 | "max-line-length": [
27 | true,
28 | 140
29 | ],
30 | "member-access": false,
31 | "member-ordering": [
32 | true,
33 | {
34 | "order": [
35 | "static-field",
36 | "instance-field",
37 | "static-method",
38 | "instance-method"
39 | ]
40 | }
41 | ],
42 | "no-arg": true,
43 | "no-bitwise": true,
44 | "no-console": [
45 | true,
46 | "debug",
47 | "info",
48 | "time",
49 | "timeEnd",
50 | "trace"
51 | ],
52 | "no-construct": true,
53 | "no-debugger": true,
54 | "no-duplicate-super": true,
55 | "no-empty": false,
56 | "no-empty-interface": true,
57 | "no-eval": true,
58 | "no-inferrable-types": [
59 | true,
60 | "ignore-params"
61 | ],
62 | "no-misused-new": true,
63 | "no-non-null-assertion": true,
64 | "no-shadowed-variable": true,
65 | "no-string-literal": false,
66 | "no-string-throw": true,
67 | "no-switch-case-fall-through": true,
68 | "no-trailing-whitespace": true,
69 | "no-unnecessary-initializer": true,
70 | "no-unused-expression": true,
71 | "no-use-before-declare": true,
72 | "no-var-keyword": true,
73 | "object-literal-sort-keys": false,
74 | "one-line": [
75 | true,
76 | "check-open-brace",
77 | "check-catch",
78 | "check-else",
79 | "check-whitespace"
80 | ],
81 | "prefer-const": true,
82 | "quotemark": [
83 | true,
84 | "single"
85 | ],
86 | "radix": true,
87 | "semicolon": [
88 | true,
89 | "always"
90 | ],
91 | "triple-equals": [
92 | true,
93 | "allow-null-check"
94 | ],
95 | "typedef-whitespace": [
96 | true,
97 | {
98 | "call-signature": "nospace",
99 | "index-signature": "nospace",
100 | "parameter": "nospace",
101 | "property-declaration": "nospace",
102 | "variable-declaration": "nospace"
103 | }
104 | ],
105 | "unified-signatures": true,
106 | "variable-name": false,
107 | "whitespace": [
108 | true,
109 | "check-branch",
110 | "check-decl",
111 | "check-operator",
112 | "check-separator",
113 | "check-type"
114 | ],
115 | "directive-selector": [
116 | true,
117 | "attribute",
118 | "app",
119 | "camelCase"
120 | ],
121 | "component-selector": [
122 | true,
123 | "element",
124 | "app",
125 | "page",
126 | "kebab-case"
127 | ],
128 | "no-output-on-prefix": true,
129 | "use-input-property-decorator": true,
130 | "use-output-property-decorator": true,
131 | "use-host-property-decorator": true,
132 | "no-input-rename": true,
133 | "no-output-rename": true,
134 | "use-life-cycle-interface": true,
135 | "use-pipe-transform-interface": true,
136 | "directive-class-suffix": true
137 | }
138 | }
139 |
--------------------------------------------------------------------------------