Use Angular 17 and .NET 8 to create a medium.com clone. This uses Nx to manage monorepos.
Before reading this tutorial, finish this tutorial first.
- gothinkster demo app
A monorepo is a single repository containing multiple distinct projects, with well-defined relationships.
![image](https://private-user-images.githubusercontent.com/30603497/288949696-2a374d20-6776-4e5a-a05e-27c03560e1d4.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODg5NDk2OTYtMmEzNzRkMjAtNjc3Ni00ZTVhLWEwNWUtMjdjMDM1NjBlMWQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTNiMjVlOGM3OWMyYzFlODc3OGY1OGFjNjZkMGVkZDRhZTdiOGU5ZGI3NTZjMGM4YThlMTFmMjdjNzMzMDQ5ZTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.o70Kt--VZ6JxKAoTzD71WDLc9TkalnUbAy-f-uEnU10)
Make sure you're setup for frontend development. Follow this guide if you haven't already.
In addition to plugins you installed during the setup mentioned above, install these plugins
In this example, I created this repo to host my projects. Note that I'll use the name of this repo (angular-dotnet-realworld
) as the name of the workspace when I create nx-workspace below.
Reference: Watch this YT video from Nx first
Reference: Or this Nx tutorial if you don't like videos
Reference: Getting Started with Nx
Reference: Create Nx Workspace
Open Terminal in your rider and create nx workspace
Ashishs-MacBook-Pro:RiderProjects ashishkhanal$ npx create-nx-workspace@latest --skipGit=true
(As you can see, I'm not inside the repo/ project folder while running the command. The repo folder will be created by above command)
The questions and answers I chose are as follows
✔ Where would you like to create your workspace? · angular-dotnet-realworld
✔ Which stack do you want to use? · angular
✔ Integrated monorepo, or standalone project? · integrated (Integrated Monorepo: Nx creates a monorepo that contains multiple projects.)
✔ Application name · angular-client
✔ Which bundler would you like to use? · esbuild
✔ Default stylesheet format · css
✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? · No
✔ Test runner to use for end to end (E2E) tests · playwright
✔ Enable distributed caching to make your CI faster · No
Typically, an integrated Nx workspace places application projects in the apps folder and library projects in the libs folder. Applications are encouraged to be as light-weight as possible so that more code is pushed into libraries and can be reused in other projects.
The nx.json file contains configuration settings for Nx itself and global default settings that individual projects inherit.
.prettierrc
just means configuration file for Prettier. It contains rules and settings that need to be applied when the tool is run. It's a convention that started with UNIX systems where an "rc file" contained "run commands" to be run at startup.
-
angular-dotnet-realworld/.editorconfig
Lines 1 to 19 in 7e92ad7
Read more here. -
This file is used to specify files or directories that ESlint should ignore while linting the code.
ESLint is a configurable JavaScript linter. It helps you find and fix problems in your JavaScript code. Problems can be anything from potential runtime bugs, to not following best practices, to styling issues. Read more here. -
angular-dotnet-realworld/.gitignore
Line 1 in 7e92ad7
-
angular-dotnet-realworld/.prettierignore
Lines 1 to 5 in 7e92ad7
-
angular-dotnet-realworld/.prettierrc
Lines 1 to 3 in 7e92ad7
.prettierrc
just means configuration file for Prettier. It contains rules and settings that need to be applied when the tool is run. It's a convention (the rc part) that started with UNIX systems where an "rc file" contained "run commands" to be run at startup. -
This is the configuration file for ESlint. It is used to specify the parser options and rules for ESlint.
As you can see there are no rules (formatting) in the file. Because config for that is already stored in
.prettierrc
. - This file is used to specify the configuration for Jest, a JavaScript testing framework. You can specify options that define how Jest runs your tests, which files it should test, any setup tasks that need to be completed before tests run, and more. By convention, it is written in TypeScript (ts).
package.json note about being only one package.json for multiple projects: https://stackoverflow.com/a/52761971/8644294
Note:
Eslint | Prettier |
---|---|
It has formating rules | It has formating rules |
It ensures code quality |
This package is going to turn off all of eslint's configuration for things that prettier already handles.
angular-dotnet-realworld/package.json
Line 47 in 5346b0b
You can run that package like so if you'd like
npx eslint-config-prettier somefile.js
Let's check out this package and learn
![image](https://private-user-images.githubusercontent.com/30603497/289391537-7d79ab1e-5203-4af3-a5ab-645b727da7e4.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkzOTE1MzctN2Q3OWFiMWUtNTIwMy00YWYzLWE1YWItNjQ1YjcyN2RhN2U0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWU2NjFhNTNkYTkwYjJmMzk2MTAyZTM1YzVlYzA2ZGVkMGM3NTkwNTNkOGFlNWI2MWM4MWQzYTZjOGY4MWMyOTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.fB0Ds-Olf7coV4QjO7NzkioQf2tEZwIQmG9xR3ag50k)
In Node.js
, each file has its own module object (global object), and module.exports
is a property of that object.
module.exports
is used to export a module's public API, i.e., the values, functions, or objects that you want to make available for other modules to import and use. Here, it's used to export a function that word-wraps a string.
By assigning a value to module.exports
, we specify what will be exposed from a file when require is called.
/* This is inside index.js*/
module.exports = function(str, options) {
options = options || {};
if (str == null) {
return str;
}
}
To use it
// assuming word-wrap.js is in the same directory.
var wrap = require('./word-wrap');
var textToWrap = "This is some really long text that we want to wrap to multiple lines";
var wrapOptions = {
width: 20,
};
var wrappedText = wrap(textToWrap, wrapOptions);
Read this SO answer first
This is a TypeScript declaration file. It provides types for JavaScript code to the TypeScript compiler. The .d.ts
extension indicates that this is a Declaration File, hence the d in the name. Declaration files are used to tell TypeScript that some specific types of objects conform to an interface, without providing an actual implementation.
/* This is inside index.d.ts*/
export = wrap;
declare function wrap(str: string, options?: wrap.IOptions): string;
declare namespace wrap {
export interface IOptions {
width?: number;
/*and so on...*/
}
}
export = wrap
is a TypeScript-specific syntax that indicates the object (wrap
function in this case) to be exported as a single entity. In CommonJS (Node.js) terms, it is equivalent to module.exports = wrap
.
declare function wrap(...)
, tells TypeScript that there is a function called wrap
that takes certain parameters and returns a specific type, but we're not going to provide the implementation here. We are simply describing the shape of a function.
The type signature specified in index.d.ts should match the actual JavaScript implementation in index.js file.
We could use this package in some TS code like so
import * as wrap from 'word-wrap';
const options: wrap.IOptions = {
width: 30
};
const input = "This is a really long string that needs to be wrapped after a certain number of characters per line";
const result = wrap(input, options);
console.log(result);
If there was no index.d.ts
file, we could use index.js
file present inside node_modules/word-wrap
folder in JS code like so
// load the module
// Node.js will look for the module named 'word-wrap'
// (it does this by searching in node_modules directory and some other locations),
// find its JavaScript entry file (index.js in the case of most modules),
// and execute the code in that file.
// Whatever is assigned to module.exports in index.js is then returned by require('word-wrap').
var wrap = require('word-wrap');
// prepare the options
var options = {width: 30};
// prepare the text to wrap
var input = "This is a really long string that needs to be wrapped after a certain number of characters per line";
// Use the function
var result = wrap(input, options);
console.log(result);
in a TS
- .eslintignore: This file is used to specify files or directories that ESlint should ignore while linting the code. ESlint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. .prettierignore: Similar to .eslintignore, this file is used to specify the files or directories that Prettier should ignore. Prettier is an opinionated code format tool which ensures that all your code adheres to a consistent style. .prettierrc: This is the configuration file for Prettier. You can specify your preferences for formatting options like print width, tab width, use of semi-colons, and single or double quotes, etc. .eslintrc: This is the configuration file for ESlint. It is used to specify the parser options and rules for ESlint. jest.config.js: This is the configuration file for Jest, a JavaScript testing framework developed by Facebook. Here you can specify settings related to how Jest will run tests on your application. nx.json: This is the configuration file for Nrwl NX. Nx is a suite of powerful, extensible dev tools to help you architect, test, and build at any scale – integrating seamlessly with modern technologies and libraries while providing a robust CLI, caching, dependency management and more. package.json: This is a file is used by NPM to store metadata for projects published as NPM modules. It contains information about your project, such as its name, version, description, and what dependencies it has that npm needs to install. package-lock.json: This is automatically generated for any operations where npm modifies the node_modules or the package.json. It describes the exact tree that was generated, such that subsequent installs are able to generate identical trees, regardless of intermediate dependency updates. tsconfig.base.json: This is a special TypeScript configuration file that specifies root files and compiler options required to compile the project. TypeScript is a strict syntactical superset of JavaScript and adds optional static typing to the language. .base is typically the main config file, while others tsconfig.*.json on the workspace extend this main file.
Navigate to the workspace folder and server the app
cd angular-dotnet-realworld
nx serve angular-frontend
Or you can just use Nx Console to run it
![image](https://private-user-images.githubusercontent.com/30603497/289233717-71bfef49-ad36-4adf-9f73-12f2e79dd146.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkyMzM3MTctNzFiZmVmNDktYWQzNi00YWRmLTlmNzMtMTJmMmU3OWRkMTQ2LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI5NWI5NTkzZTA3N2Y4MTJiOWM0MGZlM2ExNDNhOTZjYjU4NjkzMGU5MDFjYjFhMTFjMTMzNzQyYjEwZWE4OGQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.vJlH9CNkLZpzgZIu1Y_H_TBeMyiQq-xNeZEtkbuBQV0)
The app starts running at http://localhost:4200/
![image](https://private-user-images.githubusercontent.com/30603497/289233747-d9af2dfd-fcb4-4545-b349-9d78182170e3.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkyMzM3NDctZDlhZjJkZmQtZmNiNC00NTQ1LWIzNDktOWQ3ODE4MjE3MGUzLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTc3NTNlMTIzYjk3NWQ1ZjdjODBkMjMwNGJhYTA4NmY1YjgxZmVjMjBiYmVjODQwZjAyN2E1MjZkNTZiMDkxMzQmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.EG1BMnXNElyMwLjk4KgAxlj1UV3hZ5xuWElZ4Mw1W0Q)
Get ready for next steps
![image](https://private-user-images.githubusercontent.com/30603497/289233756-0b4c52b4-bf55-4a7e-a890-381451b58dbb.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkyMzM3NTYtMGI0YzUyYjQtYmY1NS00YTdlLWE4OTAtMzgxNDUxYjU4ZGJiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWFiM2I4YWVlNDE2MGI1MTIxMTlmN2IzNThkMjBkMWUxMDViODgxN2M1NGNkMGNkZjdmOTM4Y2I2MDIxMjMyNWMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.XFBMA7yl_JQ8Nx1cbjMgNzA9UMNffrnL1-oOakDvFCY)
Clone this empty Git repo down to your local.
Make sure to change the name so as not to match with what you already have, i.e. angular-dotnet-realworld
.
![image](https://private-user-images.githubusercontent.com/30603497/289222021-de9c4725-e347-43e4-8a05-c5f0fc57f8fb.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkyMjIwMjEtZGU5YzQ3MjUtZTM0Ny00M2U0LThhMDUtYzVmMGZjNTdmOGZiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWQ5ZTUzZjEwZTEwYmEwNTFhMTMwNGI5Y2M1MjlkMDI3YzZkODY1NDkzZjY3OGFkYzk5Njc2MGE0ODVmNjk0OTImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.SEmawtEo-FBVI2oo4OIe3W3kbS9Z_11WoBRlg11R_c0)
Now copy hidden .git
folder from this cloned folder to your nx-workspace folder.
![image](https://private-user-images.githubusercontent.com/30603497/289222139-f720faff-c924-47fa-ad8a-754ab2079f3b.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkyMjIxMzktZjcyMGZhZmYtYzkyNC00N2ZhLWFkOGEtNzU0YWIyMDc5ZjNiLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWIzMDExYjBkNGFlZjMzMTNiMzdjNjc0NDYyMjhlNjg2NjQzNWFiNWM4Njk4MTQ3OWEwYzE2YTRhZjZkZTY1MDgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.pCYhyiz6zWTkpfsPsPOeleawssO6BNyDCf8YyJJvH54)
Since I already have my README
file from GitHub repo creation, rename the README
from nx-workspace to NxREADME
.
Go to commit window, add the files and commit + push to GitHub.
![image](https://private-user-images.githubusercontent.com/30603497/289222729-202928e0-882a-4aea-8cf2-2e18ef6639ae.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkyMjI3MjktMjAyOTI4ZTAtODgyYS00YWVhLThjZjItMmUxOGVmNjYzOWFlLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWUwYTkzMWY1OTc1ZTc5MWE3YTQ1YzM3NDEzNTQ2MTc3MmVkNGJjOTVmMTE3ZjU1MmUzZmQ3ZjJmN2JmNGY0OTgmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.0pG1pXM5IBfMnYf_B2Jh2COBCqo9K9uT-Jq_q9BJDgk)
Run this command to see what you get
nx list @nx/angular
You'll see a list of Generators and Executors/ Builders
For eg:
![image](https://private-user-images.githubusercontent.com/30603497/289397417-4c7cc922-b1c8-489d-a379-c802248bb108.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODkzOTc0MTctNGM3Y2M5MjItYjFjOC00ODlkLWEzNzktYzgwMjI0OGJiMTA4LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWYyMzBhODgyZmU3ZGQ2NjFjMjM3MjRiNWFkYTU1ZmM0MGFkOGJlMWViNWExMDFlZjQyNzk2N2ZkOTAzOTg1ZTEmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.Wxo2rLSu1_UeJIP-XFDmlQybN1sHlvlFvbrXvkI5lGk)
Now to create a component, you can run something like
npx nx g @nx/angular:component hello-world --standalone --dry-run
All those options are found in Nx docs. Easier way is just using Nx console plugin.
nx build
This will create a Prod build of our application and place that under the dist folder.
nx g @nx/angular:lib products --directory=modules --standalone --simpleName --dry-run
nx g @nx/angular:lib orders --directory=modules --standalone --simpleName --dry-run
nx g @nx/angular:lib ui --directory=modules/shared --standalone --simpleName --dry-run
It'll create the usual stuffs for component and also the index.ts
file.
This is basically the public api for your library. This is where you control what components or other utilities you want to expose to other libraries and your application.
Whenever the libs were generated, it also added them to paths inside tsconfig.base.json
.
The static loading is just using it with import like
import { ProductsComponent } from '@myngapp/modules/products'
But we're going to do it the dynamic way by lazily loading them.
The lazy loading can be seen in the nx graph
with dashed-lines.
Go to app.routes.ts and add this
export const appRoutes: Route[] = [
{
path: ''.
component: NxWelcomeComponent,
pathMatch: 'full'
},
{
path: 'products',
loadComponent: () =>
import('@myngapp/modules/products').then(onfulfilled:(c) => c.ProductsComponent)
}
// And so on
]
Go to modules/products/project.json
and add tags for type and scope.
You can use arbitrary strings here.
"tags": ["type:feature", "scope:products"]
Go to the root level .eslintrc.base.json
file
Reference: Nx .NET Tutorial
Reference: [Nx using .NET and Github Actions]https://www.youtube.com/live/uS9RSoqTwVw?si=WozC85bXrGn7aSWD)
npx add -D @nx-dotnet/core
Check everything about the plugin nx list @nx-dotnet/core
It'll show you info about the generators and executors/builders.
nx g @nx-dotnet/core:init <appName. Not sure if this is optional. Time will tell.>
This updates nx.json, package.json, creates Directory.Build.props, Directory.Build.targets etc.
Now nx.json will have "nx-dotnet/core" inside "plugins array. It just tell that "hey, now we're going to analyze dotnet files now"
paclage.json will have "@nrwl/js" for TS type generation and also have "@nx-dotnet/core"
It'll also add "dotnet-tools.json", this enables you to extend the .NET cli. It keeps track if tools (like EF) you install and what version they're at.
Directory.Build.props, Directory.Build.targets get added to root.
Directory.Build.props <--- Kinda like variable declarions. Directory.Build.targets <---
Now you can add your .NET project anywhere like by going to a folder (inside the nx workspace) and do dotnet new webapi
or something and it should be picked up by Nx and shown in the nx graph
.
But Nx recommends creating apps with their generatoru because you get niceer stuffs.
nx g @nx-dotnet/core:app dotnet-api
There's a question: Which path naming conventions should the project use? Since you're using nx, use nx.
nx <--- use-kebab-case dotnet <--- Use.Pascal.Case
There's guide for dealing with .sln files in the docs. Solutions aren't preferred when you're using Nx workspace though.
You can install NxConsole and take a look at it.
You can hit "Serve" to launch the app.
Inside libs, you'll see few projects. Like thge swagger one. If you run it, you'll update swagger.json.
libs/generated/your-api-swagger/project.json There's codegen which if you run will update TS types. You can run it like
nx run your-api-swagger:codegen
You can see this generated type in:
libs/generated/your-api-types/src/interfaces/todo.ts <-- DTO for your C# endpoint to be used in frontend
which can be used by the frontend client.
If you use this in frontned like so
import { Todo } from '@deno-todo/generated/your-api-types';
Nx will trace the depenedency back to this generated library, and this generated library knows that whenever it builds it has to run codegen first, codegen knows it has to run swagger first.
So if you change your model in the backend you'll start getting error in the frontend automatically. There's nice type safety there.
Take a look at the graph.
``nx graph`
Fullstack example - Looks nice
Nx -> RxJs (+Signals in the future) -> Setup Rider for Frontend dev -> Code along with Monsterlessons guy + official docs
- Clone this repo down to your IDE
- Create a new app
ng new medium
Workspace: A collection of angular projects (that is, applications and libraries) powered by the Angular CLI that are typically co-located in a single source-control repository.
A single threaded, non-blocking, asynchronous, concurrent language. It has a call stack, an event loop, a callback queue and some other apis.
JS runtime like V8 has call stack and a heap, but not the other stuffs.
![image](https://private-user-images.githubusercontent.com/30603497/288582132-95c1dc0d-7993-4edb-a774-0bacc0b0108d.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODg1ODIxMzItOTVjMWRjMGQtNzk5My00ZWRiLWE3NzQtMGJhY2MwYjAxMDhkLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWI4MzdjOWE4Y2M4ZDE1Y2MxYzQ4ZjBlNzI2MjFkMzg5NTczYmZhYzM4OWI4OGEzODBiMjk2ZDJmZTZkYTA1ZDcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.gQ8hpQ9Je5uPCUT1jwW8BpuxJil_Qp1kdfSUjGEvrWM)
Since JS is single threaded, it has single stack. So it can only do one thing at a time.
For eg:
Here the main() is the anonymous function you see in browser console which calls printSquare(4)
which then calls square(n)
and finally multiply(n, n)
. When return a * b
is executed, the stack gets removed in reverse order.
![image](https://private-user-images.githubusercontent.com/30603497/288582597-c0f71944-314c-4c5d-8403-13af8d64f023.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MDk1MTkwNzAsIm5iZiI6MTcwOTUxODc3MCwicGF0aCI6Ii8zMDYwMzQ5Ny8yODg1ODI1OTctYzBmNzE5NDQtMzE0Yy00YzVkLTg0MDMtMTNhZjhkNjRmMDIzLnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDAzMDQlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwMzA0VDAyMTkzMFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTkxYjgwOTJmNWI1Y2IzNDExMjI3M2Q3ZDRlMTY1OGVmYjBkN2JmY2YzNDhkZjRmOTg1MTYzMWUwODdlMjA1NzImWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.ZWndaRkSKnQfc3tx458weQvKlbBmDtz6qOrfyao2uMo)
If the call stack has things on it, the browser can't do anything else. It gets stuck.
The solution is asynchronous callbacks.
For eg:
Below shows how the browser and runtime work to make an asynchronous call.
Call Stack:
-
At first line
log('Hi') main()
-
At second line
$.get('url', cb) main()
The code for running this AJAX request (
XHR
) doesn't live in the JS runtime, it lives in the browser as a web api so we spin it up with a callback. And the code continues to run by going to next line. -
At third line
log('JSConfEU') main()
After third line completes, the call stack is empty.
-
When the
$.get
AJAX request completes, callback gets pushed to the queue -
Event loop picks the callback up and pushes it to the stack and it's run.
-
The end