diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
index d0d829bbf3..a5089e06f7 100644
--- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
+++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json
@@ -13,11 +13,8 @@
"Permission:Edit": "Edit",
"Permission:Delete": "Delete",
"Permission:Create": "Create",
- "Permission:Accounting": "Accounting",
- "Permission:Accounting:Quotation": "Quotation",
- "Permission:Accounting:Invoice": "Invoice",
"Menu:Organizations": "Organizations",
- "Menu:Accounting": "Accounting",
+ "Menu:Accounting": "Accounting",
"Menu:Packages": "Packages",
"NpmPackageDeletionWarningMessage": "This NPM Package will be deleted. Do you confirm that?",
"NugetPackageDeletionWarningMessage": "This Nuget Package will be deleted. Do you confirm that?",
@@ -97,28 +94,13 @@
"UsernameOrEmail": "Username or email",
"UsernameOrEmailPlaceholder": "Username or email...",
"Member": "Member",
- "PurchaseOrderNo": "Purchase order no",
- "QuotationDate": "Quotation date",
- "CompanyName": "Company name",
- "CompanyAddress": "Company address",
+ "QuotationPurchasedOrderNo": "Quotation Purchased Order No",
+ "QuotationTime": "Quotation Time",
+ "CompanyName": "Company Name",
+ "CompanyAddress": "Company Address",
"Price": "Price",
- "DiscountText": "Discount text",
- "DiscountQuantity": "Discount quantity",
- "DiscountPrice": "Discount price",
- "Quotation": "Quotation",
"ExtraText": "Extra Text",
"ExtraAmount": "Extra Amount",
- "DownloadQuotation": "Download Quotation",
- "Invoice": "Invoice",
- "TaxNumber": "Tax Number",
- "InvoiceNumber": "Invoice Number",
- "InvoiceDate": "Invoice Date",
- "Quantity": "Quantity",
- "AddProduct": "Add Product",
- "AddProductWarning": "You need to add product!",
- "TotalPrice": "Total Price",
- "Generate": "Generate",
- "MissingQuantityField": "The quantity field is required!",
- "MissingPriceField": "The Price field is required!"
+ "DownloadQuotation": "Download Quotation"
}
}
\ No newline at end of file
diff --git a/common.DotSettings b/common.DotSettings
index 6f40d029a7..0eb4875d49 100644
--- a/common.DotSettings
+++ b/common.DotSettings
@@ -20,11 +20,5 @@
FalseFalseSQL
- False
- False
- False
- False
- False
- FalseTrue
\ No newline at end of file
diff --git a/common.props b/common.props
index ab7b86e3b2..19b6f81fe6 100644
--- a/common.props
+++ b/common.props
@@ -1,7 +1,7 @@
latest
- 2.8.0
+ 2.7.0$(NoWarn);CS1591https://abp.io/assets/abp_nupkg.pnghttps://abp.io
diff --git a/docs/en/CLI.md b/docs/en/CLI.md
index 75e3e027b4..b687a5244a 100644
--- a/docs/en/CLI.md
+++ b/docs/en/CLI.md
@@ -135,8 +135,6 @@ abp update [options]
* `--include-previews` or `-p`: Includes preview, beta and rc packages while checking the latest versions.
* `--npm`: Only updates NPM packages.
* `--nuget`: Only updates NuGet packages.
-* `--solution-path` or `-sp`: Specify the solution path. Use the current directory by default
-* `--solution-name` or `-sn`: Specify the solution name. Search `*.sln` files in the directory by default.
### switch-to-preview
@@ -172,11 +170,7 @@ Some features of the CLI requires to be logged in to abp.io platform. To login w
abp login
```
-```bash
-abp login -p
-```
-
-Notice that, a new login with an already active session, overwrites the previous session.
+Notice that, a new login with an already active session, will kill the previous session and creates a new one.
### logout
diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md
index 00e791a94e..e0772579ec 100644
--- a/docs/en/Entity-Framework-Core-Migrations.md
+++ b/docs/en/Entity-Framework-Core-Migrations.md
@@ -546,8 +546,6 @@ Entity extension system solves the main problem of the extra properties: It can
All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
-See the [entity extension system](Customizing-Application-Modules-Extending-Entities.md) for details.
-
###### Creating a New Table
Instead of creating a new entity and mapping to the same table, you can also create **your own table** to store your properties. You typically duplicate some values of the original entity. For example, you can add `Name` field to your own table which is a duplication of the `Name` field in the original table.
diff --git a/docs/en/Getting-Started.md b/docs/en/Getting-Started.md
index fa7f47f276..07e55f553e 100644
--- a/docs/en/Getting-Started.md
+++ b/docs/en/Getting-Started.md
@@ -25,12 +25,6 @@ The following tools should be installed on your development machine:
* [Node v12+](https://nodejs.org)
* [Yarn v1.19+](https://classic.yarnpkg.com/)
-{{ if Tiered == "Yes" }}
-
-* [Redis](https://redis.io/): The applications use Redis as as [distributed cache](../Caching.md). So, you need to have Redis installed & running.
-
-{{ end }}
-
> You can use another editor instead of Visual Studio as long as it supports .NET Core and ASP.NET Core.
@@ -406,8 +400,8 @@ Enter **admin** as the username and **1q2w3E*** as the password to login to the
The application is up and running. You can continue to develop your application based on this startup template.
-> The [application startup template](Startup-Templates/Application.md) includes the TenantManagement and Identity modules.
+> The [application startup template](startup-templates/application/index.md) includes the TenantManagement and Identity modules.
## What's next?
-[Application development tutorial](Tutorials/Part-1.md)
+[Application development tutorial](tutorials/book-store/part-1.md)
diff --git a/docs/en/Localization.md b/docs/en/Localization.md
index 954fe8620f..11b838cf7f 100644
--- a/docs/en/Localization.md
+++ b/docs/en/Localization.md
@@ -87,21 +87,6 @@ A JSON localization file content is shown below:
* Every localization file should define the `culture` code for the file (like "en" or "en-US").
* `texts` section just contains key-value collection of the localization strings (keys may have spaces too).
-### Default Resource
-
-`AbpLocalizationOptions.DefaultResourceType` can be set to a resource type, so it is used when the localization resource was not specified:
-
-````csharp
-Configure(options =>
-{
- options.DefaultResourceType = typeof(TestResource);
-});
-````
-
-> The [application startup template](Startup-Templates/Application.md) sets `DefaultResourceType` to the localization resource of the application.
-
-See the *Client Side* section below for a use case.
-
### Short Localization Resource Name
Localization resources are also available in the client (JavaScript) side. So, setting a short name for the localization resource makes it easy to use localization texts. Example:
@@ -181,10 +166,6 @@ public class MyService
}
````
-##### Format Arguments
-
-Format arguments can be passed after the localization key. If your message is `Hello {0}, welcome!`, then you can pass the `{0}` argument to the localizer like `_localizer["HelloMessage", "John"]`
-
#### Simplest Usage In A Razor View/Page
````c#
@@ -199,47 +180,18 @@ Refer to the [Microsoft's localization documentation](https://docs.microsoft.com
ABP provides JavaScript services to use the same localized texts in the client side.
-#### getResource
-
-`abp.localization.getResource` function is used to get a localization resource:
+Get a localization resource:
````js
var testResource = abp.localization.getResource('Test');
````
-Then you can localize a string based on this resource:
+Localize a string:
````js
var str = testResource('HelloWorld');
````
-#### localize
-
-`abp.localization.localize` function is a shortcut where you can both specify the text name and the resource name:
-
-````js
-var str = abp.localization.localize('HelloWorld', 'Test');
-````
-
-`HelloWorld` is the text to localize, where `Test` is the localization resource name here.
-
-If you don't specify the localization resource name, it uses the default localization resource defined on the `AbpLocalizationOptions` (see the *Default Resource* section above). Example:
-
-````js
-var str = abp.localization.localize('HelloWorld'); //uses the default resource
-````
-
-##### Format Arguments
-
-If your localized string contains arguments, like `Hello {0}, welcome!`, you can pass arguments to the localization methods. Examples:
-
-````js
-var str1 = abp.localization.getResource('Test')('HelloWelcomeMessage', 'John');
-var str2 = abp.localization.localize('HelloWorld', 'Test', 'John');
-````
-
-Both of the samples above produce the output `Hello John, welcome!`.
-
## See Also
* [Localization in Angular UI](UI/Angular/Localization.md)
\ No newline at end of file
diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md
index bee229dc27..6eff328c85 100644
--- a/docs/en/Object-Extensions.md
+++ b/docs/en/Object-Extensions.md
@@ -197,8 +197,8 @@ ObjectExtensionManager.Instance
"SocialSecurityNumber",
options =>
{
- options.Attributes.Add(new RequiredAttribute());
- options.Attributes.Add(
+ options.ValidationAttributes.Add(new RequiredAttribute());
+ options.ValidationAttributes.Add(
new StringLengthAttribute(32) {
MinimumLength = 6
}
@@ -248,12 +248,12 @@ ObjectExtensionManager.Instance
objConfig.AddOrUpdateProperty("Password", propertyConfig =>
{
- propertyConfig.Attributes.Add(new RequiredAttribute());
+ propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
objConfig.AddOrUpdateProperty("PasswordRepeat", propertyConfig =>
{
- propertyConfig.Attributes.Add(new RequiredAttribute());
+ propertyConfig.ValidationAttributes.Add(new RequiredAttribute());
});
//Write a common validation logic works on multiple properties
diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md
index 51ab468d07..a0aa6612e9 100644
--- a/docs/en/Tutorials/Part-1.md
+++ b/docs/en/Tutorials/Part-1.md
@@ -38,7 +38,7 @@ Create a new project named `Acme.BookStore` where `Acme` is the company name and
#### Create the project
-By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `{{UI_Value}}`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document.
+By running the below command, it creates a new ABP project with the database provider `{{DB_Text}}` and UI option `MVC`. To see the other CLI options, check out [ABP CLI](https://docs.abp.io/en/abp/latest/CLI) document.
```bash
abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none
@@ -61,7 +61,7 @@ After creating the project, you need to apply the initial migrations and create
To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}}
-Further information, see the [running the application section](../Getting-Started?UI={{UI}}#run-the-application).
+Further information, see the [running the application section](../../Getting-Started-{{if UI == "NG"}}Angular{{else}}AspNetCore-MVC{{end}}-Template#running-the-application).Getting-Started-AspNetCore-MVC-Template#running-the-application

@@ -335,7 +335,7 @@ INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES
### Create the application service
-The next step is to create an [application service](../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects:
+The next step is to create an [application service](../../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects:
* `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces.
* `Acme.BookStore.Application` contains the implementations of your application services.
@@ -874,52 +874,97 @@ We'll see **book-list works!** text on the books page:
Run the following command in the terminal to create a new state, named `BooksState`:
+
+
```bash
-npx @ngxs/cli --name books --directory src/app/books
+yarn ng generate ngxs-schematic:state books
```
-* This command creates `books.state.ts` and `books.actions.ts` files in the `src/app/books/state` folder. See the [NGXS CLI documentation](https://www.ngxs.io/plugins/cli).
+* This command creates several new files and updates `app.modules.ts` file to import the `NgxsModule` with the new state.
+
+#### Get books data from backend
+
+Create data types to map the data from the backend (you can check Swagger UI or your backend API to see the data format).
-Import the `BooksState` to the `app.module.ts` in the `src/app` folder and then add the `BooksState` to `forRoot` static method of `NgxsModule` as an array element of the first parameter of the method.
+
+
+Open the `books.ts` file in the `app\store\models` folder and replace the content as below:
```js
-// ...
-import { BooksState } from './books/state/books.state'; //<== imported BooksState ==>
+export namespace Books {
+ export interface State {
+ books: Response;
+ }
-@NgModule({
- imports: [
- // other imports
+ export interface Response {
+ items: Book[];
+ totalCount: number;
+ }
- NgxsModule.forRoot([BooksState]), //<== added BooksState ==>
+ export interface Book {
+ name: string;
+ type: BookType;
+ publishDate: string;
+ price: number;
+ lastModificationTime: string;
+ lastModifierId: string;
+ creationTime: string;
+ creatorId: string;
+ id: string;
+ }
- //other imports
- ],
- // ...
-})
-export class AppModule {}
+ export enum BookType {
+ Undefined,
+ Adventure,
+ Biography,
+ Dystopia,
+ Fantastic,
+ Horror,
+ Science,
+ ScienceFiction,
+ Poetry,
+ }
+}
```
-#### Generate proxies
+* Added `Book` interface that represents a book object and `BookType` enum which represents a book category.
-ABP CLI provides `generate-proxy` command that generates client proxies for your HTTP APIs to make easy to consume your services from the client side. Before running generate-proxy command, your host must be up and running. See the [CLI documentation](../CLI.md)
+#### BooksService
-Run the following command in the `angular` folder:
+Create a new service, named `BooksService` to perform `HTTP` calls to the server:
```bash
-abp generate-proxy --module app
+yarn ng generate service books/shared/books
```
-
+
-The generated files looks like below:
+Open the `books.service.ts` file in `app\books\shared` folder and replace the content as below:
-
+```js
+import { Injectable } from '@angular/core';
+import { RestService } from '@abp/ng.core';
+import { Books } from '../../store/models';
+import { Observable } from 'rxjs';
-#### GetBooks Action
+@Injectable({
+ providedIn: 'root',
+})
+export class BooksService {
+ constructor(private restService: RestService) {}
+
+ get(): Observable {
+ return this.restService.request({
+ method: 'GET',
+ url: '/api/app/book'
+ });
+ }
+}
+```
-Actions can either be thought of as a command which should trigger something to happen, or as the resulting event of something that has already happened. [See NGXS Actions documentation](https://www.ngxs.io/concepts/actions).
+* We added the `get` method to get the list of books by performing an HTTP request to the related endpoint.
-Open the `books.actions.ts` file in `app/books/state` folder and replace the content below:
+Open the`books.actions.ts` file in `app\store\actions` folder and replace the content below:
```js
export class GetBooks {
@@ -929,49 +974,43 @@ export class GetBooks {
#### Implement BooksState
-Open the `books.state.ts` file in `app/books/state` folder and replace the content below:
+Open the `books.state.ts` file in `app\store\states` folder and replace the content below:
```js
-import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
-import { GetBooks } from './books.actions';
-import { BookService } from '../../app/shared/services';
+import { GetBooks } from '../actions/books.actions';
+import { Books } from '../models/books';
+import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
-import { BookDto } from '../../app/shared/models';
-export class BooksStateModel {
- public book: PagedResultDto;
-}
-
-@State({
+@State({
name: 'BooksState',
- defaults: { book: {} } as BooksStateModel,
+ defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
- static getBooks(state: BooksStateModel) {
- return state.book.items || [];
+ static getBooks(state: Books.State) {
+ return state.books.items || [];
}
- constructor(private bookService: BookService) {}
+ constructor(private booksService: BooksService) {}
@Action(GetBooks)
- get(ctx: StateContext) {
- return this.bookService.getListByInput().pipe(
- tap((booksResponse) => {
+ get(ctx: StateContext) {
+ return this.booksService.get().pipe(
+ tap(booksResponse => {
ctx.patchState({
- book: booksResponse,
+ books: booksResponse,
});
- })
+ }),
);
}
}
```
-* We added the book property to BooksStateModel model.
-* We added `@Injectable()` decorator to BookState class (Regquired for Ivy to work properly).
-* We added the `GetBooks` action that retrieves the books data via `BooksService` that generated via ABP CLI and patches the state.
+
+* We added the `GetBooks` action that retrieves the books data via `BooksService` and patches the state.
* `NGXS` requires to return the observable without subscribing it in the get function.
#### BookListComponent
@@ -980,12 +1019,11 @@ Open the `book-list.component.ts` file in `app\books\book-list` folder and repla
```js
import { Component, OnInit } from '@angular/core';
-import { Select, Store } from '@ngxs/store';
+import { Store, Select } from '@ngxs/store';
+import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
-import { finalize } from 'rxjs/operators';
-import { BookDto, BookType } from '../../app/shared/models';
-import { GetBooks } from '../state/books.actions';
-import { BooksState } from '../state/books.state';
+import { Books } from '../../store/models';
+import { GetBooks } from '../../store/actions';
@Component({
selector: 'app-book-list',
@@ -994,13 +1032,13 @@ import { BooksState } from '../state/books.state';
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
- books$: Observable;
+ books$: Observable;
- booksType = BookType;
+ booksType = Books.BookType;
loading = false;
- constructor(private store: Store) {}
+ constructor(private store: Store) { }
ngOnInit() {
this.get();
@@ -1008,10 +1046,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
- this.store
- .dispatch(new GetBooks())
- .pipe(finalize(() => (this.loading = false)))
- .subscribe(() => {});
+ this.store.dispatch(new GetBooks()).subscribe(() => {
+ this.loading = false;
+ });
}
}
```
diff --git a/docs/en/Tutorials/Part-2.md b/docs/en/Tutorials/Part-2.md
index cfe9936812..9565724cb8 100644
--- a/docs/en/Tutorials/Part-2.md
+++ b/docs/en/Tutorials/Part-2.md
@@ -456,76 +456,161 @@ Run the application and try to delete a book.
In this section, you will learn how to create a new modal dialog form to create a new book.
+#### Type definition
+
+Open `books.ts` file in `app\store\models` folder and replace the content as below:
+
+```js
+export namespace Books {
+ export interface State {
+ books: Response;
+ }
+
+ export interface Response {
+ items: Book[];
+ totalCount: number;
+ }
+
+ export interface Book {
+ name: string;
+ type: BookType;
+ publishDate: string;
+ price: number;
+ lastModificationTime: string;
+ lastModifierId: string;
+ creationTime: string;
+ creatorId: string;
+ id: string;
+ }
+
+ export enum BookType {
+ Undefined,
+ Adventure,
+ Biography,
+ Dystopia,
+ Fantastic,
+ Horror,
+ Science,
+ ScienceFiction,
+ Poetry,
+ }
+
+ //<== added CreateUpdateBookInput interface ==>
+ export interface CreateUpdateBookInput {
+ name: string;
+ type: BookType;
+ publishDate: string;
+ price: number;
+ }
+}
+```
+
+* We added `CreateUpdateBookInput` interface.
+* You can see the properties of this interface from Swagger UI.
+* The `CreateUpdateBookInput` interface matches with the `CreateUpdateBookDto` in the backend.
+
+#### Service method
+
+Open the `books.service.ts` file in `app\books\shared` folder and replace the content as below:
+
+```js
+import { Injectable } from '@angular/core';
+import { RestService } from '@abp/ng.core';
+import { Books } from '../../store/models';
+import { Observable } from 'rxjs';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class BooksService {
+ constructor(private restService: RestService) {}
+
+ get(): Observable {
+ return this.restService.request({
+ method: 'GET',
+ url: '/api/app/book'
+ });
+ }
+
+ //<== added create method ==>
+ create(createBookInput: Books.CreateUpdateBookInput): Observable {
+ return this.restService.request({
+ method: 'POST',
+ url: '/api/app/book',
+ body: createBookInput
+ });
+ }
+}
+```
+
+- We added the `create` method to perform an HTTP Post request to the server.
+- `restService.request` function gets generic parameters for the types sent to and received from the server. This example sends a `CreateUpdateBookInput` object and receives a `Book` object (you can set `void` for request or return type if not used).
+
#### State definitions
-Open `books.action.ts` in `books\state` folder and replace the content as below:
+Open `books.action.ts` in `app\store\actions` folder and replace the content as below:
```js
-import { CreateUpdateBookDto } from '../../app/shared/models'; //<== added this line ==>
+import { Books } from '../models'; //<== added this line ==>
export class GetBooks {
static readonly type = '[Books] Get';
}
-// added CreateUpdateBook class
+//added CreateUpdateBook class
export class CreateUpdateBook {
static readonly type = '[Books] Create Update Book';
- constructor(public payload: CreateUpdateBookDto) { }
+ constructor(public payload: Books.CreateUpdateBookInput) { }
}
```
-* We imported the `CreateUpdateBookDto` model and created the `CreateUpdateBook` action.
+* We imported the Books namespace and created the `CreateUpdateBook` action.
-Open `books.state.ts` file in `books\state` folder and replace the content as below:
+Open `books.state.ts` file in `app\store\states` and replace the content as below:
```js
-import { PagedResultDto } from '@abp/ng.core';
import { State, Action, StateContext, Selector } from '@ngxs/store';
-import { GetBooks, CreateUpdateBook } from './books.actions'; // <== added CreateUpdateBook==>
-import { BookService } from '../../app/shared/services';
+import { GetBooks, CreateUpdateBook } from '../actions/books.actions'; //<== added CreateUpdateBook==>
+import { Books } from '../models/books';
+import { BooksService } from '../../books/shared/books.service';
import { tap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
-import { BookDto } from '../../app/shared/models';
-
-export class BooksStateModel {
- public book: PagedResultDto;
-}
-@State({
+@State({
name: 'BooksState',
- defaults: { book: {} } as BooksStateModel,
+ defaults: { books: {} } as Books.State,
})
@Injectable()
export class BooksState {
@Selector()
- static getBooks(state: BooksStateModel) {
- return state.book.items || [];
+ static getBooks(state: Books.State) {
+ return state.books.items || [];
}
- constructor(private bookService: BookService) {}
+ constructor(private booksService: BooksService) { }
@Action(GetBooks)
- get(ctx: StateContext) {
- return this.bookService.getListByInput().pipe(
- tap((bookResponse) => {
+ get(ctx: StateContext) {
+ return this.booksService.get().pipe(
+ tap(booksResponse => {
ctx.patchState({
- book: bookResponse,
+ books: booksResponse,
});
- })
+ }),
);
}
- // added CreateUpdateBook action listener
+ //added CreateUpdateBook action listener
@Action(CreateUpdateBook)
- save(ctx: StateContext, action: CreateUpdateBook) {
- return this.bookService.createByInput(action.payload);
+ save(ctx: StateContext, action: CreateUpdateBook) {
+ return this.booksService.create(action.payload);
}
}
```
* We imported `CreateUpdateBook` action and defined the `save` method that will listen to a `CreateUpdateBook` action to create a book.
-When the `SaveBook` action dispatched, the save method is being executed. It calls `createByInput` method of the `BookService`.
+When the `SaveBook` action dispatched, the save method is being executed. It calls `create` method of the `BooksService`.
#### Add a modal to BookListComponent
@@ -609,12 +694,11 @@ Open `book-list.component.ts` file in `books\book-list` folder and replace the c
```js
import { Component, OnInit } from '@angular/core';
-import { Select, Store } from '@ngxs/store';
+import { Store, Select } from '@ngxs/store';
+import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
-import { finalize } from 'rxjs/operators';
-import { BookDto, BookType } from '../../app/shared/models';
-import { GetBooks } from '../state/books.actions';
-import { BooksState } from '../state/books.state';
+import { Books } from '../../store/models';
+import { GetBooks } from '../../store/actions';
@Component({
selector: 'app-book-list',
@@ -623,15 +707,15 @@ import { BooksState } from '../state/books.state';
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
- books$: Observable;
+ books$: Observable;
- booksType = BookType;
+ booksType = Books.BookType;
loading = false;
- isModalOpen = false; // <== added this line ==>
+ isModalOpen = false; //<== added this line ==>
- constructor(private store: Store) {}
+ constructor(private store: Store) { }
ngOnInit() {
this.get();
@@ -639,13 +723,12 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
- this.store
- .dispatch(new GetBooks())
- .pipe(finalize(() => (this.loading = false)))
- .subscribe(() => {});
+ this.store.dispatch(new GetBooks()).subscribe(() => {
+ this.loading = false;
+ });
}
- // added createBook method
+ //added createBook method
createBook() {
this.isModalOpen = true;
}
@@ -666,13 +749,12 @@ Open `book-list.component.ts` file in `app\books\book-list` folder and replace t
```js
import { Component, OnInit } from '@angular/core';
-import { Select, Store } from '@ngxs/store';
+import { Store, Select } from '@ngxs/store';
+import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
-import { finalize } from 'rxjs/operators';
-import { BookDto, BookType } from '../../app/shared/models';
-import { GetBooks } from '../state/books.actions';
-import { BooksState } from '../state/books.state';
-import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== added this line ==>
+import { Books } from '../../store/models';
+import { GetBooks } from '../../store/actions';
+import { FormGroup, FormBuilder, Validators } from '@angular/forms'; //<== added this line ==>
@Component({
selector: 'app-book-list',
@@ -681,17 +763,17 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; // <== adde
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
- books$: Observable;
+ books$: Observable;
- booksType = BookType;
+ booksType = Books.BookType;
loading = false;
isModalOpen = false;
- form: FormGroup; // <== added this line ==>
+ form: FormGroup;
- constructor(private store: Store, private fb: FormBuilder) {} // <== added FormBuilder ==>
+ constructor(private store: Store, private fb: FormBuilder) { } //<== added FormBuilder ==>
ngOnInit() {
this.get();
@@ -699,10 +781,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
- this.store
- .dispatch(new GetBooks())
- .pipe(finalize(() => (this.loading = false)))
- .subscribe(() => {});
+ this.store.dispatch(new GetBooks()).subscribe(() => {
+ this.loading = false;
+ });
}
createBook() {
@@ -710,7 +791,7 @@ export class BookListComponent implements OnInit {
this.isModalOpen = true;
}
- // added buildForm method
+ //added buildForm method
buildForm() {
this.form = this.fb.group({
name: ['', Validators.required],
@@ -723,7 +804,6 @@ export class BookListComponent implements OnInit {
```
* We imported `FormGroup, FormBuilder and Validators`.
-* We added `form: FormGroup` variable.
* We injected `fb: FormBuilder` service to the constructor. The [FormBuilder](https://angular.io/api/forms/FormBuilder) service provides convenient methods for generating controls. It reduces the amount of boilerplate needed to build complex forms.
* We added `buildForm` method to the end of the file and executed `buildForm()` in the `createBook` method. This method creates a reactive form to be able to create a new book.
* The `group` method of `FormBuilder`, `fb` creates a `FormGroup`.
@@ -800,34 +880,35 @@ export class BooksModule { }
* We imported `NgbDatepickerModule` to be able to use the date picker.
+
+
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
-import { Select, Store } from '@ngxs/store';
+import { Store, Select } from '@ngxs/store';
+import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
-import { finalize } from 'rxjs/operators';
-import { BookDto, BookType } from '../../app/shared/models';
-import { GetBooks } from '../state/books.actions';
-import { BooksState } from '../state/books.state';
+import { Books } from '../../store/models';
+import { GetBooks } from '../../store/actions';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
-import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; // <== added this line ==>
+import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap'; //<== added this line ==>
@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
- providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }], // <== added this line ==>
+ providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }] //<== added this line ==>
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
- books$: Observable;
+ books$: Observable;
- booksType = BookType;
+ booksType = Books.BookType;
//added bookTypeArr array
- bookTypeArr = Object.keys(BookType).filter(
- (bookType) => typeof this.booksType[bookType] === 'number'
+ bookTypeArr = Object.keys(Books.BookType).filter(
+ bookType => typeof this.booksType[bookType] === 'number'
);
loading = false;
@@ -836,7 +917,7 @@ export class BookListComponent implements OnInit {
form: FormGroup;
- constructor(private store: Store, private fb: FormBuilder) {}
+ constructor(private store: Store, private fb: FormBuilder) { }
ngOnInit() {
this.get();
@@ -844,10 +925,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
- this.store
- .dispatch(new GetBooks())
- .pipe(finalize(() => (this.loading = false)))
- .subscribe(() => {});
+ this.store.dispatch(new GetBooks()).subscribe(() => {
+ this.loading = false;
+ });
}
createBook() {
@@ -885,16 +965,35 @@ Now, you can open your browser to see the changes:
#### Saving the book
+Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
+
+```html
+
+
+
+
+
+
+```
+
+* This adds a save button to the bottom area of the modal:
+
+
+
Open `book-list.component.ts` file in `app\books\book-list` folder and replace the content as below:
```js
import { Component, OnInit } from '@angular/core';
-import { Select, Store } from '@ngxs/store';
+import { Store, Select } from '@ngxs/store';
+import { BooksState } from '../../store/states';
import { Observable } from 'rxjs';
-import { finalize } from 'rxjs/operators';
-import { BookDto, BookType } from '../../app/shared/models';
-import { GetBooks, CreateUpdateBook } from '../state/books.actions'; // <== added CreateUpdateBook ==>
-import { BooksState } from '../state/books.state';
+import { Books } from '../../store/models';
+import { GetBooks, CreateUpdateBook } from '../../store/actions'; //<== added CreateUpdateBook ==>
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap';
@@ -902,17 +1001,16 @@ import { NgbDateNativeAdapter, NgbDateAdapter } from '@ng-bootstrap/ng-bootstrap
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.scss'],
- providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }],
+ providers: [{ provide: NgbDateAdapter, useClass: NgbDateNativeAdapter }]
})
export class BookListComponent implements OnInit {
@Select(BooksState.getBooks)
- books$: Observable;
+ books$: Observable;
- booksType = BookType;
+ booksType = Books.BookType;
- //added bookTypeArr array
- bookTypeArr = Object.keys(BookType).filter(
- (bookType) => typeof this.booksType[bookType] === 'number'
+ bookTypeArr = Object.keys(Books.BookType).filter(
+ bookType => typeof this.booksType[bookType] === 'number'
);
loading = false;
@@ -921,7 +1019,7 @@ export class BookListComponent implements OnInit {
form: FormGroup;
- constructor(private store: Store, private fb: FormBuilder) {}
+ constructor(private store: Store, private fb: FormBuilder) { }
ngOnInit() {
this.get();
@@ -929,10 +1027,9 @@ export class BookListComponent implements OnInit {
get() {
this.loading = true;
- this.store
- .dispatch(new GetBooks())
- .pipe(finalize(() => (this.loading = false)))
- .subscribe(() => {});
+ this.store.dispatch(new GetBooks()).subscribe(() => {
+ this.loading = false;
+ });
}
createBook() {
@@ -965,46 +1062,37 @@ export class BookListComponent implements OnInit {
```
* We imported `CreateUpdateBook`.
-* We added `save` method
-
-Open `book-list.component.html` in `app\books\book-list` folder and add the following `abp-button` to save the new book.
-
-```html
-
-
-
-
-
-
-```
-
-Find the `