在Angular中处理数据

大家好,我叫Sergey,我是网络开发人员。 原谅德米特里·卡洛夫斯基Dmitry Karlovsky)的借用介绍,但正是他的出版物启发了我写这篇文章。


今天,我想谈谈在Angular应用程序中,特别是在领域模型中使用数据的问题。


假设我们有一个以表格形式从服务器接收的用户列表


[ { "id": 1, "first_name": "James", "last_name": "Hetfield", "position": "Web developer" }, { "id": 2, "first_name": "Elvis", "last_name": "", "position": "Project manager" }, { "id": 3, "first_name": "Steve", "last_name": "Vai", "position": "QA engineer" } ] 

您需要将其显示为图片中的


用户名单


看起来很简单-试试吧。 当然,获得这个名单中,我们将服务UserService以下方式。 请注意,指向用户头像的链接不会立即出现在响应中,而是基于用户id形成的。


 // UserService import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; import {UserServerResponse} from './user-server-response.interface'; @Injectable() export class UserService { constructor(private http: HttpClient) { } getUsers(): Observable<UserServerResponse[]> { return this.http.get<UserServerResponse[]>('/users'); } getUserAvatar(userId: number): string { return `/users/${userId}/avatar`; } } 

UserListComponent组件将负责显示用户列表。


 // UserListComponent import {Component} from '@angular/core'; import {UserService} from '../services/user.service'; @Component({ selector: 'app-user-list', template: ` <div *ngFor="let user of users | async"> <img [src]="userService.getUserAvatar(user.id)"> <p><b>{{user.first_name}} {{user.last_name}}</b>, {{user.position}}</p> </div> ` }) export class UserListComponent { users = this.userService.getUsers(); constructor(public userService: UserService) { } } 

在这里,我们已经有了一个确定的问题 。 注意服务器响应。 last_name字段可能为空,如果我们以这种形式保留组件,则在逗号前会收到不需要的空格。 解决方案有哪些?


  1. 您可以稍微更正显示模板


     <p> <b>{{[user.first_name, user.last_name].filter(el => !!el).join(' ')}}</b>, {{user.position}} </p> 

    但是通过这种方式,我们使模板超载了逻辑,即使对于这样一个简单的任务,其可读性也很差。 但是应用程序仍然必须不断增长...


  2. 通过添加类型方法将代码从模板拉入组件类


     getUserFullName(user: UserServerResponse): string { return [user.first_name, user.last_name].filter(el => !!el).join(' '); } 

    更好,但完整的用户名很可能不会显示在应用程序的某个位置,因此我们将不得不重复此代码。 您可以从组件到服务采用这种方法。 这样,我们将消除可能的代码重复,但是我也不太喜欢该选项。 我不喜欢它,因为事实证明,一些更通用的实体( UserService )应该知道传递给它的较小User实体的结构。 在我看来,这不是她的责任等级。



在我看来,该问题主要是由于我们仅将服务器响应视为数据集这一事实引起的。 尽管实际上,这是我们应用程序主题领域中的实体的列表-用户列表。 而且,如果我们正在谈论使用实体,那么值得为此使用最合适的工具-面向对象编程方法。


让我们从创建User类开始


 // User export class User { readonly id; readonly firstName; readonly lastName; readonly position; constructor(userData: UserServerResponse) { this.id = userData.id; this.firstName = userData.first_name; this.lastName = userData.last_name; this.position = userData.position; } fullName(): string { return [this.firstName, this.lastName].filter(el => !!el).join(' '); } avatar(): string { return `/users/${this.id}/avatar`; } } 

类构造函数是服务器响应反序列化器。 用于确定完整用户名的逻辑自然地变为User类的对象的方法,以及用于获得化身的逻辑。 现在,我们将重新制作UserService以便作为处理服务器响应的结果返回User类的对象


 // UserService import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {map} from 'rxjs/operators'; import {UserServerResponse} from './user-server-response.interface'; import {User} from './user.model'; @Injectable() export class UserService { constructor(private http: HttpClient) { } getUsers(): Observable<User[]> { return this.http.get<UserServerResponse[]>('/users') .pipe(map(listOfUsers => listOfUsers.map(singleUser => new User(singleUser)))); } } 

结果,我们组件的代码变得更加清晰和易读。 可以称为业务逻辑的所有内容都封装在模型中,并且可以完全重用。


 import {Component} from '@angular/core'; import {UserService} from '../services/user.service'; @Component({ selector: 'app-user-list', template: ` <div *ngFor="let user of users | async"> <img [src]="user.avatar()"> <p><b>{{user.fullName()}}</b>, {{user.position}}</p> </div> ` }) export class UserListComponent { users = this.userService.getUsers(); constructor(private userService: UserService) { } } 

现在让我们扩展模型的功能。 从理论上讲(在这种情况下,我喜欢使用ActiveRecord模式进行类比),用户模型对象不仅应负责获取有关其自身的数据,而且还应负责更改它们。 例如,我们也许可以更改用户的个人资料图片。 用这种功能扩展的用户模型将是什么样?


 // User export class User { // ... constructor(userData: UserServerResponse, private http: HttpClient, private storage: StorageService, private auth: AuthService) { // ... } // ... updateAvatar(file: Blob) { const data = new FormData(); data.append('avatar', file); return this.http.put(`/users/${this.id}/avatar`, data); } } 

看起来不错,但该模型User正在使用该服务HttpClient和,一般来说,它可以很好地连接,并使用各种其他服务-在这种情况下StorageServiceAuthService (他们不习惯,只是添加为例)。 事实证明,如果要在其他服务或组件中使用User模型,则必须连接与其关联的所有服务才能创建该模型的对象。 看起来非常不便。您可以使用Injector服务(当然,它也必须实现,但可以保证只有一个),甚至可以创建不必实现的外部注射器实体 ,但是我看到了将User类对象的创建委托给UserService服务的更正确方法他负责获取用户列表。


 // UserService import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {UserServerResponse} from './user-server-response.interface'; import {User} from './user.model'; @Injectable() export class UserService { constructor(private http: HttpClient, private storage: StorageService, private auth: AuthService) { } createUser(userData: UserServerResponse) { return new User(userData, this.http, this.storage, this.auth); } getUsers(): Observable<User[]> { return this.http.get<UserServerResponse[]>('/users') .pipe(map(listOfUsers => listOfUsers.map(singleUser => this.createUser(singleUser)))); } } 

因此,我们将用户创建方法移至UserService ,现在更适合调用工厂,并将所有实现依赖项的工作转移到Angular的肩膀上-我们只需要在构造函数中连接UserService


最后,让我们从方法名称中删除重复项,并为注入的依赖项的名称引入约定。 在我看来,该服务的最终版本应如下所示。


 import {Injectable} from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {Observable} from 'rxjs'; import {map} from 'rxjs/operators'; import {UserServerResponse} from './user-server-response.interface'; import {User} from './user.model'; @Injectable() export class UserFactory { constructor(private http: HttpClient, private storage: StorageService, private auth: AuthService) { } create(userData: UserServerResponse) { return new User(userData, this.http, this.storage, this.auth); } list(): Observable<User[]> { return this.http.get<UserServerResponse[]>('/users') .pipe(map(listOfUsers => listOfUsers.map(singleUser => this.create(singleUser)))); } } 

并建议以UserUserFactory实现


 import { Component } from '@angular/core'; import {HttpClient} from '@angular/common/http'; import {UserFactory} from './services/user.service'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'app'; users = this.User.list(); constructor(private User: UserFactory) { } } 

在这种情况下, UserFactory类的对象看起来像User类,具有用于获取用户列表的静态方法和用于创建新实体的特殊方法,并且其对象包含与特定实体关联的所有必需的业务逻辑方法。


在此,我告诉了我想要的一切。 我期待在评论中进行讨论。


更新资料


我想对所有发表评论的人表示感谢。 您正确地指出,要解决显示名称的问题,值得使用Pipe 。 我完全同意并感到惊讶,为什么我没有做出这个决定。 尽管如此,本文的主要目标是显示一个创建域模型(在本例中为User )的示例,该模型可以方便地封装与其本质相关的所有业务逻辑。 同时,我尝试解决依赖注入带来的问题。

Source: https://habr.com/ru/post/zh-CN418463/


All Articles