在前端中理解MVC服务之 Angular篇(完结)

2020-04-07 15:39:54 浏览数 (1)

介绍

本文是该系列中的第三篇,旨在了解 MVC 体系结构如何创建前端应用程序。目的是了解如何构建前端应用程序。这是通过从使用 JavaScript 作为脚本语言的网页演变为使用 JavaScript/TypeScript 作为面向对象语言的应用程序来实现的。在第三篇文章中,应用程序将使用 Angular 构建,该版本来自TypeScript 的第二个版本。因此,本文介绍应用程序从 TypeScript 到Angular的迁移。但是,了解应用的所有部分如何相关联以及其结构方式非常重要。角度允许我们忘记DOM,所以,让user.view.ts文件从我们的应用中消失。最后,在最后一篇文章中,我们将转换代码以将其与 Angular 框架集成。

  • 第 1 部分。了解前端的 MVC 服务:VanillaJS 点击直达
  • 第 2 部分。了解前端的 MVC 服务:TypeScript 点击直达
  • 第 3 部分。了解前端的 MVC 服务:Angular 点击直达

项目架构

什么是MVC架构?

MVC 架构是一个具有三个层/部分的体系

  • Model -管理应用的数据,这些模型将是不可见的,因为它们将被引用于服务。
  • View 模型的直观表示,即用户所看到的部分
  • Controller - Model与View中的链接

下图是我们的项目结构

该文件将充当一个画布,使用 元素动态构建整个应用程序。最后,我们的文件架构由以下JavaScript文件组成:

  • user.model.ts —用户的属性(模型)
  • user.service.ts —管理用户的所有操作
  • users.component.ts 负责加入Service和View的部分
  • users.component.html —负责刷新和更改显示屏幕

应用模块如下所示:

代码语言:javascript复制
import { FormsModule, ReactiveFormsModule } from '@angular/forms';

import { AppComponent } from './app.component';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { UserService } from './shared/services/user.service';
import { UsersComponent } from './views/users/users.component';

@NgModule({
  declarations: [AppComponent, UsersComponent],
  imports: [BrowserModule, FormsModule, ReactiveFormsModule],
  providers: [UserService],
  bootstrap: [AppComponent]
})
export class AppModule {}

我们可以看到,在我们的应用中有三个模块:BrowserModuleFormsModulereactiveFormsModule,第一个模块用于从 Angular 获取基本结构和属性指令,而第二个和第三个模块用于创建窗体。但在此示例中,我们的目标是向您展示从 JavaScript 到 Angular 的演化过程。

Models (贫血模式)

此示例中的第一个生成类是应用程序模型,user.model.ts由类属性和生成随机 D 的私有方法(这些代码可能来自服务器中的数据库)。模型将具有以下字段:

  • id 唯一值
  • name 用户名
  • age 用户年龄
  • complete bool值,可以知道此条数据是否有用

用户的Class已经被写在TS中。不管怎么样,该对象从Localstorage中构建一个接受一个普通对象,该对象将会提供数据。此纯对象必须符合接口,以便任何纯对象都不能实例化,而是满足定义的接口对象。 user.model.ts如下列代码所示:

代码语言:javascript复制
export interface UserDto {
  name: string;
  age: string;
  complete: boolean;
}

export class User {
  public id: string;
  public name: string;
  public age: string;
  public complete: boolean;

  constructor(
    { name, age, complete }: UserDto = {
      name: null,
      age: null,
      complete: false
    }
  ) {
    this.id = this.uuidv4();
    this.name = name;
    this.age = age;
    this.complete = complete;
  }

  uuidv4(): string {
    return (([1e7] as any)   -1e3   -4e3   -8e3   -1e11).replace(
      /[018]/g,
      (c: number) =>
        (
          c ^
          (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
        ).toString(16)
    );
  }
}

Service

对用户执行的操作在Service中执行。该服务允许Model贫血化,因为所有的逻辑负载都在其中。在此特定情况下,我们将使用数组来存储所有用户,并生成与读取、修改、创建和删除 (CRUD) 用户关联的四种方法。您应该注意,Service使用Model,将从Localstarage中提取的对象实例化到 。这是因为Localstarage只存储数据,而不是存储数据的原型。从后端到前端的数据也是如此:它们没有实例化其Class.

我们Class的构造函数如下:

代码语言:javascript复制
constructor() {
  const users: UserDto[] = JSON.parse(localStorage.getItem('users')) || [];
  this.users = users.map(user => new User(user));
}

我们定义了一个名为"类变量"的类变量,该变量在所有用户从纯对象转换为Class的原型对象后存储它们。

在服务中我们必须定义的下一件事是我们想要开发的每个操作。使用 TypeScript 如下所示:

代码语言:javascript复制
add(user: User) {
    this.users.push(new User(user));
    this._commit(this.users);
  }

  edit(userID: string, userToEdit: User) {
    this.users = this.users.map(user =>
      user.id === userID
        ? new User({
            ...user,
            ...userToEdit
          })
        : user
    );

    this._commit(this.users);
  }

  delete(userID: string) {
    this.users = this.users.filter(({ id }) => id !== userID);
    this._commit(this.users);
  }

  toggle(userID: string) {
    this.users = this.users.map(user =>
      user.id === userID
        ? new User({ ...user, complete: !user.complete })
        : user
    );

    this._commit(this.users);
  }

这个负责存储在Localstarage中的方法仍然有待定义:

代码语言:javascript复制
_commit(users: User[]) {
  localStorage.setItem('users', JSON.stringify(users));
}

此方法不会调用创建服务时绑定的函数,在 JavaScript 或 TypeScript 中开发时callback是必需的,因为 Angular 执行此任务,在Cont和Model之间执行绑定。 user.service.ts 文件如下所示:

代码语言:javascript复制
import { User, UserDto } from "../models/user.model";

export class UserService {
  public users: User[];

  constructor() {
    const users: UserDto[] = JSON.parse(localStorage.getItem("users")) || [];
    this.users = users.map(user => new User(user));
  }

  _commit(users: User[]) {
    localStorage.setItem("users", JSON.stringify(users));
  }

  add(user: User) {
    this.users.push(new User(user));
    this._commit(this.users);
  }

  edit(userID: string, userToEdit: User) {
    this.users = this.users.map(user =>
      user.id === userID
        ? new User({
            ...user,
            ...userToEdit
          })
        : user
    );

    this._commit(this.users);
  }

  delete(userID: string) {
    this.users = this.users.filter(({ id }) => id !== userID);
    this._commit(this.users);
  }

  toggle(userID: string) {
    this.users = this.users.map(user =>
      user.id === userID
        ? new User({ ...user, complete: !user.complete })
        : user
    );

    this._commit(this.users);
  }
}

Views

这个部分与前两篇文章相比,是变化最大的一部分,在这种情况之下,我们不需要使用DOM,因为Angular将执行动态操作 DOM 的艰巨任务。但是,我们必须正确定义模板。

下面是为此示例创建的模板(一个角度丰富的 HTML 版本):

代码语言:javascript复制
<h1>Users</h1>

<form [formGroup]="userForm" (ngSubmit)="add(userForm.value)">
  <input
    type="text"
    placeholder="Name"
    name="name"
    formControlName="name"
  /><input
    type="text"
    placeholder="Age"
    name="age"
    formControlName="age"
  /><button>Submit</button>
</form>
<ul class="user-list">
  <li *ngFor="let user of users">
    <input type="checkbox" (change)="toggle(user)" [checked]="user.complete" />
    <span>
      <s *ngIf="user.complete; else uncompleteName">{{ user.name }}</s>
      <ng-template #uncompleteName>{{ user.name }}</ng-template>
    </span>
    <span
      #age
      contenteditable="true"
      class="editable"
      (focusout)="edit(user, age)"
    >
      <s *ngIf="user.complete; else uncompleteAge">{{ user.age }}</s>
      <ng-template #uncompleteAge>{{ user.age }}</ng-template></span
    >
    <button class="delete" (click)="delete(user)">Delete</button>
  </li>
</ul>

这不是一个Angular教程,而是一系列的变化,你可以看到Web应用程序从JavaScript到TypeScript到Angular的演变。

但是,我们注意到,前几部分中的许多 DOM 操作代码已通过 Angular 得到解决,它们提供了两个结构指令,如 @ ngFor 和 _ ngIf,它们允许从模板本身轻松操作 DOM。

另一个有趣的点是,Angular 在此示例中帮助我们使用反应形式。有了这些,模板连接到控制器,而无需我们发送处理程序来建立连接。

Controller

此体系结构的最后一个文件则是Controller(user.controller.ts)。Controller将通过依赖注入(DI)接收其具有的两个依赖项(ServiceformBuilder).这些依赖项将存储在Controller中的私有变量。

Controller仅侧重于管理连接到View(模板)的属性并调用Service。与我们的第一个 JavaScript 代码或前几篇文章的第二个 TypeScript 版本完全一样。在这种情况下,我们离开了框架与 DOM 关联的所有任务。这是users.component.ts 文件:

代码语言:javascript复制
import { Component, OnInit } from '@angular/core';

import { FormBuilder } from '@angular/forms';
import { UserService } from 'src/app/shared/services/user.service';

@Component({
  selector: 'app-users',
  templateUrl: './users.component.html',
  styleUrls: ['./users.component.css']
})
export class UsersComponent implements OnInit {
  public users;
  public userForm;

  constructor(
    private userService: UserService,
    private formBuilder: FormBuilder
  ) {
    this.userForm = this.formBuilder.group({
      name: '',
      age: ''
    });
  }

  ngOnInit() {
    this.refreshUsers();
  }
  refreshUsers() {
    this.users = this.userService.users;
  }

  add(userForm) {
    this.userService.add(userForm);
    this.refreshUsers();
    this.userForm.reset();
  }
  delete({ id }) {
    this.userService.delete(id);
    this.refreshUsers();
  }
  edit(user, { innerText: age }) {
    const { id } = user;
    this.userService.edit(id, { ...user, age });
    this.refreshUsers();
  }
  toggle({ id }) {
    this.userService.toggle(id);
    this.refreshUsers();
  }
}

总结

在第三部分中,我们开发了一个 Web 应用程序,其中项目的结构遵循 MVC 体系结构,其中使用了贫血模型,逻辑的责任在于Service

需要强调的是,这篇文章的要点是,让你了解不同文件中具有不同功能的项目结构,以及View如何完全独立于Model/Service和Controller。

还必须注意的是,在本文中,我们将应用程序从 TypeScript 迁移到了 Angular,让我们忘记了那些与我们开发的所有 Web 应用程序都相同的重复任务。

我建议你从第一篇与JavaScript相关的帖子开始,了解所使用的体系结构。下一步是通过应用 TypeScript(在第二篇文章中)来强化代码,最后查看此文章中的代码已适应框架。

本文原文来自Medium 本文仅做翻译。 原作者:Carlos Caballero 如需转载,请注明原创

0 人点赞