Skip to content
meta-d edited this page Mar 8, 2024 · 3 revisions

English | δΈ­ζ–‡

New Page

πŸ—οΈ App Structure

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

New Page Component

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.

Routing

Group routing

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.

Component Routing

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.

Component

1. Add base layout template

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 {}

2. Call odata service

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.

3. Add table component

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)
    })
  }
}
  1. The loading attribute is a signal used to control the loading status of the table.
  2. tableColumns is a signal used to control the columns of the table.
  3. items is a signal used to control the data of the table.
  4. When the component is initialized, the queryProducts function is called to get the data, and then the loading status is set to false, and the data is set to the items signal.
  5. The data will show in the table.

4. Add styles

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.

5. Add i18n

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": "ζŸ₯θ―’"
    }
  }
}

Summary

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.