├── apps ├── .gitkeep ├── conduit │ ├── src │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── app │ │ │ ├── app.component.css │ │ │ ├── components │ │ │ │ ├── footer │ │ │ │ │ ├── footer.component.css │ │ │ │ │ ├── footer.component.html │ │ │ │ │ └── footer.component.ts │ │ │ │ └── header │ │ │ │ │ ├── header.component.css │ │ │ │ │ ├── header.component.ts │ │ │ │ │ └── header.component.html │ │ │ ├── app.component.html │ │ │ ├── app.component.ts │ │ │ ├── routing.module.ts │ │ │ ├── app.component.spec.ts │ │ │ └── app.module.ts │ │ ├── test-setup.ts │ │ ├── environments │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── styles.css │ │ ├── favicon.ico │ │ ├── main.ts │ │ ├── index.html │ │ └── polyfills.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tslint.json │ ├── tsconfig.spec.json │ ├── jest.config.js │ └── .browserslistrc └── conduit-e2e │ ├── src │ ├── support │ │ ├── app.po.ts │ │ ├── index.ts │ │ └── commands.ts │ ├── fixtures │ │ └── example.json │ ├── integration │ │ └── app.spec.ts │ └── plugins │ │ └── index.js │ ├── tslint.json │ ├── tsconfig.json │ ├── tsconfig.e2e.json │ └── cypress.json ├── libs ├── .gitkeep ├── home │ ├── src │ │ ├── lib │ │ │ ├── home.page.css │ │ │ ├── components │ │ │ │ └── tag-list │ │ │ │ │ ├── tag-list.component.css │ │ │ │ │ ├── +state │ │ │ │ │ ├── tags.actions.ts │ │ │ │ │ ├── tags.selectors.ts │ │ │ │ │ └── tags.state.ts │ │ │ │ │ ├── tag-list.component.html │ │ │ │ │ ├── tag-list.module.ts │ │ │ │ │ └── tag-list.component.ts │ │ │ ├── home.module.ts │ │ │ ├── home.page.ts │ │ │ └── home.page.html │ │ ├── index.ts │ │ └── test-setup.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── login │ ├── src │ │ ├── lib │ │ │ ├── login.page.scss │ │ │ ├── login.module.ts │ │ │ ├── login.page.ts │ │ │ └── login.page.html │ │ ├── index.ts │ │ └── test-setup.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── profile │ ├── src │ │ ├── lib │ │ │ ├── profile.page.scss │ │ │ ├── components │ │ │ │ ├── my-articles │ │ │ │ │ ├── my-articles.component.scss │ │ │ │ │ ├── my-articles.component.html │ │ │ │ │ └── my-articles.component.ts │ │ │ │ └── my-favorite-articles │ │ │ │ │ ├── my-favorite-articles.component.scss │ │ │ │ │ ├── my-favorite-articles.component.html │ │ │ │ │ └── my-favorite-articles.component.ts │ │ │ ├── +state │ │ │ │ ├── profile.actions.ts │ │ │ │ ├── profile.model.ts │ │ │ │ ├── profile.selectors.ts │ │ │ │ └── profile.state.ts │ │ │ ├── profile.page.ts │ │ │ ├── profile.module.ts │ │ │ └── profile.page.html │ │ ├── index.ts │ │ └── test-setup.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── register │ ├── src │ │ ├── lib │ │ │ ├── register.page.scss │ │ │ ├── register.module.ts │ │ │ ├── register.page.ts │ │ │ └── register.page.html │ │ ├── test-setup.ts │ │ └── index.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── settings │ ├── src │ │ ├── lib │ │ │ ├── settings │ │ │ │ ├── settings.page.scss │ │ │ │ ├── settings.page.ts │ │ │ │ └── settings.page.html │ │ │ ├── settings.module.ts │ │ │ └── guards │ │ │ │ └── settings │ │ │ │ └── settings.guard.ts │ │ ├── test-setup.ts │ │ └── index.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── utils │ ├── src │ │ ├── index.ts │ │ └── lib │ │ │ └── utils.ts │ ├── tslint.json │ ├── README.md │ ├── tsconfig.lib.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── jest.config.js ├── editor │ ├── src │ │ ├── lib │ │ │ ├── pages │ │ │ │ ├── edit-article │ │ │ │ │ ├── edit-article.page.scss │ │ │ │ │ ├── edit-article.page.html │ │ │ │ │ └── edit-article.page.ts │ │ │ │ └── create-article │ │ │ │ │ ├── create-article.page.scss │ │ │ │ │ ├── create-article.page.html │ │ │ │ │ └── create-article.page.ts │ │ │ ├── components │ │ │ │ └── article-form │ │ │ │ │ ├── article-form.component.scss │ │ │ │ │ ├── article-form.component.html │ │ │ │ │ └── article-form.component.ts │ │ │ ├── +state │ │ │ │ ├── editor.selectors.ts │ │ │ │ ├── editor.actions.ts │ │ │ │ └── editor.state.ts │ │ │ └── editor.module.ts │ │ ├── test-setup.ts │ │ └── index.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── data-access │ ├── src │ │ ├── test-setup.ts │ │ ├── lib │ │ │ ├── +state │ │ │ │ ├── auth.model.ts │ │ │ │ ├── auth.selectors.ts │ │ │ │ ├── auth.actions.ts │ │ │ │ └── auth.state.ts │ │ │ ├── data-access.module.ts │ │ │ ├── conduit-api.model.ts │ │ │ └── conduit-api.service.ts │ │ ├── index.ts │ │ └── ngxs-next.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js └── article-list │ ├── src │ ├── test-setup.ts │ ├── lib │ │ ├── article-list.component.scss │ │ ├── components │ │ │ └── article-list-item │ │ │ │ ├── article-list-item.component.scss │ │ │ │ ├── article-list-item.component.ts │ │ │ │ └── article-list-item.component.html │ │ ├── +state │ │ │ ├── article-list.model.ts │ │ │ ├── article-list.actions.ts │ │ │ ├── article-list.selectors.ts │ │ │ ├── ngxs-next.ts │ │ │ └── article-list.state.ts │ │ ├── article-list.module.ts │ │ ├── article-list.component.html │ │ └── article-list.component.ts │ └── index.ts │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.spec.json │ ├── tslint.json │ ├── tsconfig.lib.json │ └── jest.config.js ├── tools ├── schematics │ └── .gitkeep └── tsconfig.tools.json ├── .commitlintrc.json ├── logo.png ├── favicon.ico ├── .prettierignore ├── .huskyrc.json ├── .lintstagedrc.json ├── jest.config.js ├── .editorconfig ├── .prettierrc ├── .gitignore ├── readme.md ├── nx.json ├── tsconfig.base.json ├── .github └── workflows │ └── master.yml ├── tslint.json ├── package.json ├── decorate-angular-cli.js ├── angular.json └── FRONTEND_INSTRUCTIONS.md /apps/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tools/schematics/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/conduit/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.page.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/conduit/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/login/src/lib/login.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/profile/src/lib/profile.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/register/src/lib/register.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/settings/src/lib/settings/settings.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/utils'; 2 | -------------------------------------------------------------------------------- /apps/conduit/src/app/components/footer/footer.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/conduit/src/app/components/header/header.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/editor/src/lib/pages/edit-article/edit-article.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/editor/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/home/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/home.module'; 2 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/tag-list.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/home/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/login/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/login.module'; 2 | -------------------------------------------------------------------------------- /libs/login/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /apps/conduit/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/data-access/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/editor/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/editor.module'; 2 | -------------------------------------------------------------------------------- /libs/editor/src/lib/pages/create-article/create-article.page.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/profile/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/profile.module'; 2 | -------------------------------------------------------------------------------- /libs/profile/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/register/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/settings/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/article-list/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular'; 2 | -------------------------------------------------------------------------------- /libs/editor/src/lib/components/article-form/article-form.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/profile/src/lib/components/my-articles/my-articles.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /libs/register/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/register.module'; 2 | -------------------------------------------------------------------------------- /libs/settings/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/settings.module'; 2 | -------------------------------------------------------------------------------- /.commitlintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@commitlint/config-conventional"] 3 | } 4 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngxs/realworld-angular-nx-ngxs/HEAD/logo.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngxs/realworld-angular-nx-ngxs/HEAD/favicon.ico -------------------------------------------------------------------------------- /libs/profile/src/lib/components/my-favorite-articles/my-favorite-articles.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/conduit-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/article-list.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /apps/conduit/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /apps/conduit/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /libs/utils/tslint.json: -------------------------------------------------------------------------------- 1 | { "extends": "../../tslint.json", "linterOptions": { "exclude": ["!**/*"] }, "rules": {} } 2 | -------------------------------------------------------------------------------- /apps/conduit/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngxs/realworld-angular-nx-ngxs/HEAD/apps/conduit/src/favicon.ico -------------------------------------------------------------------------------- /apps/conduit-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /apps/conduit/src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/+state/tags.actions.ts: -------------------------------------------------------------------------------- 1 | export class LoadTags { 2 | static readonly type = '[Tags] Load'; 3 | } 4 | -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "lint-staged", 4 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /apps/conduit-e2e/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "linterOptions": { "exclude": ["!**/*"] }, 4 | "rules": {} 5 | } 6 | -------------------------------------------------------------------------------- /libs/profile/src/lib/components/my-articles/my-articles.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /libs/utils/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | export function parseError(err) { 2 | return Object.keys(err.error.errors).map((key) => `${key} ${err.error.errors[key]}`); 3 | } 4 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/components/article-list-item/article-list-item.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | } 4 | 5 | .page-link { 6 | cursor: pointer; 7 | } 8 | -------------------------------------------------------------------------------- /libs/profile/src/lib/components/my-favorite-articles/my-favorite-articles.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /libs/home/README.md: -------------------------------------------------------------------------------- 1 | # home 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test home` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/login/README.md: -------------------------------------------------------------------------------- 1 | # login 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test login` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/editor/README.md: -------------------------------------------------------------------------------- 1 | # editor 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test editor` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/profile/README.md: -------------------------------------------------------------------------------- 1 | # profile 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test profile` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/register/README.md: -------------------------------------------------------------------------------- 1 | # register 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test register` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/settings/README.md: -------------------------------------------------------------------------------- 1 | # settings 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test settings` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/article-list/README.md: -------------------------------------------------------------------------------- 1 | # article-list 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test article-list` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/data-access/README.md: -------------------------------------------------------------------------------- 1 | # data-access 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test data-access` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /libs/utils/README.md: -------------------------------------------------------------------------------- 1 | # utils 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `ng test utils` to execute the unit tests via [Jest](https://jestjs.io). 8 | -------------------------------------------------------------------------------- /apps/conduit-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.e2e.json" 8 | } 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /apps/conduit/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 | -------------------------------------------------------------------------------- /libs/article-list/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/article-list.module'; 2 | export * from './lib/+state/article-list.actions'; 3 | export * from './lib/+state/article-list.selectors'; 4 | export * from './lib/+state/article-list.model'; 5 | -------------------------------------------------------------------------------- /libs/utils/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "exclude": ["**/*.spec.ts"], 8 | "include": ["**/*.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.ts": ["prettier --write", "git add"], 3 | "*.html": ["prettier --write", "git add"], 4 | "*.scss": ["prettier --write", "git add"], 5 | "*.json": ["prettier --write", "git add"], 6 | "*.md": ["prettier --write", "git add"] 7 | } 8 | -------------------------------------------------------------------------------- /libs/editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/home/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/login/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/conduit/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 | } 14 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/+state/auth.model.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | email: string; 3 | token: string; 4 | username: string; 5 | bio: string; 6 | image: string; 7 | } 8 | 9 | export interface AuthStateModel { 10 | user: User; 11 | errors: string[]; 12 | } 13 | -------------------------------------------------------------------------------- /libs/data-access/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/tag-list.component.html: -------------------------------------------------------------------------------- 1 | 2 |

Popular Tags

3 | 4 |
5 | {{ tag }} 6 |
7 |
8 | -------------------------------------------------------------------------------- /libs/profile/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/register/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/settings/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /libs/article-list/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "files": [], 4 | "include": [], 5 | "references": [ 6 | { 7 | "path": "./tsconfig.lib.json" 8 | }, 9 | { 10 | "path": "./tsconfig.spec.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /apps/conduit/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'conduit'; 10 | } 11 | -------------------------------------------------------------------------------- /apps/conduit-e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js"] 10 | } 11 | -------------------------------------------------------------------------------- /libs/data-access/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/conduit-api.service'; 2 | export * from './lib/conduit-api.model'; 3 | export * from './lib/+state/auth.actions'; 4 | export * from './lib/+state/auth.selectors'; 5 | export * from './lib/+state/auth.model'; 6 | export * from './lib/data-access.module'; 7 | -------------------------------------------------------------------------------- /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 | }, 10 | "include": ["**/*.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /apps/conduit/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "app", "camelCase"], 5 | "component-selector": [true, "element", "app", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], 3 | transform: { 4 | '^.+\\.(ts|js|html)$': 'ts-jest', 5 | }, 6 | resolver: '@nrwl/jest/plugins/resolver', 7 | moduleFileExtensions: ['ts', 'js', 'html'], 8 | coverageReporters: ['html'], 9 | }; 10 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/+state/tags.selectors.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | 3 | import { TagsState } from './tags.state'; 4 | 5 | export class TagsSelectors { 6 | @Selector([TagsState]) 7 | static getTags(state: string[]) { 8 | return state; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/home/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 | -------------------------------------------------------------------------------- /libs/login/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/conduit/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 | -------------------------------------------------------------------------------- /libs/editor/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 | -------------------------------------------------------------------------------- /libs/home/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "conduit", "camelCase"], 5 | "component-selector": [true, "element", "conduit", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/profile/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 | -------------------------------------------------------------------------------- /libs/register/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 | -------------------------------------------------------------------------------- /libs/settings/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 | -------------------------------------------------------------------------------- /libs/article-list/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 | -------------------------------------------------------------------------------- /libs/data-access/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 | -------------------------------------------------------------------------------- /libs/login/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "conduit", "camelCase"], 5 | "component-selector": [true, "element", "conduit", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 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 | -------------------------------------------------------------------------------- /libs/article-list/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "conduit", "camelCase"], 5 | "component-selector": [true, "element", "conduit", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/data-access/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "conduit", "camelCase"], 5 | "component-selector": [true, "element", "conduit", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/utils/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": ["**/*.spec.ts", "**/*.spec.tsx", "**/*.spec.js", "**/*.spec.jsx", "**/*.d.ts"] 9 | } 10 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "htmlWhitespaceSensitivity": "css", 9 | "jsxBracketSameLine": true, 10 | "bracketSpacing": true, 11 | "arrowParens": "always", 12 | "proseWrap": "always" 13 | } -------------------------------------------------------------------------------- /libs/editor/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "realworldAngularNxNgxs", "camelCase"], 5 | "component-selector": [true, "element", "realworld-angular-nx-ngxs", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/profile/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "realworldAngularNxNgxs", "camelCase"], 5 | "component-selector": [true, "element", "realworld-angular-nx-ngxs", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/register/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "realworldAngularNxNgxs", "camelCase"], 5 | "component-selector": [true, "element", "realworld-angular-nx-ngxs", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/settings/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tslint.json", 3 | "rules": { 4 | "directive-selector": [true, "attribute", "realworldAngularNxNgxs", "camelCase"], 5 | "component-selector": [true, "element", "realworld-angular-nx-ngxs", "kebab-case"] 6 | }, 7 | "linterOptions": { 8 | "exclude": ["!**/*"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /libs/editor/src/lib/pages/create-article/create-article.page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/data-access.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NgxsModule } from '@ngxs/store'; 4 | import { AuthState } from './+state/auth.state'; 5 | 6 | @NgModule({ 7 | imports: [CommonModule, NgxsModule.forFeature([AuthState])] 8 | }) 9 | export class DataAccessModule {} 10 | -------------------------------------------------------------------------------- /apps/conduit/src/app/components/footer/footer.component.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /libs/editor/src/lib/pages/edit-article/edit-article.page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /libs/utils/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'utils', 3 | preset: '../../jest.config.js', 4 | globals: { 5 | 'ts-jest': { 6 | tsConfig: '/tsconfig.spec.json' 7 | } 8 | }, 9 | transform: { 10 | '^.+\\.[tj]sx?$': 'ts-jest' 11 | }, 12 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], 13 | coverageDirectory: '../../coverage/libs/utils' 14 | }; 15 | -------------------------------------------------------------------------------- /libs/editor/src/lib/+state/editor.selectors.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | import { EditorState, EditorStateModel } from './editor.state'; 3 | 4 | export class EditorSelectors { 5 | @Selector([EditorState]) 6 | static errors(state: EditorStateModel) { 7 | return state.errors; 8 | } 9 | 10 | @Selector([EditorState]) 11 | static article(state: EditorStateModel) { 12 | return state.article; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /apps/conduit/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() 12 | .bootstrapModule(AppModule) 13 | .catch((err) => console.error(err)); 14 | -------------------------------------------------------------------------------- /apps/conduit/src/app/components/footer/footer.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-footer', 5 | templateUrl: './footer.component.html', 6 | styleUrls: ['./footer.component.css'], 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | }) 9 | export class FooterComponent implements OnInit { 10 | constructor() {} 11 | 12 | ngOnInit(): void {} 13 | } 14 | -------------------------------------------------------------------------------- /apps/conduit-e2e/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileServerFolder": ".", 3 | "fixturesFolder": "./src/fixtures", 4 | "integrationFolder": "./src/integration", 5 | "modifyObstructiveCode": false, 6 | "pluginsFile": "./src/plugins/index", 7 | "supportFile": "./src/support/index.ts", 8 | "video": true, 9 | "videosFolder": "../../dist/cypress/apps/conduit-e2e/videos", 10 | "screenshotsFolder": "../../dist/cypress/apps/conduit-e2e/screenshots", 11 | "chromeWebSecurity": false 12 | } 13 | -------------------------------------------------------------------------------- /apps/conduit-e2e/src/integration/app.spec.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('conduit', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome to conduit!'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /libs/editor/src/lib/+state/editor.actions.ts: -------------------------------------------------------------------------------- 1 | import { Article } from '@realworld-angular-nx-ngxs/data-access'; 2 | 3 | export class CreateArticle { 4 | static readonly type = '[Editor] Create'; 5 | constructor(public payload: Article) {} 6 | } 7 | 8 | export class EditArticle { 9 | static readonly type = '[Editor] Edit'; 10 | constructor(public payload: Article) {} 11 | } 12 | 13 | export class GetArticle { 14 | static readonly type = '[Editor] GetArticle'; 15 | constructor(public payload: string) {} 16 | } 17 | -------------------------------------------------------------------------------- /libs/login/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/editor/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/profile/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/register/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/settings/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/data-access/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true 15 | }, 16 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 17 | "include": ["**/*.ts"] 18 | } 19 | -------------------------------------------------------------------------------- /libs/login/src/lib/login.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { LoginPage } from './login.page'; 4 | import { RouterModule } from '@angular/router'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | RouterModule.forChild([{ path: '', pathMatch: 'full', component: LoginPage }]), 11 | ReactiveFormsModule 12 | ], 13 | declarations: [LoginPage] 14 | }) 15 | export class LoginModule {} 16 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/tag-list.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { NgxsModule } from '@ngxs/store'; 4 | 5 | import { TagsState } from './+state/tags.state'; 6 | import { TagListComponent } from './tag-list.component'; 7 | 8 | const COMPONENTS = [TagListComponent]; 9 | 10 | @NgModule({ 11 | imports: [CommonModule, NgxsModule.forFeature([TagsState])], 12 | declarations: COMPONENTS, 13 | exports: COMPONENTS 14 | }) 15 | export class TagListModule {} 16 | -------------------------------------------------------------------------------- /libs/profile/src/lib/+state/profile.actions.ts: -------------------------------------------------------------------------------- 1 | import { ListConfig } from '@realworld-angular-nx-ngxs/data-access'; 2 | 3 | export namespace ProfileActions { 4 | export class Get { 5 | static readonly type = '[Profile] Get'; 6 | constructor(public user: string) {} 7 | } 8 | export class Follow { 9 | static readonly type = '[Profile] Follow'; 10 | constructor(public user: string) {} 11 | } 12 | export class UnFollow { 13 | static readonly type = '[Profile] UnFollow'; 14 | constructor(public user: string) {} 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /libs/register/src/lib/register.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RegisterPage } from './register.page'; 4 | import { RouterModule } from '@angular/router'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | 7 | @NgModule({ 8 | imports: [ 9 | CommonModule, 10 | ReactiveFormsModule, 11 | RouterModule.forChild([{ path: '', pathMatch: 'full', component: RegisterPage }]) 12 | ], 13 | declarations: [RegisterPage] 14 | }) 15 | export class RegisterModule {} 16 | -------------------------------------------------------------------------------- /libs/home/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictTemplates": true 17 | }, 18 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 19 | "include": ["**/*.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /libs/article-list/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "target": "es2015", 6 | "declaration": true, 7 | "inlineSources": true, 8 | "types": [], 9 | "lib": ["dom", "es2018"] 10 | }, 11 | "angularCompilerOptions": { 12 | "skipTemplateCodegen": true, 13 | "strictMetadataEmit": true, 14 | "enableResourceInlining": true, 15 | "fullTemplateTypeCheck": true, 16 | "strictTemplates": true 17 | }, 18 | "exclude": ["src/test-setup.ts", "**/*.spec.ts"], 19 | "include": ["**/*.ts"] 20 | } 21 | -------------------------------------------------------------------------------- /libs/profile/src/lib/+state/profile.model.ts: -------------------------------------------------------------------------------- 1 | import { Article, ListConfig, ListType } from '@realworld-angular-nx-ngxs/data-access'; 2 | export interface ArticleListModel { 3 | articles: Article[]; 4 | articlesCount: number; 5 | listConfig: ListConfig; 6 | } 7 | 8 | export interface PageInfo { 9 | type: ListType; 10 | currentPage: number; 11 | totalPages: number[]; 12 | } 13 | 14 | export const articleListInitialState: ArticleListModel = { 15 | listConfig: { 16 | type: 'ALL', 17 | currentPage: 0, 18 | filters: { 19 | limit: 10 20 | } 21 | }, 22 | articles: null, 23 | articlesCount: 0 24 | }; 25 | -------------------------------------------------------------------------------- /libs/profile/src/lib/+state/profile.selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, Selector } from '@ngxs/store'; 2 | import { AuthSelectors, Profile } from '@realworld-angular-nx-ngxs/data-access'; 3 | import { ProfileState, ProfileStateModel } from './profile.state'; 4 | 5 | export class ProfileSelectors { 6 | @Selector([ProfileState]) 7 | static profile(state: ProfileStateModel) { 8 | return state?.profile; 9 | } 10 | 11 | static itsMe = createSelector( 12 | [ProfileSelectors.profile, AuthSelectors.username], 13 | (profile: Profile, username: string) => { 14 | return profile?.username === username; 15 | } 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { ArticleListModule } from '@realworld-angular-nx-ngxs/article-list'; 5 | 6 | import { TagListModule } from './components/tag-list/tag-list.module'; 7 | import { HomePage } from './home.page'; 8 | 9 | @NgModule({ 10 | imports: [ 11 | CommonModule, 12 | RouterModule.forChild([{ path: '', pathMatch: 'full', component: HomePage }]), 13 | TagListModule, 14 | ArticleListModule 15 | ], 16 | declarations: [HomePage] 17 | }) 18 | export class HomeModule {} 19 | -------------------------------------------------------------------------------- /libs/settings/src/lib/settings.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { SettingsPage } from './settings/settings.page'; 4 | import { RouterModule } from '@angular/router'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | import { SettingsGuard } from './guards/settings/settings.guard'; 7 | 8 | @NgModule({ 9 | imports: [ 10 | CommonModule, 11 | RouterModule.forChild([{ path: '', pathMatch: 'full', component: SettingsPage, canActivate: [SettingsGuard] }]), 12 | ReactiveFormsModule 13 | ], 14 | declarations: [SettingsPage] 15 | }) 16 | export class SettingsModule {} 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /apps/conduit-e2e/src/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/+state/tags.state.ts: -------------------------------------------------------------------------------- 1 | import { tap } from 'rxjs/operators'; 2 | 3 | import { Injectable } from '@angular/core'; 4 | import { Action, State, StateContext } from '@ngxs/store'; 5 | import { ConduitApiService } from '@realworld-angular-nx-ngxs/data-access'; 6 | 7 | import { LoadTags } from './tags.actions'; 8 | 9 | @State({ 10 | name: 'tags', 11 | defaults: [] 12 | }) 13 | @Injectable() 14 | export class TagsState { 15 | constructor(private conduitApi: ConduitApiService) {} 16 | 17 | @Action(LoadTags) 18 | loadTags({ setState }: StateContext) { 19 | return this.conduitApi.getTags().pipe(tap((tags) => setState(tags))); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/conduit/src/app/components/header/header.component.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | import { Component, OnInit } from '@angular/core'; 4 | import { Select, Store } from '@ngxs/store'; 5 | import { AuthSelectors } from '@realworld-angular-nx-ngxs/data-access'; 6 | 7 | @Component({ 8 | selector: 'app-header', 9 | templateUrl: './header.component.html', 10 | styleUrls: ['./header.component.css'] 11 | }) 12 | export class HeaderComponent { 13 | @Select(AuthSelectors.loggedIn) loggedIn$: Observable; 14 | @Select(AuthSelectors.loggedOut) loggedOut$: Observable; 15 | @Select(AuthSelectors.username) username$: Observable; 16 | 17 | constructor() {} 18 | } 19 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/+state/article-list.model.ts: -------------------------------------------------------------------------------- 1 | import { Article, ListConfig } from '@realworld-angular-nx-ngxs/data-access'; 2 | import { ListType } from '../../../../data-access/src/lib/conduit-api.model'; 3 | 4 | export interface ArticleListModel { 5 | articles: Article[]; 6 | articlesCount: number; 7 | listConfig: ListConfig; 8 | } 9 | 10 | export interface PageInfo { 11 | type: ListType; 12 | currentPage: number; 13 | totalPages: number[]; 14 | } 15 | 16 | export const articleListInitialState: ArticleListModel = { 17 | listConfig: { 18 | type: 'ALL', 19 | currentPage: 0, 20 | filters: { 21 | limit: 10 22 | } 23 | }, 24 | articles: null, 25 | articlesCount: 0 26 | }; 27 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/components/article-list-item/article-list-item.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; 2 | import { Article } from '@realworld-angular-nx-ngxs/data-access'; 3 | 4 | @Component({ 5 | selector: 'conduit-article-list-item', 6 | templateUrl: './article-list-item.component.html', 7 | styleUrls: ['./article-list-item.component.scss'], 8 | changeDetection: ChangeDetectionStrategy.OnPush 9 | }) 10 | export class ArticleListItemComponent { 11 | @Input() article: Article; 12 | @Output() toggleFavorite: EventEmitter
= new EventEmitter(); 13 | @Output() navigateToArticle: EventEmitter = new EventEmitter(); 14 | } 15 | -------------------------------------------------------------------------------- /apps/conduit/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 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/article-list.module.ts: -------------------------------------------------------------------------------- 1 | import { CommonModule } from '@angular/common'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule } from '@angular/router'; 4 | import { NgxsModule } from '@ngxs/store'; 5 | 6 | import { ArticleListState } from './+state/article-list.state'; 7 | import { ArticleListComponent } from './article-list.component'; 8 | import { 9 | ArticleListItemComponent, 10 | } from './components/article-list-item/article-list-item.component'; 11 | 12 | @NgModule({ 13 | imports: [CommonModule, RouterModule, NgxsModule.forFeature([ArticleListState])], 14 | declarations: [ArticleListComponent, ArticleListItemComponent], 15 | exports: [ArticleListComponent] 16 | }) 17 | export class ArticleListModule {} 18 | -------------------------------------------------------------------------------- /libs/home/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'home', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/home', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/profile/src/lib/components/my-articles/my-articles.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Store } from '@ngxs/store'; 3 | import { ArticleListActions, ArticleListSelectors } from '@realworld-angular-nx-ngxs/article-list'; 4 | 5 | @Component({ 6 | selector: 'conduit-my-articles', 7 | templateUrl: './my-articles.component.html', 8 | styleUrls: ['./my-articles.component.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class MyArticlesComponent implements OnInit { 12 | myArticles$ = this.store.select(ArticleListSelectors.myArticles); 13 | 14 | constructor(private store: Store) {} 15 | 16 | ngOnInit() { 17 | this.store.dispatch(new ArticleListActions.GetMyArticles()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/editor/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'editor', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/editor', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/login/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'login', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/login', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/profile/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'profile', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/profile', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /apps/conduit/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'conduit', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer', 12 | ], 13 | }, 14 | }, 15 | coverageDirectory: '../../coverage/apps/conduit', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js', 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /libs/register/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'register', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/register', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/settings/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'settings', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/settings', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/article-list/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'article-list', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/article-list', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/data-access/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | name: 'data-access', 3 | preset: '../../jest.config.js', 4 | setupFilesAfterEnv: ['/src/test-setup.ts'], 5 | globals: { 6 | 'ts-jest': { 7 | tsConfig: '/tsconfig.spec.json', 8 | stringifyContentPathRegex: '\\.(html|svg)$', 9 | astTransformers: [ 10 | 'jest-preset-angular/build/InlineFilesTransformer', 11 | 'jest-preset-angular/build/StripStylesTransformer' 12 | ] 13 | } 14 | }, 15 | coverageDirectory: '../../coverage/libs/data-access2', 16 | snapshotSerializers: [ 17 | 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 18 | 'jest-preset-angular/build/AngularSnapshotSerializer.js', 19 | 'jest-preset-angular/build/HTMLCommentSerializer.js' 20 | ] 21 | }; 22 | -------------------------------------------------------------------------------- /libs/home/src/lib/components/tag-list/tag-list.component.ts: -------------------------------------------------------------------------------- 1 | import { Observable } from 'rxjs'; 2 | 3 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 4 | import { Select, Store } from '@ngxs/store'; 5 | 6 | import { LoadTags } from './+state/tags.actions'; 7 | import { TagsSelectors } from './+state/tags.selectors'; 8 | 9 | @Component({ 10 | selector: 'conduit-tag-list', 11 | templateUrl: './tag-list.component.html', 12 | styleUrls: ['./tag-list.component.css'], 13 | changeDetection: ChangeDetectionStrategy.OnPush 14 | }) 15 | export class TagListComponent implements OnInit { 16 | @Select(TagsSelectors.getTags) tags$: Observable; 17 | 18 | constructor(private store: Store) {} 19 | 20 | ngOnInit(): void { 21 | this.store.dispatch(new LoadTags()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/profile/src/lib/components/my-favorite-articles/my-favorite-articles.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Store } from '@ngxs/store'; 3 | import { ArticleListActions, ArticleListSelectors } from '@realworld-angular-nx-ngxs/article-list'; 4 | 5 | @Component({ 6 | selector: 'conduit-my-favorite-articles', 7 | templateUrl: './my-favorite-articles.component.html', 8 | styleUrls: ['./my-favorite-articles.component.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush 10 | }) 11 | export class MyFavoriteArticlesComponent implements OnInit { 12 | myFavArticles$ = this.store.select(ArticleListSelectors.myFavArticles); 13 | 14 | constructor(private store: Store) {} 15 | 16 | ngOnInit() { 17 | this.store.dispatch(new ArticleListActions.GetMyFavArticles()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/+state/auth.selectors.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | 3 | import { createPropertySelectors } from '../../ngxs-next'; 4 | import { AuthStateModel, User } from './auth.model'; 5 | import { AuthState } from './auth.state'; 6 | 7 | export class AuthSelectors { 8 | static slices = createPropertySelectors(AuthState); 9 | 10 | @Selector([AuthSelectors.slices.user]) 11 | static username(user: User) { 12 | return user?.username; 13 | } 14 | 15 | @Selector([AuthSelectors.slices.user]) 16 | static token(user: User) { 17 | return user?.token; 18 | } 19 | 20 | @Selector([AuthSelectors.slices.user]) 21 | static loggedOut(user: User) { 22 | return !user; 23 | } 24 | 25 | @Selector([AuthSelectors.slices.user]) 26 | static loggedIn(user: User) { 27 | return !!user; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libs/settings/src/lib/guards/settings/settings.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; 3 | import { Navigate } from '@ngxs/router-plugin'; 4 | import { Store } from '@ngxs/store'; 5 | import { AuthSelectors } from '@realworld-angular-nx-ngxs/data-access'; 6 | import { tap } from 'rxjs/operators'; 7 | 8 | @Injectable({ 9 | providedIn: 'root' 10 | }) 11 | export class SettingsGuard implements CanActivate { 12 | constructor(public store: Store) {} 13 | 14 | canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot) { 15 | return this.store.selectOnce(AuthSelectors.loggedIn).pipe( 16 | tap((loggedIn) => { 17 | if (!loggedIn) { 18 | this.store.dispatch(new Navigate(['/'])); 19 | } 20 | }) 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/+state/auth.actions.ts: -------------------------------------------------------------------------------- 1 | import { LoginRequest, RegisterRequest, UpdateAuthUserRequest } from '../conduit-api.model'; 2 | 3 | type LoginPayload = LoginRequest; 4 | type RegisterPayload = RegisterRequest; 5 | type UpdateAuthUserPayload = UpdateAuthUserRequest; 6 | 7 | export class Login { 8 | static readonly type = '[Auth] Login'; 9 | constructor(public payload: LoginPayload) {} 10 | } 11 | 12 | export class Logout { 13 | static readonly type = '[Auth] Logout'; 14 | constructor() {} 15 | } 16 | export class Register { 17 | static readonly type = '[Auth] Register'; 18 | constructor(public payload: RegisterPayload) {} 19 | } 20 | 21 | export class GetAuthUser { 22 | static readonly type = '[Auth] GetAuthUser'; 23 | constructor() {} 24 | } 25 | 26 | export class UpdateAuthUser { 27 | static readonly type = '[Auth] UpdateAuthUser'; 28 | constructor(public payload: UpdateAuthUserPayload) {} 29 | } 30 | -------------------------------------------------------------------------------- /apps/conduit/.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 9-10 # Angular support for IE 9-10 has been deprecated and will be removed as of Angular v11. To opt-in, remove the 'not' prefix on this line. 18 | not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. 19 | -------------------------------------------------------------------------------- /apps/conduit-e2e/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example plugins/index.js can be used to load plugins 3 | // 4 | // You can change the location of this file or turn off loading 5 | // the plugins file with the 'pluginsFile' configuration option. 6 | // 7 | // You can read more here: 8 | // https://on.cypress.io/plugins-guide 9 | // *********************************************************** 10 | 11 | // This function is called when a project is opened or re-opened (e.g. due to 12 | // the project's config changing) 13 | 14 | const { preprocessTypescript } = require('@nrwl/cypress/plugins/preprocessor'); 15 | 16 | module.exports = (on, config) => { 17 | // `on` is used to hook into various events Cypress emits 18 | // `config` is the resolved Cypress config 19 | 20 | // Preprocess Typescript file using Nx helper 21 | on('file:preprocessor', preprocessTypescript(config)); 22 | }; 23 | -------------------------------------------------------------------------------- /libs/profile/src/lib/profile.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { ActivatedRoute } from '@angular/router'; 3 | import { Store } from '@ngxs/store'; 4 | import { ProfileActions } from './+state/profile.actions'; 5 | import { ProfileSelectors } from './+state/profile.selectors'; 6 | 7 | @Component({ 8 | selector: 'conduit-profile', 9 | templateUrl: './profile.page.html', 10 | styleUrls: ['./profile.page.scss'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class ProfilePage implements OnInit { 14 | profile$ = this.store.select(ProfileSelectors.profile); 15 | itsMe$ = this.store.select(ProfileSelectors.itsMe); 16 | 17 | constructor(private store: Store, private activatedRoute: ActivatedRoute) {} 18 | 19 | ngOnInit() { 20 | const user = this.activatedRoute.snapshot.params.user; 21 | this.store.dispatch(new ProfileActions.Get(user)); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ![RealWorld Example App](logo.png) 2 | 3 | > ### Angular | Nx | NGXS codebase containing real world examples (CRUD, auth, advanced patterns, etc) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) spec and API. 4 | 5 | ### [Demo](https://ngxs.github.io/realworld-angular-nx-ngxs/)    [RealWorld](https://github.com/gothinkster/realworld) 6 | 7 | This codebase was created to demonstrate a fully fledged fullstack application built with **Angular/NX/NGXS** including 8 | CRUD operations, authentication, routing, pagination, and more. 9 | 10 | We've gone to great lengths to adhere to the **Angular/NX/NGXS** community styleguides & best practices. 11 | 12 | For more information on how to this works with other frontends/backends, head over to the 13 | [RealWorld](https://github.com/gothinkster/realworld) repo. 14 | 15 | # How it works 16 | 17 | > Describe the general architecture of your app here 18 | 19 | # Getting started 20 | 21 | > npm install, npm start, etc. 22 | -------------------------------------------------------------------------------- /apps/conduit/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Conduit 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/article-list.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | Loading articles... 4 |
5 |
6 | 7 | 8 | 13 | 14 | 15 | 16 |
17 | No articles are here... yet. 18 |
19 |
20 | 21 | 22 | 32 | 33 | -------------------------------------------------------------------------------- /libs/editor/src/lib/editor.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule } from '@angular/router'; 4 | import { CreateArticlePage } from './pages/create-article/create-article.page'; 5 | import { ReactiveFormsModule } from '@angular/forms'; 6 | import { NgxsModule } from '@ngxs/store'; 7 | import { EditorState } from './+state/editor.state'; 8 | import { EditArticlePage } from './pages/edit-article/edit-article.page'; 9 | import { ArticleFormComponent } from './components/article-form/article-form.component'; 10 | 11 | @NgModule({ 12 | imports: [ 13 | CommonModule, 14 | ReactiveFormsModule, 15 | RouterModule.forChild([{ path: '', pathMatch: 'full', component: CreateArticlePage }]), 16 | RouterModule.forChild([{ path: ':slug', pathMatch: 'full', component: EditArticlePage }]), 17 | NgxsModule.forFeature([EditorState]) 18 | ], 19 | declarations: [CreateArticlePage, EditArticlePage, ArticleFormComponent] 20 | }) 21 | export class EditorModule {} 22 | -------------------------------------------------------------------------------- /libs/login/src/lib/login.page.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Navigate } from '@ngxs/router-plugin'; 4 | import { Store } from '@ngxs/store'; 5 | import { AuthSelectors, Login } from '@realworld-angular-nx-ngxs/data-access'; 6 | 7 | @Component({ 8 | selector: 'conduit-login', 9 | templateUrl: './login.page.html', 10 | styleUrls: ['./login.page.scss'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class LoginPage implements OnInit { 14 | form = new FormGroup({ 15 | email: new FormControl('', [Validators.required, Validators.email]), 16 | password: new FormControl('', Validators.required) 17 | }); 18 | errors$ = this.store.select(AuthSelectors.slices.errors); 19 | 20 | constructor(private store: Store) {} 21 | 22 | ngOnInit() {} 23 | 24 | login() { 25 | this.store.dispatch(new Login(this.form.value)).subscribe(() => { 26 | this.store.dispatch(new Navigate(['/home'])); 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /libs/editor/src/lib/pages/create-article/create-article.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Navigate } from '@ngxs/router-plugin'; 3 | import { Select } from '@ngxs/store'; 4 | import { Store } from '@ngxs/store'; 5 | import { Article } from '@realworld-angular-nx-ngxs/data-access'; 6 | import { CreateArticle } from './../../+state/editor.actions'; 7 | import { EditorSelectors } from './../../+state/editor.selectors'; 8 | 9 | @Component({ 10 | selector: 'conduit-editor', 11 | templateUrl: './create-article.page.html', 12 | styleUrls: ['./create-article.page.scss'], 13 | changeDetection: ChangeDetectionStrategy.OnPush 14 | }) 15 | export class CreateArticlePage implements OnInit { 16 | constructor(private store: Store) {} 17 | 18 | ngOnInit() {} 19 | 20 | submit(article: Article) { 21 | this.store.dispatch(new CreateArticle(article)).subscribe(() => { 22 | const slug = this.store.selectSnapshot(EditorSelectors.article).slug; 23 | this.store.dispatch(new Navigate(['/article', slug])); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /libs/register/src/lib/register.page.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; 3 | import { Navigate } from '@ngxs/router-plugin'; 4 | import { Store } from '@ngxs/store'; 5 | import { AuthSelectors, Register } from '@realworld-angular-nx-ngxs/data-access'; 6 | 7 | @Component({ 8 | selector: 'realworld-angular-nx-ngxs-register', 9 | templateUrl: './register.page.html', 10 | styleUrls: ['./register.page.scss'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class RegisterPage implements OnInit { 14 | form = new FormGroup({ 15 | username: new FormControl('', Validators.required), 16 | email: new FormControl('', [Validators.required, Validators.email]), 17 | password: new FormControl('', Validators.required) 18 | }); 19 | errors$ = this.store.select(AuthSelectors.slices.errors); 20 | 21 | constructor(private store: Store) {} 22 | 23 | ngOnInit() {} 24 | 25 | signUp() { 26 | this.store.dispatch(new Register(this.form.value)).subscribe(() => { 27 | this.store.dispatch(new Navigate(['/home'])); 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/+state/article-list.actions.ts: -------------------------------------------------------------------------------- 1 | import { ListConfig } from '@realworld-angular-nx-ngxs/data-access'; 2 | 3 | export namespace ArticleListActions { 4 | export class Load { 5 | static readonly type = '[ArticleList] Load'; 6 | constructor(public readonly offset = 0) {} 7 | } 8 | 9 | export class GetMyArticles { 10 | static readonly type = '[ArticleList] GetMyArticles'; 11 | constructor() {} 12 | } 13 | 14 | export class GetMyFavArticles { 15 | static readonly type = '[ArticleList] GetMyFavArticles'; 16 | constructor() {} 17 | } 18 | export class Favorite { 19 | static readonly type = '[ArticleList] Favorite'; 20 | constructor(public readonly articleSlug: string) {} 21 | } 22 | export class UnFavorite { 23 | static readonly type = '[ArticleList] Unfavorite'; 24 | constructor(public readonly articleSlug: string) {} 25 | } 26 | export class SetPage { 27 | static readonly type = '[ArticleList] Set Page'; 28 | constructor(public readonly page: number) {} 29 | } 30 | export class SetListConfig { 31 | static readonly type = '[ArticleList] Set List Config'; 32 | constructor(public readonly config: ListConfig) {} 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/article-list.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | import { Store } from '@ngxs/store'; 3 | import { Article } from '@realworld-angular-nx-ngxs/data-access'; 4 | import { ArticleListActions } from './+state/article-list.actions'; 5 | import { PageInfo } from './+state/article-list.model'; 6 | 7 | @Component({ 8 | selector: 'conduit-article-list', 9 | templateUrl: './article-list.component.html', 10 | styleUrls: ['./article-list.component.scss'], 11 | changeDetection: ChangeDetectionStrategy.OnPush 12 | }) 13 | export class ArticleListComponent implements OnInit { 14 | @Input() articles: Article[]; 15 | @Input() pageInfo: PageInfo; 16 | @Output() setPage = new EventEmitter(); 17 | 18 | constructor(private store: Store) {} 19 | 20 | ngOnInit(): void { 21 | this.store.dispatch(new ArticleListActions.Load()); 22 | } 23 | 24 | toggleFavorite(article: Article): void { 25 | const action = article.favorited 26 | ? new ArticleListActions.UnFavorite(article.slug) 27 | : new ArticleListActions.Favorite(article.slug); 28 | 29 | this.store.dispatch(action); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/conduit-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // eslint-disable-next-line @typescript-eslint/no-namespace 11 | declare namespace Cypress { 12 | interface Chainable { 13 | login(email: string, password: string): void; 14 | } 15 | } 16 | // 17 | // -- This is a parent command -- 18 | Cypress.Commands.add('login', (email, password) => { 19 | console.log('Custom command example: Login', email, password); 20 | }); 21 | // 22 | // -- This is a child command -- 23 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 24 | // 25 | // 26 | // -- This is a dual command -- 27 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 28 | // 29 | // 30 | // -- This will overwrite an existing command -- 31 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 32 | -------------------------------------------------------------------------------- /libs/editor/src/lib/components/article-form/article-form.component.html: -------------------------------------------------------------------------------- 1 |
    2 |
  • {{ error }}
  • 3 |
4 | 5 |
6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | 20 |
21 |
22 | 23 |
24 |
25 | 28 |
29 |
30 | -------------------------------------------------------------------------------- /apps/conduit/src/app/routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { PreloadAllModules, Route, RouterModule } from '@angular/router'; 3 | 4 | export const routes: Route[] = [ 5 | { path: 'home', loadChildren: () => import('@realworld-angular-nx-ngxs/home').then((m) => m.HomeModule) }, 6 | { path: 'login', loadChildren: () => import('@realworld-angular-nx-ngxs/login').then((m) => m.LoginModule) }, 7 | { path: 'sign-up', loadChildren: () => import('@realworld-angular-nx-ngxs/register').then((m) => m.RegisterModule) }, 8 | { path: 'settings', loadChildren: () => import('@realworld-angular-nx-ngxs/settings').then((m) => m.SettingsModule) }, 9 | { 10 | path: 'profile/:user', 11 | loadChildren: () => import('@realworld-angular-nx-ngxs/profile').then((m) => m.ProfileModule) 12 | }, 13 | { path: 'editor', loadChildren: () => import('@realworld-angular-nx-ngxs/editor').then((m) => m.EditorModule) }, 14 | { path: '', redirectTo: 'home', pathMatch: 'full' } 15 | ]; 16 | 17 | @NgModule({ 18 | imports: [ 19 | RouterModule.forRoot(routes, { 20 | initialNavigation: 'enabled', 21 | preloadingStrategy: PreloadAllModules 22 | }) 23 | ], 24 | exports: [RouterModule] 25 | }) 26 | export class RoutingModule {} 27 | -------------------------------------------------------------------------------- /libs/profile/src/lib/profile.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { RouterModule, Route } from '@angular/router'; 4 | import { ProfilePage } from './profile.page'; 5 | import { ProfileState } from './+state/profile.state'; 6 | import { NgxsModule } from '@ngxs/store'; 7 | import { ArticleListModule } from '@realworld-angular-nx-ngxs/article-list'; 8 | import { MyArticlesComponent } from './components/my-articles/my-articles.component'; 9 | import { MyFavoriteArticlesComponent } from './components/my-favorite-articles/my-favorite-articles.component'; 10 | 11 | export const profileRoutes: Route[] = []; 12 | 13 | @NgModule({ 14 | imports: [ 15 | CommonModule, 16 | RouterModule.forChild([ 17 | { 18 | path: '', 19 | component: ProfilePage, 20 | children: [ 21 | { path: '', component: MyArticlesComponent, pathMatch: 'full' }, 22 | { path: 'favorites', component: MyFavoriteArticlesComponent } 23 | ] 24 | } 25 | ]), 26 | NgxsModule.forFeature([ProfileState]), 27 | ArticleListModule 28 | ], 29 | declarations: [ProfilePage, MyArticlesComponent, MyFavoriteArticlesComponent] 30 | }) 31 | export class ProfileModule {} 32 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/components/article-list-item/article-list-item.component.html: -------------------------------------------------------------------------------- 1 |
2 | 26 | 27 |

{{ article.title }}

28 |

{{ article.description }}

29 | Read more... 30 |
    31 |
  • 32 | {{ tag }} 33 |
  • 34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.page.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Select, Store } from '@ngxs/store'; 3 | import { 4 | ArticleListActions, 5 | articleListInitialState, 6 | ArticleListSelectors, 7 | PageInfo 8 | } from '@realworld-angular-nx-ngxs/article-list'; 9 | import type { Article, ListType } from '@realworld-angular-nx-ngxs/data-access'; 10 | import { AuthSelectors } from '@realworld-angular-nx-ngxs/data-access'; 11 | import { Observable } from 'rxjs'; 12 | 13 | @Component({ 14 | selector: 'conduit-home', 15 | templateUrl: './home.page.html', 16 | styleUrls: ['./home.page.css'] 17 | }) 18 | export class HomePage { 19 | @Select(AuthSelectors.loggedIn) loggedIn$: Observable; 20 | @Select(ArticleListSelectors.slices.articles) articleList$: Observable; 21 | @Select(ArticleListSelectors.pageInfo) pageInfo$: Observable; 22 | 23 | constructor(private store: Store) {} 24 | 25 | onSetPage(pageNumber: number) { 26 | this.store.dispatch(new ArticleListActions.SetPage(pageNumber)); 27 | } 28 | 29 | onArticleTypeChange(type: ListType) { 30 | this.store.dispatch( 31 | new ArticleListActions.SetListConfig({ 32 | ...articleListInitialState.listConfig, 33 | type 34 | }) 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "npmScope": "realworld-angular-nx-ngxs", 3 | "affected": { 4 | "defaultBase": "master" 5 | }, 6 | "implicitDependencies": { 7 | "angular.json": "*", 8 | "package.json": { 9 | "dependencies": "*", 10 | "devDependencies": "*" 11 | }, 12 | "tsconfig.base.json": "*", 13 | "tslint.json": "*", 14 | "nx.json": "*" 15 | }, 16 | "tasksRunnerOptions": { 17 | "default": { 18 | "runner": "@nrwl/workspace/tasks-runners/default", 19 | "options": { 20 | "cacheableOperations": ["build", "lint", "test", "e2e"] 21 | } 22 | } 23 | }, 24 | "projects": { 25 | "conduit": { 26 | "tags": [] 27 | }, 28 | "conduit-e2e": { 29 | "tags": [], 30 | "implicitDependencies": ["conduit"] 31 | }, 32 | "home": { 33 | "tags": [] 34 | }, 35 | "data-access": { 36 | "tags": [] 37 | }, 38 | "article-list": { 39 | "tags": [] 40 | }, 41 | "login": { 42 | "tags": [] 43 | }, 44 | "register": { 45 | "tags": [] 46 | }, 47 | "settings": { 48 | "tags": [] 49 | }, 50 | "profile": { 51 | "tags": [] 52 | }, 53 | "editor": { 54 | "tags": [] 55 | }, 56 | "utils": { 57 | "tags": [] 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /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 | "typeRoots": ["node_modules/@types"], 14 | "lib": ["es2017", "dom"], 15 | "skipLibCheck": true, 16 | "skipDefaultLibCheck": true, 17 | "baseUrl": ".", 18 | "paths": { 19 | "@realworld-angular-nx-ngxs/home": ["libs/home/src/index.ts"], 20 | "@realworld-angular-nx-ngxs/data-access": ["libs/data-access/src/index.ts"], 21 | "@realworld-angular-nx-ngxs/article-list": ["libs/article-list/src/index.ts"], 22 | "@realworld-angular-nx-ngxs/login": ["libs/login/src/index.ts"], 23 | "@realworld-angular-nx-ngxs/register": ["libs/register/src/index.ts"], 24 | "@realworld-angular-nx-ngxs/settings": ["libs/settings/src/index.ts"], 25 | "@realworld-angular-nx-ngxs/profile": ["libs/profile/src/index.ts"], 26 | "@realworld-angular-nx-ngxs/editor": ["libs/editor/src/index.ts"], 27 | "@realworld-angular-nx-ngxs/utils": ["libs/utils/src/index.ts"] 28 | } 29 | }, 30 | "exclude": ["node_modules", "tmp"] 31 | } 32 | -------------------------------------------------------------------------------- /libs/login/src/lib/login.page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Sign in

6 |

7 | Need an account? 8 |

9 | 10 |
    11 |
  • {{error}}
  • 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 25 |
26 | 33 |
34 |
35 |
36 |
37 |
38 | -------------------------------------------------------------------------------- /libs/editor/src/lib/pages/edit-article/edit-article.page.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Navigate, RouterState } from '@ngxs/router-plugin'; 3 | import { Select, Store } from '@ngxs/store'; 4 | import { Article } from '@realworld-angular-nx-ngxs/data-access'; 5 | import { Observable } from 'rxjs'; 6 | import { EditArticle, GetArticle } from '../../+state/editor.actions'; 7 | import { EditorSelectors } from '../../+state/editor.selectors'; 8 | 9 | @Component({ 10 | selector: 'conduit-edit-article', 11 | templateUrl: './edit-article.page.html', 12 | styleUrls: ['./edit-article.page.scss'], 13 | changeDetection: ChangeDetectionStrategy.OnPush 14 | }) 15 | export class EditArticlePage implements OnInit { 16 | @Select(EditorSelectors.article) article$: Observable
; 17 | 18 | constructor(private store: Store) {} 19 | 20 | ngOnInit() { 21 | const slug = this.store.selectSnapshot(RouterState.state).root.firstChild.firstChild.paramMap.get('slug'); 22 | this.store.dispatch(new GetArticle(slug)); 23 | } 24 | 25 | submit(article: Article) { 26 | this.store.dispatch(new EditArticle(article)).subscribe(() => { 27 | const slug = this.store.selectSnapshot(EditorSelectors.article).slug; 28 | this.store.dispatch(new Navigate(['/article', slug])); 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /apps/conduit/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { NgxsModule } from '@ngxs/store'; 4 | import { AppComponent } from './app.component'; 5 | import { FooterComponent } from './components/footer/footer.component'; 6 | import { HeaderComponent } from './components/header/header.component'; 7 | 8 | describe('AppComponent', () => { 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [RouterTestingModule, NgxsModule.forRoot()], 12 | declarations: [AppComponent, HeaderComponent, FooterComponent] 13 | }).compileComponents(); 14 | })); 15 | 16 | it('should create the app', () => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.componentInstance; 19 | expect(app).toBeTruthy(); 20 | }); 21 | 22 | it(`should have as title 'conduit'`, () => { 23 | const fixture = TestBed.createComponent(AppComponent); 24 | const app = fixture.componentInstance; 25 | expect(app.title).toEqual('conduit'); 26 | }); 27 | 28 | it('should render title', () => { 29 | const fixture = TestBed.createComponent(AppComponent); 30 | fixture.detectChanges(); 31 | const compiled = fixture.nativeElement; 32 | expect(compiled.querySelector('a').textContent).toContain('conduit'); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/+state/article-list.selectors.ts: -------------------------------------------------------------------------------- 1 | import { Selector } from '@ngxs/store'; 2 | import { Article, AuthSelectors, ListConfig, User } from '@realworld-angular-nx-ngxs/data-access'; 3 | import { ArticleListModel, PageInfo } from './article-list.model'; 4 | import { ArticleListState } from './article-list.state'; 5 | import { createPropertySelectors } from './ngxs-next'; 6 | 7 | export class ArticleListSelectors { 8 | static slices = createPropertySelectors(ArticleListState); 9 | 10 | @Selector([ArticleListSelectors.slices.articlesCount, ArticleListSelectors.slices.listConfig]) 11 | static pageInfo(articlesCount: number, listConfig: ListConfig): PageInfo { 12 | return { 13 | type: listConfig.type, 14 | currentPage: listConfig.currentPage, 15 | totalPages: Array.from(new Array(Math.ceil(articlesCount / listConfig.filters.limit)), (val, index) => index + 1) 16 | }; 17 | } 18 | 19 | @Selector([ArticleListSelectors.slices.articles, AuthSelectors.slices.user]) 20 | static myArticles(articles: Article[], user: User): Article[] { 21 | return articles?.filter((article) => article?.author?.username === user?.username); 22 | } 23 | 24 | @Selector([ArticleListSelectors.slices.articles, AuthSelectors.slices.user]) 25 | static myFavArticles(articles: Article[], user: User): Article[] { 26 | return articles?.filter((article) => article.favorited); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/conduit/src/app/components/header/header.component.html: -------------------------------------------------------------------------------- 1 | 31 | -------------------------------------------------------------------------------- /apps/conduit/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { NgModule } from '@angular/core'; 3 | import { BrowserModule } from '@angular/platform-browser'; 4 | import { NgxsModule, NoopNgxsExecutionStrategy } from '@ngxs/store'; 5 | import { NgxsRouterPluginModule } from '@ngxs/router-plugin'; 6 | import { NgxsStoragePluginModule } from '@ngxs/storage-plugin'; 7 | 8 | import { environment } from '../environments/environment'; 9 | import { AppComponent } from './app.component'; 10 | import { FooterComponent } from './components/footer/footer.component'; 11 | import { HeaderComponent } from './components/header/header.component'; 12 | import { RoutingModule } from './routing.module'; 13 | import { DataAccessModule } from '@realworld-angular-nx-ngxs/data-access'; 14 | 15 | @NgModule({ 16 | declarations: [AppComponent, FooterComponent, HeaderComponent], 17 | imports: [ 18 | BrowserModule, 19 | RoutingModule, 20 | HttpClientModule, 21 | NgxsModule.forRoot([], { 22 | developmentMode: !environment.production, 23 | selectorOptions: { 24 | injectContainerState: false, 25 | suppressErrors: false 26 | }, 27 | executionStrategy: NoopNgxsExecutionStrategy 28 | }), 29 | NgxsRouterPluginModule.forRoot(), 30 | NgxsStoragePluginModule.forRoot({ key: ['auth'] }), 31 | DataAccessModule 32 | ], 33 | providers: [], 34 | bootstrap: [AppComponent] 35 | }) 36 | export class AppModule {} 37 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: master 2 | on: push 3 | 4 | jobs: 5 | build-and-test: 6 | runs-on: ubuntu-latest 7 | name: Build, Test and Deploy 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | with: 12 | ref: ${{ github.event.pull_request.head.ref }} 13 | fetch-depth: 0 14 | - run: | 15 | git fetch --no-tags --prune --depth=5 origin master 16 | - name: Use Node.js 10.x 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: '10.x' 20 | - name: Set yarn cache directory path 21 | id: yarn-cache-dir-path 22 | run: echo "::set-output name=dir::$(yarn cache dir)" 23 | - name: Get yarn cache 24 | uses: actions/cache@v1 25 | id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) 26 | with: 27 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 28 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 29 | restore-keys: | 30 | ${{ runner.os }}-yarn- 31 | - name: Install 32 | run: yarn install 33 | - name: Lint 34 | run: yarn lint 35 | - name: Format 36 | run: yarn format:check --base=origin/master 37 | - name: Test 38 | run: yarn test 39 | - name: Build and Deploy 40 | env: 41 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 42 | run: yarn deploy 43 | -------------------------------------------------------------------------------- /libs/settings/src/lib/settings/settings.page.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; 2 | import { FormControl, FormGroup } from '@angular/forms'; 3 | import { Navigate } from '@ngxs/router-plugin'; 4 | import { Store } from '@ngxs/store'; 5 | import { AuthSelectors, GetAuthUser, Logout, UpdateAuthUser } from '@realworld-angular-nx-ngxs/data-access'; 6 | import { of } from 'rxjs'; 7 | import { catchError } from 'rxjs/operators'; 8 | 9 | @Component({ 10 | selector: 'realworld-angular-nx-ngxs-settings', 11 | templateUrl: './settings.page.html', 12 | styleUrls: ['./settings.page.scss'], 13 | changeDetection: ChangeDetectionStrategy.OnPush 14 | }) 15 | export class SettingsPage implements OnInit { 16 | form = new FormGroup({ 17 | username: new FormControl(), 18 | bio: new FormControl(), 19 | image: new FormControl(), 20 | email: new FormControl(), 21 | password: new FormControl() 22 | }); 23 | 24 | constructor(private store: Store) {} 25 | 26 | ngOnInit() { 27 | this.store.dispatch(new GetAuthUser()).subscribe(() => { 28 | const user = this.store.selectSnapshot(AuthSelectors.slices.user); 29 | this.form.patchValue(user); 30 | }); 31 | } 32 | 33 | updateSettings() { 34 | const authUser = this.form.value; 35 | this.store.dispatch(new UpdateAuthUser(authUser)); 36 | } 37 | 38 | logout() { 39 | this.store.dispatch(new Logout()).subscribe(() => { 40 | this.store.dispatch(new Navigate(['/home'])); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /libs/editor/src/lib/components/article-form/article-form.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | OnInit, 4 | ChangeDetectionStrategy, 5 | Output, 6 | EventEmitter, 7 | Input, 8 | OnChanges, 9 | SimpleChanges 10 | } from '@angular/core'; 11 | import { FormGroup, FormControl } from '@angular/forms'; 12 | import { Select } from '@ngxs/store'; 13 | import { Article } from '@realworld-angular-nx-ngxs/data-access'; 14 | import { Observable } from 'rxjs'; 15 | import { EditorSelectors } from '../../+state/editor.selectors'; 16 | 17 | @Component({ 18 | selector: 'conduit-article-form', 19 | templateUrl: './article-form.component.html', 20 | styleUrls: ['./article-form.component.scss'], 21 | changeDetection: ChangeDetectionStrategy.OnPush 22 | }) 23 | export class ArticleFormComponent implements OnInit, OnChanges { 24 | @Output() onSubmit: EventEmitter
= new EventEmitter(); 25 | @Input() article: Article; 26 | @Select(EditorSelectors.errors) errors$: Observable; 27 | 28 | form = new FormGroup({ 29 | title: new FormControl(''), 30 | description: new FormControl(''), 31 | body: new FormControl(''), 32 | tagList: new FormControl(''), 33 | slug: new FormControl('') 34 | }); 35 | 36 | constructor() {} 37 | 38 | ngOnInit() {} 39 | 40 | ngOnChanges(changes: SimpleChanges) { 41 | if (changes['article']) { 42 | this.form.patchValue(changes['article'].currentValue); 43 | } 44 | } 45 | 46 | submit() { 47 | this.onSubmit.emit(this.form.value); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /libs/data-access/src/ngxs-next.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, StateToken } from '@ngxs/store'; 2 | import { StateClass } from '@ngxs/store/internals'; 3 | 4 | type SelectorOnly = StateToken | ((...arg: any) => TModel); 5 | 6 | type Selector = StateClass | SelectorOnly; 7 | 8 | export type PropertySelectors = { 9 | [P in keyof TModel]: (model: TModel) => TModel[P]; 10 | }; 11 | 12 | export function createPropertySelectors(state: Selector): PropertySelectors { 13 | const cache: Partial> = {}; 14 | return new Proxy( 15 | {}, 16 | { 17 | get(target: any, prop: string) { 18 | const selector = cache[prop] || createSelector([state], (s: TModel) => s?.[prop]); 19 | cache[prop] = selector; 20 | return selector; 21 | } 22 | } 23 | ); 24 | } 25 | 26 | interface SelectorMap { 27 | [key: string]: SelectorOnly; 28 | } 29 | 30 | type MappedSelector = (...args: any[]) => MappedResult; 31 | 32 | type MappedResult = { 33 | [P in keyof TSelectorMap]: TSelectorMap[P] extends SelectorOnly ? R : never; 34 | }; 35 | 36 | export function createMappedSelector(selectorMap: T): MappedSelector { 37 | const selectors = Object.values(selectorMap); 38 | return createSelector(selectors, (...args) => { 39 | return Object.keys(selectorMap).reduce((obj, key, index) => { 40 | (obj as any)[key] = args[index]; 41 | return obj; 42 | }, {} as MappedResult); 43 | }) as MappedSelector; 44 | } 45 | -------------------------------------------------------------------------------- /libs/register/src/lib/register.page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Sign up

6 |

7 | Have an account? 8 |

9 | 10 |
    11 |
  • {{error}}
  • 12 |
13 | 14 |
15 |
16 | 22 |
23 |
24 | 25 |
26 |
27 | 33 |
34 | 41 |
42 |
43 |
44 |
45 |
46 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/+state/ngxs-next.ts: -------------------------------------------------------------------------------- 1 | import { createSelector, StateToken } from '@ngxs/store'; 2 | import { StateClass } from '@ngxs/store/internals'; 3 | 4 | type SelectorOnly = StateToken | ((...arg: any) => TModel); 5 | 6 | type Selector = StateClass | SelectorOnly; 7 | 8 | export type PropertySelectors = { 9 | [P in keyof TModel]: (model: TModel) => TModel[P]; 10 | }; 11 | 12 | export function createPropertySelectors( 13 | state: Selector, 14 | ): PropertySelectors { 15 | const cache: Partial> = {}; 16 | return new Proxy( 17 | {}, 18 | { 19 | get(target: any, prop: string) { 20 | const selector = cache[prop] || createSelector([state], (s: TModel) => s?.[prop]); 21 | cache[prop] = selector; 22 | return selector; 23 | }, 24 | }, 25 | ); 26 | } 27 | 28 | interface SelectorMap { 29 | [key: string]: SelectorOnly; 30 | } 31 | 32 | type MappedSelector = (...args: any[]) => MappedResult; 33 | 34 | type MappedResult = { 35 | [P in keyof TSelectorMap]: TSelectorMap[P] extends SelectorOnly ? R : never; 36 | }; 37 | 38 | export function createMappedSelector(selectorMap: T): MappedSelector { 39 | const selectors = Object.values(selectorMap); 40 | return createSelector(selectors, (...args) => { 41 | return Object.keys(selectorMap).reduce((obj, key, index) => { 42 | (obj as any)[key] = args[index]; 43 | return obj; 44 | }, {} as MappedResult); 45 | }) as MappedSelector; 46 | } 47 | -------------------------------------------------------------------------------- /libs/home/src/lib/home.page.html: -------------------------------------------------------------------------------- 1 |
2 | 8 | 9 |
10 |
11 |
12 |
13 | 34 |
35 | 36 | 41 |
42 | 43 |
44 | 47 |
48 |
49 |
50 |
51 | -------------------------------------------------------------------------------- /libs/profile/src/lib/+state/profile.state.ts: -------------------------------------------------------------------------------- 1 | import { tap } from 'rxjs/operators'; 2 | import { Injectable } from '@angular/core'; 3 | import { Action, State, StateContext } from '@ngxs/store'; 4 | import { patch } from '@ngxs/store/operators'; 5 | import { ConduitApiService, Profile } from '@realworld-angular-nx-ngxs/data-access'; 6 | import { ProfileActions } from './profile.actions'; 7 | 8 | export interface ProfileStateModel { 9 | profile: Profile; 10 | } 11 | 12 | @State({ 13 | name: 'profile', 14 | defaults: { 15 | profile: null 16 | } 17 | }) 18 | @Injectable() 19 | export class ProfileState { 20 | constructor(private conduitApi: ConduitApiService) {} 21 | 22 | @Action(ProfileActions.Get) 23 | get(ctx: StateContext, { user }: ProfileActions.Get) { 24 | return this.conduitApi.getProfile(user).pipe( 25 | tap((profile) => { 26 | ctx.patchState({ profile }); 27 | }) 28 | ); 29 | } 30 | 31 | @Action(ProfileActions.Follow) 32 | follow(ctx: StateContext, { user }: ProfileActions.Follow) { 33 | return this.conduitApi.followProfile(user).pipe( 34 | tap(() => 35 | ctx.setState( 36 | patch({ profile: { following: true } as Profile }) 37 | ) 38 | ) 39 | ); 40 | } 41 | 42 | @Action(ProfileActions.UnFollow) 43 | unfollow(ctx: StateContext, { user }: ProfileActions.UnFollow) { 44 | return this.conduitApi.unfollowProfile(user).pipe( 45 | tap(() => 46 | ctx.setState( 47 | patch({ profile: { following: false } as Profile }) 48 | ) 49 | ) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /libs/profile/src/lib/profile.page.html: -------------------------------------------------------------------------------- 1 |
2 | 24 | 25 |
26 |
27 |
28 |
29 | 43 |
44 | 45 |
46 |
47 |
48 |
49 | -------------------------------------------------------------------------------- /libs/settings/src/lib/settings/settings.page.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

Your Settings

6 | 7 |
8 |
9 |
10 | 11 |
12 |
13 | 19 |
20 |
21 | 27 |
28 |
29 | 30 |
31 |
32 | 38 |
39 | 42 |
43 |
44 |
45 | 48 |
49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /libs/editor/src/lib/+state/editor.state.ts: -------------------------------------------------------------------------------- 1 | import { catchError, tap } from 'rxjs/operators'; 2 | import { Injectable } from '@angular/core'; 3 | import { Action, Selector, State, StateContext } from '@ngxs/store'; 4 | import { Article, ConduitApiService } from '@realworld-angular-nx-ngxs/data-access'; 5 | import { throwError } from 'rxjs'; 6 | import { parseError } from '@realworld-angular-nx-ngxs/utils'; 7 | import { CreateArticle, EditArticle, GetArticle } from './editor.actions'; 8 | 9 | export type EditorStateModel = { 10 | article: Article; 11 | errors: string[]; 12 | }; 13 | 14 | @State({ 15 | name: 'articles', 16 | defaults: { 17 | article: null, 18 | errors: [] 19 | } 20 | }) 21 | @Injectable() 22 | export class EditorState { 23 | constructor(private conduitApi: ConduitApiService) {} 24 | 25 | @Action(CreateArticle) 26 | createArticle(ctx: StateContext, { payload }: CreateArticle) { 27 | ctx.patchState({ article: null, errors: [] }); 28 | return this.conduitApi.createArticle({ article: payload }).pipe( 29 | tap((article) => { 30 | ctx.patchState({ article }); 31 | }), 32 | catchError((error) => { 33 | ctx.patchState({ errors: parseError(error) }); 34 | return throwError(error); 35 | }) 36 | ); 37 | } 38 | 39 | @Action(EditArticle) 40 | editArticle(ctx: StateContext, { payload }: CreateArticle) { 41 | ctx.patchState({ article: null, errors: [] }); 42 | return this.conduitApi.updateArticle({ article: payload }).pipe( 43 | tap((article) => { 44 | ctx.patchState({ article }); 45 | }), 46 | catchError((error) => { 47 | ctx.patchState({ errors: parseError(error) }); 48 | return throwError(error); 49 | }) 50 | ); 51 | } 52 | 53 | @Action(GetArticle) 54 | getArticle(ctx: StateContext, { payload }: GetArticle) { 55 | ctx.patchState({ article: null, errors: [] }); 56 | return this.conduitApi.getArticle(payload).pipe( 57 | tap((article) => { 58 | ctx.patchState({ article }); 59 | }) 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/conduit-api.model.ts: -------------------------------------------------------------------------------- 1 | import { User } from './+state/auth.model'; 2 | 3 | export interface Profile { 4 | username: string; 5 | bio: string; 6 | image: string; 7 | following: boolean; 8 | loading: boolean; 9 | } 10 | export interface Article { 11 | slug: string; 12 | title: string; 13 | description: string; 14 | body: string; 15 | tagList: string[]; 16 | createdAt: string; 17 | updatedAt: string; 18 | favorited: boolean; 19 | favoritesCount: number; 20 | author: Profile; 21 | } 22 | 23 | export interface TagsResponse { 24 | tags: string[]; 25 | } 26 | 27 | export interface ArticlesResponse { 28 | articles: Article[]; 29 | articlesCount: number; 30 | } 31 | 32 | export interface ListConfig { 33 | type: ListType; 34 | currentPage: number; 35 | filters: Filters; 36 | } 37 | 38 | export interface Filters { 39 | tag?: string; 40 | author?: string; 41 | favorited?: string; 42 | limit?: number; 43 | offset?: number; 44 | } 45 | 46 | export type ListType = 'ALL' | 'FEED'; 47 | export interface LoginResponse { 48 | user: User; 49 | } 50 | 51 | export interface GetCurrentUserResponse { 52 | user: User; 53 | } 54 | 55 | export interface GetProfileResponse { 56 | profile: Profile; 57 | } 58 | 59 | export interface UpdateCurrentUserResponse { 60 | user: User; 61 | } 62 | 63 | export interface RegisterResponse { 64 | user: User; 65 | } 66 | 67 | export interface LoginRequest { 68 | email: string; 69 | password: string; 70 | } 71 | 72 | export interface RegisterRequest { 73 | username: string; 74 | email: string; 75 | password: string; 76 | } 77 | 78 | export interface UpdateAuthUserRequest { 79 | email: string; 80 | token: string; 81 | username: string; 82 | bio: string; 83 | image: string; 84 | } 85 | 86 | export interface CreateArticleRequest { 87 | article: Article; 88 | } 89 | 90 | export type UpdateArticleRequest = CreateArticleRequest; 91 | export interface CreateArticleResponse { 92 | article; 93 | } 94 | 95 | export type UpdateArticleResponse = CreateArticleResponse; 96 | 97 | export type GetArticleResponse = { 98 | article: Article; 99 | }; 100 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/+state/auth.state.ts: -------------------------------------------------------------------------------- 1 | import { throwError } from 'rxjs'; 2 | import { catchError, tap } from 'rxjs/operators'; 3 | 4 | import { Injectable } from '@angular/core'; 5 | import { Action, State, StateContext } from '@ngxs/store'; 6 | 7 | import { ConduitApiService } from '../conduit-api.service'; 8 | import { GetAuthUser, Login, Logout, Register, UpdateAuthUser } from './auth.actions'; 9 | import type { AuthStateModel } from './auth.model'; 10 | import { parseError } from '@realworld-angular-nx-ngxs/utils'; 11 | 12 | @State({ 13 | name: 'auth', 14 | defaults: { 15 | user: null, 16 | errors: null 17 | } 18 | }) 19 | @Injectable() 20 | export class AuthState { 21 | constructor(private conduitApi: ConduitApiService) {} 22 | 23 | @Action(Login) 24 | login(ctx: StateContext, { payload }: Login) { 25 | ctx.patchState({ user: null, errors: null }); 26 | return this.conduitApi.login(payload).pipe( 27 | tap((user) => ctx.patchState({ user })), 28 | catchError((err) => { 29 | ctx.patchState({ 30 | errors: parseError(err) 31 | }); 32 | return throwError(err); 33 | }) 34 | ); 35 | } 36 | 37 | @Action(Register) 38 | register(ctx: StateContext, { payload }: Register) { 39 | ctx.patchState({ user: null, errors: null }); 40 | return this.conduitApi.register(payload).pipe( 41 | tap((user) => ctx.patchState({ user })), 42 | catchError((err) => { 43 | ctx.patchState({ 44 | errors: parseError(err) 45 | }); 46 | return throwError(err); 47 | }) 48 | ); 49 | } 50 | 51 | @Action(GetAuthUser) 52 | getAuthUser(ctx: StateContext) { 53 | return this.conduitApi.getAuthUser().pipe( 54 | tap((user) => { 55 | ctx.patchState({ user }); 56 | }), 57 | catchError((err) => { 58 | ctx.patchState({ 59 | user: null 60 | }); 61 | return throwError(err); 62 | }) 63 | ); 64 | } 65 | 66 | @Action(UpdateAuthUser) 67 | updateAuthUser(ctx: StateContext, { payload }: UpdateAuthUser) { 68 | return this.conduitApi.updateAuthUser(payload).pipe(tap(() => ctx.patchState({ user: payload }))); 69 | } 70 | 71 | @Action(Logout) 72 | logout(ctx: StateContext) { 73 | ctx.patchState({ user: null, errors: null }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rulesDirectory": [ 3 | "node_modules/@nrwl/workspace/src/tslint", 4 | "node_modules/codelyzer" 5 | ], 6 | "linterOptions": { 7 | "exclude": ["**/*"] 8 | }, 9 | "rules": { 10 | "arrow-return-shorthand": true, 11 | "callable-types": true, 12 | "class-name": true, 13 | "deprecation": { 14 | "severity": "warn" 15 | }, 16 | "forin": true, 17 | "import-blacklist": [true, "rxjs/Rx"], 18 | "interface-over-type-literal": true, 19 | "member-access": false, 20 | "member-ordering": [ 21 | true, 22 | { 23 | "order": [ 24 | "static-field", 25 | "instance-field", 26 | "static-method", 27 | "instance-method" 28 | ] 29 | } 30 | ], 31 | "no-arg": true, 32 | "no-bitwise": true, 33 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], 34 | "no-construct": true, 35 | "no-debugger": true, 36 | "no-duplicate-super": true, 37 | "no-empty": false, 38 | "no-empty-interface": true, 39 | "no-eval": true, 40 | "no-inferrable-types": [true, "ignore-params"], 41 | "no-misused-new": true, 42 | "no-non-null-assertion": true, 43 | "no-shadowed-variable": true, 44 | "no-string-literal": false, 45 | "no-string-throw": true, 46 | "no-switch-case-fall-through": true, 47 | "no-unnecessary-initializer": true, 48 | "no-unused-expression": true, 49 | "no-var-keyword": true, 50 | "object-literal-sort-keys": false, 51 | "prefer-const": true, 52 | "radix": true, 53 | "triple-equals": [true, "allow-null-check"], 54 | "unified-signatures": true, 55 | "variable-name": false, 56 | "nx-enforce-module-boundaries": [ 57 | true, 58 | { 59 | "enforceBuildableLibDependency": true, 60 | "allow": [], 61 | "depConstraints": [ 62 | { 63 | "sourceTag": "*", 64 | "onlyDependOnLibsWithTags": ["*"] 65 | } 66 | ] 67 | } 68 | ], 69 | "directive-selector": [true, "attribute", "app", "camelCase"], 70 | "component-selector": [true, "element", "app", "kebab-case"], 71 | "no-conflicting-lifecycle": true, 72 | "no-host-metadata-property": true, 73 | "no-input-rename": true, 74 | "no-inputs-metadata-property": true, 75 | "no-output-native": true, 76 | "no-output-on-prefix": true, 77 | "no-output-rename": true, 78 | "no-outputs-metadata-property": true, 79 | "template-banana-in-box": true, 80 | "template-no-negated-async": true, 81 | "use-lifecycle-interface": true, 82 | "use-pipe-transform-interface": true 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "realworld-angular-nx-ngxs", 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 --first-only --create-ivy-entry-points", 8 | "nx": "nx", 9 | "start": "ng serve", 10 | "build": "ng build", 11 | "test": "ng test", 12 | "lint": "nx workspace-lint && ng lint", 13 | "e2e": "ng e2e", 14 | "affected:apps": "nx affected:apps", 15 | "affected:libs": "nx affected:libs", 16 | "affected:build": "nx affected:build", 17 | "affected:e2e": "nx affected:e2e", 18 | "affected:test": "nx affected:test", 19 | "affected:lint": "nx affected:lint", 20 | "affected:dep-graph": "nx affected:dep-graph", 21 | "affected": "nx affected", 22 | "format": "nx format:write", 23 | "format:write": "nx format:write", 24 | "format:check": "nx format:check", 25 | "update": "ng update @nrwl/workspace", 26 | "workspace-schematic": "nx workspace-schematic", 27 | "dep-graph": "nx dep-graph", 28 | "help": "nx help", 29 | "deploy": "ng deploy --base-href=/realworld-angular-nx-ngxs/ --name=NGXS --email=ngxs@github.com" 30 | }, 31 | "private": true, 32 | "dependencies": { 33 | "@angular/animations": "^10.0.0", 34 | "@angular/common": "^10.0.0", 35 | "@angular/compiler": "^10.0.0", 36 | "@angular/core": "^10.0.0", 37 | "@angular/forms": "^10.0.0", 38 | "@angular/platform-browser": "^10.0.0", 39 | "@angular/platform-browser-dynamic": "^10.0.0", 40 | "@angular/router": "^10.0.0", 41 | "@ngxs/router-plugin": "^3.7.0", 42 | "@ngxs/storage-plugin": "^3.7.0", 43 | "@ngxs/store": "^3.6.2", 44 | "@nrwl/angular": "10.0.7", 45 | "rxjs": "~6.5.5", 46 | "zone.js": "^0.10.2" 47 | }, 48 | "devDependencies": { 49 | "@angular-devkit/build-angular": "~0.1000.0", 50 | "@angular/cli": "~10.0.0", 51 | "@angular/compiler-cli": "^10.0.0", 52 | "@angular/language-service": "^10.0.0", 53 | "@commitlint/cli": "^9.1.1", 54 | "@commitlint/config-angular": "^9.1.1", 55 | "@commitlint/config-conventional": "^9.1.1", 56 | "@nrwl/cypress": "10.0.7", 57 | "@nrwl/jest": "10.0.7", 58 | "@nrwl/workspace": "10.0.7", 59 | "@types/jest": "25.1.4", 60 | "@types/node": "~8.9.4", 61 | "angular-cli-ghpages": "^0.6.2", 62 | "codelyzer": "~5.0.1", 63 | "cypress": "^4.1.0", 64 | "dotenv": "6.2.0", 65 | "eslint": "6.8.0", 66 | "husky": "^4.2.5", 67 | "jest": "25.2.3", 68 | "jest-preset-angular": "8.1.2", 69 | "lint-staged": "^10.2.11", 70 | "prettier": "2.0.4", 71 | "ts-jest": "25.2.1", 72 | "ts-node": "~7.0.0", 73 | "tslint": "~6.0.0", 74 | "typescript": "~3.9.3" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /apps/conduit/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 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js/dist/zone'; // Included with Angular CLI. 59 | 60 | /*************************************************************************************************** 61 | * APPLICATION IMPORTS 62 | */ 63 | -------------------------------------------------------------------------------- /libs/article-list/src/lib/+state/article-list.state.ts: -------------------------------------------------------------------------------- 1 | import { tap } from 'rxjs/operators'; 2 | 3 | import { HttpClient } from '@angular/common/http'; 4 | import { Injectable } from '@angular/core'; 5 | import { Action, State, StateContext, Store } from '@ngxs/store'; 6 | import { patch } from '@ngxs/store/operators'; 7 | import { Article, AuthSelectors, ConduitApiService, Filters, ListConfig } from '@realworld-angular-nx-ngxs/data-access'; 8 | 9 | import { ArticleListActions } from './article-list.actions'; 10 | import { articleListInitialState, ArticleListModel } from './article-list.model'; 11 | 12 | @State({ 13 | name: 'articleList', 14 | defaults: articleListInitialState 15 | }) 16 | @Injectable() 17 | export class ArticleListState { 18 | constructor(private conduitApi: ConduitApiService, private store: Store) {} 19 | 20 | @Action(ArticleListActions.SetPage) 21 | setPage(ctx: StateContext, { page }: ArticleListActions.SetPage) { 22 | ctx.setState( 23 | patch({ 24 | listConfig: patch({ 25 | currentPage: page, 26 | filters: (oldFilters: Filters) => { 27 | return { 28 | ...oldFilters, 29 | offset: oldFilters.limit * page 30 | }; 31 | } 32 | }) 33 | }) 34 | ); 35 | 36 | return ctx.dispatch(new ArticleListActions.Load()); 37 | } 38 | 39 | @Action(ArticleListActions.SetListConfig) 40 | setListConfig(ctx: StateContext, { config }: ArticleListActions.SetListConfig) { 41 | ctx.setState(patch({ listConfig: config })); 42 | return ctx.dispatch(new ArticleListActions.Load()); 43 | } 44 | 45 | @Action(ArticleListActions.Load) 46 | load(ctx: StateContext, {}: ArticleListActions.Load) { 47 | const listConfig = ctx.getState().listConfig || ({} as ListConfig); 48 | return this.conduitApi 49 | .getArticles(listConfig) 50 | .pipe(tap(({ articles, articlesCount }) => ctx.setState(patch({ articles, articlesCount })))); 51 | } 52 | 53 | @Action(ArticleListActions.Favorite) 54 | favorite(ctx: StateContext, { articleSlug }: ArticleListActions.Favorite) { 55 | return this.conduitApi.favoriteArticle(articleSlug).pipe(tap(() => ctx.dispatch(new ArticleListActions.Load()))); 56 | } 57 | 58 | @Action(ArticleListActions.UnFavorite) 59 | unFavorite(ctx: StateContext
, { articleSlug }: ArticleListActions.UnFavorite) { 60 | return this.conduitApi.unfavoriteArticle(articleSlug).pipe(tap(() => ctx.dispatch(new ArticleListActions.Load()))); 61 | } 62 | 63 | @Action(ArticleListActions.GetMyArticles) 64 | getMyArticles(ctx: StateContext, {}: ArticleListActions.GetMyArticles) { 65 | const username = this.store.selectSnapshot(AuthSelectors.username); 66 | return this.conduitApi 67 | .getArticles({ filters: { author: username } } as ListConfig) 68 | .pipe(tap(({ articles, articlesCount }) => ctx.setState(patch({ articles, articlesCount })))); 69 | } 70 | 71 | @Action(ArticleListActions.GetMyFavArticles) 72 | getMyFavArticles(ctx: StateContext, {}: ArticleListActions.GetMyFavArticles) { 73 | const username = this.store.selectSnapshot(AuthSelectors.username); 74 | return this.conduitApi 75 | .getArticles({ filters: { favorited: username } } as ListConfig) 76 | .pipe(tap(({ articles, articlesCount }) => ctx.setState(patch({ articles, articlesCount })))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /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 | const { output } = require("@nrwl/workspace"); 29 | 30 | /** 31 | * Paths to files being patched 32 | */ 33 | const angularCLIInitPath = "node_modules/@angular/cli/lib/cli/index.js"; 34 | 35 | /** 36 | * Patch index.js to warn you if you invoke the undecorated Angular CLI. 37 | */ 38 | function patchAngularCLI(initPath) { 39 | const angularCLIInit = fs.readFileSync(initPath, "utf-8").toString(); 40 | 41 | if (!angularCLIInit.includes("NX_CLI_SET")) { 42 | fs.writeFileSync( 43 | initPath, 44 | ` 45 | if (!process.env['NX_CLI_SET']) { 46 | const { output } = require('@nrwl/workspace'); 47 | output.warn({ title: 'The Angular CLI was invoked instead of the Nx CLI. Use "npx ng [command]" or "nx [command]" instead.' }); 48 | } 49 | ${angularCLIInit} 50 | ` 51 | ); 52 | } 53 | } 54 | 55 | /** 56 | * Symlink of ng to nx, so you can keep using `ng build/test/lint` and still 57 | * invoke the Nx CLI and get the benefits of computation caching. 58 | */ 59 | function symlinkNgCLItoNxCLI() { 60 | try { 61 | const ngPath = "./node_modules/.bin/ng"; 62 | const nxPath = "./node_modules/.bin/nx"; 63 | if (isWindows) { 64 | /** 65 | * This is the most reliable way to create symlink-like behavior on Windows. 66 | * Such that it works in all shells and works with npx. 67 | */ 68 | ["", ".cmd", ".ps1"].forEach((ext) => { 69 | if (fs.existsSync(nxPath + ext)) 70 | fs.writeFileSync(ngPath + ext, fs.readFileSync(nxPath + ext)); 71 | }); 72 | } else { 73 | // If unix-based, symlink 74 | cp.execSync(`ln -sf ./nx ${ngPath}`); 75 | } 76 | } catch (e) { 77 | output.error({ 78 | title: 79 | "Unable to create a symlink from the Angular CLI to the Nx CLI:" + 80 | e.message, 81 | }); 82 | throw e; 83 | } 84 | } 85 | 86 | try { 87 | symlinkNgCLItoNxCLI(); 88 | patchAngularCLI(angularCLIInitPath); 89 | output.log({ 90 | title: "Angular CLI has been decorated to enable computation caching.", 91 | }); 92 | } catch (e) { 93 | output.error({ 94 | title: "Decoration of the Angular CLI did not complete successfully", 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /libs/data-access/src/lib/conduit-api.service.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpParams } from '@angular/common/http'; 2 | import { Injectable } from '@angular/core'; 3 | import { Store } from '@ngxs/store'; 4 | import { map, mapTo, pluck } from 'rxjs/operators'; 5 | import type { 6 | Article, 7 | ArticlesResponse, 8 | CreateArticleRequest, 9 | CreateArticleResponse, 10 | GetArticleResponse, 11 | GetCurrentUserResponse, 12 | GetProfileResponse, 13 | ListConfig, 14 | LoginRequest, 15 | LoginResponse, 16 | RegisterRequest, 17 | RegisterResponse, 18 | TagsResponse, 19 | UpdateArticleRequest, 20 | UpdateArticleResponse, 21 | UpdateAuthUserRequest, 22 | UpdateCurrentUserResponse 23 | } from './conduit-api.model'; 24 | 25 | @Injectable({ 26 | providedIn: 'root' 27 | }) 28 | export class ConduitApiService { 29 | readonly baseUrl = 'https://conduit.productionready.io/api'; 30 | 31 | constructor(private http: HttpClient, private store: Store) {} 32 | 33 | getTags() { 34 | return this.http.get(`${this.baseUrl}/tags`).pipe(map((response) => response.tags)); 35 | } 36 | 37 | getArticles(listConfig: ListConfig) { 38 | const articlesType = listConfig.type === 'FEED' ? '/feed' : ''; 39 | const params = new HttpParams({ 40 | fromObject: listConfig.filters as any 41 | }); 42 | return this.http.get(`${this.baseUrl}/articles${articlesType}`, { 43 | headers: this.headers(), 44 | params 45 | }); 46 | } 47 | 48 | favoriteArticle(slug: string) { 49 | return this.http 50 | .post
(`${this.baseUrl}/articles/${slug}/favorite`, null, { headers: this.headers() }) 51 | .pipe(pluck('article')); 52 | } 53 | 54 | unfavoriteArticle(slug: string) { 55 | return this.http 56 | .delete
(`${this.baseUrl}/articles/${slug}/favorite`, { headers: this.headers() }) 57 | .pipe(pluck('article')); 58 | } 59 | 60 | login(loginRequest: LoginRequest) { 61 | return this.http 62 | .post(`${this.baseUrl}/users/login`, { user: loginRequest }) 63 | .pipe(map((response) => response.user)); 64 | } 65 | 66 | register(registerRequest: RegisterRequest) { 67 | return this.http 68 | .post(`${this.baseUrl}/users`, { user: registerRequest }) 69 | .pipe(map((response) => response.user)); 70 | } 71 | 72 | getAuthUser() { 73 | return this.http 74 | .get(`${this.baseUrl}/user`, { 75 | headers: this.headers() 76 | }) 77 | .pipe(map((response) => response.user)); 78 | } 79 | 80 | getProfile(userName: string) { 81 | return this.http 82 | .get(`${this.baseUrl}/profiles/${userName}`) 83 | .pipe(map((response) => response.profile)); 84 | } 85 | 86 | followProfile(userName: string) { 87 | return this.http 88 | .post(`${this.baseUrl}/profiles/${userName}/follow`, { 89 | headers: this.headers() 90 | }) 91 | .pipe(mapTo(true)); 92 | } 93 | 94 | unfollowProfile(userName: string) { 95 | return this.http 96 | .delete(`${this.baseUrl}/profiles/${userName}/follow`, { 97 | headers: this.headers() 98 | }) 99 | .pipe(mapTo(true)); 100 | } 101 | 102 | private headers() { 103 | return { 104 | 'Content-Type': 'application/json', 105 | Accept: 'application/json', 106 | Authorization: `Token ${this.store.selectSnapshot((s) => s.auth?.user?.token)}` 107 | }; 108 | } 109 | 110 | updateAuthUser(updateAuthUserRequest: UpdateAuthUserRequest) { 111 | return this.http 112 | .put(`${this.baseUrl}/user`, updateAuthUserRequest, { headers: this.headers() }) 113 | .pipe(map((response) => response.user)); 114 | } 115 | 116 | createArticle(createArticleRequest: CreateArticleRequest) { 117 | return this.http 118 | .post(`${this.baseUrl}/articles`, createArticleRequest, { 119 | headers: this.headers() 120 | }) 121 | .pipe(map((response) => response.article)); 122 | } 123 | 124 | updateArticle(updateArticleRequest: UpdateArticleRequest) { 125 | return this.http 126 | .put( 127 | `${this.baseUrl}/articles/${updateArticleRequest.article.slug}`, 128 | updateArticleRequest, 129 | { 130 | headers: this.headers() 131 | } 132 | ) 133 | .pipe(map((response) => response.article)); 134 | } 135 | 136 | deleteArticle(slug: string) { 137 | return this.http 138 | .delete(`${this.baseUrl}/articles/${slug}`, { 139 | headers: this.headers() 140 | }) 141 | .pipe(mapTo(true)); 142 | } 143 | 144 | getArticle(slug: string) { 145 | return this.http 146 | .get(`${this.baseUrl}/articles/${slug}`, { 147 | headers: this.headers() 148 | }) 149 | .pipe(map((response) => response.article)); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "projects": { 4 | "conduit": { 5 | "projectType": "application", 6 | "schematics": {}, 7 | "root": "apps/conduit", 8 | "sourceRoot": "apps/conduit/src", 9 | "prefix": "app", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-devkit/build-angular:browser", 13 | "options": { 14 | "outputPath": "dist/apps/conduit", 15 | "index": "apps/conduit/src/index.html", 16 | "main": "apps/conduit/src/main.ts", 17 | "polyfills": "apps/conduit/src/polyfills.ts", 18 | "tsConfig": "apps/conduit/tsconfig.app.json", 19 | "aot": true, 20 | "assets": ["apps/conduit/src/favicon.ico", "apps/conduit/src/assets"], 21 | "styles": ["apps/conduit/src/styles.css"], 22 | "scripts": [] 23 | }, 24 | "configurations": { 25 | "production": { 26 | "fileReplacements": [ 27 | { 28 | "replace": "apps/conduit/src/environments/environment.ts", 29 | "with": "apps/conduit/src/environments/environment.prod.ts" 30 | } 31 | ], 32 | "optimization": true, 33 | "outputHashing": "all", 34 | "sourceMap": false, 35 | "extractCss": true, 36 | "namedChunks": false, 37 | "extractLicenses": true, 38 | "vendorChunk": false, 39 | "buildOptimizer": true, 40 | "budgets": [ 41 | { 42 | "type": "initial", 43 | "maximumWarning": "2mb", 44 | "maximumError": "5mb" 45 | }, 46 | { 47 | "type": "anyComponentStyle", 48 | "maximumWarning": "6kb", 49 | "maximumError": "10kb" 50 | } 51 | ] 52 | } 53 | } 54 | }, 55 | "serve": { 56 | "builder": "@angular-devkit/build-angular:dev-server", 57 | "options": { 58 | "browserTarget": "conduit:build" 59 | }, 60 | "configurations": { 61 | "production": { 62 | "browserTarget": "conduit:build:production" 63 | } 64 | } 65 | }, 66 | "extract-i18n": { 67 | "builder": "@angular-devkit/build-angular:extract-i18n", 68 | "options": { 69 | "browserTarget": "conduit:build" 70 | } 71 | }, 72 | "lint": { 73 | "builder": "@angular-devkit/build-angular:tslint", 74 | "options": { 75 | "tsConfig": ["apps/conduit/tsconfig.app.json", "apps/conduit/tsconfig.spec.json"], 76 | "exclude": ["**/node_modules/**", "!apps/conduit/**/*"] 77 | } 78 | }, 79 | "test": { 80 | "builder": "@nrwl/jest:jest", 81 | "options": { 82 | "jestConfig": "apps/conduit/jest.config.js", 83 | "tsConfig": "apps/conduit/tsconfig.spec.json", 84 | "passWithNoTests": true, 85 | "setupFile": "apps/conduit/src/test-setup.ts" 86 | } 87 | }, 88 | "deploy": { 89 | "builder": "angular-cli-ghpages:deploy", 90 | "options": {} 91 | } 92 | } 93 | }, 94 | "conduit-e2e": { 95 | "root": "apps/conduit-e2e", 96 | "sourceRoot": "apps/conduit-e2e/src", 97 | "projectType": "application", 98 | "architect": { 99 | "e2e": { 100 | "builder": "@nrwl/cypress:cypress", 101 | "options": { 102 | "cypressConfig": "apps/conduit-e2e/cypress.json", 103 | "tsConfig": "apps/conduit-e2e/tsconfig.e2e.json", 104 | "devServerTarget": "conduit:serve" 105 | }, 106 | "configurations": { 107 | "production": { 108 | "devServerTarget": "conduit:serve:production" 109 | } 110 | } 111 | }, 112 | "lint": { 113 | "builder": "@angular-devkit/build-angular:tslint", 114 | "options": { 115 | "tsConfig": ["apps/conduit-e2e/tsconfig.e2e.json"], 116 | "exclude": ["**/node_modules/**", "!apps/conduit-e2e/**/*"] 117 | } 118 | } 119 | } 120 | }, 121 | "home": { 122 | "projectType": "library", 123 | "root": "libs/home", 124 | "sourceRoot": "libs/home/src", 125 | "prefix": "conduit", 126 | "architect": { 127 | "lint": { 128 | "builder": "@angular-devkit/build-angular:tslint", 129 | "options": { 130 | "tsConfig": ["libs/home/tsconfig.lib.json", "libs/home/tsconfig.spec.json"], 131 | "exclude": ["**/node_modules/**", "!libs/home/**/*"] 132 | } 133 | }, 134 | "test": { 135 | "builder": "@nrwl/jest:jest", 136 | "options": { 137 | "jestConfig": "libs/home/jest.config.js", 138 | "tsConfig": "libs/home/tsconfig.spec.json", 139 | "passWithNoTests": true, 140 | "setupFile": "libs/home/src/test-setup.ts" 141 | } 142 | } 143 | }, 144 | "schematics": {} 145 | }, 146 | "data-access": { 147 | "root": "libs/data-access", 148 | "sourceRoot": "libs/data-access/src", 149 | "projectType": "library", 150 | "schematics": {}, 151 | "architect": { 152 | "lint": { 153 | "builder": "@angular-devkit/build-angular:tslint", 154 | "options": { 155 | "tsConfig": ["libs/data-access/tsconfig.lib.json", "libs/data-access/tsconfig.spec.json"], 156 | "exclude": ["**/node_modules/**", "!libs/data-access/**/*"] 157 | } 158 | }, 159 | "test": { 160 | "builder": "@nrwl/jest:jest", 161 | "options": { 162 | "jestConfig": "libs/data-access/jest.config.js", 163 | "tsConfig": "libs/data-access/tsconfig.spec.json", 164 | "passWithNoTests": true 165 | } 166 | } 167 | } 168 | }, 169 | "article-list": { 170 | "projectType": "library", 171 | "root": "libs/article-list", 172 | "sourceRoot": "libs/article-list/src", 173 | "prefix": "conduit", 174 | "architect": { 175 | "lint": { 176 | "builder": "@angular-devkit/build-angular:tslint", 177 | "options": { 178 | "tsConfig": ["libs/article-list/tsconfig.lib.json", "libs/article-list/tsconfig.spec.json"], 179 | "exclude": ["**/node_modules/**", "!libs/article-list/**/*"] 180 | } 181 | }, 182 | "test": { 183 | "builder": "@nrwl/jest:jest", 184 | "options": { 185 | "jestConfig": "libs/article-list/jest.config.js", 186 | "tsConfig": "libs/article-list/tsconfig.spec.json", 187 | "passWithNoTests": true, 188 | "setupFile": "libs/article-list/src/test-setup.ts" 189 | } 190 | } 191 | }, 192 | "schematics": { 193 | "@nrwl/angular:component": { 194 | "style": "scss" 195 | } 196 | } 197 | }, 198 | "login": { 199 | "projectType": "library", 200 | "root": "libs/login", 201 | "sourceRoot": "libs/login/src", 202 | "prefix": "conduit", 203 | "architect": { 204 | "lint": { 205 | "builder": "@angular-devkit/build-angular:tslint", 206 | "options": { 207 | "tsConfig": ["libs/login/tsconfig.lib.json", "libs/login/tsconfig.spec.json"], 208 | "exclude": ["**/node_modules/**", "!libs/login/**/*"] 209 | } 210 | }, 211 | "test": { 212 | "builder": "@nrwl/jest:jest", 213 | "options": { 214 | "jestConfig": "libs/login/jest.config.js", 215 | "tsConfig": "libs/login/tsconfig.spec.json", 216 | "passWithNoTests": true, 217 | "setupFile": "libs/login/src/test-setup.ts" 218 | } 219 | } 220 | }, 221 | "schematics": { 222 | "@nrwl/angular:component": { 223 | "style": "scss" 224 | } 225 | } 226 | }, 227 | "register": { 228 | "projectType": "library", 229 | "root": "libs/register", 230 | "sourceRoot": "libs/register/src", 231 | "prefix": "conduit", 232 | "architect": { 233 | "lint": { 234 | "builder": "@angular-devkit/build-angular:tslint", 235 | "options": { 236 | "tsConfig": ["libs/register/tsconfig.lib.json", "libs/register/tsconfig.spec.json"], 237 | "exclude": ["**/node_modules/**", "!libs/register/**/*"] 238 | } 239 | }, 240 | "test": { 241 | "builder": "@nrwl/jest:jest", 242 | "options": { 243 | "jestConfig": "libs/register/jest.config.js", 244 | "tsConfig": "libs/register/tsconfig.spec.json", 245 | "passWithNoTests": true, 246 | "setupFile": "libs/register/src/test-setup.ts" 247 | } 248 | } 249 | }, 250 | "schematics": { 251 | "@nrwl/angular:component": { 252 | "style": "scss" 253 | } 254 | } 255 | }, 256 | "settings": { 257 | "projectType": "library", 258 | "root": "libs/settings", 259 | "sourceRoot": "libs/settings/src", 260 | "prefix": "conduit", 261 | "architect": { 262 | "lint": { 263 | "builder": "@angular-devkit/build-angular:tslint", 264 | "options": { 265 | "tsConfig": ["libs/settings/tsconfig.lib.json", "libs/settings/tsconfig.spec.json"], 266 | "exclude": ["**/node_modules/**", "!libs/settings/**/*"] 267 | } 268 | }, 269 | "test": { 270 | "builder": "@nrwl/jest:jest", 271 | "options": { 272 | "jestConfig": "libs/settings/jest.config.js", 273 | "tsConfig": "libs/settings/tsconfig.spec.json", 274 | "passWithNoTests": true, 275 | "setupFile": "libs/settings/src/test-setup.ts" 276 | } 277 | } 278 | }, 279 | "schematics": { 280 | "@nrwl/angular:component": { 281 | "style": "scss" 282 | } 283 | } 284 | }, 285 | "editor": { 286 | "projectType": "library", 287 | "root": "libs/editor", 288 | "sourceRoot": "libs/editor/src", 289 | "prefix": "conduit", 290 | "architect": { 291 | "lint": { 292 | "builder": "@angular-devkit/build-angular:tslint", 293 | "options": { 294 | "tsConfig": ["libs/profile/tsconfig.lib.json", "libs/profile/tsconfig.spec.json"], 295 | "exclude": ["**/node_modules/**", "!libs/profile/**/*"], 296 | "tsConfig": ["libs/editor/tsconfig.lib.json", "libs/editor/tsconfig.spec.json"], 297 | "exclude": ["**/node_modules/**", "!libs/editor/**/*"] 298 | } 299 | }, 300 | "test": { 301 | "builder": "@nrwl/jest:jest", 302 | "options": { 303 | "jestConfig": "libs/editor/jest.config.js", 304 | "tsConfig": "libs/editor/tsconfig.spec.json", 305 | "passWithNoTests": true, 306 | "setupFile": "libs/editor/src/test-setup.ts" 307 | } 308 | } 309 | } 310 | }, 311 | "profile": { 312 | "projectType": "library", 313 | "root": "libs/profile", 314 | "sourceRoot": "libs/profile/src", 315 | "prefix": "conduit", 316 | "architect": { 317 | "lint": { 318 | "builder": "@angular-devkit/build-angular:tslint", 319 | "options": { 320 | "tsConfig": ["libs/profile/tsconfig.lib.json", "libs/profile/tsconfig.spec.json"], 321 | "exclude": ["**/node_modules/**", "!libs/profile/**/*"] 322 | } 323 | }, 324 | "test": { 325 | "builder": "@nrwl/jest:jest", 326 | "options": { 327 | "jestConfig": "libs/profile/jest.config.js", 328 | "tsConfig": "libs/profile/tsconfig.spec.json", 329 | "passWithNoTests": true, 330 | "setupFile": "libs/profile/src/test-setup.ts" 331 | } 332 | } 333 | }, 334 | "schematics": { 335 | "@nrwl/angular:component": { 336 | "style": "scss" 337 | } 338 | } 339 | }, 340 | "utils": { 341 | "root": "libs/utils", 342 | "sourceRoot": "libs/utils/src", 343 | "projectType": "library", 344 | "schematics": {}, 345 | "architect": { 346 | "lint": { 347 | "builder": "@angular-devkit/build-angular:tslint", 348 | "options": { 349 | "tsConfig": ["libs/utils/tsconfig.lib.json", "libs/utils/tsconfig.spec.json"], 350 | "exclude": ["**/node_modules/**", "!libs/utils/**/*"] 351 | } 352 | }, 353 | "test": { 354 | "builder": "@nrwl/jest:jest", 355 | "options": { 356 | "jestConfig": "libs/utils/jest.config.js", 357 | "tsConfig": "libs/utils/tsconfig.spec.json", 358 | "passWithNoTests": true 359 | } 360 | } 361 | } 362 | } 363 | }, 364 | "cli": { 365 | "defaultCollection": "@nrwl/angular", 366 | "analytics": "bf1611c3-a794-426c-ad2e-c9131a3771d5" 367 | }, 368 | "schematics": { 369 | "@nrwl/angular:application": { 370 | "unitTestRunner": "jest", 371 | "e2eTestRunner": "cypress" 372 | }, 373 | "@nrwl/angular:library": { 374 | "unitTestRunner": "jest" 375 | } 376 | }, 377 | "defaultProject": "conduit" 378 | } 379 | -------------------------------------------------------------------------------- /FRONTEND_INSTRUCTIONS.md: -------------------------------------------------------------------------------- 1 | > *Note: Delete this file before publishing your app!* 2 | 3 | ### Using the hosted API 4 | 5 | Simply point your [API requests](https://github.com/gothinkster/realworld/tree/master/api) to `https://conduit.productionready.io/api` and you're good to go! 6 | 7 | ### Routing Guidelines 8 | 9 | - Home page (URL: /#/ ) 10 | - List of tags 11 | - List of articles pulled from either Feed, Global, or by Tag 12 | - Pagination for list of articles 13 | - Sign in/Sign up pages (URL: /#/login, /#/register ) 14 | - Uses JWT (store the token in localStorage) 15 | - Authentication can be easily switched to session/cookie based 16 | - Settings page (URL: /#/settings ) 17 | - Editor page to create/edit articles (URL: /#/editor, /#/editor/article-slug-here ) 18 | - Article page (URL: /#/article/article-slug-here ) 19 | - Delete article button (only shown to article's author) 20 | - Render markdown from server client side 21 | - Comments section at bottom of page 22 | - Delete comment button (only shown to comment's author) 23 | - Profile page (URL: /#/profile/:username, /#/profile/:username/favorites ) 24 | - Show basic user info 25 | - List of articles populated from author's created articles or author's favorited articles 26 | 27 | # Styles 28 | 29 | Instead of having the Bootstrap theme included locally, we recommend loading the precompiled theme from our CDN (our [header template](#header) does this by default): 30 | 31 | ```html 32 | 33 | ``` 34 | 35 | Alternatively, if you want to make modifications to the theme, check out the [theme's repo](https://github.com/gothinkster/conduit-bootstrap-template). 36 | 37 | 38 | # Templates 39 | 40 | - [Layout](#layout) 41 | - [Header](#header) 42 | - [Footer](#footer) 43 | - [Pages](#pages) 44 | - [Home](#home) 45 | - [Login/Register](#loginregister) 46 | - [Profile](#profile) 47 | - [Settings](#settings) 48 | - [Create/Edit Article](#createedit-article) 49 | - [Article](#article) 50 | 51 | 52 | ## Layout 53 | 54 | 55 | ### Header 56 | 57 | ```html 58 | 59 | 60 | 61 | 62 | Conduit 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 95 | 96 | 97 | ``` 98 | 99 | ### Footer 100 | ```html 101 |
102 |
103 | conduit 104 | 105 | An interactive learning project from Thinkster. Code & design licensed under MIT. 106 | 107 |
108 |
109 | 110 | 111 | 112 | ``` 113 | 114 | ## Pages 115 | 116 | ### Home 117 | ```html 118 |
119 | 120 | 126 | 127 |
128 |
129 | 130 |
131 |
132 | 140 |
141 | 142 |
143 | 153 | 154 |

How to build webapps that scale

155 |

This is the description for the post.

156 | Read more... 157 |
158 |
159 | 160 |
161 | 171 | 172 |

The song you won't ever stop singing. No matter how hard you try.

173 |

This is the description for the post.

174 | Read more... 175 |
176 |
177 | 178 |
179 | 180 |
181 | 195 |
196 | 197 |
198 |
199 | 200 |
201 | ``` 202 | 203 | ### Login/Register 204 | 205 | ```html 206 |
207 |
208 |
209 | 210 |
211 |

Sign up

212 |

213 | Have an account? 214 |

215 | 216 |
    217 |
  • That email is already taken
  • 218 |
219 | 220 |
221 |
222 | 223 |
224 |
225 | 226 |
227 |
228 | 229 |
230 | 233 |
234 |
235 | 236 |
237 |
238 |
239 | ``` 240 | 241 | ### Profile 242 | 243 | ```html 244 |
245 | 246 | 266 | 267 |
268 |
269 | 270 |
271 |
272 | 280 |
281 | 282 |
283 | 293 | 294 |

How to build webapps that scale

295 |

This is the description for the post.

296 | Read more... 297 |
298 |
299 | 300 |
301 | 311 | 312 |

The song you won't ever stop singing. No matter how hard you try.

313 |

This is the description for the post.

314 | Read more... 315 |
    316 |
  • Music
  • 317 |
  • Song
  • 318 |
319 |
320 |
321 | 322 | 323 |
324 | 325 |
326 |
327 | 328 |
329 | ``` 330 | 331 | ### Settings 332 | 333 | ```html 334 |
335 |
336 |
337 | 338 |
339 |

Your Settings

340 | 341 |
342 |
343 |
344 | 345 |
346 |
347 | 348 |
349 |
350 | 351 |
352 |
353 | 354 |
355 |
356 | 357 |
358 | 361 |
362 |
363 |
364 | 365 |
366 |
367 |
368 | ``` 369 | 370 | ### Create/Edit Article 371 | 372 | ```html 373 |
374 |
375 |
376 | 377 |
378 |
379 |
380 |
381 | 382 |
383 |
384 | 385 |
386 |
387 | 388 |
389 |
390 |
391 |
392 | 395 |
396 |
397 |
398 | 399 |
400 |
401 |
402 | 403 | 404 | ``` 405 | 406 | ### Article 407 | 408 | ```html 409 |
410 | 411 | 437 | 438 |
439 | 440 |
441 |
442 |

443 | Web development technologies have evolved at an incredible clip over the past few years. 444 |

445 |

Introducing RealWorld.

446 |

It's a great solution for learning how other frameworks work.

447 |
448 |
449 | 450 |
451 | 452 |
453 | 472 |
473 | 474 |
475 | 476 |
477 | 478 |
479 |
480 | 481 |
482 | 488 |
489 | 490 |
491 |
492 |

With supporting text below as a natural lead-in to additional content.

493 |
494 | 502 |
503 | 504 |
505 |
506 |

With supporting text below as a natural lead-in to additional content.

507 |
508 | 520 |
521 | 522 |
523 | 524 |
525 | 526 |
527 | 528 |
529 | ``` 530 | --------------------------------------------------------------------------------