.Page - meta-d/sap-fiori-templates GitHub Wiki
English | δΈζ
Application in NGen is organized into group. Each group is a folder with multiple app pages and a routing. Each page is a folder with a component, html, style and any other files that are specific to that page.
If you want to create new page to an existing group, you can simply create a new folder under the group folder and add the page component and any other files that are specific to that page.
If you want to create a new group, you can create a new folder under the src/app/pages/
folder and add the routing file in the group folder.
The application path pattern is src/app/pages/<New Group>/<New App>
.
The application page folder structure is:
src/app/pages/
β
ββββgroup1
ββββββββrouting.ts
β ββββapp1
β ββββapp2
β ββββapp2.component.html
β ββββapp2.component.scss
β ββββapp2.component.ts
ββββgroup2
ββββapp2-1
ββββapp2-2
You can create a new page component (eg. products
in admin
group) manually or using the following command:
yarn nx g @nx/angular:component products --directory=apps/launchpad/src/app/pages/admin/products --nameAndDirectoryFormat=as-provided
Add the page component into routing file see Component Routing. Then you can access the page by the url http://localhost:4200/admin/products
, and the page menu Products will be rendered in the Admin menu group.
If you created a new group, you need to add the group routes into the src/app/app.routes.ts
file:
import { environment } from '@/environments/environment'
import { Routes } from '@angular/router'
export const appRoutes: Routes = [
..., // other routes
{
path: 'admin',
title: 'Admin',
loadChildren: () => import('./pages/admin/admin-routing'),
data: {
icon: 'setting',
key: 'admin',
// authorization: {
// name: 'S_SERVICE',
// params: {
// SRV_NAME: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx',
// SRV_TYPE: 'HT'
// }
// }
}
}
]
We recommend use the loadChildren
to lazy load the group routes, so that the initial home page loading time is reduced.
About the authorization
field, see Authorization Configurations for Route.
Add the new component into group routing file src/app/pages/admin/routing.ts
:
import { Routes } from '@angular/router'
import { ProductsComponent } from './products/products.component'
export default [
..., // other routes
{
path: 'products',
title: 'Products',
component: ProductsComponent
}
] as Routes
You can also add authorization check config for the single app route, see Authorization Configurations for Route.
Add the base layout template to the component html file src/app/pages/admin/products/products.component.html
:
<nz-page-header nzBackIcon [nzGhost]="false">
<nz-page-header-title>Products</nz-page-header-title>
<nz-page-header-subtitle></nz-page-header-subtitle>
<nz-page-header-extra>
<nz-space>
<button *nzSpaceItem nz-button nzType="primary">
{{ 'ZNG.Common.Query' | translate: {Default: 'Query'} }}
</button>
</nz-space>
</nz-page-header-extra>
<nz-page-header-content></nz-page-header-content>
</nz-page-header>
<div class="zng-page-body-ghost-wrapper">
<div class="flex justify-between items-center p-2">
<div>Products List</div>
</div>
</div>
You also need import two modules TranslateModule, ZngAntdModule
into component:
import { ZngAntdModule } from '@/app/core/shared.module'
import { CommonModule } from '@angular/common'
import { Component } from '@angular/core'
import { TranslateModule } from '@ngx-translate/core'
@Component({
selector: 'zng-products',
standalone: true,
imports: [CommonModule, TranslateModule, ZngAntdModule],
templateUrl: './products.component.html',
styleUrl: './products.component.scss'
})
export class ProductsComponent {}
You can create a ts file src/app/pages/admin/products/odata.service.ts
to call the odata service.
For detail usage of the odata client, see OData Service.
import { Keys, ODataQueryOptions, StoreStatus, defineODataStore } from '@metad/cap-odata'
const demoODataStore = defineODataStore('OData.svc', {
base: '/odata.org/V3/OData/'
})
export const useDemoODataStore = () => {
const { store, init } = demoODataStore
if (store.value.status === StoreStatus.init || store.value.status === StoreStatus.error) {
init()
}
return demoODataStore
}
export async function queryProducts(options?: ODataQueryOptions) {
const { query } = useDemoODataStore()
const result = await query<ProductType>('Products', {
...options
})
return result
}
export type ProductType = {
ID: string
Name: string
Description: string
ReleaseDate: Date
DiscontinuedDate: Date
Rating: number
Price: number
Categories: CategoryType[]
Supplier: SupplierType
ProductDetail: ProductDetailType
}
export type CategoryType = {
ID: string
Name: string
}
export type ProductDetailType = {
ProductID: number
Details: string
Product?: ProductType
}
export type SupplierType = {
ID: number
Name: string
Address: string
Location: string
Concurrency: string
Products?: ProductType[]
}
And then call functions in the component:
import { queryProducts } from './odata.service'
...
export class ProductsComponent {
constructor() {
queryProducts().then((result) => console.log(result))
}
}
You can got products list log in the broswer console.
Add the table component to the component html file src/app/pages/admin/products/products.component.html
:
<div class="zng-page-body-ghost-wrapper p-4">
<div class="flex justify-between items-center p-2">
<div>Products List</div>
</div>
<nz-table #expandTable class="h-full w-full" [nzFrontPagination]="false" [nzLoading]="loading()" nzBordered nzOuterBordered
[nzData]="items()" [nzScroll]="{ x: '1100px', y: '500px' }"
nzShowPagination
nzShowSizeChanger>
<thead>
<tr>
@for (column of tableColumns(); track column.name) {
<th [nzLeft]="column.freeze === 'left' || column.freeze === true" [nzRight]="column.freeze === 'right'" [nzAlign]="column.align!"
>
{{column.label}}
</th>
}
</tr>
</thead>
<tbody>
@for (row of items(); track row.ID) {
<tr>
@for (column of tableColumns(); track column.name) {
<td [nzLeft]="column.freeze === 'left' || column.freeze === true" [nzRight]="column.freeze === 'right'">
@switch (column.name) {
@default {
<span>{{ row[column.name] }}</span>
}
}
</td>
}
</tr>
}
</tbody>
</nz-table>
</div>
The logic is:
export class ProductsComponent {
readonly loading = signal(true)
readonly tableColumns = signal<TableColumn<ProductType>[]>([
{
name: 'ID',
label: 'ID'
},
{
name: 'Name',
label: 'Name'
},
{
name: 'Description',
label: 'Description'
},
{
name: 'ReleaseDate',
label: 'Release Date'
},
{
name: 'DiscontinuedDate',
label: 'Discontinued Date'
},
{
name: 'Rating',
label: 'Rating'
},
{
name: 'Price',
label: 'Price'
}
])
readonly items = signal<ProductType[]>([])
constructor() {
queryProducts().then((result) => {
this.loading.set(false)
this.items.set(result)
})
}
}
- The
loading
attribute is a signal used to control the loading status of the table. -
tableColumns
is a signal used to control the columns of the table. -
items
is a signal used to control the data of the table. - When the component is initialized, the
queryProducts
function is called to get the data, and then theloading
status is set tofalse
, and the data is set to theitems
signal. - The data will show in the table.
Add the styles to the component scss file src/app/pages/admin/products/products.component.scss
:
.ant-table-cell {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
The styles are used to control the table cell text overflow.
We have added the i18n to the button element in component html file:
<button *nzSpaceItem nz-button nzType="primary">
{{ 'ZNG.Common.Query' | translate: {Default: 'Query'} }}
</button>
App will use the key ZNG.Common.Query
as json path to get the value from the i18n file in assets/i18n/
. If the key is not found, the default value Query
will be used.
Add the key/value to the i18n json file src/assets/i18n/<language>.json
:
{
"ZNG": {
"Common: {
"Query": "ζ₯θ―’"
}
}
}
In this page, we have learned how to create a new page in the application, how to add the page to the routing. We can show the data from remote odata service in the table, and add the i18n to the page.