├── 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 |
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 |
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 |
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 | # 
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 | 0">
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 |
4 |
5 |
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 |
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 |
13 |
14 |
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 |
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 |
3 |
4 |
conduit
5 |
A place to share your knowledge.
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | -
15 |
23 |
24 | -
25 |
32 |
33 |
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 |
3 |
4 |
5 |
6 |
![]()
7 |
{{profile.username}}
8 |
9 | {{profile.bio}}
10 |
11 |
17 |
18 | Edit Profile Settings
19 |
20 |
21 |
22 |
23 |
24 |
25 |
48 |
49 |
--------------------------------------------------------------------------------
/libs/settings/src/lib/settings/settings.page.html:
--------------------------------------------------------------------------------
1 |
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 |
109 |
110 |
111 |
112 | ```
113 |
114 | ## Pages
115 |
116 | ### Home
117 | ```html
118 |
119 |
120 |
121 |
122 |
conduit
123 |
A place to share your knowledge.
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
141 |
142 |
159 |
160 |
177 |
178 |
179 |
180 |
181 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | ```
202 |
203 | ### Login/Register
204 |
205 | ```html
206 |
239 | ```
240 |
241 | ### Profile
242 |
243 | ```html
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |

252 |
Eric Simons
253 |
254 | Cofounder @GoThinkster, lived in Aol's HQ for a few months, kinda looks like Peeta from the Hunger Games
255 |
256 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
281 |
282 |
299 |
300 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 | ```
330 |
331 | ### Settings
332 |
333 | ```html
334 |
368 | ```
369 |
370 | ### Create/Edit Article
371 |
372 | ```html
373 |
402 |
403 |
404 | ```
405 |
406 | ### Article
407 |
408 | ```html
409 |
410 |
411 |
412 |
413 |
414 |
How to build webapps that scale
415 |
416 |
417 |

418 |
422 |
427 |
428 |
433 |
434 |
435 |
436 |
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 |
454 |

455 |
459 |
460 |
465 |
466 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
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 |
--------------------------------------------------------------------------------