docs(nx): add an intro tutorial

This commit is contained in:
Victor Savkin 2019-02-28 10:13:37 -05:00
parent 3eede56891
commit a063168a82
13 changed files with 912 additions and 0 deletions

View File

@ -25,6 +25,60 @@
}
]
},
{
"name": "Tutorial",
"id": "tutorial",
"itemList": [
{
"name": "1 - Create Application",
"id": "01-create-application"
},
{
"name": "2 - Add E2E Test",
"id": "02-add-e2e-test"
},
{
"name": "3 - Display Todos",
"id": "03-display-todos"
},
{
"name": "4 - Connect to API",
"id": "04-connect-to-api"
},
{
"name": "5 - Add Node Application",
"id": "05-add-node-app"
},
{
"name": "6 - Add Node Application",
"id": "06-proxy"
},
{
"name": "7 - Share Code",
"id": "07-share-code"
},
{
"name": "8 - Create Libraries",
"id": "08-create-libs"
},
{
"name": "9 - Dep Graph",
"id": "09-dep-graph"
},
{
"name": "10 - Test Affected Projects",
"id": "10-test-affected-projects"
},
{
"name": "11 - Build Affected Projects",
"id": "11-build-affected-projects"
},
{
"name": "12 - Summary",
"id": "12-summary"
}
]
},
{
"name": "Fundamentals",
"id": "fundamentals",

View File

@ -0,0 +1,105 @@
# Step 1: Create Application
In this tutorial you will use Nx to build a full-stack application out of common libraries using modern technologies like Cypress and Nest.
## Create a New Workspace
**Start by creating a new workspace.**
```bash
npx -p @nrwl/schematics create-nx-workspace myorg
```
When asked about 'preset', select `empty`.
```treeview
myorg/
├── apps/
├── libs/
├── tools/
├── nx.json
├── angular.json
├── package.json
├── tsconfig.json
├── tslint.json
└── README.md
```
This is an empty Nx workspace without any applications or libraries: nothing to run and nothing to test.
## Create an Angular Application
**Create you first Angular application.**
```bash
ng g app todos
```
Nx will ask you a few questions about the application you are trying to create: the directory it will be placed it, the tags used for linting, etc.. As your workspace grows, those things become really important. For now the default answers are good enough.
After Nx generated the code and ran `npm install`, you should see something like this:
```treeview
myorg/
├── apps/
│   ├── todos/
│   │   ├── browserslist
│   │   ├── jest.conf.js
│   │   ├── src/
│   │   │   ├── app/
│   │   │   ├── assets/
│   │   │   ├── environments/
│   │   │   ├── favicon.ico
│   │   │   ├── index.html
│   │   │   ├── main.ts
│   │   │   ├── polyfills.ts
│   │   │   ├── styles.scss
│   │   │   └── test.ts
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.spec.json
│   │   └── tslint.json
│   └── todos-e2e/
│      ├── cypress.json
│      ├── src/
│      │   ├── fixtures/
│      │   │   └── example.json
│      │   ├── integration/
│      │   │   └── app.spec.ts
│      │   ├── plugins/
│      │   │   └── index.ts
│      │   └── support/
│      │      ├── app.po.ts
│      │      ├── commands.ts
│      │      └── index.ts
│      ├── tsconfig.e2e.json
│      ├── tsconfig.json
│      └── tslint.json
├── libs/
├── tools/
├── angular.json
├── nx.json
├── package.json
├── tsconfig.json
├── tslint.json
└── README.md
```
The generate command added two projects to our workspace:
- An Angular application
- E2E tests for the Angular application
**Serve the newly created application.**
```bash
ng serve todos
```
---
## Open http://localhost:4200 in the browser. What do you see?
Page saying "This project was generated with Angular CLI using Nrwl Nx"
Page saying "This project was created using Angular CLI"
404

View File

@ -0,0 +1,40 @@
# Step 2: Add E2E Test
By default, Nx uses Cypress to run E2E tests.
**Open `apps/todos-e2e/src/support/app.po.ts`.** It's a page object file that contains helpers for querying the page.
**Add the following two helpers:**
```typescript
export const getTodos = () => cy.get('li.todo');
export const getAddTodoButton = () => cy.get('button#add-todo');
```
**Next, update `apps/todos-e2e/src/integration/app.spec.ts`.**
```typescript
import { getAddTodoButton, getTodos } from '../support/app.po';
describe('TodoApps', () => {
beforeEach(() => cy.visit('/'));
it('should display todos', () => {
getTodos().should(t => expect(t.length).equal(2));
getAddTodoButton().click();
getTodos().should(t => expect(t.length).equal(3));
});
});
```
This is, obviously, not a great example of an E2E test, but the purposes of this tutorial it will do.
**If you haven't done it already, stop the `ng serve` command and run `ng e2e todos-e2e --watch`.**
!!!!!
What assertion fails?
!!!!!
Expect 0 to equal 2
Nothing fails. Everything works.
Cannot find any elements matching 'li.todo'
Cannot find any elements matching 'button#add-todo'

View File

@ -0,0 +1,86 @@
# Step 3: Display Todos
Great! You have a failing E2E test. Let's make it pass!
The best way to work with Cypress is to keep the failing E2E test running while working on the app. This helps you see the progress you are making.
## Show Todos
**Open `apps/todos`.** If you have used Angular CLI, this should look very familiar: same layout, same module and component files. The only difference is that Nx uses Jest instead of Karma.
**To make the first assertion of the e2e test pass, update `apps/todos/src/app/app.component.ts`:**
```typescript
import { Component } from '@angular/core';
interface Todo {
title: string;
}
@Component({
selector: 'myorg-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];
}
```
and `apps/todos/src/app/app.component.html`:
```html
<h1>Todos</h1>
<ul>
<li *ngFor="let t of todos" class="todo">{{ t.title }}</li>
</ul>
```
**Refresh the e2e test.** Now the test will fail while trying to find the add todo button.
## Add Todos
**Add the `add-todo` button with the corresponding click handler.**
```typescript
import { Component } from '@angular/core';
interface Todo {
title: string;
}
@Component({
selector: 'myorg-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];
addTodo() {
this.todos.push({
title: `New todo ${Math.floor(Math.random() * 1000)}`
});
}
}
```
```html
<h1>Todos</h1>
<ul>
<li *ngFor="let t of todos" class="todo">{{ t.title }}</li>
</ul>
<button id="add-todo" (click)="addTodo()">Add Todo</button>
```
The tests should pass now.
---
## What will you see if you run `ng e2e todos-e2e --headless`
Cypress will run in the headless mode, and the test will pass.
Cypress will run in the headless mode, and the test will fail.

View File

@ -0,0 +1,63 @@
# Step 4: Connect to API
Real-world applications dont live in isolationthey need APIs to talk to. Let's sketch something out!
**Open `apps/todos/src/app/app.module.ts` to import `HttpClientModule`.**
```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
```
**Now, use `HttpClient` in the component to get the data from the api.**
```typescript
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
interface Todo {
title: string;
}
@Component({
selector: 'myorg-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos: Todo[] = [];
constructor(private http: HttpClient) {
this.fetch();
}
fetch() {
this.http.get<Todo[]>('/api/todos').subscribe(t => (this.todos = t));
}
addTodo() {
this.http.post('/api/addTodo', {}).subscribe(() => {
this.fetch();
});
}
}
```
---
## Run `ng serve todos` and open localhost:4200. What do you see?
"the server responded with a status of 404 (Not Found)" in Console.
Blank screen.
Exception rendered on the screen.

View File

@ -0,0 +1,129 @@
# Step 5: Add Node Application Implementing API
Using Nx you can develop node applications next to your Angular applications. You can use same commands to run and test them. You can share code between the backend and the frontend. Let's use this capability to implement the API service.
**Run the following to generate a new Node application:**
```bash
ng g node-app api --frontendProject=todos
```
Nx will ask you a few questions, and, as with the Angular application, the defaults will work well here.
After Nx is done installing the required dependencies, you should see something like this:
```treeview
myorg/
├── apps/
│   ├── todos/
│   ├── todos-e2e/
│   └── api/
│      ├── jest.conf.js
│      ├── proxy.conf.json
│      ├── src/
│      │   ├── app/
│      │   │   ├── app.controller.ts
│      │   │   ├── app.controller.spec.ts
│      │   │   ├── app.module.ts
│      │   │   ├── app.service.ts
│      │   │   └── app.service.spec.ts
│      │   ├── assets/
│      │   ├── environments/
│      │   │   ├── environment.ts
│      │   │ └── environment.prod.ts
│      │   └── main.ts
│      ├── tsconfig.app.json
│      ├── tsconfig.json
│      ├── tsconfig.spec.json
│      └── tslint.json
├── libs/
├── nx.json
├── package.json
├── tools/
├── tsconfig.json
└── tslint.json
```
The `apps` directory is where Nx places anything you can run: frontend applications, backend applications, e2e test suites. That's why the `api` application appeared there.
You can run:
- `ng serve api` to serve the application
- `ng build api` to build the application
- `ng test api` to test the application
**Open `apps/api/src/app/app.module.ts`.**
```typescript
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}
```
By default, Nx uses the Nest framework when creating node applications. Nest is heavily inspired by Angular, so the configuration for your backend and your frontend will look similar. Also, a lot of best practices used in Angular can be used in Nest as well.
In this case you have an application that registers a service and a controller. Services in Nest are responsible for the business logic, and controllers are responsible for implementing Http endpoints.
**Update `AppService`:**
```typescript
import { Injectable } from '@nestjs/common';
interface Todo {
title: string;
}
@Injectable()
export class AppService {
todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];
getData(): Todo[] {
return this.todos;
}
addTodo() {
this.todos.push({
title: `New todo ${Math.floor(Math.random() * 1000)}`
});
}
}
```
**Next, update the controller to invoke the service:**
```typescript
import { Controller, Get, Post } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get('todos')
getData() {
return this.appService.getData();
}
@Post('addTodo')
addTodo() {
return this.appService.addTodo();
}
}
```
---
## Run `ng serve api` and open https://localhost:3333/api/todos. What do you see?
`[{"title":"Todo 1"},{"title":"Todo 2"}]`
Blank screen
404

47
docs/tutorial/06-proxy.md Normal file
View File

@ -0,0 +1,47 @@
# Step 6: Proxy
You passed `--frontendProject=todos` when creating the node application. What did that argument do?
It created a proxy configuration that allows the Angular application to talk to the API.
**To see how it works, open `angular.json` and find the `serve` target of the todos app.**
```json
{
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "todos:build",
"proxyConfig": "apps/todos/proxy.conf.json"
},
"configurations": {
"production": {
"browserTarget": "todos:build:production"
}
}
}
}
```
**Note the `proxyConfig` property.**
**Now open `proxy.conf.json`:**
```json
{
"/api": {
"target": "http://localhost:3333",
"secure": false
}
}
```
This configuration tells `ng serve` to forward all requests starting with `/api` to the process listening on port 3333.
---
## Now run both `ng serve todos` and `ng serve api`, open localhost:4200. What do you see?
Todos application working!
404 in the console
Todos are displayed but the Add Todo button doesn't work

View File

@ -0,0 +1,109 @@
# Step 7: Share Code
There is a problem. Both the backend and the frontend define the `Todo` interface. The interface is in sync now, but in a real application, over time, it will diverge, and, as a result, runtime errors will creep in. You need to share this interface between the backend and the frontend. In Nx, you can do it by creating a library.
**Run the following generator to create a library:**
```bash
ng g lib data
```
**When asked "What framework should this library use?", select `TypeScript`.** The result should look like this:
```treeview
myorg/
├── apps/
│   ├── frontend/
│   ├── frontend-e2e/
│   └── api/
├── libs/
│   └── data/
│      ├── jest.conf.js
│      ├── src/
│      │   ├── lib/
│      │   └── index.ts
│      ├── tsconfig.app.json
│      ├── tsconfig.json
│      ├── tsconfig.spec.json
│      └── tslint.json
├── nx.json
├── package.json
├── tools/
├── tsconfig.json
└── tslint.json
```
**Copy the interface into the library's index file.**
```typescript
interface Todo {
title: string;
}
```
## Update Backend
**Now update `app.service.ts` to import the interface:**
```typescript
import { Injectable } from '@nestjs/common';
import { Todo } from '@myorg/data';
@Injectable()
export class AppService {
todos: Todo[] = [{ title: 'Todo 1' }, { title: 'Todo 2' }];
getData(): Todo[] {
return this.todos;
}
addTodo() {
this.todos.push({
title: `New todo ${Math.floor(Math.random() * 1000)}`
});
}
}
```
## Update Frontend
**Next import the interface on the frontend:**
```typescript
import { Component } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Todo } from '@myorg/data';
@Component({
selector: 'myorg-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
todos: Todo[] = [];
constructor(private http: HttpClient) {
this.fetch();
}
fetch() {
this.http.get<Todo[]>('/api/todos').subscribe(t => (this.todos = t));
}
addTodo() {
this.http.post('/api/addTodo', {}).subscribe(() => {
this.fetch();
});
}
}
```
Every time you add a new library, you have to restart `ng serve`. **So restart both `ng serve api` and `ng serve todos` and you should see the application running.**
---
## Nx allows you to share code...
Between frontend and backend apps
Between different frontend apps
Between different node apps

View File

@ -0,0 +1,158 @@
# Step 8: Create Libs
Libraries aren't just a way to share code in Nx. They are also useful for factoring out code into small units with well-defined public API.
## Public API
Every library has an `index.ts` file, which defines its public API. Other applications and libraries can only access what the `index.ts` exports. Everything else defined in the library is private.
## UI Libraries
To illustrate how useful libraries can be, create a library of Angular components.
**Run `ng g lib ui`, and select Angular as the library framework.**
```treeview
myorg/
├── apps/
│   ├── frontend/
│   ├── frontend-e2e/
│   └── api/
├── libs/
│   ├── data/
│ └── ui/
│      ├── jest.conf.js
│      ├── src/
│      │   ├── lib/
│      │   │ ├── ui.module.spec.ts
│      │   │ └── ui.module.ts
│      │   └── index.ts
│      ├── tsconfig.app.json
│      ├── tsconfig.json
│      ├── tsconfig.spec.json
│      └── tslint.json
├── nx.json
├── package.json
├── tools/
├── tsconfig.json
└── tslint.json
```
The `ui.module.ts` file looks like this:
```typescript
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule]
})
export class UiModule {}
```
## Add Component
**Add a component to the newly created ui library by running:**
```bash
ng g component todos --project=ui --export
```
```treeview
myorg/
├── apps/
│   ├── frontend/
│   ├── frontend-e2e/
│   └── api/
├── libs/
│   ├── data/
│ └── ui/
│      ├── jest.conf.js
│      ├── src/
│      │   ├── lib/
│ │ │ ├── todos/
│ │ │ │ ├── todos.component.css
│ │ │ │ ├── todos.component.html
│ │ │ │ ├── todos.component.spec.ts
│ │ │ │ └── todos.component.ts
│      │   │ ├── ui.module.spec.ts
│      │   │ └── ui.module.ts
│      │   └── index.ts
│      ├── tsconfig.app.json
│      ├── tsconfig.json
│      ├── tsconfig.spec.json
│      └── tslint.json
├── nx.json
├── package.json
├── tools/
├── tsconfig.json
└── tslint.json
```
**Add the `todos` input to the `TodosComponent`.**
```typescript
import { Component, OnInit, Input } from '@angular/core';
import { Todo } from '@myorg/data';
@Component({
selector: 'myorg-todos',
templateUrl: './todos.component.html',
styleUrls: ['./todos.component.css']
})
export class TodosComponent implements OnInit {
@Input() todos: Todo[];
constructor() {}
ngOnInit() {}
}
```
**And update `todos.component.html` to display the given todos:**
```html
<ul>
<li *ngFor="let t of todos">{{ t.title }}</li>
</ul>
```
## Use Ui Library
**Now import `UiModule` into `AppModule`.**
```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';
import { UiModule } from '@myorg/ui';
@NgModule({
declarations: [AppComponent],
imports: [BrowserModule, HttpClientModule, UiModule],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {}
```
**And update `app.component.html`:**
```html
<h1>Todos</h1>
<myorg-todos [todos]="todos"></myorg-todos>
<button (click)="addTodo()">Add Todo</button>
```
**Restart both `ng serve api` and `ng serve todos` and you should see the application running.**
---
## Libraries' public API is defined in...
index.ts files
angular.json and tsconfig.json files

View File

@ -0,0 +1,15 @@
# Step 9: Dep Graph
An Nx workspace can contain dozens (or hundreds) of applications and libraries. It can be difficult to understand how they depend upon each other, and what are the implications of making a particular change.
Previously, some senior architect would create an ad-hoc dependency diagram and upload it to a corporate wiki. The diagram isnt correct even on Day 1, and gets more and more out of sync with every passing day.
With Nx, you can do better than that.
---
## Run `npm run dep-graph`. What do you see?
A dependency diagram in the browser
A dep-graph.html file created at the root of the workspace
A json document printed out in the terminal

View File

@ -0,0 +1,54 @@
# Step 10: Test Affected Projects
Because Nx understands the dependency graph of your workspace, Nx can be efficient at retesting and rebuilding your projects.
**Commit all the changes in the repo**:
```bash
git add .
git commit -am 'init'
```
**Open `todos.component.html` and change the template:**
```html
<ul>
<li *ngFor="let t of todos">{{ t.title }}!</li>
</ul>
```
**Run `npm run affected:apps -- --base=master`**, and you should see `todos` printed out. The `affected:apps` looks at what you have changed compared to `base` (in this case `master`) and uses the dependency graph to figure out which apps can be affected by this change.
**Run `npm run affected:libs -- --base=master`**, and you should see `ui` printed out. This command works similarly, but instead of printing the affected apps, it prints the affected libs.
## Test Affected Projects
Printing the affected projects can be handy, but usually you want to do something with them. For instance, you may want to test everything that has been affected.
**Run `npm run affected:test -- --base=master` to retest all the affected projects.**
You will see the following:
```
Running test for affected projects failed.
Failed projects: todos
You can isolate the above projects by passing --only-failed
```
One of the projects failed. Instead of retesting every single project on every change, pass `--only-failed` to only retest the failed ones.
**Run `npm run affected:test -- --base=master --only-failed` to retest the failed projects.**
## Testing in Parallel
Some changes affect every single project in the repository. To speed up the testing of this change, pass `--parallel`.
**Run `npm run affected:test -- --base=master --parallel` to test all projects in parallel**
---
## Check in the changes into master and run `npm run affected:test -- --base=master`. What do you see?
No tests ran
The `todos` project failed as before
`Cannot run tests against master` error

View File

@ -0,0 +1,32 @@
# Step 11: Build Affected Projects
## Build Affected Apps
**Once again make a change to `todos.component.html`:**
```html
<ul>
<li *ngFor="let t of todos">{{ t.title }}!!!!!</li>
</ul>
```
**Run `npm run affected:build -- --base=master`**
Nx will rebuild `todos` app. Why didn't it rebuild `ui`?
By default, Nx build libraries in the context of some application. You can change it if you mark a library as `publishable`.
## Affected:\*
You can run any target against the affected projects in the graph like this:
```bash
npm run affected -- --target=build --base=master
```
---
## Run `npm run affected -- --target=invalid --base=master`. What do you see?
No projects to run invalid
An error message saying that the "invalid" target is invalid

View File

@ -0,0 +1,20 @@
# Step 12: Summary
In this tutorial you:
- Built a full stack application using Angular and Nest
- Shared code between the frontend and the backend
- Created a UI library
- Used Nx dep graph capabilities to only retest and rebuild what is affected
## Learn More
Watch the video showing how to use Nx and Angular Console to build full-stack applications.
<iframe width="560" height="380" src="https://www.youtube.com/embed/XZpp52IqD2A" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>
### Read Fundamentals
- [Using Modern Tools](/fundamentals/use-modern-tools)
- [Building Full-Stack Applications](/fundamentals/build-full-stack-applications)
- [Developing Like Google: Monorepos and Automation](/fundamentals/develop-like-google)