mirror of https://github.com/abpframework/abp.git
committed by
GitHub
2 changed files with 760 additions and 0 deletions
@ -0,0 +1,760 @@ |
|||||
|
# Quick Start |
||||
|
|
||||
|
````json |
||||
|
//[doc-params] |
||||
|
{ |
||||
|
"UI": ["MVC", "BlazorServer", "NG"], |
||||
|
"DB": ["EF", "Mongo"] |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
This is a single-part quick-start tutorial to build a simple todo application with the ABP Framework. Here's a screenshot from the final application: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). |
||||
|
|
||||
|
## Pre-Requirements |
||||
|
|
||||
|
* An IDE (e.g. [Visual Studio](https://visualstudio.microsoft.com/vs/)) that supports [.NET 6.0+](https://dotnet.microsoft.com/download/dotnet) development. |
||||
|
|
||||
|
{{if DB=="Mongo"}} |
||||
|
|
||||
|
* [MongoDB Server 4.0+](https://docs.mongodb.com/manual/administration/install-community/) |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
{{if UI=="NG"}} |
||||
|
|
||||
|
* [Node v14.x](https://nodejs.org/) |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
## Creating a New Solution |
||||
|
|
||||
|
In this tutorial, we will use the [ABP CLI](../../CLI.md) to create the sample application with the ABP Framework. You can run the following command in a command-line terminal to install the **ABP CLI**, if you haven't installed it yet: |
||||
|
|
||||
|
````bash |
||||
|
dotnet tool install -g Volo.Abp.Cli |
||||
|
```` |
||||
|
|
||||
|
Then create an empty folder, open a command-line terminal and execute the following command in the terminal: |
||||
|
|
||||
|
````bash |
||||
|
abp new TodoApp -t app-nolayers{{if UI=="BlazorServer"}} -u blazor-server{{else if UI=="NG"}} -u angular{{end}}{{if DB=="Mongo"}} -d mongodb{{end}} |
||||
|
```` |
||||
|
|
||||
|
{{if UI=="NG"}} |
||||
|
|
||||
|
This will create a new solution with a single project, named *TodoApp* with `angular` and `aspnet-core` folders. Once the solution is ready, open the solution in your favorite IDE. |
||||
|
|
||||
|
{{else}} |
||||
|
|
||||
|
This will create a new solution with a single project, named *TodoApp*. Once the solution is ready, open it in your favorite IDE. |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
### Create the Database |
||||
|
|
||||
|
You can run the following command in the directory of your project to create the database and seed the initial data: |
||||
|
|
||||
|
```bash |
||||
|
dotnet run --migrate-database |
||||
|
``` |
||||
|
|
||||
|
This command will create the database and seed the initial data for you. Then you can run the application. |
||||
|
|
||||
|
### Run the Application |
||||
|
|
||||
|
{{if UI=="MVC" || UI=="BlazorServer"}} |
||||
|
|
||||
|
It is good to run the application before starting the development. Running the application is pretty straight-forward, you can run the application with any IDE that supports .NET or by running the `dotnet run` CLI command in the directory of your project: |
||||
|
|
||||
|
{{else if UI=="NG"}} |
||||
|
|
||||
|
It is good to run the application before starting the development. The solution has two main applications: |
||||
|
|
||||
|
* `TodoApp` (in the .NET solution) hosts the server-side HTTP API, so the Angular application can consume it. (server-side application) |
||||
|
* `angular` folder contains the Angular application. (client-side application) |
||||
|
|
||||
|
Firstly, run the `TodoApp` project in your favorite IDE (or run the `dotnet run` CLI command on your project directory) to see the server-side HTTP API on [Swagger UI](https://swagger.io/tools/swagger-ui/): |
||||
|
|
||||
|
 |
||||
|
|
||||
|
You can explore and test your HTTP API with this UI. If it works, then we can run the Angular client application. |
||||
|
|
||||
|
You can run the application using the following (or `yarn start`) command: |
||||
|
|
||||
|
````bash |
||||
|
npm start |
||||
|
```` |
||||
|
|
||||
|
This command takes time, but eventually runs and opens the application in your default browser: |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
 |
||||
|
|
||||
|
You can click on the *Login* button and use `admin` as the username and `1q2w3E*` as the password to login to the application. |
||||
|
|
||||
|
All right. We can start coding! |
||||
|
|
||||
|
## Defining Entities |
||||
|
|
||||
|
This application will have a single [entity](../../../Entities.md) and we can start by creating it. So, create a new `TodoItem` class under the **Entities** folder of the project: |
||||
|
|
||||
|
````csharp |
||||
|
using Volo.Abp.Domain.Entities; |
||||
|
|
||||
|
namespace TodoApp.Entities; |
||||
|
|
||||
|
public class TodoItem : BasicAggregateRoot<Guid> |
||||
|
{ |
||||
|
public string Text { get; set; } |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
`BasicAggregateRoot` is the simplest base class to create root entities, and `Guid` is the primary key (`Id`) of the entity here. |
||||
|
|
||||
|
## Database Integration |
||||
|
|
||||
|
{{if DB=="EF"}} |
||||
|
|
||||
|
Next step is to setup the [Entity Framework Core](../../../Entity-Framework-Core.md) configuration. |
||||
|
|
||||
|
### Mapping Configuration |
||||
|
|
||||
|
Open the `TodoAppDbContext` class (under the **Data** folder) and add a new `DbSet` property to this class: |
||||
|
|
||||
|
````csharp |
||||
|
public DbSet<TodoItem> TodoItems { get; set; } |
||||
|
```` |
||||
|
|
||||
|
Then navigate to the `OnModelCreating` method in the same class and add the following mapping code for the `TodoItem ` entity: |
||||
|
|
||||
|
````csharp |
||||
|
protected override void OnModelCreating(ModelBuilder builder) |
||||
|
{ |
||||
|
base.OnModelCreating(builder); |
||||
|
|
||||
|
/* Include modules to your migration db context */ |
||||
|
|
||||
|
builder.ConfigurePermissionManagement(); |
||||
|
... |
||||
|
|
||||
|
/* Configure your own tables/entities inside here */ |
||||
|
builder.Entity<TodoItem>(b => |
||||
|
{ |
||||
|
b.ToTable("TodoItems"); |
||||
|
}); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
We've mapped the `TodoItem` entity to the `TodoItems` table in the database. The next step is to create a migration and apply the changes to the database. |
||||
|
|
||||
|
### Code First Migrations |
||||
|
|
||||
|
The startup solution is configured to use Entity Framework Core [Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations). Since we've changed the database mapping configuration, we should create a new migration and apply changes to the database. |
||||
|
|
||||
|
Open a command-line terminal in the directory of your project and type the following command: |
||||
|
|
||||
|
````bash |
||||
|
dotnet ef migrations add Added_TodoItem |
||||
|
```` |
||||
|
|
||||
|
This will add a new migration class to the project. You should see the new migration in the **Migrations** folder. |
||||
|
|
||||
|
 |
||||
|
|
||||
|
Then, you can apply changes to the database using the following command, in the same command-line terminal: |
||||
|
|
||||
|
````bash |
||||
|
dotnet ef database update |
||||
|
```` |
||||
|
|
||||
|
{{else if DB=="Mongo"}} |
||||
|
|
||||
|
The next step is to setup the [MongoDB](../../../MongoDB.md) configuration. Open the `TodoAppDbContext` class (under the **Data** folder) in your project and make the following changes: |
||||
|
|
||||
|
1. Add a new property to the class: |
||||
|
|
||||
|
````csharp |
||||
|
public IMongoCollection<TodoItem> TodoItems => Collection<TodoItem>(); |
||||
|
```` |
||||
|
|
||||
|
2. Add the following code inside the `CreateModel` method: |
||||
|
|
||||
|
````csharp |
||||
|
modelBuilder.Entity<TodoItem>(b => |
||||
|
{ |
||||
|
b.CollectionName = "TodoItems"; |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
After the database integrations, now we can start to create application service methods and implement our use-cases. |
||||
|
|
||||
|
## Creating the Application Service |
||||
|
|
||||
|
An [Application Service](../../Application-Services.md) is used to perform the use cases of the application. We need to perform the following use cases in this application: |
||||
|
|
||||
|
* Get the list of the todo items |
||||
|
* Create a new todo item |
||||
|
* Delete an existing todo item |
||||
|
|
||||
|
Before starting to implement these use cases, first we need to create DTOs. |
||||
|
|
||||
|
### Creating the Data Transfer Object (DTO) |
||||
|
|
||||
|
`ApplicationService` typically gets and returns DTOs ([Data Transfer Objects](../../../Data-Transfer-Objects.md)) instead of entities. So, create a new `TodoItemDto` class under the **Dtos** folder (under the **Services** folder): |
||||
|
|
||||
|
```csharp |
||||
|
namespace TodoApp.Services.Dtos; |
||||
|
|
||||
|
public class TodoItemDto |
||||
|
{ |
||||
|
public Guid Id { get; set; } |
||||
|
public string Text { get; set; } |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
* This is a very simple DTO class that has the same properties as the `TodoItem` entity. Now, we are ready to implement our use-cases. |
||||
|
|
||||
|
### Application Service Implementation |
||||
|
|
||||
|
Create a `TodoAppService` class under the **Services** folder of your project, as shown below: |
||||
|
|
||||
|
```csharp |
||||
|
using TodoApp.Entities; |
||||
|
using Volo.Abp.Application.Services; |
||||
|
using Volo.Abp.Domain.Repositories; |
||||
|
|
||||
|
namespace TodoApp.Services; |
||||
|
|
||||
|
public class TodoAppService : ApplicationService |
||||
|
{ |
||||
|
private readonly IRepository<TodoItem, Guid> _todoItemRepository; |
||||
|
|
||||
|
public TodoAppService(IRepository<TodoItem, Guid> todoItemRepository) |
||||
|
{ |
||||
|
_todoItemRepository = todoItemRepository; |
||||
|
} |
||||
|
|
||||
|
// TODO: Implement the methods here... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
This class inherits from the `ApplicationService` class of the ABP Framework and implements our use-cases. ABP provides default generic [repositories](../../../Repositories.md) for the entities. We can use them to perform the fundamental database operations. This class [injects](../../../Dependency-Injection.md) `IRepository<TodoItem, Guid>`, which is the default repository for the `TodoItem` entity. We will use it to implement the use cases (`GetListAsync`, `CreateAsync` and `DeleteAsync`) described before. |
||||
|
|
||||
|
#### Getting the Todo Items |
||||
|
|
||||
|
Let's start by implementing the `GetListAsync` method: |
||||
|
|
||||
|
````csharp |
||||
|
public async Task<List<TodoItemDto>> GetListAsync() |
||||
|
{ |
||||
|
var items = await _todoItemRepository.GetListAsync(); |
||||
|
return items |
||||
|
.Select(item => new TodoItemDto |
||||
|
{ |
||||
|
Id = item.Id, |
||||
|
Text = item.Text |
||||
|
}).ToList(); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
We are simply getting the `TodoItem` list from the database, mapping them to the `TodoItemDto` objects and returning as the result. |
||||
|
|
||||
|
#### Creating a New Todo Item |
||||
|
|
||||
|
The next method is `CreateAsync` and we can implement it as shown below: |
||||
|
|
||||
|
````csharp |
||||
|
public async Task<TodoItemDto> CreateAsync(string text) |
||||
|
{ |
||||
|
var todoItem = await _todoItemRepository.InsertAsync( |
||||
|
new TodoItem {Text = text} |
||||
|
); |
||||
|
|
||||
|
return new TodoItemDto |
||||
|
{ |
||||
|
Id = todoItem.Id, |
||||
|
Text = todoItem.Text |
||||
|
}; |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
The repository's `InsertAsync` method inserts the given `TodoItem` to the database and returns the same `TodoItem` object. It also sets the `Id`, so we can use it on the returning object. We are simply returning a `TodoItemDto` by creating from the new `TodoItem` entity. |
||||
|
|
||||
|
#### Deleting a Todo Item |
||||
|
|
||||
|
Finally, we can implement the `DeleteAsync` as the following code block: |
||||
|
|
||||
|
````csharp |
||||
|
public async Task DeleteAsync(Guid id) |
||||
|
{ |
||||
|
await _todoItemRepository.DeleteAsync(id); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
The application service is ready to be used from the UI layer. So, let's implement it. |
||||
|
|
||||
|
## User Interface |
||||
|
|
||||
|
It is time to show the todo items on the UI! Before starting to write the code, it would be good to remember what we are trying to build. Here's a sample screenshot from the final UI: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
{{if UI=="MVC"}} |
||||
|
|
||||
|
### Index.cshtml.cs |
||||
|
|
||||
|
Open the `Index.cshtml.cs` file in the `Pages` folder and replace the content with the following code block: |
||||
|
|
||||
|
```csharp |
||||
|
using TodoApp.Services; |
||||
|
using TodoApp.Services.Dtos; |
||||
|
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
||||
|
|
||||
|
namespace TodoApp.Pages; |
||||
|
|
||||
|
public class IndexModel : AbpPageModel |
||||
|
{ |
||||
|
public List<TodoItemDto> TodoItems { get; set; } |
||||
|
|
||||
|
private readonly TodoAppService _todoAppService; |
||||
|
|
||||
|
public IndexModel(TodoAppService todoAppService) |
||||
|
{ |
||||
|
_todoAppService = todoAppService; |
||||
|
} |
||||
|
|
||||
|
public async Task OnGetAsync() |
||||
|
{ |
||||
|
TodoItems = await _todoAppService.GetListAsync(); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
This class uses `TodoAppService` to get the list of todo items and assign the `TodoItems` property. We will use it to render the todo items on the razor page. |
||||
|
|
||||
|
### Index.cshtml |
||||
|
|
||||
|
Open the `Index.cshtml` file in the `Pages` folder and replace it with the following content: |
||||
|
|
||||
|
```xml |
||||
|
@page |
||||
|
@model TodoApp.Pages.IndexModel |
||||
|
|
||||
|
@section styles { |
||||
|
<abp-style src="/Pages/Index.cshtml.css" /> |
||||
|
} |
||||
|
@section scripts { |
||||
|
<abp-script src="/Pages/Index.cshtml.js" /> |
||||
|
} |
||||
|
|
||||
|
<div class="container"> |
||||
|
<abp-card> |
||||
|
<abp-card-header> |
||||
|
<abp-card-title> |
||||
|
TODO LIST |
||||
|
</abp-card-title> |
||||
|
</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<!-- FORM FOR NEW TODO ITEMS --> |
||||
|
<form id="NewItemForm" class="row row-cols-lg-auto g-3 align-items-center"> |
||||
|
<div class="col-12"> |
||||
|
<div class="input-group"> |
||||
|
<input id="NewItemText" type="text" class="form-control" placeholder="enter text..."> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-12"> |
||||
|
<button type="submit" class="btn btn-primary">Submit</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
<!-- TODO ITEMS LIST --> |
||||
|
<ul id="TodoList"> |
||||
|
@foreach (var todoItem in Model.TodoItems) |
||||
|
{ |
||||
|
<li data-id="@todoItem.Id"> |
||||
|
<i class="fa fa-trash-o"></i> @todoItem.Text |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
We are using ABP's [card tag helper](../../UI/AspNetCore/Tag-Helpers/Cards.md) to create a simple card view. You could directly use the standard bootstrap HTML structure, however the ABP [tag helpers](../../UI/AspNetCore/Tag-Helpers/Index.md) make it much easier and type safe. |
||||
|
|
||||
|
This page imports a CSS and a JavaScript file, so we should also create them. |
||||
|
|
||||
|
### Index.cshtml.js |
||||
|
|
||||
|
Open the `Index.cshtml.js` file in the `Pages` folder and replace with the following content: |
||||
|
|
||||
|
````js |
||||
|
$(function () { |
||||
|
|
||||
|
// DELETING ITEMS ///////////////////////////////////////// |
||||
|
$('#TodoList').on('click', 'li i', function(){ |
||||
|
var $li = $(this).parent(); |
||||
|
var id = $li.attr('data-id'); |
||||
|
|
||||
|
todoApp.services.todo.delete(id).then(function(){ |
||||
|
$li.remove(); |
||||
|
abp.notify.info('Deleted the todo item.'); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
// CREATING NEW ITEMS ///////////////////////////////////// |
||||
|
$('#NewItemForm').submit(function(e){ |
||||
|
e.preventDefault(); |
||||
|
|
||||
|
var todoText = $('#NewItemText').val(); |
||||
|
todoApp.services.todo.create(todoText).then(function(result){ |
||||
|
$('<li data-id="' + result.id + '">') |
||||
|
.html('<i class="fa fa-trash-o"></i> ' + result.text) |
||||
|
.appendTo($('#TodoList')); |
||||
|
$('#NewItemText').val(''); |
||||
|
}); |
||||
|
}); |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
In the first part, we subscribed to the click events of the trash icons near the todo items, deleted the related item on the server and showed a notification on the UI. Also, we removed the deleted item from the DOM, so we wouldn't need to refresh the page. |
||||
|
|
||||
|
In the second part, we created a new todo item on the server. If it succeeded, we would then manipulate the DOM to insert a new `<li>` element to the todo list. This way, we wouldn't need to refresh the whole page after creating a new todo item. |
||||
|
|
||||
|
The interesting part here is how we communicate with the server. See the [*Dynamic JavaScript Proxies & Auto API Controllers*](#dynamic-javascript-proxies--auto-api-controllers) section to understand how it works. But now, let's continue and complete the application. |
||||
|
|
||||
|
### Index.cshtml.css |
||||
|
|
||||
|
As for the final touch, open the `Index.cshtml.css` file in the `Pages` folder and replace with the following content: |
||||
|
|
||||
|
````css |
||||
|
#TodoList{ |
||||
|
list-style: none; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
#TodoList li { |
||||
|
padding: 5px; |
||||
|
margin: 5px 0px; |
||||
|
border: 1px solid #cccccc; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
#TodoList li i |
||||
|
{ |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
#TodoList li i:hover |
||||
|
{ |
||||
|
opacity: 1; |
||||
|
color: #ff0000; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
This is a simple styling for the todo page. We believe that you can do much better :) |
||||
|
|
||||
|
Now, you can run the application again and see the result. |
||||
|
|
||||
|
### Dynamic JavaScript Proxies & Auto API Controllers |
||||
|
|
||||
|
In the `Index.cshtml.js` file, we've used the `todoApp.services.todo.delete(...)` and `todoApp.services.todo.create(...)` functions to communicate with the server. These functions are dynamically created by the ABP Framework, thanks to the [Dynamic JavaScript Client Proxy](../../../UI/AspNetCore/Dynamic-JavaScript-Proxies.md) system. They perform HTTP API calls to the server and return a promise, so you can register a callback to the `then` function as we've done above. |
||||
|
|
||||
|
> **services** keyword comes from the namespace (`namespace TodoApp.Services;`). It's a naming convention. |
||||
|
|
||||
|
However, you may notice that we haven't created any API Controllers, so how does the server handle these requests? This question brings us to the [Auto API Controller](../../../API/Auto-API-Controllers.md) feature of the ABP Framework. It automatically converts the application services to **API Controllers** by convention. |
||||
|
|
||||
|
If you open [Swagger UI](https://swagger.io/tools/swagger-ui/) by entering the `/swagger` URL in your application, you can see the Todo API: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
{{else if UI=="BlazorServer"}} |
||||
|
|
||||
|
### Index.razor.cs |
||||
|
|
||||
|
Open the `Index.razor.cs` file in the `Pages` folder and replace the content with the following code block: |
||||
|
|
||||
|
```csharp |
||||
|
using Microsoft.AspNetCore.Components; |
||||
|
using TodoApp.Services; |
||||
|
using TodoApp.Services.Dtos; |
||||
|
|
||||
|
namespace TodoApp.Pages; |
||||
|
|
||||
|
public partial class Index |
||||
|
{ |
||||
|
[Inject] |
||||
|
private TodoAppService TodoAppService { get; set; } |
||||
|
|
||||
|
private List<TodoItemDto> TodoItems { get; set; } = new List<TodoItemDto>(); |
||||
|
private string NewTodoText { get; set; } |
||||
|
|
||||
|
protected override async Task OnInitializedAsync() |
||||
|
{ |
||||
|
TodoItems = await TodoAppService.GetListAsync(); |
||||
|
} |
||||
|
|
||||
|
private async Task Create() |
||||
|
{ |
||||
|
var result = await TodoAppService.CreateAsync(NewTodoText); |
||||
|
TodoItems.Add(result); |
||||
|
NewTodoText = null; |
||||
|
} |
||||
|
|
||||
|
private async Task Delete(TodoItemDto todoItem) |
||||
|
{ |
||||
|
await TodoAppService.DeleteAsync(todoItem.Id); |
||||
|
await Notify.Info("Deleted the todo item."); |
||||
|
TodoItems.Remove(todoItem); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
This class uses the `TodoAppService` to get the list of todo items. It manipulates the `TodoItems` list after create and delete operations. This way, we don't need to refresh the whole todo list from the server. |
||||
|
|
||||
|
### Index.razor |
||||
|
|
||||
|
Open the `Index.razor` file in the `Pages` folder and replace the content with the following code block: |
||||
|
|
||||
|
```xml |
||||
|
@page "/" |
||||
|
@inherits TodoAppComponentBase |
||||
|
|
||||
|
<div class="container"> |
||||
|
<Card> |
||||
|
<CardHeader> |
||||
|
<CardTitle> |
||||
|
TODO LIST |
||||
|
</CardTitle> |
||||
|
</CardHeader> |
||||
|
<CardBody> |
||||
|
<!-- FORM FOR NEW TODO ITEMS --> |
||||
|
<form id="NewItemForm" @onsubmit:preventDefault @onsubmit="() => Create()" class="row row-cols-lg-auto g-3 align-items-center"> |
||||
|
<div class="col-12"> |
||||
|
<div class="input-group"> |
||||
|
<input name="NewTodoText" type="text" @bind-value="@NewTodoText" class="form-control" placeholder="enter text..." /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-12"> |
||||
|
<button type="submit" class="btn btn-primary">Submit</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
<!-- TODO ITEMS LIST --> |
||||
|
<ul id="TodoList"> |
||||
|
@foreach (var todoItem in TodoItems) |
||||
|
{ |
||||
|
<li data-id="@todoItem.Id"> |
||||
|
<i class="far fa-trash-alt" |
||||
|
@onclick="() => Delete(todoItem)"></i> |
||||
|
@todoItem.Text |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
</CardBody> |
||||
|
</Card> |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
### Index.razor.css |
||||
|
|
||||
|
As the final touch, open the `Index.razor.css` file in the `Pages` folder and replace it with the following content: |
||||
|
|
||||
|
````css |
||||
|
#TodoList{ |
||||
|
list-style: none; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
#TodoList li { |
||||
|
padding: 5px; |
||||
|
margin: 5px 0px; |
||||
|
border: 1px solid #cccccc; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
#TodoList li i |
||||
|
{ |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
#TodoList li i:hover |
||||
|
{ |
||||
|
opacity: 1; |
||||
|
color: #ff0000; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
This is a simple styling for the todo page. We believe that you can do much better :) |
||||
|
|
||||
|
Now, you can run the application again to see the result. |
||||
|
|
||||
|
{{else if UI=="NG"}} |
||||
|
|
||||
|
### Service Proxy Generation |
||||
|
|
||||
|
ABP provides a handy feature to automatically create client-side services to easily consume HTTP APIs provided by the server. |
||||
|
|
||||
|
You first need to run the `TodoApp` project since the proxy generator reads API definitions from the server application. |
||||
|
|
||||
|
Once you run the `TodoApp` project (**Swagger API Definition** will be shown), open a command-line terminal in the directory of `angular` folder and run the following command: |
||||
|
|
||||
|
```bash |
||||
|
abp generate-proxy -t ng |
||||
|
``` |
||||
|
|
||||
|
If everything goes well, it should generate an output as shown below: |
||||
|
|
||||
|
```bash |
||||
|
CREATE src/app/proxy/generate-proxy.json (182755 bytes) |
||||
|
CREATE src/app/proxy/README.md (1000 bytes) |
||||
|
CREATE src/app/proxy/services/todo.service.ts (833 bytes) |
||||
|
CREATE src/app/proxy/services/dtos/models.ts (71 bytes) |
||||
|
CREATE src/app/proxy/services/dtos/index.ts (26 bytes) |
||||
|
CREATE src/app/proxy/services/index.ts (81 bytes) |
||||
|
CREATE src/app/proxy/index.ts (61 bytes) |
||||
|
``` |
||||
|
|
||||
|
Then, we can use the `TodoService` to use the server-side HTTP APIs, as we'll do in the next section. |
||||
|
|
||||
|
### home.component.ts |
||||
|
|
||||
|
Open the `/angular/src/app/home/home.component.ts` file and replace its content with the following code block: |
||||
|
|
||||
|
```ts |
||||
|
import { ToasterService } from "@abp/ng.theme.shared"; |
||||
|
import { Component, OnInit } from '@angular/core'; |
||||
|
import { TodoItemDto } from "@proxy/services/dtos"; |
||||
|
import { TodoService } from "@proxy/services"; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'app-home', |
||||
|
templateUrl: './home.component.html', |
||||
|
styleUrls: ['./home.component.scss'], |
||||
|
}) |
||||
|
|
||||
|
export class HomeComponent implements OnInit { |
||||
|
|
||||
|
todoItems: TodoItemDto[]; |
||||
|
newTodoText: string; |
||||
|
|
||||
|
constructor( |
||||
|
private todoService: TodoService, |
||||
|
private toasterService: ToasterService) |
||||
|
{ } |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.todoService.getList().subscribe(response => { |
||||
|
this.todoItems = response; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
create(): void{ |
||||
|
this.todoService.create(this.newTodoText).subscribe((result) => { |
||||
|
this.todoItems = this.todoItems.concat(result); |
||||
|
this.newTodoText = null; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
delete(id: string): void { |
||||
|
this.todoService.delete(id).subscribe(() => { |
||||
|
this.todoItems = this.todoItems.filter(item => item.id !== id); |
||||
|
this.toasterService.info('Deleted the todo item.'); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
We've used `TodoService` to get the list of todo items and assigned the returning value to the `todoItems` array. We've also added `create` and `delete` methods. These methods will be used on the view side. |
||||
|
|
||||
|
### home.component.html |
||||
|
|
||||
|
Open the `/angular/src/app/home/home.component.html` file and replace its content with the following code block: |
||||
|
|
||||
|
````html |
||||
|
<div class="container"> |
||||
|
<div class="card"> |
||||
|
<div class="card-header"> |
||||
|
<div class="card-title">TODO LIST</div> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
<!-- FORM FOR NEW TODO ITEMS --> |
||||
|
<form class="row row-cols-lg-auto g-3 align-items-center" (ngSubmit)="create()"> |
||||
|
<div class="col-12"> |
||||
|
<div class="input-group"> |
||||
|
<input name="NewTodoText" type="text" [(ngModel)]="newTodoText" class="form-control" placeholder="enter text..." /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-12"> |
||||
|
<button type="submit" class="btn btn-primary">Submit</button> |
||||
|
</div> |
||||
|
</form> |
||||
|
<!-- TODO ITEMS LIST --> |
||||
|
<ul id="TodoList"> |
||||
|
<li *ngFor="let todoItem of todoItems"> |
||||
|
<i class="fa fa-trash-o" (click)="delete(todoItem.id)"></i> {%{{{ todoItem.text }}}%} |
||||
|
</li> |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
```` |
||||
|
|
||||
|
### home.component.scss |
||||
|
|
||||
|
As the final touch, open the `/angular/src/app/home/home.component.scss` file and replace its content with the following code block: |
||||
|
|
||||
|
````css |
||||
|
#TodoList{ |
||||
|
list-style: none; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
} |
||||
|
|
||||
|
#TodoList li { |
||||
|
padding: 5px; |
||||
|
margin: 5px 0px; |
||||
|
border: 1px solid #cccccc; |
||||
|
background-color: #f5f5f5; |
||||
|
} |
||||
|
|
||||
|
#TodoList li i |
||||
|
{ |
||||
|
opacity: 0.5; |
||||
|
} |
||||
|
|
||||
|
#TodoList li i:hover |
||||
|
{ |
||||
|
opacity: 1; |
||||
|
color: #ff0000; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
This is a simple styling for the todo page. We believe that you can do much better :) |
||||
|
|
||||
|
Now, you can run the application again to see the result. |
||||
|
|
||||
|
{{end}} |
||||
|
|
||||
|
## Conclusion |
||||
|
|
||||
|
In this tutorial, we've built a very simple application to warm up with the ABP Framework. |
||||
|
|
||||
|
## Source Code |
||||
|
|
||||
|
You can find the source code of the completed application [here](https://github.com/abpframework/abp-samples/tree/master/TodoApp-SingleLayer). |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* Check the [Web Application Development Tutorial](../Part-1.md) to see a real-life web application development in a layered architecture using the [Application Startup Template](../../../Startup-Templates/Application.md). |
||||
|
After Width: | Height: | Size: 8.7 KiB |
Loading…
Reference in new issue