{"id":20495313,"url":"https://github.com/jualoppaz/single-spa-angular-app","last_synced_at":"2025-04-13T17:43:37.123Z","repository":{"id":38622553,"uuid":"236161457","full_name":"jualoppaz/single-spa-angular-app","owner":"jualoppaz","description":"Angular v8 application with two example pages for be included in a single-spa application as registered app.","archived":false,"fork":false,"pushed_at":"2023-01-07T14:06:30.000Z","size":2300,"stargazers_count":5,"open_issues_count":23,"forks_count":7,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-27T08:45:10.286Z","etag":null,"topics":["angular","angular8","bootstrap","single-spa","single-spa-angular","single-spa-demo","webpack"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jualoppaz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-01-25T11:31:06.000Z","updated_at":"2024-06-28T20:26:30.000Z","dependencies_parsed_at":"2023-02-07T09:17:40.886Z","dependency_job_id":null,"html_url":"https://github.com/jualoppaz/single-spa-angular-app","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jualoppaz%2Fsingle-spa-angular-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jualoppaz%2Fsingle-spa-angular-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jualoppaz%2Fsingle-spa-angular-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jualoppaz%2Fsingle-spa-angular-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jualoppaz","download_url":"https://codeload.github.com/jualoppaz/single-spa-angular-app/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248756457,"owners_count":21156770,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["angular","angular8","bootstrap","single-spa","single-spa-angular","single-spa-demo","webpack"],"created_at":"2024-11-15T17:45:25.670Z","updated_at":"2025-04-13T17:43:37.104Z","avatar_url":"https://github.com/jualoppaz.png","language":"TypeScript","readme":"\u003cp float=\"left\"\u003e\r\n  \u003cimg src=\"https://single-spa.js.org/img/logo-white-bgblue.svg\" width=\"50\" height=\"50\"\u003e\r\n  \u003cimg src=\"https://angular.io/assets/images/logos/angular/angular.png\" width=\"50\" height=\"50\"\u003e\r\n\u003c/p\u003e\r\n\r\n[![npm version](https://img.shields.io/npm/v/single-spa-angular-app.svg?style=flat-square)](https://www.npmjs.org/package/single-spa-angular-app)\r\n\r\n# single-spa-angular-app\r\n\r\nThis is an Angular v8 application example used as NPM package in [single-spa-login-example-with-npm-packages](https://github.com/jualoppaz/single-spa-login-example-with-npm-packages) in order to register an application. See the installation instructions there.\r\n\r\n## ✍🏻 Motivation\r\n\r\nThis is an Angular v8 application which contains two routed pages for embbed the app inside a root single-spa application.\r\n\r\n## How it works ❓\r\n\r\nThere are several files for the right working of this application and they are:\r\n\r\n- src/app/app.module.ts\r\n- src/app/app-routing.module.ts\r\n- src/main.single-spa.ts\r\n- angular.json\r\n- extra-webpack.config.ts\r\n- package.json\r\n\r\nThis file has no custom config. But we must set desired config here if needed.\r\n\r\n### src/app/app.module.ts\r\n\r\n```javascript\r\nimport { AppComponent } from './app.component';\r\n\r\nimport {APP_BASE_HREF} from '@angular/common';\r\nimport { ListComponent } from './list/list.component';\r\n    BrowserModule,\r\n    AppRoutingModule,\r\n  ],\r\n  providers: [\r\n    {provide: APP_BASE_HREF, useValue: '/'}\r\n  ],\r\n  bootstrap: [AppComponent]\r\n})\r\nexport class AppModule { }\r\n```\r\n\r\nAs this application will be mounted when browser url starts with **/angular**, we need to **provide** the **APP_BASE_HREF** property with **/angular** value. However, as is documented [here](https://single-spa.js.org/docs/ecosystem-angular#configure-routes), this config causes strange behaviours in angular router when navigating between registered apps.\r\n\r\nA simple way of avoid this is set **/** as value of **APP_BASE_HREF** property and repeat **angular** prefix in all routes as you can see in **app-routing.module.ts** file.\r\n\r\n### src/app/app-routing.module.ts\r\n\r\n```javascript\r\nimport { NgModule } from '@angular/core';\r\nimport { Routes, RouterModule } from '@angular/router';\r\n\r\nimport {ListComponent} from './list/list.component';\r\nimport {DetailComponent} from './detail/detail.component';\r\nimport {EmptyRouteComponent} from './empty-route/empty-route.component';\r\n\r\nconst routes: Routes = [\r\n  { path: 'angular', component: ListComponent },\r\n  { path: 'angular/detail',      component: DetailComponent },\r\n  { path: '**', component: EmptyRouteComponent }\r\n];\r\n\r\n@NgModule({\r\n  imports: [RouterModule.forRoot(routes)],\r\n  exports: [RouterModule]\r\n})\r\nexport class AppRoutingModule { }\r\n```\r\n\r\nAs it is explained in **src/app/app.module.ts** section we need to add **angular** prefix in every routes.\r\n\r\n### src/main.single-spa.ts\r\n\r\n```javascript\r\nimport { enableProdMode, NgZone } from '@angular/core';\r\n\r\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\r\nimport { Router } from '@angular/router';\r\nimport { AppModule } from './app/app.module';\r\nimport { environment } from './environments/environment';\r\nimport singleSpaAngular from 'single-spa-angular';\r\nimport { singleSpaPropsSubject } from './single-spa/single-spa-props';\r\n\r\nif (environment.production) {\r\n  enableProdMode();\r\n}\r\n\r\nconst lifecycles = singleSpaAngular({\r\n  bootstrapFunction: singleSpaProps =\u003e {\r\n    singleSpaPropsSubject.next(singleSpaProps);\r\n    return platformBrowserDynamic().bootstrapModule(AppModule);\r\n  },\r\n  template: '\u003capp-root /\u003e',\r\n  Router,\r\n  NgZone,\r\n  domElementGetter: () =\u003e document.getElementById('angular-app')\r\n});\r\n\r\nexport const bootstrap = lifecycles.bootstrap;\r\nexport const mount = lifecycles.mount;\r\nexport const unmount = lifecycles.unmount;\r\n```\r\n\r\nThe **lifecycles** object contains all **single-spa-angular** methods for the **single-spa** lifecycle of this app. All used config is default one but the custom config of the **domElementGetter** option. It's assumed that an element with **angular-app** id is defined in the **index.html** where this application will be mounted.\r\n\r\n### angular.json\r\n```json\r\n{\r\n  \"$schema\": \"./node_modules/@angular/cli/lib/config/schema.json\",\r\n  \"version\": 1,\r\n  \"newProjectRoot\": \"projects\",\r\n  \"projects\": {\r\n    \"single-spa-angular-app\": {\r\n      \"projectType\": \"application\",\r\n      \"schematics\": {\r\n        \"@schematics/angular:component\": {\r\n          \"style\": \"scss\"\r\n        }\r\n      },\r\n      \"root\": \"\",\r\n      \"sourceRoot\": \"src\",\r\n      \"prefix\": \"app\",\r\n      \"architect\": {\r\n        \"build\": {\r\n          \"builder\": \"@angular-builders/custom-webpack:browser\",\r\n          \"options\": {\r\n            \"outputPath\": \"dist\",\r\n            \"index\": \"src/index.html\",\r\n            \"main\": \"src/main.single-spa.ts\",\r\n            \"polyfills\": \"src/polyfills.ts\",\r\n            \"tsConfig\": \"tsconfig.app.json\",\r\n            \"aot\": false,\r\n            \"assets\": [\r\n              \"src/favicon.ico\",\r\n              \"src/assets\"\r\n            ],\r\n            \"styles\": [\r\n              \"src/styles.scss\"\r\n            ],\r\n            \"scripts\": [],\r\n            \"customWebpackConfig\": {\r\n              \"path\": \"./extra-webpack.config.js\"\r\n            }\r\n          },\r\n          \"configurations\": {\r\n            \"production\": {\r\n              \"fileReplacements\": [\r\n                {\r\n                  \"replace\": \"src/environments/environment.ts\",\r\n                  \"with\": \"src/environments/environment.prod.ts\"\r\n                }\r\n              ],\r\n              \"optimization\": true,\r\n              \"outputHashing\": \"none\",\r\n              \"sourceMap\": false,\r\n              \"extractCss\": true,\r\n              \"namedChunks\": false,\r\n              \"aot\": true,\r\n              \"extractLicenses\": true,\r\n              \"vendorChunk\": false,\r\n              \"buildOptimizer\": true,\r\n              \"budgets\": [\r\n                {\r\n                  \"type\": \"initial\",\r\n                  \"maximumWarning\": \"2mb\",\r\n                  \"maximumError\": \"5mb\"\r\n                },\r\n                {\r\n                  \"type\": \"anyComponentStyle\",\r\n                  \"maximumWarning\": \"6kb\",\r\n                  \"maximumError\": \"10kb\"\r\n                }\r\n              ]\r\n            }\r\n          }\r\n        },\r\n        \"serve\": {\r\n          \"builder\": \"@angular-builders/custom-webpack:dev-server\",\r\n          \"options\": {\r\n            \"browserTarget\": \"single-spa-angular-app:build\"\r\n          },\r\n          \"configurations\": {\r\n            \"production\": {\r\n              \"browserTarget\": \"single-spa-angular-app:build:production\"\r\n            }\r\n          }\r\n        },\r\n        \"extract-i18n\": {\r\n          \"builder\": \"@angular-devkit/build-angular:extract-i18n\",\r\n          \"options\": {\r\n            \"browserTarget\": \"single-spa-angular-app:build\"\r\n          }\r\n        },\r\n        \"test\": {\r\n          \"builder\": \"@angular-devkit/build-angular:karma\",\r\n          \"options\": {\r\n            \"main\": \"src/test.ts\",\r\n            \"polyfills\": \"src/polyfills.ts\",\r\n            \"tsConfig\": \"tsconfig.spec.json\",\r\n            \"karmaConfig\": \"karma.conf.js\",\r\n            \"assets\": [\r\n              \"src/favicon.ico\",\r\n              \"src/assets\"\r\n            ],\r\n            \"styles\": [\r\n              \"src/styles.scss\"\r\n            ],\r\n            \"scripts\": []\r\n          }\r\n        },\r\n        \"lint\": {\r\n          \"builder\": \"@angular-devkit/build-angular:tslint\",\r\n          \"options\": {\r\n            \"tsConfig\": [\r\n              \"tsconfig.app.json\",\r\n              \"tsconfig.spec.json\",\r\n              \"e2e/tsconfig.json\"\r\n            ],\r\n            \"exclude\": [\r\n              \"**/node_modules/**\"\r\n            ]\r\n          }\r\n        },\r\n        \"e2e\": {\r\n          \"builder\": \"@angular-devkit/build-angular:protractor\",\r\n          \"options\": {\r\n            \"protractorConfig\": \"e2e/protractor.conf.js\",\r\n            \"devServerTarget\": \"single-spa-angular-app:serve\"\r\n          },\r\n          \"configurations\": {\r\n            \"production\": {\r\n              \"devServerTarget\": \"single-spa-angular-app:serve:production\"\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n  },\r\n  \"defaultProject\": \"single-spa-angular-app\"\r\n}\r\n```\r\n\r\nThe essential config is in **@angular-builders/custom-webpack:browser** builder. Be care of this config is autogenerated when install **single-spa-angular**.\r\n\r\n### extra-webpack.config.js\r\n\r\n```javascript\r\nconst singleSpaAngularWebpack = require('single-spa-angular/lib/webpack').default\r\n\r\nmodule.exports = (angularWebpackConfig, options) =\u003e {\r\n  const singleSpaWebpackConfig = singleSpaAngularWebpack(angularWebpackConfig, options)\r\n\r\n  // Feel free to modify this webpack config however you'd like to\r\n  return singleSpaWebpackConfig\r\n}\r\n```\r\n\r\n### package.json\r\n\r\n```json\r\n{\r\n  \"name\": \"single-spa-angular-app\",\r\n  \"version\": \"0.1.5\",\r\n  \"description\": \"Angular v8 application with two example pages for be included in a single-spa application as registered app.\",\r\n  \"main\": \"dist/main-es2015.js\",\r\n  \"scripts\": {\r\n    \"ng\": \"ng\",\r\n    \"test\": \"ng test\",\r\n    \"lint\": \"ng lint\",\r\n    \"build:single-spa\": \"ng build --prod\"\r\n  },\r\n  \"dependencies\": {\r\n    \"@angular-builders/custom-webpack\": \"8.4.1\",\r\n    \"@angular/common\": \"8.2.14\",\r\n    \"@angular/compiler\": \"8.2.14\",\r\n    \"@angular/core\": \"8.2.14\",\r\n    \"@angular/forms\": \"8.2.14\",\r\n    \"@angular/platform-browser\": \"8.2.14\",\r\n    \"@angular/platform-browser-dynamic\": \"8.2.14\",\r\n    \"@angular/router\": \"8.2.14\",\r\n    \"rxjs\": \"6.4.0\",\r\n    \"single-spa-angular\": \"3.1.0\",\r\n    \"tslib\": \"1.10.0\",\r\n    \"zone.js\": \"0.9.1\"\r\n  },\r\n  \"devDependencies\": {\r\n    \"@angular-devkit/build-angular\": \"0.803.23\",\r\n    \"@angular-devkit/build-ng-packagr\": \"0.803.23\",\r\n    \"@angular/cli\": \"8.3.23\",\r\n    \"@angular/compiler-cli\": \"8.2.14\",\r\n    \"@angular/language-service\": \"8.2.14\",\r\n    \"@types/node\": \"8.9.5\",\r\n    \"@types/jasmine\": \"3.3.16\",\r\n    \"@types/jasminewd2\": \"2.0.8\",\r\n    \"codelyzer\": \"5.2.1\",\r\n    \"jasmine-core\": \"3.4.0\",\r\n    \"jasmine-spec-reporter\": \"4.2.1\",\r\n    \"karma\": \"4.1.0\",\r\n    \"karma-chrome-launcher\": \"2.2.0\",\r\n    \"karma-coverage-istanbul-reporter\": \"2.0.6\",\r\n    \"karma-jasmine\": \"2.0.1\",\r\n    \"karma-jasmine-html-reporter\": \"1.5.1\",\r\n    \"ng-packagr\": \"5.7.1\",\r\n    \"protractor\": \"5.4.2\",\r\n    \"ts-node\": \"7.0.1\",\r\n    \"tsickle\": \"0.37.1\",\r\n    \"tslint\": \"5.15.0\",\r\n    \"typescript\": \"3.5.3\"\r\n  }\r\n}\r\n```\r\n\r\nThere are several scripts in this project:\r\n\r\n- **ng**: for use global ng cli\r\n- **test**: use for run unit tests\r\n- **lint**: for run **eslint** in all project\r\n- **build:single-spa**: for compile the application and build it as a **libray** in **umd** format\r\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjualoppaz%2Fsingle-spa-angular-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjualoppaz%2Fsingle-spa-angular-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjualoppaz%2Fsingle-spa-angular-app/lists"}