preface
The data model described in this article is not a library, nor is it a package that requires NPM, but simply a set of rules that can be drawn up during multi-team development. At least for now, our team has done away with mocks (though it didn’t start out that way either), and we don’t have to worry about field or structure changes in the background data, and we really have the happy pattern of front-background parallel development.
This article's stack includes Typescript, Rxjs, and AngularXCopy the code
Define the Model
Analogy in Java classes, our Model is a class that is the class TS, we according to the requirements and design drawings or prototypes plan the base class for a specific module Model, and to define some fields, and enumerated types, method attributes, etc., does not need to force and the background field is consistent, to ensure the hundred pure separation before and after the end, an example
For example, the development of a background management project, there are Product module, User module and so on
So we’ll define the base class of BaseProduct in the Model folder
export class BaseProductModel {
constructorPublic id: number = null; public name: string =' '; /... more... /}Copy the code
Base class definition is necessary to save a lot of unnecessary code, do not need to write a page or component to redefine the new model, if a component needs to expand the content of the product can be directly inherited, will not affect other files with this base class
We believe that all base classes must be inherited, not directly constructed
The fields and attributes of a product in a real project must not only include ID and name, but may also include version, thumbnail address, unique identifier, product, price of corresponding specifications, status, creation time, etc. These properties can be placed in the base class, because all products have these properties. When it comes to defining types and states, note that
You must never use an enumerable property directly with a corresponding property returned by a background or third party
For example, the most basic status attribute in a production module is assumed to have a corresponding state defined behind the scenes
0: disabled 1: enabled 2: hidden 3: unavailableCopy the code
For these four types, if we directly use the numbers corresponding to the states to judge or conduct logical processing in the project, we will discuss them separately. If the numbers of the states change midway or later, GG. We may feel that such a case is rare, but it is not without, once there is a pile of bugs to change.
So for this enumerable property we’ll define an enumeration class (Enum)
export enum EStatus {
BAN = 0,
OPEN = 1,
HIDE = 2,
NOTBUY = 3
}
Copy the code
And then we do this in model
exportclass BaseProductModel { // ...... Public status: string = EStatus[1]Copy the code
And we don’t care what the numbers are for each state when we make logical judgments, we just care whether it’s BAN or OPEN, simple and clear. Right
We can also add a read-only property to the model to return a Chinese prompt for this state (this is a common requirement).
public get conversionStatusHint() : string {
const _ = { BAN: 'disabled', OPEN: 'enable', HIDE: 'hide', NOTBUY: 'I can't.' }
return _[this.status] ? _[this.status] : ' '
}
Copy the code
This eliminates the need to write a method in each component that returns a Chinese name for the parameter
Now that we’ve defined our BaseProductModel, we need to define a method for this model
The goal is to convert the fields and data structures returned in the background into our own defined fields and data structures
Converting background data
Now, a lot of people are going to think that this is a bit of a waste of time, because the background just returns the data and then converts it into whatever it returns. But in large team development projects, no one can guarantee that a field will not be modified, a field will not be deleted or added or missing, pulling the whole body together. Life is short. In addition, another situation is that this project may be carried out at the front end first, and the background has not been involved, so the front end needs to plan and develop the overall functions and styles according to the design drawings.
exportclass BaseProductModel { // ...... // Convert background data to publicsetData( data: BaseProductModel ): void {
if (data) {
for (let e in this) {
if ((<Object>data).hasOwnProperty(e)) {
if( e == 'status' ) {
this.status = EStatus[(<any>data)[e]]
} else {
this[e] = (<any>data)[e];
}
}
}
}
}
}
Copy the code
And then when you call it
Public ProductModel: ProductModel = new ProductModel(); /... more... / this. ProductModel. SetData (< BaseProductModel > {/ / assume that the background field is defined the creation time create_at, set in the model creation time is createTime createTime: data.create_at }); // Even if the data structure is inconsistent, the unified transformation can be done hereCopy the code
After the transformation, all data changes and data structure changes are modified in the same place that is done. At this time, no matter how the background changes, happy changes, will not affect our subsequent logical processing and field changes. Similarly, it is much easier to convert the data in the post to the background. The background needs any data and fields to be converted again.
The above data model can very well reduce the probability of front and background fights. Mock? Don’t need
Below is a common base class for tabular data models that we have extracted
import { BehaviorSubject } from 'rxjs'
// Paging configuration
export interface PaginationConfig {
// Current page number
pageIndex: number;
/ / the total number of
total: number;
// The number of pages to display on the currently selected page
rows: number;
// Optional number of numbers to display per pagerowsOptions? :Array<number>;
}
// Paging configure initial data
export let PaginationInitConfig: PaginationConfig = {
pageIndex: 1.total: 0.rows: 10.rowsOptions: [10.20.50]}// Table configuration
export interface TableConfig extends PaginationConfig {
// Whether to display loading effectsisLoading? : boolean;// Whether the state is half-selectedisCheckIndeterminate? : boolean;// Whether to select allisCheckAll? : boolean;// Whether to disable the checkisCheckDisable? : boolean;// No data promptnoResult? : string; }/ / headers
export interface TableHead {
titles: string[]; widths? : string[];SRC /styles/ has common table style classesclasses? : string[]; sorts? : (boolean | string)[]; }// Paging parameters
export interface PageParam {
page: number;
rows: number;
}
// Sort type
export type orderType = 'desc' | 'asc' | null | ' '
// Sort parameters
exportinterface SortParam { orderBy? : string; order? : orderType }// Base class for all tables
export class BaseTableModel<T> {
// Table configuration
tableConfig: TableConfig
// Table header configuration
tableHead: TableHead
// Table data flow
tableData$: BehaviorSubject<T[]>
// Sort type
orderType: orderType
// The current sort identifier
currentSortBy: string
constructor(// selected key private checkKey: string = 'isChecked', // disabledKey private disabledKey: string = 'isDisabled') {this.initData()
}
// Reset data
public initData(): void {
this.tableHead = {
titles: []}this.tableConfig = {
pageIndex: 1.total: 0.rows: 10.rowsOptions: [10.20.50].isLoading: false.isCheckIndeterminate: false.isCheckAll: false.isCheckDisable: false.noResult: 'No data at present'
}
this.tableData$ = new BehaviorSubject([])
}
/** * Set table configuration * @author gr-05 * @param conf */
setConfig(conf: TableConfig): void {
this.tableConfig = Object.assign(this.tableConfig, conf)
}
/** * Set table header titles * @author gr-05 * @param titles */
setHeadTitles(titles: string[]): void {
this.tableHead.titles = titles
}
/** * Set table header width * @author gr-05 * @param widths */
setHeadWidths(widths: string[]): void {
this.tableHead.widths = widths
}
/** * Set table header style class * @author gr-05 * @param classes */
setHeadClasses(classes: string[]): void {
this.tableHead.classes = classes
}
/** * Sets table sorts * @author gr-05 * @param Sorts */
setHeadSorts(sorts: (boolean | string)[]): void {
this.tableHead.sorts = sorts
}
/** * Set the current sort type * @param ot */
setSortType(ot: orderType) {
this.orderType = ot
}
/** * sets the current sort identifier * @param orderBy */
setSortBy(orderBy: string) {
this.currentSortBy = orderBy
}
/** * sets the currently clicked sort identifier * @param I sort array index */
sortByClick(i: number) {
if (this.tableHead.sorts && this.tableHead.sorts[i]) {
if (!this.orderType) {
this.orderType = 'desc'
} else {
this.orderType == 'desc' ? this.orderType = 'asc' : this.orderType = 'desc'
}
this.currentSortBy = this.tableHead.sorts[i] as string
}
}
/** * gets the current sort parameter */
getCurrentSort(): SortParam {
return {
order: this.orderType,
orderBy: this.currentSortBy
}
}
/** * Set table loading * @author gr-05 * @param flag */
setLoading(flag: boolean = true) :void {
this.tableConfig.isLoading = flag
}
/** * Set the total number of current table data * @author gr-05 * @param total */
setTotal(total: number): void {
this.tableConfig.total = total
}
setPageAndRows(pageIndex: number, rows: number = 10) {
this.tableConfig.pageIndex = pageIndex
this.tableConfig.rows = rows
}
/** * Update table dataList * @author gr-05 * @param dataList */
setDataList(dataList: T[]): void {
this.tableConfig.isCheckAll = false
this.tableConfig.isCheckIndeterminate = dataList.filter(item= >! item[this.disabledKey]).some(item= > item[this.checkKey] == true)
this.tableConfig.isCheckAll = dataList.filter(item= >! item[this.disabledKey]).every(item= > item[this.checkKey] == true)
this.tableConfig.isCheckAll ? this.tableConfig.isCheckIndeterminate = false : {}
this.tableData$.next(dataList);
if (dataList.length == 0) {
this.tableConfig.isCheckAll = false}}/**
* 获取已选的项
* @author GR-05
*/
getCheckItem(): T[] {
return this.tableData$.value.filter(item= > item[this.checkKey] == true && !item[this.disabledKey])
}
}
Copy the code
The main reason why we do not separate components into a class like data model is that we are not sure of the unique style of components, but the data and processing logic are similar. Where we need to use them, we can just create a new one in the component.
Where the T after BaseTableModel can be any model class you want to render on the table, such as ProductModel, page requirements need to display the table list of products, then
export class TableModel extends BaseTableModel<ProductModel> {
constructor() { super(); }}Copy the code
In the end, you just need to make the tableData$data next from the BaseTableModel into a ProdcuModel array.