@ -0,0 +1,163 @@ |
|||||
|
# Confirmation Popup |
||||
|
|
||||
|
You can use the `ConfirmationService` in @abp/ng.theme.shared package to display a confirmation popup by placing at the root level in your project. |
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
You do not have to provide the `ConfirmationService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. |
||||
|
|
||||
|
|
||||
|
```js |
||||
|
import { ConfirmationService } from '@abp/ng.theme.shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private confirmation: ConfirmationService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
You can use the `success`, `warn`, `error`, and `info` methods of `ConfirmationService` to display a confirmation popup. |
||||
|
|
||||
|
### How to Display a Confirmation Popup |
||||
|
|
||||
|
```js |
||||
|
const confirmationStatus$ = this.confirmation.success('Message', 'Title'); |
||||
|
``` |
||||
|
|
||||
|
- The `ConfirmationService` methods accept three parameters that are `message`, `title`, and `options`. |
||||
|
- `success`, `warn`, `error`, and `info` methods return an [RxJS Subject](https://rxjs-dev.firebaseapp.com/guide/subject) to listen to confirmation popup closing event. The type of event value is [`Confirmation.Status`](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts#L24) that is an enum. |
||||
|
|
||||
|
### How to Listen Closing Event |
||||
|
|
||||
|
You can subscribe to the confirmation closing event like below: |
||||
|
|
||||
|
```js |
||||
|
import { Confirmation, ConfirmationService } from '@abp/ng.theme.shared'; |
||||
|
|
||||
|
constructor(private confirmation: ConfirmationService) {} |
||||
|
|
||||
|
this.confirmation |
||||
|
.warn('::WillBeDeleted', { key: '::AreYouSure', defaultValue: 'Are you sure?' }) |
||||
|
.subscribe((status: Confirmation.Status) => { |
||||
|
// your code here |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
|
||||
|
- The `message` and `title` parameters accept a string, localization key or localization object. See the [localization document](./Localization.md) |
||||
|
- `Confirmation.Status` is an enum and has three properties; |
||||
|
- `Confirmation.Status.confirm` is a closing event value that will be emitted when the popup is closed by the confirm button. |
||||
|
- `Confirmation.Status.reject` is a closing event value that will be emitted when the popup is closed by the cancel button. |
||||
|
- `Confirmation.Status.dismiss` is a closing event value that will be emitted when the popup is closed by pressing the escape. |
||||
|
|
||||
|
|
||||
|
If you are not interested in the confirmation status, you do not have to subscribe to the returned observable: |
||||
|
|
||||
|
```js |
||||
|
this.confirmation.error('You are not authorized.', 'Error'); |
||||
|
``` |
||||
|
|
||||
|
### How to Display a Confirmation Popup With Given Options |
||||
|
|
||||
|
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods: |
||||
|
|
||||
|
```js |
||||
|
const options: Partial<Confirmation.Options> = { |
||||
|
hideCancelBtn: false, |
||||
|
hideYesBtn: false, |
||||
|
cancelText: 'Close', |
||||
|
yesText: 'Confirm', |
||||
|
messageLocalizationParams: ['Demo'], |
||||
|
titleLocalizationParams: [], |
||||
|
}; |
||||
|
|
||||
|
this.confirmation.warn( |
||||
|
'AbpIdentity::RoleDeletionConfirmationMessage', |
||||
|
'Are you sure?', |
||||
|
options, |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
- `hideCancelBtn` option hides the cancellation button when `true`. Default value is `false` |
||||
|
- `hideYesBtn` option hides the confirmation button when `true`. Default value is `false` |
||||
|
- `cancelText` is the text of the cancellation button. A localization key or localization object can be passed. Default value is `AbpUi::Cancel` |
||||
|
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes` |
||||
|
- `messageLocalizationParams` is the interpolation parameters for the localization of the message. |
||||
|
- `titleLocalizationParams` is the interpolation parameters for the localization of the title. |
||||
|
|
||||
|
With the options above, the confirmation popup looks like this: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### How to Remove a Confirmation Popup |
||||
|
|
||||
|
The open confirmation popup can be removed manually via the `clear` method: |
||||
|
|
||||
|
```js |
||||
|
this.confirmation.clear(); |
||||
|
``` |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### success |
||||
|
|
||||
|
```js |
||||
|
success( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Confirmation.Options>, |
||||
|
): Observable<Confirmation.Status> |
||||
|
``` |
||||
|
|
||||
|
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Confirmation` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/confirmation.ts) |
||||
|
|
||||
|
|
||||
|
### warn |
||||
|
|
||||
|
```js |
||||
|
warn( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Confirmation.Options>, |
||||
|
): Observable<Confirmation.Status> |
||||
|
``` |
||||
|
|
||||
|
### error |
||||
|
|
||||
|
```js |
||||
|
error( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Confirmation.Options>, |
||||
|
): Observable<Confirmation.Status> |
||||
|
``` |
||||
|
|
||||
|
### info |
||||
|
|
||||
|
```js |
||||
|
info( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Confirmation.Options>, |
||||
|
): Observable<Confirmation.Status> |
||||
|
``` |
||||
|
|
||||
|
### clear |
||||
|
|
||||
|
```js |
||||
|
clear( |
||||
|
status: Confirmation.Status = Confirmation.Status.dismiss |
||||
|
): void |
||||
|
``` |
||||
|
|
||||
|
- `status` parameter is the value of the confirmation closing event. |
||||
|
|
||||
|
|
||||
|
## What's Next? |
||||
|
|
||||
|
- [Toast Overlay](./Toaster-Service.md) |
||||
@ -0,0 +1,157 @@ |
|||||
|
# Toast Overlay |
||||
|
|
||||
|
You can use the `ToasterService` in @abp/ng.theme.shared package to display messages in an overlay by placing at the root level in your project. |
||||
|
|
||||
|
|
||||
|
## Getting Started |
||||
|
|
||||
|
You do not have to provide the `ToasterService` at module or component level, because it is already **provided in root**. You can inject and start using it immediately in your components, directives, or services. |
||||
|
|
||||
|
|
||||
|
```js |
||||
|
import { ToasterService } from '@abp/ng.theme.shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private toaster: ToasterService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## Usage |
||||
|
|
||||
|
You can use the `success`, `warn`, `error`, and `info` methods of `ToasterService` to display an overlay. |
||||
|
|
||||
|
### How to Display a Toast Overlay |
||||
|
|
||||
|
```js |
||||
|
this.toast.success('Message', 'Title'); |
||||
|
``` |
||||
|
|
||||
|
- The `ToasterService` methods accept three parameters that are `message`, `title`, and `options`. |
||||
|
- `success`, `warn`, `error`, and `info` methods return the id of opened toast overlay. The toast can be removed with this id. |
||||
|
|
||||
|
### How to Display a Toast Overlay With Given Options |
||||
|
|
||||
|
Options can be passed as the third parameter to `success`, `warn`, `error`, and `info` methods: |
||||
|
|
||||
|
```js |
||||
|
import { Toaster, ToasterService } from '@abp/ng.theme.shared'; |
||||
|
//... |
||||
|
|
||||
|
constructor(private toaster: ToasterService) {} |
||||
|
|
||||
|
//... |
||||
|
const options: Partial<Toaster.ToastOptions> = { |
||||
|
life: 10000, |
||||
|
sticky: false, |
||||
|
closable: true, |
||||
|
tapToDismiss: true, |
||||
|
messageLocalizationParams: ['Demo', '1'], |
||||
|
titleLocalizationParams: [] |
||||
|
}; |
||||
|
|
||||
|
this.toaster.error('AbpUi::EntityNotFoundErrorMessage', 'AbpUi::Error', options); |
||||
|
``` |
||||
|
|
||||
|
- `life` option is the closing time in milliseconds. Default value is `5000`. |
||||
|
- `sticky` option keeps toast overlay on the screen by ignoring the `life` option when `true`. Default value is `false`. |
||||
|
- `closable` option displays the close icon on the toast overlay when it is `true`. Default value is `true`. |
||||
|
- `tapToDismiss` option, when `true`, allows closing the toast overlay by clicking over it. Default value is `false`. |
||||
|
- `yesText` is the text of the confirmation button. A localization key or localization object can be passed. Default value is `AbpUi::Yes`. |
||||
|
- `messageLocalizationParams` is the interpolation parameters for the localization of the message. |
||||
|
- `titleLocalizationParams` is the interpolation parameters for the localization of the title. |
||||
|
|
||||
|
With the options above, the toast overlay looks like this: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### How to Remove a Toast Overlay |
||||
|
|
||||
|
The open toast overlay can be removed manually via the `remove` method by passing the `id` of toast: |
||||
|
|
||||
|
```js |
||||
|
const toastId = this.toast.success('Message', 'Title') |
||||
|
|
||||
|
this.toast.remove(toastId); |
||||
|
``` |
||||
|
|
||||
|
### How to Remove All Toasts |
||||
|
|
||||
|
The all open toasts can be removed manually via the `clear` method: |
||||
|
|
||||
|
```js |
||||
|
this.toast.clear(); |
||||
|
``` |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### success |
||||
|
|
||||
|
```js |
||||
|
success( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Toaster.ToastOptions>, |
||||
|
): number |
||||
|
``` |
||||
|
|
||||
|
- `Config` namespace can be imported from `@abp/ng.core`. |
||||
|
- `Toaster` namespace can be imported from `@abp/ng.theme.shared`. |
||||
|
|
||||
|
> See the [`Config.LocalizationParam` type](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/core/src/lib/models/config.ts#L46) and [`Toaster` namespace](https://github.com/abpframework/abp/blob/master/npm/ng-packs/packages/theme-shared/src/lib/models/toaster.ts) |
||||
|
|
||||
|
|
||||
|
### warn |
||||
|
|
||||
|
```js |
||||
|
warn( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Toaster.ToastOptions>, |
||||
|
): number |
||||
|
``` |
||||
|
|
||||
|
### error |
||||
|
|
||||
|
```js |
||||
|
error( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Toaster.ToastOptions>, |
||||
|
): number |
||||
|
``` |
||||
|
|
||||
|
### info |
||||
|
|
||||
|
```js |
||||
|
info( |
||||
|
message: Config.LocalizationParam, |
||||
|
title: Config.LocalizationParam, |
||||
|
options?: Partial<Toaster.ToastOptions>, |
||||
|
): number |
||||
|
``` |
||||
|
|
||||
|
### remove |
||||
|
|
||||
|
```js |
||||
|
remove(id: number): void |
||||
|
``` |
||||
|
|
||||
|
Removes an open toast by the given id. |
||||
|
|
||||
|
### clear |
||||
|
|
||||
|
```js |
||||
|
clear(): void |
||||
|
``` |
||||
|
|
||||
|
Removes all open toasts. |
||||
|
|
||||
|
## See Also |
||||
|
- [Confirmation Popup](./Confirmation-Service.md) |
||||
|
|
||||
|
## What's Next? |
||||
|
|
||||
|
- [Config State](./Config-State.md) |
||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,3 @@ |
|||||
|
## 规约 |
||||
|
|
||||
|
TODO.. |
||||
@ -1,3 +0,0 @@ |
|||||
## Creating a Settings Tab |
|
||||
|
|
||||
TODO... |
|
||||
@ -1,3 +1,294 @@ |
|||||
## 配置状态 |
## 配置状态 |
||||
|
|
||||
TODO... |
`ConfigStateService` 是一个单例服务,即在应用程序的根级别提供,用于与 `Store` 中的应用程序配置状态进行交互. |
||||
|
|
||||
|
## 使用前 |
||||
|
|
||||
|
为了使用 `ConfigStateService`,你必须将其注入到你的类中. |
||||
|
|
||||
|
```js |
||||
|
import { ConfigStateService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private config: ConfigStateService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
你不必在模块或组件/指令级别提供 `ConfigStateService`,因为它已经在**根中**提供. |
||||
|
|
||||
|
## 选择器方法 |
||||
|
|
||||
|
`ConfigStateService` 有许多选择器方法允许你从 `Store` 获取特定或所有的配置. |
||||
|
|
||||
|
### 如何从Store获取所有的配置 |
||||
|
|
||||
|
你可以使用 `ConfigStateService` 的 `getAll` 方法从Store获取所有的配置对象. 用法如下: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const config = this.config.getAll(); |
||||
|
``` |
||||
|
|
||||
|
### 如何从Store获取特定的配置 |
||||
|
|
||||
|
你可以使用 `ConfigStateService` 的 `getOne` 方法从Store获取特定的配置属性. 你需要将属性名做为参数传递给方法: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const currentUser = this.config.getOne("currentUser"); |
||||
|
``` |
||||
|
|
||||
|
有时你想要获取具体信息,而不是当前用户. 例如你只想获取到 `tenantId`: |
||||
|
|
||||
|
```js |
||||
|
const tenantId = this.config.getDeep("currentUser.tenantId"); |
||||
|
``` |
||||
|
|
||||
|
或通过提供键数组作为参数: |
||||
|
|
||||
|
```js |
||||
|
const tenantId = this.config.getDeep(["currentUser", "tenantId"]); |
||||
|
``` |
||||
|
|
||||
|
`getDeep` 可以执行 `getOne` 的所有操作. 但 `getOne` 的执行效率要高一些. |
||||
|
|
||||
|
#### 配置状态属性 |
||||
|
|
||||
|
请参阅 `Config.State` 类型,你可以通过 `getOne` 和 `getDeep` 获取所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L7)中找到. |
||||
|
|
||||
|
### 如何从Store获取应用程序信息 |
||||
|
|
||||
|
`getApplicationInfo` 方法从存储为配置状态存储的环境变量中获取应用程序信息. 你可以这样使用它: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const appInfo = this.config.getApplicationInfo(); |
||||
|
``` |
||||
|
|
||||
|
该方法不会返回 `undefined` 或 `null`,而是会返回一个空对象(`{}`). 换句话说,当你使用上面代码中的 `appInfo` 属性时,永远不会出现错误. |
||||
|
|
||||
|
#### 应用程序信息属性 |
||||
|
|
||||
|
请参阅 `Config.State` 类型,你可以通过 `getApplicationInfo` 获取所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L21)中找到. |
||||
|
|
||||
|
### 如何从Store获取 |
||||
|
|
||||
|
`getApplicationInfo` 方法从存储为配置状态存储的环境变量中获取特定的API URL. 你可以这样使用它: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const apiUrl = this.config.getApiUrl(); |
||||
|
// environment.apis.default.url |
||||
|
|
||||
|
const searchUrl = this.config.getApiUrl("search"); |
||||
|
// environment.apis.search.url |
||||
|
``` |
||||
|
|
||||
|
该方法返回给定键的特定的API `url`. 如果没有Key,则使用 `default`. |
||||
|
|
||||
|
### 如何从Store获取所有的设置 |
||||
|
|
||||
|
你可以使用 `ConfigStateService` 的 `getSettings` 获取配置状态所有的设置对象. 你可以这样使用它: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const settings = this.config.getSettings(); |
||||
|
``` |
||||
|
|
||||
|
实际上该方法可以通过**传递关键字**来搜索设置. |
||||
|
|
||||
|
```js |
||||
|
const localizationSettings = this.config.getSettings("Localization"); |
||||
|
/* |
||||
|
{ |
||||
|
'Abp.Localization.DefaultLanguage': 'en' |
||||
|
} |
||||
|
*/ |
||||
|
``` |
||||
|
|
||||
|
请注意, **设置搜索区分大小写**. |
||||
|
|
||||
|
### 如何从Store获取特定的设置 |
||||
|
|
||||
|
你可以使用 `ConfigStateService` 的 `getSetting` 获取配置状态特定的设置. 你可以这样使用它: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const defaultLang = this.config.getSetting("Abp.Localization.DefaultLanguage"); |
||||
|
// 'en' |
||||
|
``` |
||||
|
|
||||
|
### 如何从Store获取特定的权限 |
||||
|
|
||||
|
你可以使用 `ConfigStateService` 的 `getGrantedPolicy` 获取配置状态特定的权限. 你应该将策略key做为参数传递给方法: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const hasIdentityPermission = this.config.getGrantedPolicy("Abp.Identity"); |
||||
|
// true |
||||
|
``` |
||||
|
|
||||
|
你还可以使用 **组合策略key** 来微调你的选择: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const hasIdentityAndAccountPermission = this.config.getGrantedPolicy( |
||||
|
"Abp.Identity && Abp.Account" |
||||
|
); |
||||
|
// false |
||||
|
|
||||
|
const hasIdentityOrAccountPermission = this.config.getGrantedPolicy( |
||||
|
"Abp.Identity || Abp.Account" |
||||
|
); |
||||
|
// true |
||||
|
``` |
||||
|
|
||||
|
创建权限选择器时,请考虑以下**规则**: |
||||
|
|
||||
|
- 最多可组合两个键. |
||||
|
- `&&` 操作符查找两个键. |
||||
|
- `||` 操作符查找任意一个键. |
||||
|
- 空字符串 `''` 做为键将返回 `true` |
||||
|
- 使用没有第二个键的操作符将返回 `false` |
||||
|
|
||||
|
### 如何从Store中获取翻译 |
||||
|
|
||||
|
`ConfigStateService` 的 `getLocalization` 方法用于翻译. 这里有一些示例: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const identity = this.config.getLocalization("AbpIdentity::Identity"); |
||||
|
// 'identity' |
||||
|
|
||||
|
const notFound = this.config.getLocalization("AbpIdentity::IDENTITY"); |
||||
|
// 'AbpIdentity::IDENTITY' |
||||
|
|
||||
|
const defaultValue = this.config.getLocalization({ |
||||
|
key: "AbpIdentity::IDENTITY", |
||||
|
defaultValue: "IDENTITY" |
||||
|
}); |
||||
|
// 'IDENTITY' |
||||
|
``` |
||||
|
|
||||
|
请参阅[本地化文档](./Localization.md)了解详情. |
||||
|
|
||||
|
## 分发方法 |
||||
|
|
||||
|
`ConfigStateService` 有几种分发方法,让你方便地将预定义操作分发到 `Store`. |
||||
|
|
||||
|
### 如何从服务器获取应用程序配置 |
||||
|
|
||||
|
`dispatchGetAppConfiguration` 触发对端点的请求,该端点使用应用程序状态进行响应,然后将此响应作为配置状态放置到 `Store`中. |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
this.config.dispatchGetAppConfiguration(); |
||||
|
// returns a state stream which emits after dispatch action is complete |
||||
|
``` |
||||
|
|
||||
|
请注意,**你不必在应用程序启动时调用此方法**,因为在启动时已经从服务器收到了应用程序配置. |
||||
|
|
||||
|
### 如何修补路由配置 |
||||
|
|
||||
|
`dispatchPatchRouteByName` 根据名称查找路由, 并将其在 `Store` 中的配置替换为作为第二个参数传递的新配置. |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const newRouteConfig: Partial<ABP.Route> = { |
||||
|
name: "Home", |
||||
|
path: "home", |
||||
|
children: [ |
||||
|
{ |
||||
|
name: "Dashboard", |
||||
|
path: "dashboard" |
||||
|
} |
||||
|
] |
||||
|
}; |
||||
|
|
||||
|
this.config.dispatchPatchRouteByName("::Menu:Home", newRouteConfig); |
||||
|
// returns a state stream which emits after dispatch action is complete |
||||
|
``` |
||||
|
|
||||
|
### 如何添加新路由配置 |
||||
|
|
||||
|
`dispatchAddRoute` 向 `Store` 的配置状态添加一个新路由. 应该将路由配置做为方法参数传递. |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const newRoute: ABP.Route = { |
||||
|
name: "My New Page", |
||||
|
iconClass: "fa fa-dashboard", |
||||
|
path: "page", |
||||
|
invisible: false, |
||||
|
order: 2, |
||||
|
requiredPolicy: "MyProjectName::MyNewPage" |
||||
|
}; |
||||
|
|
||||
|
this.config.dispatchAddRoute(newRoute); |
||||
|
// returns a state stream which emits after dispatch action is complete |
||||
|
``` |
||||
|
|
||||
|
`newRoute` 将被放置在根级别,没有任何父路由,并且其url将存储为 `'/path'`. |
||||
|
|
||||
|
如果你想要**添加一个子路由,你可以这样做:** |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
const newRoute: ABP.Route = { |
||||
|
parentName: "AbpAccount::Login", |
||||
|
name: "My New Page", |
||||
|
iconClass: "fa fa-dashboard", |
||||
|
path: "page", |
||||
|
invisible: false, |
||||
|
order: 2, |
||||
|
requiredPolicy: "MyProjectName::MyNewPage" |
||||
|
}; |
||||
|
|
||||
|
this.config.dispatchAddRoute(newRoute); |
||||
|
// returns a state stream which emits after dispatch action is complete |
||||
|
``` |
||||
|
|
||||
|
`newRoute` 做为 `'AbpAccount::Login'` 父路由的子路由被放置,它的url被设置为 `'/account/login/page'`. |
||||
|
|
||||
|
#### 路由配置属性 |
||||
|
|
||||
|
请参阅 `ABP.Route` 类型,获取可在参数中传递给 `dispatchSetEnvironment` 的所有属性. 你可以在[common.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/common.ts#L27)中找到. |
||||
|
|
||||
|
### 如何设置环境 |
||||
|
|
||||
|
`dispatchSetEnvironment` 将传递给它的环境变量放在 `Store` 中的配置状态下. 使用方法如下: |
||||
|
|
||||
|
```js |
||||
|
// this.config is instance of ConfigStateService |
||||
|
|
||||
|
this.config.dispatchSetEnvironment({ |
||||
|
/* environment properties here */ |
||||
|
}); |
||||
|
// returns a state stream which emits after dispatch action is complete |
||||
|
``` |
||||
|
|
||||
|
注意,**你不必在应用程序启动时调用此方法**,因为环境变量已经在启动时存储了. |
||||
|
|
||||
|
#### 环境属性 |
||||
|
|
||||
|
请参阅 `Config.Environment` 类型,获取可在参数中传递给 `dispatchSetEnvironment` 的所有属性. 你可以在[config.ts 文件](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L13)中找到. |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
* [组件替换](./Component-Replacement.md) |
||||
@ -1,3 +1,140 @@ |
|||||
# Dom插入(Scripts与Styles) |
# Dom插入(Scripts与Styles) |
||||
|
|
||||
TODO... |
你可以使用@abp/ng.core包提供的 `DomInsertionService` 以简单的方式的插入脚本与样式. |
||||
|
|
||||
|
## 入门 |
||||
|
|
||||
|
你不必在模块或组件级别提供 `DomInsertionService` ,因为它已经在**根中**提供. 你可以在组件,指令或服务中直接注入并使用它. |
||||
|
|
||||
|
```js |
||||
|
import { DomInsertionService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private domInsertionService: DomInsertionService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 用法 |
||||
|
|
||||
|
你可以使用 `DomInsertionService` 提供的 `insertContent` 方法去创建一个 `<script>` 或 `<style>` 元素到DOM的指定位置. 还有 `projectContent` 方法用于渲染组件和模板. |
||||
|
|
||||
|
### 如何插入Scripts |
||||
|
|
||||
|
`insertContent` 方法的第一个参数需要一个 `ContentStrategy`. 如果传递 `ScriptContentStrategy` 实例, `DomInsertionService` 将创建具有给定内容的 `<script>` 元素并放置在指定的DOM位置. |
||||
|
|
||||
|
```js |
||||
|
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private domInsertionService: DomInsertionService) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
const scriptElement = this.domInsertionService.insertContent( |
||||
|
CONTENT_STRATEGY.AppendScriptToBody('alert()') |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
在上面的示例中,将 `<script>alert()</script>` 元素放置在 `<body>`的末尾, `scriptElement` 类型是一个 `HTMLScriptElement`. |
||||
|
|
||||
|
请参考[ContentStrategy](./Content-Strategy.md)查看所有可用的内容策略以及如何构建自己的内容策略. |
||||
|
|
||||
|
> 重要说明: `DomInsertionService` 不会两次插入相同的内容. 为了再次添加内容你首先应该使用 `removeContent` 方法删除旧内容. |
||||
|
|
||||
|
### 如何插入Styles |
||||
|
|
||||
|
`insertContent` 方法的第一个参数需要一个 `ContentStrategy`. 如果传递 `StyleContentStrategy` 实例, `DomInsertionService` 将创建具有给定内容的 `<style>` 元素并放置在指定的DOM位置. |
||||
|
|
||||
|
```js |
||||
|
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private domInsertionService: DomInsertionService) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
const styleElement = this.domInsertionService.insertContent( |
||||
|
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}') |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
在上面的示例中,将 `<style>body {margin: 0;}</style>` 元素放置在 `<head>`的末尾, `styleElement` 类型是一个 `HTMLStyleElement`. |
||||
|
|
||||
|
请参考[ContentStrategy](./Content-Strategy.md)查看所有可用的内容策略以及如何构建自己的内容策略. |
||||
|
. |
||||
|
> 重要说明: `DomInsertionService` 不会两次插入相同的内容. 为了再次添加内容你首先应该使用 `removeContent` 方法删除旧内容. |
||||
|
|
||||
|
### 如何删除已插入的 Scripts & Styles |
||||
|
|
||||
|
如果你传递 `HTMLScriptElement` 或 `HTMLStyleElement` 做为 `removeContent` 方法的第一个参数, `DomInsertionService` 将删除给定的元素. |
||||
|
|
||||
|
```js |
||||
|
import { DomInsertionService, CONTENT_STRATEGY } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
private styleElement: HTMLStyleElement; |
||||
|
|
||||
|
constructor(private domInsertionService: DomInsertionService) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.styleElement = this.domInsertionService.insertContent( |
||||
|
CONTENT_STRATEGY.AppendStyleToHead('body {margin: 0;}') |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy() { |
||||
|
this.domInsertionService.removeContent(this.styleElement); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
在上面的示例中,销毁组件时,将从 `<head>` 中删除 `<style>body {margin: 0;}</style>` 元素. |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### insertContent |
||||
|
|
||||
|
```js |
||||
|
insertContent<T extends HTMLScriptElement | HTMLStyleElement>( |
||||
|
contentStrategy: ContentStrategy<T>, |
||||
|
): T |
||||
|
``` |
||||
|
|
||||
|
- `contentStrategy` 是方法的重要参数,已经在上方进行说明. |
||||
|
- 根据给定的策略返回 `HTMLScriptElement` 或 `HTMLStyleElement`. |
||||
|
|
||||
|
### removeContent |
||||
|
|
||||
|
```js |
||||
|
removeContent(element: HTMLScriptElement | HTMLStyleElement): void |
||||
|
``` |
||||
|
|
||||
|
- `element` 参数是已插入的 `HTMLScriptElement` 或 `HTMLStyleElement` 元素,它们应由 `insertContent` 方法返回. |
||||
|
|
||||
|
### has |
||||
|
|
||||
|
```js |
||||
|
has(content: string): boolean |
||||
|
``` |
||||
|
|
||||
|
`has` 返回一个布尔值,用于表示给定的内容是否插入到DOM. |
||||
|
|
||||
|
- `content` 参数是 `HTMLScriptElement` 或 `HTMLStyleElement` 元素的内容. |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
- [ContentProjectionService](./Content-Projection-Service.md) |
||||
@ -1,3 +1,179 @@ |
|||||
## HTTP请求 |
## HTTP请求 |
||||
|
|
||||
TODO... |
## 关于 HttpClient |
||||
|
|
||||
|
Angular具有很棒的 `HttpClient` 与后端服务进行通信. 它位于顶层,是[XMLHttpRequest Web API](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest)的封装. 同时也是Angular建议用于任何HTTP请求的代理,在你的ABP项目中使用 `HttpClient` 是最佳做法. |
||||
|
|
||||
|
但是 `HttpClient` 将错误处理留给调用方,换句话说HTTP错误是通过手动处理的,通过挂接到返回的 `Observable` 的观察者中来处理. |
||||
|
|
||||
|
```js |
||||
|
getConfig() { |
||||
|
this.http.get(this.configUrl).subscribe( |
||||
|
config => this.updateConfig(config), |
||||
|
error => { |
||||
|
// Handle error here |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
上面的代码尽管清晰灵活,但即使将错误处理委派给Store或任何其他注入. 以这种方式处理错误也是重复性的工作. |
||||
|
|
||||
|
`HttpInterceptor` 能够捕获 `HttpErrorResponse` 并可用于集中的错误处理. 然而,在必须放置错误处理程序(也就是拦截器)的情况下,需要额外的工作以及对Angular内部机制的理解. 检查[这个issue](https://github.com/angular/angular/issues/20203)了解详情. |
||||
|
|
||||
|
## RestService |
||||
|
|
||||
|
ABP核心模块有用于HTTP请求的实用程序服务: `RestService`. 除非另有明确配置,否则它将捕获HTTP错误并调度 `RestOccurError` 操作, 然后由 `ThemeSharedModule` 引入的 `ErrorHandler` 捕获此操作. 你应该已经在应用程序中导入了此模块,在使用 `RestService` 时,默认情况下将自动处理所有HTTP错误. |
||||
|
|
||||
|
### RestService 入门 |
||||
|
|
||||
|
为了使用 `RestService`, 你必须将它注入到你的类中. |
||||
|
|
||||
|
```js |
||||
|
import { RestService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Injectable({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoService { |
||||
|
constructor(private rest: RestService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
你不必在模块或组件/指令级别提供 `estService`,因为它已经在**根中**中提供了. |
||||
|
|
||||
|
### 如何使用RestService发出请求 |
||||
|
|
||||
|
你可以使用 `RestService` 的 `request` 方法来处理HTTP请求. 示例: |
||||
|
|
||||
|
```js |
||||
|
getFoo(id: number) { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'GET', |
||||
|
url: '/api/some/path/to/foo/' + id, |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, FooResponse>(request); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
`request` 方法始终返回 `Observable<T>`. 无论何时使用 `getFoo` 方法,都可以执行以下操作: |
||||
|
|
||||
|
```js |
||||
|
doSomethingWithFoo(id: number) { |
||||
|
this.demoService.getFoo(id).subscribe( |
||||
|
foo => { |
||||
|
// Do something with foo. |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**你不必担心关于取消订阅**. `RestService` 在内部使用 `HttpClient`,因此它返回的每个可观察对象都是有限的可观察对象,成功或出错后将自动关闭订阅. |
||||
|
|
||||
|
如你所见,`request` 方法获取一个具有 `Rest.Reques<T>` 类型的请求选项对象. 此泛型类型需要请求主体的接口. 当没有正文时,例如在 `GET` 或 `DELETE` 请求中,你可以传递 `null`. 示例: |
||||
|
|
||||
|
```js |
||||
|
postFoo(body: Foo) { |
||||
|
const request: Rest.Request<Foo> = { |
||||
|
method: 'POST', |
||||
|
url: '/api/some/path/to/foo', |
||||
|
body |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<Foo, FooResponse>(request); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
你可以在[此处检查](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23)完整的 `Rest.Request<T>` 类型,与Angular中的[HttpRequest](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L23)类相比只有很少的改动. |
||||
|
|
||||
|
### 如何禁用RestService的默认错误处理程序 |
||||
|
|
||||
|
默认 `request` 方法始终处理错误. 让我们看看如何改变这种行为并由自己处理错误: |
||||
|
|
||||
|
```js |
||||
|
deleteFoo(id: number) { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'DELETE', |
||||
|
url: '/api/some/path/to/foo/' + id, |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, void>(request, { skipHandleError: true }); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
`skipHandleError` 配置选项设置为 `true` 时,禁用错误处理程序,并返回 `observable` 引发错误,你可以在订阅中捕获该错误. |
||||
|
|
||||
|
```js |
||||
|
removeFooFromList(id: number) { |
||||
|
this.demoService.deleteFoo(id).subscribe( |
||||
|
foo => { |
||||
|
// Do something with foo. |
||||
|
}, |
||||
|
error => { |
||||
|
// Do something with error. |
||||
|
} |
||||
|
) |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 如何从应用程序配置获取特定的API端点 |
||||
|
|
||||
|
`request` 方法接收到的另一个配置选项是 `apiName` (在v2.4中可用),它用于从应用程序配置获取特定的模块端点. |
||||
|
|
||||
|
```js |
||||
|
putFoo(body: Foo, id: string) { |
||||
|
const request: Rest.Request<Foo> = { |
||||
|
method: 'PUT', |
||||
|
url: '/' + id, |
||||
|
body |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<Foo, void>(request, {apiName: 'foo'}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
上面的putFoo将请求 `https://localhost:44305/api/some/path/to/foo/{id}` 当环境变量如下: |
||||
|
|
||||
|
```js |
||||
|
// environment.ts |
||||
|
|
||||
|
export const environment = { |
||||
|
apis: { |
||||
|
default: { |
||||
|
url: 'https://localhost:44305', |
||||
|
}, |
||||
|
foo: { |
||||
|
url: 'https://localhost:44305/api/some/path/to/foo', |
||||
|
}, |
||||
|
}, |
||||
|
|
||||
|
/* rest of the environment variables here */ |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 如何观察响应对象或HTTP事件而不是正文 |
||||
|
|
||||
|
`RestService` 假定你通常对响应的正文感兴趣,默认情况下将 `observe` 属性设置为 `body`. 但是有时你可能对其他内容(例如自定义标头)非常感兴趣. 为此, `request` 方法在 `config` 对象中接收 `watch` 属性. |
||||
|
|
||||
|
```js |
||||
|
getSomeCustomHeaderValue() { |
||||
|
const request: Rest.Request<null> = { |
||||
|
method: 'GET', |
||||
|
url: '/api/some/path/that/sends/some-custom-header', |
||||
|
}; |
||||
|
|
||||
|
return this.rest.request<null, HttpResponse<any>>( |
||||
|
request, |
||||
|
{observe: Rest.Observe.Response}, |
||||
|
).pipe( |
||||
|
map(response => response.headers.get('Some-Custom-Header')) |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
你可以在[此处](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/rest.ts#L10)找到 `Rest.Observe` 枚举. |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
* [本地化](./Localization.md) |
||||
@ -1,3 +1,189 @@ |
|||||
# 如何懒加载 Scripts 与 Styles |
# 如何懒加载 Scripts 与 Styles |
||||
|
|
||||
TODO... |
你可以使用@abp/ng.core包中的 `LazyLoadService` 以简单明了的方式延迟加载脚本和样式. |
||||
|
|
||||
|
## 入门 |
||||
|
|
||||
|
你不必在模块或组件/指令级别提供 `LazyLoadService`,因为它已经在**根中**中提供了. 你可以在组件,指令或服务中注入并使用它. |
||||
|
|
||||
|
```js |
||||
|
import { LazyLoadService } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
/* class metadata here */ |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
constructor(private lazyLoadService: LazyLoadService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 用法 |
||||
|
|
||||
|
你可以使用 `LazyLoadService` 的 `load` 方法在DOM中的所需位置创建 `<script>` 或 `<link>` 元素并强制浏览器下载目标资源. |
||||
|
|
||||
|
### 如何加载 Scripts |
||||
|
|
||||
|
`load` 方法的第一个参数需要一个 `LoadingStrategy`. 如果传递 `ScriptLoadingStrategy` 实例,`LazyLoadService` 将使用给定的 `src` 创建一个 `<script>` 元素并放置在指定的DOM位置. |
||||
|
|
||||
|
```js |
||||
|
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<some-component *ngIf="libraryLoaded$ | async"></some-component> |
||||
|
` |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
libraryLoaded$ = this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/some-library.js'), |
||||
|
); |
||||
|
|
||||
|
constructor(private lazyLoadService: LazyLoadService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
`load` 方法返回一个 `observable`,你可以在组件中或通过 `AsyncPipe` 订阅它. 在上面的示例中**仅当脚本成功加载或之前已经加载脚本时**, `NgIf` 指令才会呈现 `<some-component>`. |
||||
|
|
||||
|
> 你可以使用 `async` 管道在模板中多次订阅,脚本将仅加载一次 |
||||
|
|
||||
|
请参阅[LoadingStrategy](./Loading-Strategy.md)查看所有可用的加载策略以及如何构建自己的加载策略. |
||||
|
|
||||
|
### 如何加载 Styles |
||||
|
|
||||
|
如果传递给 `load` 方法第一个参数为 `StyleLoadingStrategy` 实例,`LazyLoadService` 将使用给定的 `href` 创建一个 `<link>` 元素并放置在指定的DOM位置. |
||||
|
|
||||
|
```js |
||||
|
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; |
||||
|
|
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<some-component *ngIf="stylesLoaded$ | async"></some-component> |
||||
|
` |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
stylesLoaded$ = this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.AppendAnonymousStyleToHead('/assets/some-styles.css'), |
||||
|
); |
||||
|
|
||||
|
constructor(private lazyLoadService: LazyLoadService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
`load` 方法返回一个 `observable`,你可以在组件中或通过 `AsyncPipe` 订阅它. 在上面的示例中**仅当样式成功加载或之前已经加载样式时**, `NgIf` 指令才会呈现 `<some-component>`. |
||||
|
|
||||
|
> 你可以使用 `async` 管道在模板中多次订阅,样式将仅加载一次 |
||||
|
|
||||
|
请参阅[LoadingStrategy](./Loading-Strategy.md)查看所有可用的加载策略以及如何构建自己的加载策略. |
||||
|
|
||||
|
### 高级用法 |
||||
|
|
||||
|
你有**很大的自由度来定义延迟加载的工作方式**. 示例: |
||||
|
|
||||
|
```js |
||||
|
const domStrategy = DOM_STRATEGY.PrependToHead(); |
||||
|
|
||||
|
const crossOriginStrategy = CROSS_ORIGIN_STRATEGY.Anonymous( |
||||
|
'sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh', |
||||
|
); |
||||
|
|
||||
|
const loadingStrategy = new StyleLoadingStrategy( |
||||
|
'https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css', |
||||
|
domStrategy, |
||||
|
crossOriginStrategy, |
||||
|
); |
||||
|
|
||||
|
this.lazyLoad.load(loadingStrategy, 1, 2000); |
||||
|
``` |
||||
|
|
||||
|
此代码将创建具有给定URL和完整性哈希的 `<link>` 元素,将其插入到 `<head>` 元素的顶部,如果第一次尝试失败,则在2秒后重试一次. |
||||
|
|
||||
|
一个常见的用例是在**使用功能之前加载多个脚本/样式**: |
||||
|
|
||||
|
```js |
||||
|
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; |
||||
|
import { frokJoin } from 'rxjs'; |
||||
|
|
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<some-component *ngIf="scriptsAndStylesLoaded$ | async"></some-component> |
||||
|
` |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
private stylesLoaded$ = forkJoin( |
||||
|
this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.PrependAnonymousStyleToHead('/assets/library-dark-theme.css'), |
||||
|
), |
||||
|
this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.PrependAnonymousStyleToHead('/assets/library.css'), |
||||
|
), |
||||
|
); |
||||
|
|
||||
|
private scriptsLoaded$ = forkJoin( |
||||
|
this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/library.js'), |
||||
|
), |
||||
|
this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/other-library.css'), |
||||
|
), |
||||
|
); |
||||
|
|
||||
|
scriptsAndStylesLoaded$ = forkJoin(this.scriptsLoaded$, this.stylesLoaded$); |
||||
|
|
||||
|
constructor(private lazyLoadService: LazyLoadService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
RxJS `forkJoin` 并行加载所有脚本和样式,并仅在加载所有脚本和样式时才放行. 因此放置 `<some-component>` 时,所有必需的依赖项都可用的. |
||||
|
|
||||
|
> 注意到我们在文档头上添加了样式吗? 有时这是必需的因为你的应用程序样式可能会覆盖某些库样式. 在这种情况下你必须注意前置样式的顺序. 它们将一一放置,**并且在放置前,最后一个放置在最上面**. |
||||
|
|
||||
|
另一个常见的用例是**按顺序加载依赖脚本**: |
||||
|
|
||||
|
```js |
||||
|
import { LazyLoadService, LOADING_STRATEGY } from '@abp/ng.core'; |
||||
|
import { concat } from 'rxjs'; |
||||
|
|
||||
|
@Component({ |
||||
|
template: ` |
||||
|
<some-component *ngIf="scriptsLoaded$ | async"></some-component> |
||||
|
` |
||||
|
}) |
||||
|
class DemoComponent { |
||||
|
scriptsLoaded$ = concat( |
||||
|
this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.PrependAnonymousScriptToHead('/assets/library.js'), |
||||
|
), |
||||
|
this.lazyLoad.load( |
||||
|
LOADING_STRATEGY.AppendAnonymousScriptToHead('/assets/script-that-requires-library.js'), |
||||
|
), |
||||
|
); |
||||
|
|
||||
|
constructor(private lazyLoadService: LazyLoadService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
在此示例中,第二个文件需要预先加载第一个文件, RxJS `concat` 函数将允许你以给定的顺序一个接一个地加载所有脚本,并且仅在加载所有脚本时放行. |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### loaded |
||||
|
|
||||
|
```js |
||||
|
loaded: Set<string> |
||||
|
``` |
||||
|
|
||||
|
所有以前加载的路径都可以通过此属性访问. 它是一个简单的[JavaScript集] |
||||
|
|
||||
|
### load |
||||
|
|
||||
|
```js |
||||
|
load(strategy: LoadingStrategy, retryTimes?: number, retryDelay?: number): Observable<Event> |
||||
|
``` |
||||
|
|
||||
|
- `strategy` 是主要参数,上面已经介绍过. |
||||
|
- `retryTimes` 定义加载失败前再次尝试多少次(默认值:2). |
||||
|
- `retryDelay` 定义重试之间的延迟(默认为1000). |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
- [DomInsertionService](./Dom-Insertion-Service.md) |
||||
@ -0,0 +1,95 @@ |
|||||
|
# LoadingStrategy |
||||
|
|
||||
|
`LoadingStrategy` 是@abp/ng.core包暴露的抽象类. 扩展它的有两种加载策略: `ScriptLoadingStrategy` 和 `StyleLoadingStrategy`. 它们实现相同的方法和属性,这两种策略都可以帮助你定义延迟加载的工作方式. |
||||
|
|
||||
|
## API |
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
public path: string, |
||||
|
protected domStrategy?: DomStrategy, |
||||
|
protected crossOriginStrategy?: CrossOriginStrategy |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `path` 将 `<script>` 元素做为 `src` 与 `<link>` 元素做为 `href` 属性. |
||||
|
- `domStrategy` 是在插入创建的元素时将使用的 `DomStrategy`. (默认值: AppendToHead_) |
||||
|
- `crossOriginStrategy` 是 `CrossOriginStrategy`,它在插入元素之前在创建的元素上使用. (默认值: Anonymous_) |
||||
|
|
||||
|
请参阅[DomStrategy](./Dom-Strategy.md)和[CrossOriginStrategy](./Cross-Origin-Strategy.md)文档以了解其用法. |
||||
|
|
||||
|
### createElement |
||||
|
|
||||
|
```js |
||||
|
createElement(): HTMLScriptElement | HTMLLinkElement |
||||
|
``` |
||||
|
|
||||
|
该方法创建并返回 `path` 设置为 `src` 或 `href` 的 `<script>` 或 `<link>` 的元素. |
||||
|
|
||||
|
### createStream |
||||
|
|
||||
|
```js |
||||
|
createStream(): Observable<Event> |
||||
|
``` |
||||
|
|
||||
|
该方法创建并返回一个observable流,该流在成功时发出,在错误时抛出. |
||||
|
|
||||
|
## ScriptLoadingStrategy |
||||
|
|
||||
|
`ScriptLoadingStrategy` 是扩展 `LoadingStrategy` 的类. 它使你可以**延迟加载脚本**. |
||||
|
|
||||
|
## StyleLoadingStrategy |
||||
|
|
||||
|
`StyleLoadingStrategy` 是扩展 `LoadingStrategy` 的类. 它使你可以**延迟加载样式**. |
||||
|
|
||||
|
## 预定义的加载策略 |
||||
|
|
||||
|
可通过 `LOADING_STRATEGY` 常量访问预定义的加载策略. |
||||
|
|
||||
|
### AppendAnonymousScriptToHead |
||||
|
|
||||
|
```js |
||||
|
LOADING_STRATEGY.AppendAnonymousScriptToHead(src: string, integrity?: string) |
||||
|
``` |
||||
|
|
||||
|
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<script>` 元素的属性,并放置在文档中 `<head>` 标签的**末尾**. |
||||
|
|
||||
|
### PrependAnonymousScriptToHead |
||||
|
|
||||
|
```js |
||||
|
LOADING_STRATEGY.PrependAnonymousScriptToHead(src: string, integrity?: string) |
||||
|
``` |
||||
|
|
||||
|
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<script>` 元素的属性,并放置在文档中 `<head>` 标签的**开始**. |
||||
|
|
||||
|
### AppendAnonymousScriptToBody |
||||
|
|
||||
|
```js |
||||
|
LOADING_STRATEGY.AppendAnonymousScriptToBody(src: string, integrity?: string) |
||||
|
``` |
||||
|
|
||||
|
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<script>` 元素的属性,并放置在文档中 `<body>` 标签的**末尾**. |
||||
|
|
||||
|
|
||||
|
### AppendAnonymousStyleToHead |
||||
|
|
||||
|
```js |
||||
|
LOADING_STRATEGY.AppendAnonymousStyleToHead(href: string, integrity?: string) |
||||
|
``` |
||||
|
|
||||
|
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<style>` 元素的属性,并放置在文档中 `<head>` 标签的**末尾**. |
||||
|
|
||||
|
|
||||
|
### PrependAnonymousStyleToHead |
||||
|
|
||||
|
```js |
||||
|
LOADING_STRATEGY.PrependAnonymousStyleToHead(href: string, integrity?: string) |
||||
|
``` |
||||
|
|
||||
|
将给定的参数和 `crossorigin="anonymous"` 设置为创建的 `<style>` 元素的属性,并放置在文档中 `<head>` 标签的**开始**. |
||||
|
|
||||
|
## 另请参阅 |
||||
|
|
||||
|
- [LazyLoadService](./Lazy-Load-Service.md) |
||||
@ -1,3 +1,139 @@ |
|||||
# Localization |
# 本地化 |
||||
|
|
||||
TODO... |
在阅读本地化管道和本地化服务之前你应该了解本地化Key. |
||||
|
|
||||
|
本地化key格式由两个部分组成,分别是**资源名**和**Key** |
||||
|
`ResourceName::Key` |
||||
|
|
||||
|
> 如果你没有指定资源名称,它默认是在 `environment.ts` 中声明的 `defaultResourceName`. |
||||
|
|
||||
|
```js |
||||
|
const environment = { |
||||
|
//... |
||||
|
localization: { |
||||
|
defaultResourceName: 'MyProjectName', |
||||
|
}, |
||||
|
}; |
||||
|
``` |
||||
|
|
||||
|
所以这两个结果是一样的: |
||||
|
|
||||
|
```html |
||||
|
<h1>{%{{{ '::Key' | abpLocalization }}}%}</h1> |
||||
|
|
||||
|
<h1>{%{{{ 'MyProjectName::Key' | abpLocalization }}}%}</h1> |
||||
|
``` |
||||
|
|
||||
|
## 使用本地化管道 |
||||
|
|
||||
|
你可以使用 `abpLocalization` 管道来获取本地化的文本. 例: |
||||
|
|
||||
|
```html |
||||
|
<h1>{%{{{ 'Resource::Key' | abpLocalization }}}%}</h1> |
||||
|
``` |
||||
|
|
||||
|
管道将用本地化的文本替换Key. |
||||
|
|
||||
|
你还可以指定一个默认值,如下所示: |
||||
|
|
||||
|
```html |
||||
|
<h1>{%{{{ { key: 'Resource::Key', defaultValue: 'Default Value' } | abpLocalization }}}%}</h1> |
||||
|
``` |
||||
|
|
||||
|
要使用插值,必须将插值作为管道参数给出. 例如: |
||||
|
|
||||
|
本地化数据存储在键值对中: |
||||
|
|
||||
|
```js |
||||
|
{ |
||||
|
//... |
||||
|
AbpAccount: { // AbpAccount is the resource name |
||||
|
Key: "Value", |
||||
|
PagerInfo: "Showing {0} to {1} of {2} entries" |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
所以我们可以这样使用Key: |
||||
|
|
||||
|
```html |
||||
|
<h1>{%{{{ 'AbpAccount::PagerInfo' | abpLocalization:'20':'30':'50' }}}%}</h1> |
||||
|
|
||||
|
<!-- Output: Showing 20 to 30 of 50 entries --> |
||||
|
``` |
||||
|
|
||||
|
### 使用本地化服务 |
||||
|
|
||||
|
首先应该从 **@abp/ng.core** 导入 `LocalizationService`. |
||||
|
|
||||
|
```js |
||||
|
import { LocalizationService } from '@abp/ng.core'; |
||||
|
|
||||
|
class MyClass { |
||||
|
constructor(private localizationService: LocalizationService) {} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
之后你就可以使用本地化服务. |
||||
|
|
||||
|
> 你可以将插值参数作为参数添加到 `instant()` 和 `get()` 方法中. |
||||
|
|
||||
|
```js |
||||
|
this.localizationService.instant('AbpIdentity::UserDeletionConfirmation', 'John'); |
||||
|
|
||||
|
// with fallback value |
||||
|
this.localizationService.instant( |
||||
|
{ key: 'AbpIdentity::UserDeletionConfirmation', defaultValue: 'Default Value' }, |
||||
|
'John', |
||||
|
); |
||||
|
|
||||
|
// Output |
||||
|
// User 'John' will be deleted. Do you confirm that? |
||||
|
``` |
||||
|
|
||||
|
要获取[_Observable_](https://rxjs.dev/guide/observable)的本地化文本,应该使用 `get` 方法而不是 `instant`: |
||||
|
|
||||
|
```js |
||||
|
this.localizationService.get('Resource::Key'); |
||||
|
|
||||
|
// with fallback value |
||||
|
this.localizationService.get({ key: 'Resource::Key', defaultValue: 'Default Value' }); |
||||
|
``` |
||||
|
|
||||
|
### 使用配置状态 |
||||
|
|
||||
|
要使用 `getLocalization` 方法,你应该导入 `ConfigState`. |
||||
|
|
||||
|
```js |
||||
|
import { ConfigState } from '@abp/ng.core'; |
||||
|
``` |
||||
|
|
||||
|
然后你可以按以下方式使用它: |
||||
|
|
||||
|
```js |
||||
|
this.store.selectSnapshot(ConfigState.getLocalization('ResourceName::Key')); |
||||
|
``` |
||||
|
|
||||
|
`getLocalization` 方法可以与 `本地化key` 和 [`LocalizationWithDefault`](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/config.ts#L34) 接口一起使用. |
||||
|
|
||||
|
```js |
||||
|
this.store.selectSnapshot( |
||||
|
ConfigState.getLocalization( |
||||
|
{ |
||||
|
key: 'AbpIdentity::UserDeletionConfirmation', |
||||
|
defaultValue: 'Default Value', |
||||
|
}, |
||||
|
'John', |
||||
|
), |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
本地化资源存储在 `ConfigState` 的 `localization` 属性中. |
||||
|
|
||||
|
## 另请参阅 |
||||
|
|
||||
|
* [ASP.NET Core中的本地化](../../Localization.md) |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
* [权限管理](./Permission-Management.md) |
||||
@ -1,3 +1,79 @@ |
|||||
# Permission Management |
# 权限管理 |
||||
|
|
||||
TODO... |
权限是为特定用户,角色或客户端授予或禁止的简单策略. 你可以在[ABP授权文档](../../Authorization.md)中阅读更多信息. |
||||
|
|
||||
|
你可以使用 `ConfigState` 的 `getGrantedPolicy` 选择器获取经过身份验证的用户的权限. |
||||
|
|
||||
|
你可以从Store中获取权限的布尔值: |
||||
|
|
||||
|
```js |
||||
|
import { Store } from '@ngxs/store'; |
||||
|
import { ConfigState } from '../states'; |
||||
|
|
||||
|
export class YourComponent { |
||||
|
constructor(private store: Store) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const canCreate = this.store.selectSnapshot(ConfigState.getGrantedPolicy('AbpIdentity.Roles.Create')); |
||||
|
} |
||||
|
|
||||
|
// ... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
或者你可以通过 `ConfigStateService` 获取它: |
||||
|
|
||||
|
```js |
||||
|
import { ConfigStateService } from '../services/config-state.service'; |
||||
|
|
||||
|
export class YourComponent { |
||||
|
constructor(private configStateService: ConfigStateService) {} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
const canCreate = this.configStateService.getGrantedPolicy('AbpIdentity.Roles.Create'); |
||||
|
} |
||||
|
|
||||
|
// ... |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 权限指令 |
||||
|
|
||||
|
你可以使用 `PermissionDirective` 来根据用户的权限控制DOM元素是否可见. |
||||
|
|
||||
|
```html |
||||
|
<div *abpPermission="AbpIdentity.Roles"> |
||||
|
仅当用户具有`AbpIdentity.Roles`权限时,此内容才可见. |
||||
|
</div> |
||||
|
``` |
||||
|
|
||||
|
如上所示,你可以使用 `abpPermission` 结构指令从DOM中删除元素. |
||||
|
|
||||
|
该指令也可以用作属性指令,但是我们建议你将其用作结构指令. |
||||
|
|
||||
|
## 权限守卫 |
||||
|
|
||||
|
如果你想要在导航过程中控制经过身份验证的用户对路由的访问权限,可以使用 `PermissionGuard`. |
||||
|
|
||||
|
将 `requiredPolicy` 添加到路由模块中的 `routes`属性. |
||||
|
|
||||
|
```js |
||||
|
const routes: Routes = [ |
||||
|
{ |
||||
|
path: 'path', |
||||
|
component: YourComponent, |
||||
|
canActivate: [PermissionGuard], |
||||
|
data: { |
||||
|
routes: { |
||||
|
requiredPolicy: 'AbpIdentity.Roles.Create', |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
]; |
||||
|
``` |
||||
|
|
||||
|
授予的策略存储在 `ConfigState` 的 `auth` 属性中. |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
* [Config State](./Config-State.md) |
||||
@ -0,0 +1,179 @@ |
|||||
|
# ProjectionStrategy |
||||
|
|
||||
|
`ProjectionStrategy` 是@abp/ng.core包暴露出的抽象类. 有三种扩展它的投影策略: `ComponentProjectionStrategy`, `RootComponentProjectionStrategy` 和 `TemplateProjectionStrategy`. 它们实现相同的方法和属性,均可以帮助你定义内容投影的工作方式. |
||||
|
|
||||
|
## ComponentProjectionStrategy |
||||
|
|
||||
|
`ComponentProjectionStrategy` 是扩展 `ProjectionStrategy` 的类. 它使你可以将**组件投影到容器中**. |
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
component: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy?: ContextStrategy, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `component` 是你要投影的组件的类. |
||||
|
- `containerStrategy` 是在投影组件时将使用的 `ContainerStrategy`. |
||||
|
- `contextStrategy` 是将在投影组件上使用的 `ContextStrategy`. (默认值: None_) |
||||
|
|
||||
|
请参阅[ContainerStrategy](./Container-Strategy.md)和[ContextStrategy](./Context-Strategy.md)文档以了解其用法. |
||||
|
|
||||
|
### injectContent |
||||
|
|
||||
|
```js |
||||
|
injectContent(injector: Injector): ComponentRef<T> |
||||
|
``` |
||||
|
|
||||
|
该方法准备容器,解析组件,设置其上下文并将其投影到容器中. 它返回一个 `ComponentRef` 实例,你应该保留该实例以便以后清除投影的组件. |
||||
|
|
||||
|
## RootComponentProjectionStrategy |
||||
|
|
||||
|
`RootComponentProjectionStrategy` 是扩展 `ProjectionStrategy` 的类. 它使你可以将**组件投影到文档中**,例如将其附加到 `<body>`. |
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
component: T, |
||||
|
private contextStrategy?: ContextStrategy, |
||||
|
private domStrategy?: DomStrategy, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `component` 是你要投影的组件的类. |
||||
|
- `contextStrategy` 是将在投影组件上使用的 `ContextStrategy`. (默认值: None_) |
||||
|
- `domStrategy` 是插入组件时将使用的 `DomStrategy`. (默认值: AppendToBody_) |
||||
|
|
||||
|
请参阅[ContextStrategy](./Context-Strategy.md)和[DomStrategy](./Dom-Strategy.md)文档以了解其用法. |
||||
|
|
||||
|
### injectContent |
||||
|
|
||||
|
```js |
||||
|
injectContent(injector: Injector): ComponentRef<T> |
||||
|
``` |
||||
|
|
||||
|
该方法解析组件,设置其上下文并将其投影到文档中. 它返回一个 `ComponentRef` 实例,你应该保留该实例以便以后清除投影的组件. |
||||
|
|
||||
|
## TemplateProjectionStrategy |
||||
|
|
||||
|
`TemplateProjectionStrategy` 是扩展 `ProjectionStrategy` 的类.它使你可以将**模板投影到容器中**. |
||||
|
|
||||
|
### constructor |
||||
|
|
||||
|
```js |
||||
|
constructor( |
||||
|
template: T, |
||||
|
private containerStrategy: ContainerStrategy, |
||||
|
private contextStrategy?: ContextStrategy, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
- `template` 是你要投影的 `TemplateRef`. |
||||
|
- `containerStrategy` 是在投影组件时将使用的 `ContainerStrategy`. |
||||
|
- `contextStrategy` 是将在投影组件上使用的 `ContextStrategy`. (默认值: None_) |
||||
|
|
||||
|
请参阅[ContainerStrategy](./Container-Strategy.md)和[ContextStrategy](./Context-Strategy.md)文档以了解其用法. |
||||
|
|
||||
|
### injectContent |
||||
|
|
||||
|
```js |
||||
|
injectContent(): EmbeddedViewRef<T> |
||||
|
``` |
||||
|
|
||||
|
该方法准备容器,并将模板及其定义的上下文一起投影到容器. 它返回一个 `EmbeddedViewRef` 实例,你应该保留该实例以便以后清除投影的模板. |
||||
|
|
||||
|
## 预定义的投影策略 |
||||
|
|
||||
|
可以通过 `PROJECTION_STRATEGY` 常量访问预定义的投影策略. |
||||
|
|
||||
|
### AppendComponentToBody |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.AppendComponentToBody( |
||||
|
component: T, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
将给定上下文设置到组件并将放置在文档中 `<body>` 标签的**末尾**. |
||||
|
|
||||
|
### AppendComponentToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.AppendComponentToContainer( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
将给定上下文设置到组件并将放置在容器的**末尾**. |
||||
|
|
||||
|
### AppendTemplateToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.AppendTemplateToContainer( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
将给定上下文设置到模板并将其放置在容器的**末尾**. |
||||
|
|
||||
|
### PrependComponentToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.PrependComponentToContainer( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
将给定上下文设置到组件并将其放置在容器的**开头**. |
||||
|
|
||||
|
### PrependTemplateToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.PrependTemplateToContainer( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
将给定上下文设置到模板并将其放置在容器的**开头**. |
||||
|
|
||||
|
|
||||
|
### ProjectComponentToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.ProjectComponentToContainer( |
||||
|
component: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
清除容器,将给定的上下文设置到组件并放在**已清除**的容器中. |
||||
|
|
||||
|
### ProjectTemplateToContainer |
||||
|
|
||||
|
```js |
||||
|
PROJECTION_STRATEGY.ProjectTemplateToContainer( |
||||
|
templateRef: T, |
||||
|
containerRef: ViewContainerRef, |
||||
|
contextStrategy?: ComponentContextStrategy<T>, |
||||
|
) |
||||
|
``` |
||||
|
|
||||
|
清除容器,将给定的上下文设置到模板并放在**已清除**的容器中. |
||||
|
|
||||
|
## 另请参阅 |
||||
|
|
||||
|
- [DomInsertionService](./Dom-Insertion-Service.md) |
||||
@ -1,3 +1,68 @@ |
|||||
## 服务代理 |
## 服务代理 |
||||
|
|
||||
TODO... |
从Angular应用程序中调用服务器中的REST端点是很常见的, 在这种情况下我们通常创建**服务**(在服务器端具有针对每个服务方法的方法)和**模型对象**(与服务器端[DTO](../../Data-Transfer-Objects)匹配). |
||||
|
|
||||
|
除了手动创建这样的服务器交互服务之外,我们还可以使用[NSWAG](https://github.com/RicoSuter/NSwag)之类的工具来为我们生成服务代理. 但使用NSWAG过程中我们遇到以下问题: |
||||
|
|
||||
|
* 它生成一个**大的单个.ts文件**,该文件存在一些问题; |
||||
|
* 当你的应用程序增长时,它会变的**越来越大**. |
||||
|
* 它不适合ABP框架的[模块化](../../Module-Development-Basics)方法 |
||||
|
* 它创建了一些**难看的代码**. 我们希望有一个干净的代码(就像我们手动编写一样). |
||||
|
* 它不能生成在服务器端声明的相同**方法签名**(因为swagger.json并不完全反映后端服务的方法签名). 我们已经创建了一个端点公开了服务器端方法信息,以便客户端生成更好的一致的客户端代理. |
||||
|
|
||||
|
ABP CLI 的`generate-proxies` 命令在 `src/app` 文件夹中创建按模块名称分隔的文件夹,自动生成typescript客户端代理. |
||||
|
|
||||
|
在angular应用程序的**根文件夹**中运行以下命令: |
||||
|
|
||||
|
```bash |
||||
|
abp generate-proxy |
||||
|
``` |
||||
|
|
||||
|
它只为你自己的应用程序的服务创建代理. 不会为你正在使用的应用程序模块的服务创建代理(默认情况下). 有几个选项,参见[CLI文档](../../CLI). |
||||
|
|
||||
|
使用 `--module all` 选项生成的文件如下所示: |
||||
|
|
||||
|
 |
||||
|
|
||||
|
### Services |
||||
|
|
||||
|
每个生成的服务都与后端控制器匹配. 服务方法通过[RestService](./Http-Requests#restservice)调用后端API. |
||||
|
|
||||
|
在每个服务中都定义了一个名为 `apiName` 的变量(自v2.4起可用). `apiName` 与模块的 `RemoteServiceName` 匹配. 在每次请求时该变量将作为参数传递给 `RestService`. 如果环境中未定义微服务API, `RestService` 使用默认值. 请参阅[从应用程序配置中获取特定的API端点](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config) |
||||
|
|
||||
|
服务的 `providerIn` 属性定义为 `'root'`. 因此无需将服务作为提供程序添加到模块. 你可以通过将服务注入到构造函数中来使用它,如下所示: |
||||
|
|
||||
|
```js |
||||
|
import { AbpApplicationConfigurationService } from '../app/shared/services'; |
||||
|
|
||||
|
//... |
||||
|
export class HomeComponent{ |
||||
|
constructor(private appConfigService: AbpApplicationConfigurationService) {} |
||||
|
|
||||
|
ngOnInit() { |
||||
|
this.appConfigService.get().subscribe() |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
Angular编译器会从最终输出中删除那些没有被注入的服务. 参见[摇树优化的提供者文档](https://angular.cn/guide/dependency-injection-providers#tree-shakable-providers). |
||||
|
|
||||
|
### Models |
||||
|
|
||||
|
生成的模型与后端中的dto匹配. 每个模型在 `src/app/*/shared/models` 文件夹生成一个类. |
||||
|
|
||||
|
`@abp/ng.core` 包有一些[基类](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts). 一些模型扩展了这些类. |
||||
|
|
||||
|
可以如下所示创建一个类的实例: |
||||
|
|
||||
|
```js |
||||
|
import { IdentityRoleCreateDto } from '../identity/shared/models'; |
||||
|
//... |
||||
|
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true}) |
||||
|
``` |
||||
|
|
||||
|
可以选择将初始值传递给每个类构造函数. |
||||
|
|
||||
|
## 下一步是什么? |
||||
|
|
||||
|
* [HTTP请求](./Http-Requests) |
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 218 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 357 KiB |
|
After Width: | Height: | Size: 424 KiB |
|
After Width: | Height: | Size: 401 KiB |
|
After Width: | Height: | Size: 336 KiB |
|
After Width: | Height: | Size: 389 KiB |
@ -0,0 +1,600 @@ |
|||||
|
(function (global, factory) { |
||||
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('just-compare')) : |
||||
|
typeof define === 'function' && define.amd ? define('@abp/utils', ['exports', 'just-compare'], factory) : |
||||
|
(global = global || self, factory((global.abp = global.abp || {}, global.abp.utils = global.abp.utils || {}, global.abp.utils.common = {}), global.compare)); |
||||
|
}(this, (function (exports, compare) { 'use strict'; |
||||
|
|
||||
|
compare = compare && Object.prototype.hasOwnProperty.call(compare, 'default') ? compare['default'] : compare; |
||||
|
|
||||
|
/*! ***************************************************************************** |
||||
|
Copyright (c) Microsoft Corporation. All rights reserved. |
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may not use |
||||
|
this file except in compliance with the License. You may obtain a copy of the |
||||
|
License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
||||
|
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
||||
|
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED |
||||
|
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, |
||||
|
MERCHANTABLITY OR NON-INFRINGEMENT. |
||||
|
|
||||
|
See the Apache Version 2.0 License for specific language governing permissions |
||||
|
and limitations under the License. |
||||
|
***************************************************************************** */ |
||||
|
/* global Reflect, Promise */ |
||||
|
|
||||
|
var extendStatics = function(d, b) { |
||||
|
extendStatics = Object.setPrototypeOf || |
||||
|
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || |
||||
|
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; |
||||
|
return extendStatics(d, b); |
||||
|
}; |
||||
|
|
||||
|
function __extends(d, b) { |
||||
|
extendStatics(d, b); |
||||
|
function __() { this.constructor = d; } |
||||
|
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); |
||||
|
} |
||||
|
|
||||
|
var __assign = function() { |
||||
|
__assign = Object.assign || function __assign(t) { |
||||
|
for (var s, i = 1, n = arguments.length; i < n; i++) { |
||||
|
s = arguments[i]; |
||||
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; |
||||
|
} |
||||
|
return t; |
||||
|
}; |
||||
|
return __assign.apply(this, arguments); |
||||
|
}; |
||||
|
|
||||
|
function __rest(s, e) { |
||||
|
var t = {}; |
||||
|
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) |
||||
|
t[p] = s[p]; |
||||
|
if (s != null && typeof Object.getOwnPropertySymbols === "function") |
||||
|
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { |
||||
|
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) |
||||
|
t[p[i]] = s[p[i]]; |
||||
|
} |
||||
|
return t; |
||||
|
} |
||||
|
|
||||
|
function __decorate(decorators, target, key, desc) { |
||||
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; |
||||
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); |
||||
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; |
||||
|
return c > 3 && r && Object.defineProperty(target, key, r), r; |
||||
|
} |
||||
|
|
||||
|
function __param(paramIndex, decorator) { |
||||
|
return function (target, key) { decorator(target, key, paramIndex); } |
||||
|
} |
||||
|
|
||||
|
function __metadata(metadataKey, metadataValue) { |
||||
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); |
||||
|
} |
||||
|
|
||||
|
function __awaiter(thisArg, _arguments, P, generator) { |
||||
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } |
||||
|
return new (P || (P = Promise))(function (resolve, reject) { |
||||
|
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } |
||||
|
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } |
||||
|
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } |
||||
|
step((generator = generator.apply(thisArg, _arguments || [])).next()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function __generator(thisArg, body) { |
||||
|
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; |
||||
|
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; |
||||
|
function verb(n) { return function (v) { return step([n, v]); }; } |
||||
|
function step(op) { |
||||
|
if (f) throw new TypeError("Generator is already executing."); |
||||
|
while (_) try { |
||||
|
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; |
||||
|
if (y = 0, t) op = [op[0] & 2, t.value]; |
||||
|
switch (op[0]) { |
||||
|
case 0: case 1: t = op; break; |
||||
|
case 4: _.label++; return { value: op[1], done: false }; |
||||
|
case 5: _.label++; y = op[1]; op = [0]; continue; |
||||
|
case 7: op = _.ops.pop(); _.trys.pop(); continue; |
||||
|
default: |
||||
|
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } |
||||
|
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } |
||||
|
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } |
||||
|
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } |
||||
|
if (t[2]) _.ops.pop(); |
||||
|
_.trys.pop(); continue; |
||||
|
} |
||||
|
op = body.call(thisArg, _); |
||||
|
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } |
||||
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function __exportStar(m, exports) { |
||||
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; |
||||
|
} |
||||
|
|
||||
|
function __values(o) { |
||||
|
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; |
||||
|
if (m) return m.call(o); |
||||
|
if (o && typeof o.length === "number") return { |
||||
|
next: function () { |
||||
|
if (o && i >= o.length) o = void 0; |
||||
|
return { value: o && o[i++], done: !o }; |
||||
|
} |
||||
|
}; |
||||
|
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); |
||||
|
} |
||||
|
|
||||
|
function __read(o, n) { |
||||
|
var m = typeof Symbol === "function" && o[Symbol.iterator]; |
||||
|
if (!m) return o; |
||||
|
var i = m.call(o), r, ar = [], e; |
||||
|
try { |
||||
|
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); |
||||
|
} |
||||
|
catch (error) { e = { error: error }; } |
||||
|
finally { |
||||
|
try { |
||||
|
if (r && !r.done && (m = i["return"])) m.call(i); |
||||
|
} |
||||
|
finally { if (e) throw e.error; } |
||||
|
} |
||||
|
return ar; |
||||
|
} |
||||
|
|
||||
|
function __spread() { |
||||
|
for (var ar = [], i = 0; i < arguments.length; i++) |
||||
|
ar = ar.concat(__read(arguments[i])); |
||||
|
return ar; |
||||
|
} |
||||
|
|
||||
|
function __spreadArrays() { |
||||
|
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; |
||||
|
for (var r = Array(s), k = 0, i = 0; i < il; i++) |
||||
|
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) |
||||
|
r[k] = a[j]; |
||||
|
return r; |
||||
|
}; |
||||
|
|
||||
|
function __await(v) { |
||||
|
return this instanceof __await ? (this.v = v, this) : new __await(v); |
||||
|
} |
||||
|
|
||||
|
function __asyncGenerator(thisArg, _arguments, generator) { |
||||
|
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); |
||||
|
var g = generator.apply(thisArg, _arguments || []), i, q = []; |
||||
|
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; |
||||
|
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } |
||||
|
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } |
||||
|
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } |
||||
|
function fulfill(value) { resume("next", value); } |
||||
|
function reject(value) { resume("throw", value); } |
||||
|
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } |
||||
|
} |
||||
|
|
||||
|
function __asyncDelegator(o) { |
||||
|
var i, p; |
||||
|
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; |
||||
|
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } |
||||
|
} |
||||
|
|
||||
|
function __asyncValues(o) { |
||||
|
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); |
||||
|
var m = o[Symbol.asyncIterator], i; |
||||
|
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); |
||||
|
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } |
||||
|
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } |
||||
|
} |
||||
|
|
||||
|
function __makeTemplateObject(cooked, raw) { |
||||
|
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } |
||||
|
return cooked; |
||||
|
}; |
||||
|
|
||||
|
function __importStar(mod) { |
||||
|
if (mod && mod.__esModule) return mod; |
||||
|
var result = {}; |
||||
|
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; |
||||
|
result.default = mod; |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
function __importDefault(mod) { |
||||
|
return (mod && mod.__esModule) ? mod : { default: mod }; |
||||
|
} |
||||
|
|
||||
|
function __classPrivateFieldGet(receiver, privateMap) { |
||||
|
if (!privateMap.has(receiver)) { |
||||
|
throw new TypeError("attempted to get private field on non-instance"); |
||||
|
} |
||||
|
return privateMap.get(receiver); |
||||
|
} |
||||
|
|
||||
|
function __classPrivateFieldSet(receiver, privateMap, value) { |
||||
|
if (!privateMap.has(receiver)) { |
||||
|
throw new TypeError("attempted to set private field on non-instance"); |
||||
|
} |
||||
|
privateMap.set(receiver, value); |
||||
|
return value; |
||||
|
} |
||||
|
|
||||
|
/* tslint:disable:no-non-null-assertion */ |
||||
|
var ListNode = /** @class */ (function () { |
||||
|
function ListNode(value) { |
||||
|
this.value = value; |
||||
|
} |
||||
|
return ListNode; |
||||
|
}()); |
||||
|
var LinkedList = /** @class */ (function () { |
||||
|
function LinkedList() { |
||||
|
this.size = 0; |
||||
|
} |
||||
|
Object.defineProperty(LinkedList.prototype, "head", { |
||||
|
get: function () { |
||||
|
return this.first; |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
Object.defineProperty(LinkedList.prototype, "tail", { |
||||
|
get: function () { |
||||
|
return this.last; |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
Object.defineProperty(LinkedList.prototype, "length", { |
||||
|
get: function () { |
||||
|
return this.size; |
||||
|
}, |
||||
|
enumerable: true, |
||||
|
configurable: true |
||||
|
}); |
||||
|
LinkedList.prototype.attach = function (value, previousNode, nextNode) { |
||||
|
if (!previousNode) |
||||
|
return this.addHead(value); |
||||
|
if (!nextNode) |
||||
|
return this.addTail(value); |
||||
|
var node = new ListNode(value); |
||||
|
node.previous = previousNode; |
||||
|
previousNode.next = node; |
||||
|
node.next = nextNode; |
||||
|
nextNode.previous = node; |
||||
|
this.size++; |
||||
|
return node; |
||||
|
}; |
||||
|
LinkedList.prototype.attachMany = function (values, previousNode, nextNode) { |
||||
|
if (!values.length) |
||||
|
return []; |
||||
|
if (!previousNode) |
||||
|
return this.addManyHead(values); |
||||
|
if (!nextNode) |
||||
|
return this.addManyTail(values); |
||||
|
var list = new LinkedList(); |
||||
|
list.addManyTail(values); |
||||
|
list.first.previous = previousNode; |
||||
|
previousNode.next = list.first; |
||||
|
list.last.next = nextNode; |
||||
|
nextNode.previous = list.last; |
||||
|
this.size += values.length; |
||||
|
return list.toNodeArray(); |
||||
|
}; |
||||
|
LinkedList.prototype.detach = function (node) { |
||||
|
if (!node.previous) |
||||
|
return this.dropHead(); |
||||
|
if (!node.next) |
||||
|
return this.dropTail(); |
||||
|
node.previous.next = node.next; |
||||
|
node.next.previous = node.previous; |
||||
|
this.size--; |
||||
|
return node; |
||||
|
}; |
||||
|
LinkedList.prototype.add = function (value) { |
||||
|
var _this = this; |
||||
|
return { |
||||
|
after: function () { |
||||
|
var _a; |
||||
|
var params = []; |
||||
|
for (var _i = 0; _i < arguments.length; _i++) { |
||||
|
params[_i] = arguments[_i]; |
||||
|
} |
||||
|
return (_a = _this.addAfter).call.apply(_a, __spread([_this, value], params)); |
||||
|
}, |
||||
|
before: function () { |
||||
|
var _a; |
||||
|
var params = []; |
||||
|
for (var _i = 0; _i < arguments.length; _i++) { |
||||
|
params[_i] = arguments[_i]; |
||||
|
} |
||||
|
return (_a = _this.addBefore).call.apply(_a, __spread([_this, value], params)); |
||||
|
}, |
||||
|
byIndex: function (position) { return _this.addByIndex(value, position); }, |
||||
|
head: function () { return _this.addHead(value); }, |
||||
|
tail: function () { return _this.addTail(value); }, |
||||
|
}; |
||||
|
}; |
||||
|
LinkedList.prototype.addMany = function (values) { |
||||
|
var _this = this; |
||||
|
return { |
||||
|
after: function () { |
||||
|
var _a; |
||||
|
var params = []; |
||||
|
for (var _i = 0; _i < arguments.length; _i++) { |
||||
|
params[_i] = arguments[_i]; |
||||
|
} |
||||
|
return (_a = _this.addManyAfter).call.apply(_a, __spread([_this, values], params)); |
||||
|
}, |
||||
|
before: function () { |
||||
|
var _a; |
||||
|
var params = []; |
||||
|
for (var _i = 0; _i < arguments.length; _i++) { |
||||
|
params[_i] = arguments[_i]; |
||||
|
} |
||||
|
return (_a = _this.addManyBefore).call.apply(_a, __spread([_this, values], params)); |
||||
|
}, |
||||
|
byIndex: function (position) { return _this.addManyByIndex(values, position); }, |
||||
|
head: function () { return _this.addManyHead(values); }, |
||||
|
tail: function () { return _this.addManyTail(values); }, |
||||
|
}; |
||||
|
}; |
||||
|
LinkedList.prototype.addAfter = function (value, previousValue, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
var previous = this.find(function (node) { return compareFn(node.value, previousValue); }); |
||||
|
return previous ? this.attach(value, previous, previous.next) : this.addTail(value); |
||||
|
}; |
||||
|
LinkedList.prototype.addBefore = function (value, nextValue, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
var next = this.find(function (node) { return compareFn(node.value, nextValue); }); |
||||
|
return next ? this.attach(value, next.previous, next) : this.addHead(value); |
||||
|
}; |
||||
|
LinkedList.prototype.addByIndex = function (value, position) { |
||||
|
if (position < 0) |
||||
|
position += this.size; |
||||
|
else if (position >= this.size) |
||||
|
return this.addTail(value); |
||||
|
if (position <= 0) |
||||
|
return this.addHead(value); |
||||
|
var next = this.get(position); |
||||
|
return this.attach(value, next.previous, next); |
||||
|
}; |
||||
|
LinkedList.prototype.addHead = function (value) { |
||||
|
var node = new ListNode(value); |
||||
|
node.next = this.first; |
||||
|
if (this.first) |
||||
|
this.first.previous = node; |
||||
|
else |
||||
|
this.last = node; |
||||
|
this.first = node; |
||||
|
this.size++; |
||||
|
return node; |
||||
|
}; |
||||
|
LinkedList.prototype.addTail = function (value) { |
||||
|
var node = new ListNode(value); |
||||
|
if (this.first) { |
||||
|
node.previous = this.last; |
||||
|
this.last.next = node; |
||||
|
this.last = node; |
||||
|
} |
||||
|
else { |
||||
|
this.first = node; |
||||
|
this.last = node; |
||||
|
} |
||||
|
this.size++; |
||||
|
return node; |
||||
|
}; |
||||
|
LinkedList.prototype.addManyAfter = function (values, previousValue, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
var previous = this.find(function (node) { return compareFn(node.value, previousValue); }); |
||||
|
return previous ? this.attachMany(values, previous, previous.next) : this.addManyTail(values); |
||||
|
}; |
||||
|
LinkedList.prototype.addManyBefore = function (values, nextValue, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
var next = this.find(function (node) { return compareFn(node.value, nextValue); }); |
||||
|
return next ? this.attachMany(values, next.previous, next) : this.addManyHead(values); |
||||
|
}; |
||||
|
LinkedList.prototype.addManyByIndex = function (values, position) { |
||||
|
if (position < 0) |
||||
|
position += this.size; |
||||
|
if (position <= 0) |
||||
|
return this.addManyHead(values); |
||||
|
if (position >= this.size) |
||||
|
return this.addManyTail(values); |
||||
|
var next = this.get(position); |
||||
|
return this.attachMany(values, next.previous, next); |
||||
|
}; |
||||
|
LinkedList.prototype.addManyHead = function (values) { |
||||
|
var _this = this; |
||||
|
return values.reduceRight(function (nodes, value) { |
||||
|
nodes.unshift(_this.addHead(value)); |
||||
|
return nodes; |
||||
|
}, []); |
||||
|
}; |
||||
|
LinkedList.prototype.addManyTail = function (values) { |
||||
|
var _this = this; |
||||
|
return values.map(function (value) { return _this.addTail(value); }); |
||||
|
}; |
||||
|
LinkedList.prototype.drop = function () { |
||||
|
var _this = this; |
||||
|
return { |
||||
|
byIndex: function (position) { return _this.dropByIndex(position); }, |
||||
|
byValue: function () { |
||||
|
var params = []; |
||||
|
for (var _i = 0; _i < arguments.length; _i++) { |
||||
|
params[_i] = arguments[_i]; |
||||
|
} |
||||
|
return _this.dropByValue.apply(_this, params); |
||||
|
}, |
||||
|
byValueAll: function () { |
||||
|
var params = []; |
||||
|
for (var _i = 0; _i < arguments.length; _i++) { |
||||
|
params[_i] = arguments[_i]; |
||||
|
} |
||||
|
return _this.dropByValueAll.apply(_this, params); |
||||
|
}, |
||||
|
head: function () { return _this.dropHead(); }, |
||||
|
tail: function () { return _this.dropTail(); }, |
||||
|
}; |
||||
|
}; |
||||
|
LinkedList.prototype.dropMany = function (count) { |
||||
|
var _this = this; |
||||
|
return { |
||||
|
byIndex: function (position) { return _this.dropManyByIndex(count, position); }, |
||||
|
head: function () { return _this.dropManyHead(count); }, |
||||
|
tail: function () { return _this.dropManyTail(count); }, |
||||
|
}; |
||||
|
}; |
||||
|
LinkedList.prototype.dropByIndex = function (position) { |
||||
|
if (position < 0) |
||||
|
position += this.size; |
||||
|
var current = this.get(position); |
||||
|
return current ? this.detach(current) : undefined; |
||||
|
}; |
||||
|
LinkedList.prototype.dropByValue = function (value, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
var position = this.findIndex(function (node) { return compareFn(node.value, value); }); |
||||
|
return position < 0 ? undefined : this.dropByIndex(position); |
||||
|
}; |
||||
|
LinkedList.prototype.dropByValueAll = function (value, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
var dropped = []; |
||||
|
for (var current = this.first, position = 0; current; position++, current = current.next) { |
||||
|
if (compareFn(current.value, value)) { |
||||
|
dropped.push(this.dropByIndex(position - dropped.length)); |
||||
|
} |
||||
|
} |
||||
|
return dropped; |
||||
|
}; |
||||
|
LinkedList.prototype.dropHead = function () { |
||||
|
var head = this.first; |
||||
|
if (head) { |
||||
|
this.first = head.next; |
||||
|
if (this.first) |
||||
|
this.first.previous = undefined; |
||||
|
else |
||||
|
this.last = undefined; |
||||
|
this.size--; |
||||
|
return head; |
||||
|
} |
||||
|
return undefined; |
||||
|
}; |
||||
|
LinkedList.prototype.dropTail = function () { |
||||
|
var tail = this.last; |
||||
|
if (tail) { |
||||
|
this.last = tail.previous; |
||||
|
if (this.last) |
||||
|
this.last.next = undefined; |
||||
|
else |
||||
|
this.first = undefined; |
||||
|
this.size--; |
||||
|
return tail; |
||||
|
} |
||||
|
return undefined; |
||||
|
}; |
||||
|
LinkedList.prototype.dropManyByIndex = function (count, position) { |
||||
|
if (count <= 0) |
||||
|
return []; |
||||
|
if (position < 0) |
||||
|
position = Math.max(position + this.size, 0); |
||||
|
else if (position >= this.size) |
||||
|
return []; |
||||
|
count = Math.min(count, this.size - position); |
||||
|
var dropped = []; |
||||
|
while (count--) { |
||||
|
var current = this.get(position); |
||||
|
dropped.push(this.detach(current)); |
||||
|
} |
||||
|
return dropped; |
||||
|
}; |
||||
|
LinkedList.prototype.dropManyHead = function (count) { |
||||
|
if (count <= 0) |
||||
|
return []; |
||||
|
count = Math.min(count, this.size); |
||||
|
var dropped = []; |
||||
|
while (count--) |
||||
|
dropped.unshift(this.dropHead()); |
||||
|
return dropped; |
||||
|
}; |
||||
|
LinkedList.prototype.dropManyTail = function (count) { |
||||
|
if (count <= 0) |
||||
|
return []; |
||||
|
count = Math.min(count, this.size); |
||||
|
var dropped = []; |
||||
|
while (count--) |
||||
|
dropped.push(this.dropTail()); |
||||
|
return dropped; |
||||
|
}; |
||||
|
LinkedList.prototype.find = function (predicate) { |
||||
|
for (var current = this.first, position = 0; current; position++, current = current.next) { |
||||
|
if (predicate(current, position, this)) |
||||
|
return current; |
||||
|
} |
||||
|
return undefined; |
||||
|
}; |
||||
|
LinkedList.prototype.findIndex = function (predicate) { |
||||
|
for (var current = this.first, position = 0; current; position++, current = current.next) { |
||||
|
if (predicate(current, position, this)) |
||||
|
return position; |
||||
|
} |
||||
|
return -1; |
||||
|
}; |
||||
|
LinkedList.prototype.forEach = function (callback) { |
||||
|
for (var node = this.first, position = 0; node; position++, node = node.next) { |
||||
|
callback(node, position, this); |
||||
|
} |
||||
|
}; |
||||
|
LinkedList.prototype.get = function (position) { |
||||
|
return this.find(function (_, index) { return position === index; }); |
||||
|
}; |
||||
|
LinkedList.prototype.indexOf = function (value, compareFn) { |
||||
|
if (compareFn === void 0) { compareFn = compare; } |
||||
|
return this.findIndex(function (node) { return compareFn(node.value, value); }); |
||||
|
}; |
||||
|
LinkedList.prototype.toArray = function () { |
||||
|
var array = new Array(this.size); |
||||
|
this.forEach(function (node, index) { return (array[index] = node.value); }); |
||||
|
return array; |
||||
|
}; |
||||
|
LinkedList.prototype.toNodeArray = function () { |
||||
|
var array = new Array(this.size); |
||||
|
this.forEach(function (node, index) { return (array[index] = node); }); |
||||
|
return array; |
||||
|
}; |
||||
|
LinkedList.prototype.toString = function (mapperFn) { |
||||
|
if (mapperFn === void 0) { mapperFn = JSON.stringify; } |
||||
|
return this.toArray() |
||||
|
.map(function (value) { return mapperFn(value); }) |
||||
|
.join(' <-> '); |
||||
|
}; |
||||
|
// Cannot use Generator type because of ng-packagr
|
||||
|
LinkedList.prototype[Symbol.iterator] = function () { |
||||
|
var node, position; |
||||
|
return __generator(this, function (_a) { |
||||
|
switch (_a.label) { |
||||
|
case 0: |
||||
|
node = this.first, position = 0; |
||||
|
_a.label = 1; |
||||
|
case 1: |
||||
|
if (!node) return [3 /*break*/, 4]; |
||||
|
return [4 /*yield*/, node.value]; |
||||
|
case 2: |
||||
|
_a.sent(); |
||||
|
_a.label = 3; |
||||
|
case 3: |
||||
|
position++, node = node.next; |
||||
|
return [3 /*break*/, 1]; |
||||
|
case 4: return [2 /*return*/]; |
||||
|
} |
||||
|
}); |
||||
|
}; |
||||
|
return LinkedList; |
||||
|
}()); |
||||
|
|
||||
|
exports.LinkedList = LinkedList; |
||||
|
exports.ListNode = ListNode; |
||||
|
|
||||
|
Object.defineProperty(exports, '__esModule', { value: true }); |
||||
|
|
||||
|
}))); |
||||
|
//# sourceMappingURL=abp-utils.umd.js.map
|
||||