├── apps
├── .gitkeep
├── main-app
│ ├── src
│ │ ├── assets
│ │ │ └── .gitkeep
│ │ ├── app
│ │ │ ├── pages
│ │ │ │ └── simple-page
│ │ │ │ │ ├── simple-page.component.scss
│ │ │ │ │ ├── simple-page.component.html
│ │ │ │ │ └── simple-page.component.ts
│ │ │ ├── web-component-host
│ │ │ │ ├── web-component-host.component.scss
│ │ │ │ ├── web-component-host.component.html
│ │ │ │ ├── web-component-host.module.ts
│ │ │ │ └── web-component-host.component.ts
│ │ │ ├── main-app.component.ts
│ │ │ ├── main-app.component.scss
│ │ │ ├── main-app.component.html
│ │ │ └── app.module.ts
│ │ ├── styles.scss
│ │ ├── favicon.ico
│ │ ├── environments
│ │ │ ├── environment.prod.ts
│ │ │ └── environment.ts
│ │ ├── test-setup.ts
│ │ ├── index.html
│ │ ├── main.ts
│ │ └── polyfills.ts
│ ├── tsconfig.editor.json
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.json
│ ├── jest.config.js
│ ├── .browserslistrc
│ └── .eslintrc.json
└── wc-app
│ ├── src
│ ├── assets
│ │ └── .gitkeep
│ ├── app
│ │ ├── pages
│ │ │ ├── first
│ │ │ │ ├── first.component.scss
│ │ │ │ ├── first.component.html
│ │ │ │ ├── first.component.ts
│ │ │ │ └── first.component.spec.ts
│ │ │ ├── home
│ │ │ │ ├── home.component.scss
│ │ │ │ ├── home.component.html
│ │ │ │ ├── home.component.ts
│ │ │ │ └── home.component.spec.ts
│ │ │ └── second
│ │ │ │ ├── second.component.scss
│ │ │ │ ├── second.component.html
│ │ │ │ ├── second.component.ts
│ │ │ │ └── second.component.spec.ts
│ │ ├── components
│ │ │ └── links
│ │ │ │ ├── links.component.scss
│ │ │ │ ├── links.component.html
│ │ │ │ ├── links.component.ts
│ │ │ │ └── links.component.spec.ts
│ │ ├── wc-app.component.scss
│ │ ├── wc-app.component.html
│ │ ├── app-routing.module.ts
│ │ ├── app.module.ts
│ │ ├── wc-app.component.ts
│ │ └── utils
│ │ │ ├── noop-location.strategy.ts
│ │ │ └── multi-location.strategy.ts
│ ├── test-setup.ts
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── styles.scss
│ ├── favicon.ico
│ ├── index.html
│ ├── main.ts
│ └── polyfills.ts
│ ├── tsconfig.editor.json
│ ├── tsconfig.app.json
│ ├── tsconfig.spec.json
│ ├── tsconfig.json
│ ├── .browserslistrc
│ ├── jest.config.js
│ └── .eslintrc.json
├── libs
└── .gitkeep
├── tools
├── generators
│ └── .gitkeep
└── tsconfig.tools.json
├── .prettierrc
├── .prettierignore
├── jest.preset.js
├── jest.config.js
├── .vscode
└── extensions.json
├── .editorconfig
├── tsconfig.base.json
├── .gitignore
├── .eslintrc.json
├── nx.json
├── README.md
├── decorate-angular-cli.js
├── package.json
└── angular.json
/apps/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/libs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tools/generators/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/main-app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/wc-app/src/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true
3 | }
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/first/first.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/home/home.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/components/links/links.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/second/second.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/wc-app/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular';
2 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/pages/simple-page/simple-page.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/home/home.component.html:
--------------------------------------------------------------------------------
1 |
Home Page
2 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/first/first.component.html:
--------------------------------------------------------------------------------
1 | First Page
2 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/second/second.component.html:
--------------------------------------------------------------------------------
1 | Second Page
2 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/pages/simple-page/simple-page.component.html:
--------------------------------------------------------------------------------
1 | Simple Page
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | # Add files here to ignore them from prettier formatting
2 |
3 | /dist
4 | /coverage
5 |
--------------------------------------------------------------------------------
/jest.preset.js:
--------------------------------------------------------------------------------
1 | const nxPreset = require('@nrwl/jest/preset');
2 |
3 | module.exports = { ...nxPreset };
4 |
--------------------------------------------------------------------------------
/apps/main-app/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/apps/wc-app/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/apps/wc-app/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 |
--------------------------------------------------------------------------------
/apps/wc-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcellkiss/angular-web-component-example/HEAD/apps/wc-app/src/favicon.ico
--------------------------------------------------------------------------------
/apps/main-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marcellkiss/angular-web-component-example/HEAD/apps/main-app/src/favicon.ico
--------------------------------------------------------------------------------
/apps/main-app/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | wcAppScriptPath: '/wc-app/main.js',
4 | };
5 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | const { getJestProjects } = require('@nrwl/jest');
2 |
3 | module.exports = { projects: [...getJestProjects(), '/apps/wc-app'] };
4 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/web-component-host/web-component-host.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | button {
3 | height: 30px;
4 | padding: 0 10px;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/apps/main-app/tsconfig.editor.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["**/*.ts"],
4 | "compilerOptions": {
5 | "types": ["jest", "node"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/wc-app/tsconfig.editor.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["**/*.ts"],
4 | "compilerOptions": {
5 | "types": ["jest", "node"]
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/components/links/links.component.html:
--------------------------------------------------------------------------------
1 | Links: home page |
2 | first page |
3 | second page
4 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "angular.ng-template",
4 | "nrwl.angular-console",
5 | "esbenp.prettier-vscode",
6 | "firsttris.vscode-jest-runner"
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/main-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": []
6 | },
7 | "files": ["src/main.ts", "src/polyfills.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/wc-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "types": []
6 | },
7 | "files": ["src/main.ts", "src/polyfills.ts"]
8 | }
9 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/components/links/links.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'wc-app-links',
5 | templateUrl: './links.component.html',
6 | styleUrls: ['./links.component.scss'],
7 | })
8 | export class LinksComponent {}
9 |
--------------------------------------------------------------------------------
/apps/wc-app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "files": ["src/test-setup.ts"],
9 | "include": ["**/*.spec.ts", "**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/main-app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'main-app',
5 | templateUrl: './main-app.component.html',
6 | styleUrls: ['./main-app.component.scss'],
7 | })
8 | export class MainAppComponent {
9 | title = 'main-app';
10 | }
11 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/apps/main-app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../../dist/out-tsc",
5 | "module": "commonjs",
6 | "types": ["jest", "node"]
7 | },
8 | "files": ["src/test-setup.ts"],
9 | "include": ["**/*.spec.ts", "**/*.test.ts", "**/*.d.ts"]
10 | }
11 |
--------------------------------------------------------------------------------
/tools/tsconfig.tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.base.json",
3 | "compilerOptions": {
4 | "outDir": "../dist/out-tsc/tools",
5 | "rootDir": ".",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": ["node"],
9 | "importHelpers": false
10 | },
11 | "include": ["**/*.ts"]
12 | }
13 |
--------------------------------------------------------------------------------
/apps/main-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.app.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | },
12 | {
13 | "path": "./tsconfig.editor.json"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/wc-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.base.json",
3 | "files": [],
4 | "include": [],
5 | "references": [
6 | {
7 | "path": "./tsconfig.app.json"
8 | },
9 | {
10 | "path": "./tsconfig.spec.json"
11 | },
12 | {
13 | "path": "./tsconfig.editor.json"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'wc-app-home',
5 | templateUrl: './home.component.html',
6 | styleUrls: ['./home.component.scss']
7 | })
8 | export class HomeComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit(): void {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/main-app.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: block;
3 | padding: 20px;
4 | font-family: Arial, Helvetica, sans-serif;
5 |
6 | span.hl {
7 | font-style: italic;
8 | color: darkred;
9 | }
10 |
11 | .router-container {
12 | border: 1px solid green;
13 | // background-color: rgba(0, 255, 0, 0.1);
14 | padding: 10px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/first/first.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'wc-app-first',
5 | templateUrl: './first.component.html',
6 | styleUrls: ['./first.component.scss']
7 | })
8 | export class FirstComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit(): void {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/second/second.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'wc-app-second',
5 | templateUrl: './second.component.html',
6 | styleUrls: ['./second.component.scss']
7 | })
8 | export class SecondComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit(): void {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/apps/wc-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WcApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/pages/simple-page/simple-page.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'main-app-simple-page',
5 | templateUrl: './simple-page.component.html',
6 | styleUrls: ['./simple-page.component.scss'],
7 | })
8 | export class SimplePageComponent implements OnInit {
9 | constructor() {}
10 |
11 | ngOnInit(): void {}
12 |
13 | ngAfterViewInit(): void {}
14 | }
15 |
--------------------------------------------------------------------------------
/apps/wc-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import { WcAppModule } from './app/app.module';
4 | import { environment } from './environments/environment';
5 |
6 | if (environment.production) {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic()
11 | .bootstrapModule(WcAppModule)
12 | .catch((err) => console.error(`Error in wc-app`, err));
13 |
--------------------------------------------------------------------------------
/apps/main-app/src/test-setup.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular/setup-jest';
2 |
3 | import { getTestBed } from '@angular/core/testing';
4 | import {
5 | BrowserDynamicTestingModule,
6 | platformBrowserDynamicTesting,
7 | } from '@angular/platform-browser-dynamic/testing';
8 |
9 | getTestBed().resetTestEnvironment();
10 | getTestBed().initTestEnvironment(
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting(),
13 | { teardown: { destroyAfterEach: false } }
14 | );
15 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "rootDir": ".",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "importHelpers": true,
11 | "target": "es2015",
12 | "module": "esnext",
13 | "lib": ["es2017", "dom"],
14 | "skipLibCheck": true,
15 | "skipDefaultLibCheck": true,
16 | "baseUrl": ".",
17 | "paths": {}
18 | },
19 | "exclude": ["node_modules", "tmp"]
20 | }
21 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/web-component-host/web-component-host.component.html:
--------------------------------------------------------------------------------
1 | Web Component Host Page
2 |
3 | This is the Web Component:
4 |
5 |
11 |
12 |
13 | @Input
14 |
17 |
18 | @Output
19 |
20 | Message from WebComponent: {{ outputMessage }}
21 |
22 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/web-component-host/web-component-host.module.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { WebComponentHostComponent } from './web-component-host.component';
4 |
5 | const routes: Routes = [
6 | {
7 | path: '**',
8 | component: WebComponentHostComponent,
9 | },
10 | ];
11 |
12 | @NgModule({
13 | declarations: [WebComponentHostComponent],
14 | imports: [RouterModule.forChild(routes)],
15 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
16 | })
17 | export class WebComponentHostModule {}
18 |
--------------------------------------------------------------------------------
/apps/main-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | MainApp
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/apps/main-app/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 | // Include Web Component script
11 | const scriptEl = document.createElement('script');
12 | scriptEl.src = environment.wcAppScriptPath;
13 | document.head.appendChild(scriptEl);
14 |
15 | platformBrowserDynamic()
16 | .bootstrapModule(AppModule)
17 | .catch((err) => console.error(err));
18 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/wc-app.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | border: 1px solid rgba(0, 255, 255, 1);
3 | background-color: rgba(0, 255, 255, 0.1);
4 | padding: 20px;
5 | display: block;
6 | font-family: Arial, Helvetica, sans-serif;
7 |
8 | input {
9 | height: 30px;
10 | width: 300px;
11 | border-radius: 5px;
12 | padding: 0 10px;
13 | border: 1px solid gray;
14 | }
15 |
16 | button {
17 | height: 30px;
18 | padding: 0 10px;
19 | }
20 |
21 | .router-outlet-container {
22 | padding: 10px;
23 | border: 1px solid blue;
24 | background-color: rgba(0, 0, 255, 0.1);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/apps/main-app/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'main-app',
3 | preset: '../../jest.preset.js',
4 | setupFilesAfterEnv: ['/src/test-setup.ts'],
5 | globals: {
6 | 'ts-jest': {
7 | stringifyContentPathRegex: '\\.(html|svg)$',
8 |
9 | tsconfig: '/tsconfig.spec.json',
10 | },
11 | },
12 | coverageDirectory: '../../coverage/apps/main-app',
13 | snapshotSerializers: [
14 | 'jest-preset-angular/build/serializers/no-ng-attributes',
15 | 'jest-preset-angular/build/serializers/ng-snapshot',
16 | 'jest-preset-angular/build/serializers/html-comment',
17 | ],
18 | transform: { '^.+\\.(ts|js|html)$': 'jest-preset-angular' },
19 | };
20 |
--------------------------------------------------------------------------------
/.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 | /.angular/cache
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | yarn-error.log
35 | testem.log
36 | /typings
37 |
38 | # System Files
39 | .DS_Store
40 | Thumbs.db
41 |
--------------------------------------------------------------------------------
/apps/wc-app/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/home/home.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { HomeComponent } from './home.component';
4 |
5 | describe('HomeComponent', () => {
6 | let component: HomeComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ HomeComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(HomeComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/first/first.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { FirstComponent } from './first.component';
4 |
5 | describe('FirstComponent', () => {
6 | let component: FirstComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ FirstComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(FirstComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/components/links/links.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { LinksComponent } from './links.component';
4 |
5 | describe('LinksComponent', () => {
6 | let component: LinksComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ LinksComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(LinksComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/pages/second/second.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 |
3 | import { SecondComponent } from './second.component';
4 |
5 | describe('SecondComponent', () => {
6 | let component: SecondComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | declarations: [ SecondComponent ]
12 | })
13 | .compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(SecondComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/apps/main-app/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
18 |
--------------------------------------------------------------------------------
/apps/wc-app/.browserslistrc:
--------------------------------------------------------------------------------
1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2 | # For additional information regarding the format and rule options, please see:
3 | # https://github.com/browserslist/browserslist#queries
4 |
5 | # For the full list of supported browsers by the Angular framework, please see:
6 | # https://angular.io/guide/browser-support
7 |
8 | # You can see what browsers were selected by your queries by running:
9 | # npx browserslist
10 |
11 | last 1 Chrome version
12 | last 1 Firefox version
13 | last 2 Edge major versions
14 | last 2 Safari major versions
15 | last 2 iOS major versions
16 | Firefox ESR
17 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
18 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/wc-app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Web Component App
3 |
4 | Routing in Web Component App:
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
@Input
17 |
18 | message from host: {{ message }}
19 |
20 |
21 |
22 | @Output
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/apps/main-app/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | wcAppScriptPath: 'http://localhost:4210/main.js',
8 | };
9 |
10 | /*
11 | * For easier debugging in development mode, you can import the following file
12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
13 | *
14 | * This import should be commented out in production mode because it will have a negative impact
15 | * on performance if an error is thrown.
16 | */
17 | // import 'zone.js/dist/zone-error'; // Included with Angular CLI.
18 |
--------------------------------------------------------------------------------
/apps/wc-app/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | displayName: 'wc-app',
3 | preset: '../../jest.preset.js',
4 | setupFilesAfterEnv: ['/src/test-setup.ts'],
5 | globals: {
6 | 'ts-jest': {
7 | tsConfig: '/tsconfig.spec.json',
8 | stringifyContentPathRegex: '\\.(html|svg)$',
9 | astTransformers: {
10 | before: [
11 | 'jest-preset-angular/build/InlineFilesTransformer',
12 | 'jest-preset-angular/build/StripStylesTransformer',
13 | ],
14 | },
15 | },
16 | },
17 | coverageDirectory: '../../coverage/apps/wc-app',
18 | snapshotSerializers: [
19 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js',
20 | 'jest-preset-angular/build/AngularSnapshotSerializer.js',
21 | 'jest-preset-angular/build/HTMLCommentSerializer.js',
22 | ],
23 | };
24 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["**/*"],
4 | "plugins": ["@nrwl/nx"],
5 | "overrides": [
6 | {
7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
8 | "rules": {
9 | "@nrwl/nx/enforce-module-boundaries": [
10 | "error",
11 | {
12 | "enforceBuildableLibDependency": true,
13 | "allow": [],
14 | "depConstraints": [
15 | {
16 | "sourceTag": "*",
17 | "onlyDependOnLibsWithTags": ["*"]
18 | }
19 | ]
20 | }
21 | ]
22 | }
23 | },
24 | {
25 | "files": ["*.ts", "*.tsx"],
26 | "extends": ["plugin:@nrwl/nx/typescript"],
27 | "rules": {}
28 | },
29 | {
30 | "files": ["*.js", "*.jsx"],
31 | "extends": ["plugin:@nrwl/nx/javascript"],
32 | "rules": {}
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/main-app.component.html:
--------------------------------------------------------------------------------
1 | Main App
2 |
3 |
4 | This is the index.html file of the main-app, which
5 | renders main-app's
6 | AppComponent.
7 |
8 |
9 |
10 | This AppComponent renders a
11 | router-outlet, where the default route is the
12 | WebComponentHost
13 |
14 |
15 |
16 | WebComponentHost renders the
17 | wc-app as a Web Component `<wc-app>`
20 |
21 |
22 |
23 | Routing in Main App:
24 |
25 | Links:
26 | Web Component Host page |
27 | Simple page
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/apps/wc-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "extends": [
8 | "plugin:@nrwl/nx/angular",
9 | "plugin:@angular-eslint/template/process-inline-templates"
10 | ],
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "wcApp",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "wc-app",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "extends": ["plugin:@nrwl/nx/angular-template"],
33 | "rules": {}
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/apps/main-app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["../../.eslintrc.json"],
3 | "ignorePatterns": ["!**/*"],
4 | "overrides": [
5 | {
6 | "files": ["*.ts"],
7 | "extends": [
8 | "plugin:@nrwl/nx/angular",
9 | "plugin:@angular-eslint/template/process-inline-templates"
10 | ],
11 | "rules": {
12 | "@angular-eslint/directive-selector": [
13 | "error",
14 | {
15 | "type": "attribute",
16 | "prefix": "mainApp",
17 | "style": "camelCase"
18 | }
19 | ],
20 | "@angular-eslint/component-selector": [
21 | "error",
22 | {
23 | "type": "element",
24 | "prefix": "main-app",
25 | "style": "kebab-case"
26 | }
27 | ]
28 | }
29 | },
30 | {
31 | "files": ["*.html"],
32 | "extends": ["plugin:@nrwl/nx/angular-template"],
33 | "rules": {}
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { LocationStrategy } from '@angular/common';
2 | import { NgModule } from '@angular/core';
3 | import { RouterModule, Routes } from '@angular/router';
4 | import { FirstComponent } from './pages/first/first.component';
5 | import { HomeComponent } from './pages/home/home.component';
6 | import { SecondComponent } from './pages/second/second.component';
7 | import { NoopLocationStrategy } from './utils/noop-location.strategy';
8 |
9 | const routes: Routes = [
10 | {
11 | path: '',
12 | component: HomeComponent,
13 | },
14 | {
15 | path: 'first',
16 | component: FirstComponent,
17 | },
18 | {
19 | path: 'second',
20 | component: SecondComponent,
21 | },
22 | ];
23 |
24 | @NgModule({
25 | imports: [
26 | RouterModule.forRoot(routes, {
27 | // enableTracing: true,
28 | }),
29 | ],
30 | providers: [{ provide: LocationStrategy, useClass: NoopLocationStrategy }],
31 | exports: [RouterModule],
32 | })
33 | export class AppRoutingModule {}
34 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { RouterModule } from '@angular/router';
4 | import { MainAppComponent } from './main-app.component';
5 | import { SimplePageComponent } from './pages/simple-page/simple-page.component';
6 |
7 | @NgModule({
8 | declarations: [MainAppComponent],
9 | imports: [
10 | BrowserModule,
11 | RouterModule.forRoot(
12 | [
13 | {
14 | path: 'web-component-host',
15 | loadChildren: () =>
16 | import('./web-component-host/web-component-host.module').then(
17 | (m) => m.WebComponentHostModule
18 | ),
19 | },
20 | {
21 | path: 'simple-page',
22 | component: SimplePageComponent,
23 | },
24 | { path: '**', redirectTo: '/web-component-host' },
25 | ],
26 | { initialNavigation: 'enabled' }
27 | ),
28 | ],
29 | providers: [],
30 | bootstrap: [MainAppComponent],
31 | schemas: [CUSTOM_ELEMENTS_SCHEMA],
32 | })
33 | export class AppModule {}
34 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { Injector, NgModule } from '@angular/core';
2 | import { createCustomElement } from '@angular/elements';
3 | import { FormsModule } from '@angular/forms';
4 | import { BrowserModule } from '@angular/platform-browser';
5 | import { AppRoutingModule } from './app-routing.module';
6 | import { LinksComponent } from './components/links/links.component';
7 | import { FirstComponent } from './pages/first/first.component';
8 | import { HomeComponent } from './pages/home/home.component';
9 | import { SecondComponent } from './pages/second/second.component';
10 | import { WcAppComponent } from './wc-app.component';
11 |
12 | @NgModule({
13 | declarations: [
14 | WcAppComponent,
15 | HomeComponent,
16 | FirstComponent,
17 | SecondComponent,
18 | LinksComponent,
19 | ],
20 | imports: [BrowserModule, AppRoutingModule, FormsModule],
21 | providers: [],
22 | // bootstrap: [AppComponent],
23 | })
24 | export class WcAppModule {
25 | constructor(private injector: Injector) {
26 | // Create custom element
27 | const AppElement = createCustomElement(WcAppComponent, { injector });
28 | // Register the custom element with the browser.
29 | customElements.define('wc-app', AppElement);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/nx.json:
--------------------------------------------------------------------------------
1 | {
2 | "implicitDependencies": {
3 | "package.json": {
4 | "dependencies": "*",
5 | "devDependencies": "*"
6 | },
7 | ".eslintrc.json": "*"
8 | },
9 | "affected": {
10 | "defaultBase": "master"
11 | },
12 | "npmScope": "playground-nx-wc-app",
13 | "tasksRunnerOptions": {
14 | "default": {
15 | "runner": "@nrwl/workspace/tasks-runners/default",
16 | "options": {
17 | "cacheableOperations": ["build", "lint", "test", "e2e"],
18 | "parallel": 1
19 | }
20 | }
21 | },
22 | "targetDependencies": {
23 | "build": [
24 | {
25 | "target": "build",
26 | "projects": "dependencies"
27 | }
28 | ]
29 | },
30 | "cli": {
31 | "defaultCollection": "@nrwl/angular"
32 | },
33 | "defaultProject": "main-app",
34 | "generators": {
35 | "@nrwl/angular": {
36 | "application": {
37 | "linter": "eslint"
38 | },
39 | "library": {
40 | "linter": "eslint"
41 | },
42 | "storybook-configuration": {
43 | "linter": "eslint"
44 | }
45 | },
46 | "@nrwl/angular:application": {
47 | "style": "scss",
48 | "linter": "eslint",
49 | "unitTestRunner": "jest",
50 | "e2eTestRunner": "cypress"
51 | },
52 | "@nrwl/angular:library": {
53 | "style": "scss",
54 | "linter": "eslint",
55 | "unitTestRunner": "jest"
56 | },
57 | "@nrwl/angular:component": {
58 | "style": "scss"
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Web Component Example - nx app
2 |
3 | The goal of this project is to showcase an nx workspace, where the main-app (`main-app`) uses a web-component, which is the result of the build process of another app (`wc-app`)
4 |
5 | ## Getting started
6 |
7 | Run `npx nx serve main-app` and `npx nx serve wc-app`
8 |
9 | Playground app will run on `port 4200` and wc-app will run on `port 4210`.
10 |
11 | Open `http://localhost:4200` in the browser and have a look at it.
12 |
13 | Note: main-app has to be manually refreshed in the browser if a change is made to wc-app.
14 |
15 | ## Build process
16 |
17 | The build process of the `wc-app` uses an npm plugin called `ngx-build-plus` to extend the default behaviour.
18 |
19 | We use two flags of the plugin:
20 |
21 | - singleBuild
22 | - keepStyles
23 |
24 | You can find both of these settings in the `angular.json` file.
25 |
26 | ## Useful links
27 |
28 | - [Multiple Angular Apps on a single page](https://medium.com/swlh/multiple-angular-apps-on-a-single-page-9f49bc863177)
29 | - [ngx-build-plus](https://github.com/manfredsteyer/ngx-build-plus)
30 | - [Official Angular Elements Docs](https://angular.io/guide/elements)
31 | - [Building Web Components with Angular](https://buddy.works/tutorials/building-web-components-with-angular#installing-setting-up-angular-elements)
32 | - [Routing artcile](https://medium.com/@timon.grassl/how-to-use-routing-in-angular-web-components-c6a76449cdb)
33 | - [ngx routing plugin](https://www.npmjs.com/package/ngx-elements-router)
34 | - [Routing within a Web Component](https://github.com/fboeller/ngx-elements-router)
35 |
36 | ## Feedback
37 |
38 | Did I forget something? Drop a PR or a message: [marcellkiss@budacode.com](mailto:marcellkiss@budacode.com)
39 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/wc-app.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Component,
3 | EventEmitter,
4 | Input,
5 | OnChanges,
6 | OnInit,
7 | Output,
8 | SimpleChanges,
9 | } from '@angular/core';
10 | import { Router, RoutesRecognized } from '@angular/router';
11 |
12 | @Component({
13 | selector: 'wc-app',
14 | templateUrl: './wc-app.component.html',
15 | styleUrls: ['./wc-app.component.scss'],
16 | })
17 | export class WcAppComponent implements OnInit, OnChanges {
18 | // Routing i/o
19 | @Input() route: string;
20 | @Output() routeChange: EventEmitter = new EventEmitter();
21 |
22 | // Test i/o
23 | @Input() message: string;
24 | @Output() submitEvent: EventEmitter = new EventEmitter();
25 |
26 | public inputModel: string;
27 |
28 | constructor(private router: Router) {}
29 |
30 | ngOnInit() {
31 | this.forwardRouteChanges();
32 | }
33 |
34 | ngOnChanges(changes: SimpleChanges): void {
35 | console.log(`ngOnChanges in Web Component`);
36 |
37 | // React on route changes
38 | if (changes['route']) {
39 | console.log(
40 | `>>> [WebComponent]: Incoming route change: ${changes['route'].currentValue}`
41 | );
42 | this.router.navigateByUrl(changes['route'].currentValue, {
43 | state: { fromPlatform: true },
44 | });
45 | }
46 | }
47 |
48 | private forwardRouteChanges() {
49 | this.router.events.subscribe((event) => {
50 | if (
51 | event instanceof RoutesRecognized &&
52 | (!this.isFromPlatform() || this.isRedirect(event))
53 | ) {
54 | console.log(
55 | `>>> [WebComponent]: Outgoing route change: ${event.urlAfterRedirects}`
56 | );
57 | this.routeChange.next(event.urlAfterRedirects);
58 | }
59 | });
60 | }
61 |
62 | public onSubmit() {
63 | this.submitEvent.next(this.inputModel);
64 | }
65 |
66 | isFromPlatform(): boolean {
67 | return this.router.getCurrentNavigation()?.extras?.state?.fromPlatform;
68 | }
69 |
70 | isRedirect(event: RoutesRecognized): boolean {
71 | return event.url !== event.urlAfterRedirects;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/apps/main-app/src/app/web-component-host/web-component-host.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnDestroy, OnInit } from '@angular/core';
2 | import { ActivatedRoute, Router } from '@angular/router';
3 | import { Subscription } from 'rxjs';
4 |
5 | @Component({
6 | selector: 'main-app-web-component-host',
7 | templateUrl: './web-component-host.component.html',
8 | styleUrls: ['./web-component-host.component.scss'],
9 | })
10 | export class WebComponentHostComponent implements OnInit, OnDestroy {
11 | public message = '';
12 | public outputMessage = '';
13 | public routeInput: string;
14 | private subs: Subscription[] = [];
15 | private WEB_COMPONENT_BASE_URL = '/web-component-host';
16 |
17 | constructor(private route: ActivatedRoute, private router: Router) {}
18 |
19 | ngOnInit(): void {
20 | this.syncRouteInput();
21 | }
22 |
23 | ngOnDestroy(): void {
24 | this.subs.forEach((sub) => sub.unsubscribe());
25 | }
26 |
27 | private syncRouteInput() {
28 | const routeSub = this.route.url.subscribe((url) => {
29 | // Pass on just the relative part, not the whole
30 | this.routeInput = this.router.url.split(this.WEB_COMPONENT_BASE_URL)[1];
31 | });
32 |
33 | this.subs.push(routeSub);
34 | }
35 |
36 | public onSubmitEvent(submitValue: string) {
37 | console.log({ submitValue });
38 | this.outputMessage = `${submitValue}`;
39 | }
40 |
41 | public onRouteChange(relativeRoute: string): void {
42 | console.log(`>>> [Platform]: new route arrived`, relativeRoute);
43 | if (relativeRoute && relativeRoute.startsWith('/')) {
44 | this.router.navigateByUrl(this.WEB_COMPONENT_BASE_URL + relativeRoute, {
45 | replaceUrl: true, //event.replaceUrl || false,
46 | });
47 | } else {
48 | console.warn(`Invalid router event.`, event);
49 | }
50 | }
51 |
52 | public onChangeMessage() {
53 | const words = [
54 | 'Hallo Welt!',
55 | 'Moin Welt!',
56 | 'Grüß Gott Welt!',
57 | 'Szia Világ!',
58 | 'Hello World!',
59 | 'Hola Mundo!',
60 | 'Bonjour le monde!',
61 | 'Ciao mondo!',
62 | ];
63 | const randomIndex = Math.floor(Math.random() * words.length);
64 | this.message = `${words[randomIndex]}`;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/utils/noop-location.strategy.ts:
--------------------------------------------------------------------------------
1 | import {
2 | APP_BASE_HREF,
3 | LocationChangeListener,
4 | LocationStrategy,
5 | PlatformLocation,
6 | } from '@angular/common';
7 | import { Inject, Injectable, Optional } from '@angular/core';
8 |
9 | /**
10 | * Acts as a noop location strategy that does not modify the browser url.
11 | * Should be used for a RouterModule in a micro frontend.
12 | * That way, the RouterModule of the platform is in charge of modifying the browser url.
13 | *
14 | * ```
15 | * imports: [
16 | * RouterModule.forRoot([
17 | * { path: 'my-route', component: SomeComponent },
18 | * { path: '**', component: NoComponent }
19 | * ])
20 | * ],
21 | * providers: [
22 | * { provide: LocationStrategy, useClass: NoopLocationStrategy },
23 | * ]
24 | * ```
25 | */
26 | @Injectable()
27 | export class NoopLocationStrategy extends LocationStrategy {
28 | private readonly baseHref: string;
29 |
30 | constructor(
31 | private platformLocation: PlatformLocation,
32 | @Optional() @Inject(APP_BASE_HREF) baseHref?: string
33 | ) {
34 | super();
35 | this.baseHref = baseHref || this.platformLocation.getBaseHrefFromDOM();
36 | if (!this.baseHref) {
37 | throw new Error(
38 | `No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`
39 | );
40 | }
41 | }
42 |
43 | onPopState(_fn: LocationChangeListener): void {}
44 |
45 | getBaseHref(): string {
46 | return this.baseHref;
47 | }
48 |
49 | path(_includeHash: boolean = false): string {
50 | return '';
51 | }
52 |
53 | prepareExternalUrl(internal: string): string {
54 | if (this.baseHref.endsWith('/') && internal.startsWith('/')) {
55 | return this.baseHref.substring(0, this.baseHref.length - 1) + internal;
56 | } else if (this.baseHref.endsWith('/') || internal.startsWith('/')) {
57 | return this.baseHref + internal;
58 | } else {
59 | return `${this.baseHref}/${internal}`;
60 | }
61 | }
62 |
63 | pushState(
64 | _state: any,
65 | _title: string,
66 | _path: string,
67 | _queryParams: string
68 | ): void {}
69 |
70 | replaceState(
71 | _state: any,
72 | _title: string,
73 | _path: string,
74 | _queryParams: string
75 | ): void {}
76 |
77 | forward(): void {}
78 |
79 | back(): void {}
80 | }
81 |
--------------------------------------------------------------------------------
/apps/wc-app/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js/dist/zone'; // Included with Angular CLI.
49 |
50 | /***************************************************************************************************
51 | * APPLICATION IMPORTS
52 | */
53 |
--------------------------------------------------------------------------------
/apps/main-app/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/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js/dist/zone'; // Included with Angular CLI.
49 |
50 | /***************************************************************************************************
51 | * APPLICATION IMPORTS
52 | */
53 |
--------------------------------------------------------------------------------
/decorate-angular-cli.js:
--------------------------------------------------------------------------------
1 | /**
2 | * This file decorates the Angular CLI with the Nx CLI to enable features such as computation caching
3 | * and faster execution of tasks.
4 | *
5 | * It does this by:
6 | *
7 | * - Patching the Angular CLI to warn you in case you accidentally use the undecorated ng command.
8 | * - Symlinking the ng to nx command, so all commands run through the Nx CLI
9 | * - Updating the package.json postinstall script to give you control over this script
10 | *
11 | * The Nx CLI decorates the Angular CLI, so the Nx CLI is fully compatible with it.
12 | * Every command you run should work the same when using the Nx CLI, except faster.
13 | *
14 | * Because of symlinking you can still type `ng build/test/lint` in the terminal. The ng command, in this case,
15 | * will point to nx, which will perform optimizations before invoking ng. So the Angular CLI is always invoked.
16 | * The Nx CLI simply does some optimizations before invoking the Angular CLI.
17 | *
18 | * To opt out of this patch:
19 | * - Replace occurrences of nx with ng in your package.json
20 | * - Remove the script from your postinstall script in your package.json
21 | * - Delete and reinstall your node_modules
22 | */
23 |
24 | const fs = require('fs');
25 | const os = require('os');
26 | const cp = require('child_process');
27 | const isWindows = os.platform() === 'win32';
28 | let output;
29 | try {
30 | output = require('@nrwl/workspace').output;
31 | } catch (e) {
32 | console.warn('Angular CLI could not be decorated to enable computation caching. Please ensure @nrwl/workspace is installed.');
33 | process.exit(0);
34 | }
35 |
36 | /**
37 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still
38 | * invoke the Nx CLI and get the benefits of computation caching.
39 | */
40 | function symlinkNgCLItoNxCLI() {
41 | try {
42 | const ngPath = './node_modules/.bin/ng';
43 | const nxPath = './node_modules/.bin/nx';
44 | if (isWindows) {
45 | /**
46 | * This is the most reliable way to create symlink-like behavior on Windows.
47 | * Such that it works in all shells and works with npx.
48 | */
49 | ['', '.cmd', '.ps1'].forEach(ext => {
50 | if (fs.existsSync(nxPath + ext)) fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext));
51 | });
52 | } else {
53 | // If unix-based, symlink
54 | cp.execSync(`ln -sf ./nx ${ngPath}`);
55 | }
56 | }
57 | catch(e) {
58 | output.error({ title: 'Unable to create a symlink from the Angular CLI to the Nx CLI:' + e.message });
59 | throw e;
60 | }
61 | }
62 |
63 | try {
64 | symlinkNgCLItoNxCLI();
65 | require('@nrwl/cli/lib/decorate-cli').decorateCli();
66 | output.log({ title: 'Angular CLI has been decorated to enable computation caching.' });
67 | } catch(e) {
68 | output.error({ title: 'Decoration of the Angular CLI did not complete successfully' });
69 | }
70 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "playground-nx-wc-app",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "scripts": {
6 | "ng": "nx",
7 | "postinstall": "node ./decorate-angular-cli.js && ngcc --properties es2015 browser module main",
8 | "nx": "nx",
9 | "start": "run-p start:main start:wc",
10 | "start:main": "ng serve main-app",
11 | "start:wc": "ng serve wc-app",
12 | "build:main": "npx nx run main-app:build:production",
13 | "build:wc": "npx nx run wc-app:build:production",
14 | "build": "npx nx run main-app:build:production && npx nx run wc-app:build:production",
15 | "test": "ng test",
16 | "lint": "nx workspace-lint && ng lint",
17 | "e2e": "ng e2e",
18 | "affected:apps": "nx affected:apps",
19 | "affected:libs": "nx affected:libs",
20 | "affected:build": "nx affected:build",
21 | "affected:e2e": "nx affected:e2e",
22 | "affected:test": "nx affected:test",
23 | "affected:lint": "nx affected:lint",
24 | "affected:dep-graph": "nx affected:dep-graph",
25 | "affected": "nx affected",
26 | "format": "nx format:write",
27 | "format:write": "nx format:write",
28 | "format:check": "nx format:check",
29 | "update": "nx migrate latest",
30 | "workspace-generator": "nx workspace-generator",
31 | "dep-graph": "nx dep-graph",
32 | "help": "nx help"
33 | },
34 | "private": true,
35 | "dependencies": {
36 | "@angular/animations": "13.1.2",
37 | "@angular/common": "13.1.2",
38 | "@angular/compiler": "13.1.2",
39 | "@angular/core": "13.1.2",
40 | "@angular/elements": "13.1.2",
41 | "@angular/forms": "13.1.2",
42 | "@angular/platform-browser": "13.1.2",
43 | "@angular/platform-browser-dynamic": "13.1.2",
44 | "@angular/router": "13.1.2",
45 | "@nrwl/angular": "13.4.4",
46 | "ngx-build-plus": "^13.0.1",
47 | "rxjs": "~7.5.2",
48 | "tslib": "^2.3.1",
49 | "zone.js": "0.11.4"
50 | },
51 | "devDependencies": {
52 | "@angular-devkit/build-angular": "13.1.3",
53 | "@angular-eslint/eslint-plugin": "13.0.1",
54 | "@angular-eslint/eslint-plugin-template": "13.0.1",
55 | "@angular-eslint/template-parser": "13.0.1",
56 | "@angular/cli": "13.1.3",
57 | "@angular/compiler-cli": "13.1.2",
58 | "@angular/language-service": "13.1.2",
59 | "@nrwl/cli": "13.4.4",
60 | "@nrwl/cypress": "13.4.4",
61 | "@nrwl/eslint-plugin-nx": "13.4.4",
62 | "@nrwl/jest": "13.4.4",
63 | "@nrwl/linter": "13.4.4",
64 | "@nrwl/tao": "13.4.4",
65 | "@nrwl/workspace": "13.4.4",
66 | "@types/jest": "27.4.0",
67 | "@types/node": "17.0.8",
68 | "@typescript-eslint/eslint-plugin": "~5.3.0",
69 | "@typescript-eslint/parser": "~5.3.0",
70 | "cypress": "^9.2.1",
71 | "dotenv": "11.0.0",
72 | "eslint": "8.6.0",
73 | "eslint-config-prettier": "8.3.0",
74 | "eslint-plugin-cypress": "^2.12.1",
75 | "jest": "27.4.7",
76 | "jest-preset-angular": "11.0.1",
77 | "ng-packagr": "13.1.3",
78 | "npm-run-all": "^4.1.5",
79 | "postcss": "^8.3.9",
80 | "postcss-import": "^14.0.2",
81 | "postcss-preset-env": "^6.7.0",
82 | "postcss-url": "^10.1.1",
83 | "prettier": "2.5.1",
84 | "ts-jest": "27.1.2",
85 | "ts-node": "~10.4.0",
86 | "typescript": "4.5.4"
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/apps/wc-app/src/app/utils/multi-location.strategy.ts:
--------------------------------------------------------------------------------
1 | import {
2 | APP_BASE_HREF,
3 | Location,
4 | LocationChangeListener,
5 | LocationStrategy,
6 | PlatformLocation,
7 | } from '@angular/common';
8 | import { Inject, Injectable, Optional } from '@angular/core';
9 |
10 | // TODO: Fix location change on navigation arrow click, increased navigationId and routing depth > 1
11 | @Injectable()
12 | export class MultiLocationStrategy extends LocationStrategy {
13 | private _baseHref: string;
14 |
15 | constructor(
16 | private _platformLocation: PlatformLocation,
17 | @Optional() @Inject(APP_BASE_HREF) href?: string
18 | ) {
19 | super();
20 |
21 | if (href == null) {
22 | href = this._platformLocation.getBaseHrefFromDOM();
23 | }
24 |
25 | if (href == null) {
26 | throw new Error(
27 | `No base href set. Please provide a value for the APP_BASE_HREF token or add a base element to the document.`
28 | );
29 | }
30 |
31 | this._baseHref = href;
32 | }
33 |
34 | onPopState(fn: LocationChangeListener): void {
35 | this._platformLocation.onPopState(fn);
36 | this._platformLocation.onHashChange(fn);
37 | }
38 |
39 | getBaseHref(): string {
40 | return this._baseHref;
41 | }
42 |
43 | prepareExternalUrl(internal: string, state?: any): string {
44 | return this.checkPathAndInternal(this.path(), internal, state);
45 | }
46 |
47 | checkPathAndInternal(path: string, internal: string, state: any): string {
48 | const openParenthesisIndex = internal.indexOf('(');
49 | const closedParenthesisIndex = internal.indexOf(')', openParenthesisIndex);
50 | const outlet = internal.substring(
51 | openParenthesisIndex,
52 | closedParenthesisIndex + 1
53 | );
54 |
55 | //Get navigationId from RouterState
56 | let navId = 0;
57 | if (state) {
58 | navId = state.navigationId;
59 | }
60 |
61 | // add internal route to base href
62 | if (path.startsWith('/') && path.length === 1) {
63 | return internal;
64 | }
65 |
66 | if (path.includes('(')) {
67 | //There's an outlet in the path => remove if necessary or on initial navigation
68 | return navId === -1 || navId === 1
69 | ? path
70 | : path.replace(/ *\([^)]*\) */g, '');
71 | }
72 |
73 | if (path === internal) {
74 | //don't append to prevent dublicated path e.g. /foo/foo
75 | return path;
76 | }
77 |
78 | if (outlet.length < 1) {
79 | return internal;
80 | } else {
81 | // append internal url to path
82 | return path + outlet;
83 | }
84 | }
85 |
86 | path(includeHash: boolean = false): string {
87 | const pathname =
88 | this._platformLocation.pathname +
89 | Location.normalizeQueryParams(this._platformLocation.search);
90 | const hash = this._platformLocation.hash;
91 | return hash && includeHash ? `${pathname}${hash}` : pathname;
92 | }
93 |
94 | pushState(state: any, title: string, url: string, queryParams: string) {
95 | const externalUrl = this.prepareExternalUrl(
96 | url + Location.normalizeQueryParams(queryParams),
97 | state
98 | );
99 | this._platformLocation.pushState(state, title, externalUrl);
100 | }
101 |
102 | replaceState(state: any, title: string, url: string, queryParams: string) {
103 | const externalUrl = this.prepareExternalUrl(
104 | url + Location.normalizeQueryParams(queryParams),
105 | state
106 | );
107 | this._platformLocation.replaceState(state, title, externalUrl);
108 | }
109 |
110 | forward(): void {
111 | this._platformLocation.forward();
112 | }
113 |
114 | back(): void {
115 | this._platformLocation.back();
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "projects": {
4 | "main-app": {
5 | "projectType": "application",
6 | "root": "apps/main-app",
7 | "sourceRoot": "apps/main-app/src",
8 | "prefix": "main-app",
9 | "architect": {
10 | "build": {
11 | "builder": "@angular-devkit/build-angular:browser",
12 | "options": {
13 | "outputPath": "dist/apps/main-app",
14 | "index": "apps/main-app/src/index.html",
15 | "main": "apps/main-app/src/main.ts",
16 | "polyfills": "apps/main-app/src/polyfills.ts",
17 | "tsConfig": "apps/main-app/tsconfig.app.json",
18 | "aot": true,
19 | "assets": [
20 | "apps/main-app/src/favicon.ico",
21 | "apps/main-app/src/assets"
22 | ],
23 | "styles": ["apps/main-app/src/styles.scss"],
24 | "scripts": []
25 | },
26 | "configurations": {
27 | "production": {
28 | "fileReplacements": [
29 | {
30 | "replace": "apps/main-app/src/environments/environment.ts",
31 | "with": "apps/main-app/src/environments/environment.prod.ts"
32 | }
33 | ],
34 | "optimization": true,
35 | "outputHashing": "all",
36 | "sourceMap": false,
37 | "namedChunks": false,
38 | "extractLicenses": true,
39 | "vendorChunk": false,
40 | "buildOptimizer": true,
41 | "budgets": [
42 | {
43 | "type": "initial",
44 | "maximumWarning": "2mb",
45 | "maximumError": "5mb"
46 | },
47 | {
48 | "type": "anyComponentStyle",
49 | "maximumWarning": "6kb",
50 | "maximumError": "10kb"
51 | }
52 | ]
53 | }
54 | }
55 | },
56 | "serve": {
57 | "builder": "@angular-devkit/build-angular:dev-server",
58 | "options": {
59 | "browserTarget": "main-app:build",
60 | "liveReload": false
61 | },
62 | "configurations": {
63 | "production": {
64 | "browserTarget": "main-app:build:production"
65 | }
66 | }
67 | },
68 | "extract-i18n": {
69 | "builder": "@angular-devkit/build-angular:extract-i18n",
70 | "options": {
71 | "browserTarget": "main-app:build"
72 | }
73 | },
74 | "lint": {
75 | "builder": "@nrwl/linter:eslint",
76 | "options": {
77 | "lintFilePatterns": [
78 | "apps/main-app/src/**/*.ts",
79 | "apps/main-app/src/**/*.html"
80 | ]
81 | },
82 | "outputs": ["{options.outputFile}"]
83 | },
84 | "test": {
85 | "builder": "@nrwl/jest:jest",
86 | "outputs": ["coverage/apps/main-app"],
87 | "options": {
88 | "jestConfig": "apps/main-app/jest.config.js",
89 | "passWithNoTests": true
90 | }
91 | }
92 | },
93 | "tags": []
94 | },
95 | "wc-app": {
96 | "projectType": "application",
97 | "root": "apps/wc-app",
98 | "sourceRoot": "apps/wc-app/src",
99 | "prefix": "wc-app",
100 | "architect": {
101 | "build": {
102 | "builder": "ngx-build-plus:browser",
103 | "options": {
104 | "singleBundle": true,
105 | "outputPath": "dist/apps/main-app/wc-app",
106 | "index": "apps/wc-app/src/index.html",
107 | "main": "apps/wc-app/src/main.ts",
108 | "polyfills": "apps/wc-app/src/polyfills.ts",
109 | "tsConfig": "apps/wc-app/tsconfig.app.json",
110 | "aot": true,
111 | "assets": ["apps/wc-app/src/favicon.ico", "apps/wc-app/src/assets"],
112 | "styles": ["apps/wc-app/src/styles.scss"],
113 | "scripts": []
114 | },
115 | "configurations": {
116 | "production": {
117 | "fileReplacements": [
118 | {
119 | "replace": "apps/wc-app/src/environments/environment.ts",
120 | "with": "apps/wc-app/src/environments/environment.prod.ts"
121 | }
122 | ],
123 | "optimization": true,
124 | "outputHashing": "none",
125 | "sourceMap": false,
126 | "namedChunks": false,
127 | "extractLicenses": true,
128 | "vendorChunk": false,
129 | "buildOptimizer": true,
130 | "budgets": [
131 | {
132 | "type": "initial",
133 | "maximumWarning": "2mb",
134 | "maximumError": "5mb"
135 | },
136 | {
137 | "type": "anyComponentStyle",
138 | "maximumWarning": "6kb",
139 | "maximumError": "10kb"
140 | }
141 | ]
142 | }
143 | }
144 | },
145 | "serve": {
146 | "builder": "ngx-build-plus:dev-server",
147 | "options": {
148 | "singleBundle": true,
149 | "browserTarget": "wc-app:build",
150 | "port": 4210
151 | },
152 | "configurations": {
153 | "production": {
154 | "browserTarget": "wc-app:build:production"
155 | }
156 | }
157 | },
158 | "extract-i18n": {
159 | "builder": "@angular-devkit/build-angular:extract-i18n",
160 | "options": {
161 | "browserTarget": "wc-app:build"
162 | }
163 | },
164 | "lint": {
165 | "builder": "@nrwl/linter:eslint",
166 | "options": {
167 | "lintFilePatterns": [
168 | "apps/wc-app/src/**/*.ts",
169 | "apps/wc-app/src/**/*.html"
170 | ]
171 | },
172 | "outputs": ["{options.outputFile}"]
173 | },
174 | "test": {
175 | "builder": "@nrwl/jest:jest",
176 | "outputs": ["coverage/apps/wc-app"],
177 | "options": {
178 | "jestConfig": "apps/wc-app/jest.config.js",
179 | "passWithNoTests": true
180 | }
181 | }
182 | },
183 | "tags": []
184 | }
185 | }
186 | }
187 |
--------------------------------------------------------------------------------