angular mvc
When designing software with a user interface, it is important to structure the code in a way that makes it easy to extend and maintain. Over time, there have been a few approaches in separating out responsibilities of the different components of an application. Although there is plenty of literature on these design patterns around, it can be very confusing for a beginner to understand the features of limitations of the different patterns and the differences between them.
在设计带有用户界面的软件时,以易于扩展和维护的方式构造代码非常重要。 随着时间的流逝,已经有几种方法可以分离出应用程序不同组件的职责。 尽管周围有很多关于这些设计模式的文献,但是对于初学者来说,理解不同模式的局限性以及它们之间的差异可能会非常令人困惑。
In this tutorial, I want to talk about the major two approaches, the Model-View-Controller (MVC) pattern and the Model-View-ViewModel (MVVM) pattern. In the MVVM pattern, the controller is replaced by a ViewModel. The main differences between these two components are the direction of dependency between the View on one side, and the Controller or ViewModel on the other side.
在本教程中,我想谈谈两种主要方法,即Model-View-Controller(MVC)模式和Model-View-ViewModel(MVVM)模式。 在MVVM模式中,控制器由ViewModel代替。 这两个组件之间的主要区别是一侧的View与另一侧的Controller或ViewModel之间的依赖方向。
I will be developing the ideas and explaining the patterns by example using a browser application written in TypeScript and Angular. TypeScript is an extension of JavaScript that adds type information to the code. The application will mimic the popular Notes application on MacOS/iOS. Angular enforces the MVVM pattern. Let’s dive in and see the main differences between the MVC and the MVVM patterns.
我将使用TypeScript和Angular编写的浏览器应用程序,通过示例开发思想并解释模式。 TypeScript是JavaScript的扩展,可将类型信息添加到代码中。 该应用程序将模仿MacOS / iOS上流行的Notes应用程序。 Angular实施MVVM模式。 让我们深入了解MVC和MVVM模式之间的主要区别。
To start off you will need to install Angular CLI. Make sure you have Node and npm
installed first. If you haven’t done so, visit node.js.org and follow the instructions to download and install Node. Then, open a terminal on your computer and run the npm
command to install Angular CLI.
首先,您需要安装Angular CLI。 确保首先安装了Node和npm
。 如果尚未执行此操作,请访问node.js.org并按照说明下载并安装Node。 然后,在计算机上打开终端并运行npm
命令以安装Angular CLI。
npm install -g @angular/cli@7.2.1
Depending on your system configuration, you may have to run this command as the system administrator using sudo
. This will install the ng
command globally on your system. ng
is used to create, manipulate, test, and build Angular applications. You can create a new Angular application by running ng new
in a directory of your choice.
根据您的系统配置,您可能必须使用sudo
以系统管理员身份运行此命令。 这将在系统上全局安装ng
命令。 ng
用于创建,操作,测试和构建Angular应用程序。 您可以通过在所选目录中运行ng new
来创建新的Angular应用程序。
ng new AngularNotes
This will start a wizard that takes you through a couple of questions about the new application and then creates the directory layout and some files with skeleton code. The first question regards the inclusion of the routing module. Routing lets you navigate to different components in the application by changing the browser path. You will need to answer yes to this question. The second question lets you choose the CSS technology which you want to use. Because I will only include some very simple style sheets, the plain CSS format will be sufficient. When you have answered the questions, the wizard will start downloading and installing all the necessary components.
这将启动一个向导,该向导将引导您解决有关新应用程序的几个问题,然后创建目录布局和带有框架代码的一些文件。 第一个问题涉及路由模块的包含。 路由使您可以通过更改浏览器路径导航到应用程序中的不同组件。 您将需要对这个问题回答是 。 第二个问题让您选择要使用CSS技术。 因为我将只包括一些非常简单的样式表,所以纯CSS格式就足够了。 回答问题后,向导将开始下载和安装所有必需的组件。
You can use Material Design and its components to make the application look nice. These can be installed by using the npm
command inside the application directory. The ng new
command should have created a directory called AngularNotes
. Navigate into that and run the following command.
您可以使用Material Design及其组件来使应用程序看起来不错。 可以使用应用程序目录中的npm
命令安装这些文件。 ng new
命令应该已经创建了一个名为AngularNotes
的目录。 导航到该目录并运行以下命令。
npm install --save @angular/material@7.2.1 @angular/cdk@7.2.1 @angular/animations@7.2.0 @angular/flex-layout@7.0.0-beta.23
The src
directory contains the application source code. Here, src/index.html
is the main entry point for the browser. Open this file in a text editor of your choice and paste the following line into the <head>
section. This will load the font needed for the Material Icons.
src
目录包含应用程序源代码。 在这里, src/index.html
是浏览器的主要入口点。 在您选择的文本编辑器中打开此文件,并将以下行粘贴到<head>
部分。 这将加载“材质图标”所需的字体。
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
The src/style.css
style sheet contains global styles. Open this file and paste the following styles into it.
src/style.css
样式表包含全局样式。 打开此文件,然后将以下样式粘贴到其中。
@import "~@angular/material/prebuilt-themes/deeppurple-amber.css";
body {
margin: 0;
font-family: sans-serif;
}
h1, h2 {
text-align: center;
}
Next, open src/app/app.module.ts
. This file contains the imports for all the modules that you want to be globally available. Replace to contents of this file with the following code.
接下来,打开src/app/app.module.ts
。 此文件包含要全局可用的所有模块的导入。 用以下代码替换此文件的内容。
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FlexLayoutModule } from "@angular/flex-layout";
import { MatToolbarModule,
MatMenuModule,
MatIconModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatListModule,
MatDividerModule } from '@angular/material';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FlexLayoutModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
MatMenuModule,
MatIconModule,
MatInputModule,
MatFormFieldModule,
MatButtonModule,
MatListModule,
MatDividerModule,
AppRoutingModule,
],
bootstrap: [AppComponent]
})
export class AppModule { }
At this point, I could start showing you how to create the application layout in the file src/app/app.component.html
. But this would already have me dive into the discussion of the application architecture. Instead, in the next section, I want to first guide you through the implementation of the Model. I will be discussing the View and its relation to the ViewModel in the following section.
此时,我可以开始向您展示如何在文件src/app/app.component.html
创建应用程序布局。 但是,这已经使我投入了对应用程序体系结构的讨论。 相反,在下一节中,我想首先指导您完成模型的实现。 在下一节中,我将讨论View及其与ViewModel的关系。
The model contains the business end of your application. For simple CRUD (Create Read Update Delete) applications, the model is usually a simple data model. For more complex applications, the model will naturally reflect that increase in complexity. In the application you see here, the model will hold a simple array of text notes. Each note has an ID, a title, and a text. In Angular, the model is coded up in so-called services. The ng
command lets you create a new service.
该模型包含您的应用程序的业务端。 对于简单的CRUD(创建读取更新删除)应用程序,该模型通常是简单的数据模型。 对于更复杂的应用程序,模型自然会反映出复杂性的增加。 在您在此处看到的应用程序中,模型将包含一个简单的文本注释数组。 每个便笺都有一个ID ,一个标题和一个文本 。 在Angular中,模型被编码在所谓的services中 。 ng
命令可让您创建新服务。
ng generate service Notes
This will create two new files, src/app/notes.service.ts
and src/app/notes.service.spec.ts
. You can ignore the second of these files in this tutorial, just as the other .spec.ts
files. These files are used for unit testing the code. In an application that you want to release for production, you would write your tests there. Open src/app/notes.service.ts
and replace its contents with the following code.
这将创建两个新文件src/app/notes.service.ts
和src/app/notes.service.spec.ts
。 与其他.spec.ts
文件一样,您可以在本教程中忽略其中的第二个文件。 这些文件用于对代码进行单元测试。 在要发布用于生产的应用程序中,您可以在其中编写测试。 打开src/app/notes.service.ts
并将其内容替换为以下代码。
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observer } from 'rxjs';
export class NoteInfo {
id: number;
title: string;
}
export class Note {
id: number;
title: string;
text: string;
}
@Injectable({
providedIn: 'root'
})
export class NotesService {
private notes: Note[];
private nextId = 0;
private notesSubject = new BehaviorSubject<NoteInfo[]>([]);
constructor() {
this.notes = JSON.parse(localStorage.getItem('notes')) || [];
for (const note of this.notes) {
if (note.id >= this.nextId) this.nextId = note.id+1;
}
this.update();
}
subscribe(observer: Observer<NoteInfo[]>) {
this.notesSubject.subscribe(observer);
}
addNote(title: string, text: string): Note {
const note = {id: this.nextId++, title, text};
this.notes.push(note);
this.update();
return note;
}
getNote(id: number): Note {
const index = this.findIndex(id);
return this.notes[index];
}
updateNote(id: number, title: string, text: string) {
const index = this.findIndex(id);
this.notes[index] = {id, title, text};
this.update();
}
deleteNote(id: number) {
const index = this.findIndex(id);
this.notes.splice(index, 1);
this.update();
}
private update() {
localStorage.setItem('notes', JSON.stringify(this.notes));
this.notesSubject.next(this.notes.map(
note => ({id: note.id, title: note.title})
));
}
private findIndex(id: number): number {
for (let i=0; i<this.notes.length; i++) {
if (this.notes[i].id === id) return i;
}
throw new Error(`Note with id ${id} was not found!`);
}
}
Near the top of the file you can see two class definitions, NoteInfo
and Note
. The Note
class contains the full information on a note, while NoteInfo
only contains the id
and the title
. The idea is that NoteInfo
is much lighter and can be used in a list, displaying all note titles. Both Note
and NoteInfo
are simple data classes, containing no business logic. The logic is contained in NotesService
, which acts as the Model of the application. It contains a number of properties. The notes
property is an array of Notes
objects. This array acts as the source of truth for the model. The functions addNote
, getNote
, updateNote
, and deleteNote
define the CRUD operations on the model. They all directly act on the notes
array, creating, reading, updating, and deleting elements in the array. The nextId
property is used as a unique ID by which a note can be referenced.
在文件顶部附近,您可以看到两个类定义, NoteInfo
和Note
。 Note
类包含有关Note
的完整信息,而NoteInfo
仅包含id
和title
。 这个想法是NoteInfo
得多,可以在列表中使用,显示所有注释标题。 Note
和NoteInfo
都是简单的数据类,不包含任何业务逻辑。 逻辑包含在NotesService
,该逻辑充当应用程序的模型。 它包含许多属性。 notes
属性是Notes
对象的数组。 此数组充当模型真相的来源。 函数addNote
, getNote
, updateNote
和deleteNote
定义模型上的CRUD操作。 它们都直接作用于notes
数组,在数组中创建,读取,更新和删除元素。 nextId
属性用作唯一ID,可以通过该ID来引用注释。
You will notice that, whenever the notes
array is modified, the private update
method is called. This method does two things. First, it saves the notes in the local storage. As long as the browser’s local storage has not been deleted, this will persist the data locally. This allows users to close the application and open it later on and still have access to their notes. In a real-world application, the CRUD operations would access a REST API on a different server, instead of saving the data locally.
您会注意到,每当修改notes
数组时,都会调用private update
方法。 此方法有两件事。 首先,它将注释保存在本地存储中。 只要未删除浏览器的本地存储,这将在本地保留数据。 这使用户可以关闭应用程序并稍后将其打开,并且仍然可以访问其笔记。 在实际应用程序中,CRUD操作将访问其他服务器上的REST API,而不是在本地保存数据。
The second action performed by update
is to emit a new value on the notesSubject
property. notesSubject
is a BehaviorSubject
from RxJS which contains an array of the condensed NoteInfo
objects. The BehaviorSubject
act as an observable to which any observer can subscribe. This subscription is made possible through the subscribe
method of NotesService
. Any observer that has subscribed will be notified whenever update
is called.
update
执行的第二个操作是在notesSubject
属性上发出新值。 notesSubject
是notesSubject
的BehaviorSubject
,其中包含精简NoteInfo
对象的数组。 BehaviorSubject
充当可观察者,任何观察者都可以订阅。 通过NotesService
方法可以进行此subscribe
。 每当调用update
时,将通知已订阅的任何观察者。
The main thing to take away from the implementation of the Model is, that the Model is a standalone service that has no knowledge of any View or Controller. This is important in both, the MVC and the MVVM architecture. The Model must not have any dependency on the other components.
从模型的实现中获得的主要好处是,模型是一个独立的服务,不了解任何View或Controller。 这对于MVC和MVVM体系结构都很重要。 该模型不得与其他组件有任何依赖关系。
Next, I’d like to turn your attention to the View. In Angular applications, the View lives inside the .html
templates and the .css
style sheets. I have already mentioned one of these templates in the file src/app/app.component.html
. Open the file and paste the following content into it.
接下来,我想将您的注意力转向“视图”。 在Angular应用程序中,视图位于.html
模板和.css
样式表中。 我已经在文件src/app/app.component.html
提到了这些模板之一。 打开文件并将以下内容粘贴到其中。
<mat-toolbar color="primary" class="expanded-toolbar">
<span>
<button mat-button routerLink="/">{{title}}</button>
<button mat-button routerLink="/"><mat-icon>home</mat-icon></button>
</span>
<button mat-button routerLink="/notes"><mat-icon>note</mat-icon></button>
</mat-toolbar>
<router-outlet></router-outlet>
Why not add a bit of styling too? Open src/app/app.component.css
and add the following style.
为什么不添加一些样式呢? 打开src/app/app.component.css
并添加以下样式。
.expanded-toolbar {
justify-content: space-between;
align-items: center;
}
The app.component
contains the main page layout, but not any meaningful content. You will have to add some components that will render any content. Use the ng generate
command again like this.
app.component
包含主页布局,但不包含任何有意义的内容。 您将不得不添加一些将呈现任何内容的组件。 像这样再次使用ng generate
命令。
ng generate component Home
ng generate component Notes
This generates two components. Each component is made up of a .html
, .css
, and a .ts
file. For now, don’t worry about the .ts
file. I’ll get to that in the next section. (Remember, there is also a .spec.ts
file that I am ignoring completely in this tutorial.)
这将生成两个组件。 每个组件.html
, .css
和.ts
文件组成。 现在,不必担心.ts
文件。 我将在下一节中介绍。 (请记住,在本教程中,我也完全忽略了一个.spec.ts
文件。)
Open src/app/home/home.component.html
and change the content to the following.
打开src/app/home/home.component.html
并将内容更改为以下内容。
<h1>Angular Notes</h1>
<h2>A simple app showcasing the MVVM pattern.</h2>
Next, open src/app/notes/notes.component.html
and replace the content with the code below.
接下来,打开src/app/notes/notes.component.html
并将内容替换为下面的代码。
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="notes">
<mat-list fxFlex="100%" fxFlex.gt-sm="20%">
<mat-list-item *ngFor='let note of notes'>
<a>
{{note.title}}
</a>
</mat-list-item>
</mat-list>
<mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider>
<mat-divider fxShow="true" fxShow.gt-sm="false" [vertical]="false"></mat-divider>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="!editNote" class="note-container">
<h3>{{currentNote.title}}</h3>
<p>
{{currentNote.text}}
</p>
<div fxLayout="row" fxLayoutAlign="space-between center" >
<button mat-raised-button color="primary">Edit</button>
<button mat-raised-button color="warn">Delete</button>
<button mat-raised-button color="primary">New Note</button>
</div>
</div>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="editNote" class="form-container">
<form [formGroup]="editNoteForm">
<mat-form-field class="full-width">
<input matInput placeholder="Title" formControlName="title">
</mat-form-field>
<mat-form-field class="full-width">
<textarea matInput placeholder="Note text" formControlName="text"></textarea>
</mat-form-field>
<button mat-raised-button color="primary">Update</button>
</form>
</div>
</div>
The accompanying src/app/notes/notes.component.css
should look like this.
随附的src/app/notes/notes.component.css
应该看起来像这样。
.notes {
padding: 1rem;
}
.notes a {
cursor: pointer;
}
.form-container, .note-container {
padding-left: 2rem;
padding-right: 2rem;
}
.full-width {
width: 80%;
display: block;
}
So far, so good!
到目前为止,一切都很好!
Have a look at src/app/notes/notes.component.html
which represents the main View of the application. You will notice placeholders such as {{note.title}}
which look like they can be filled with values. In the version shown above, the View does not seem to refer to any piece of code in the application.
看一下src/app/notes/notes.component.html
,它代表了应用程序的主视图。 您会注意到占位符,例如{{note.title}}
,看起来可以用值填充。 在上面显示的版本中,视图似乎没有引用应用程序中的任何代码。
If you were to follow the MVC pattern, the View would define slots into which the data could be inserted. It would also provide methods for registering a callback whenever a button is clicked. In this respect, the View would remain completely ignorant of the Controller. The Controller would actively fill the values and register callback methods with the View. Only the Controller would know about both the View and the Model and link the two together.
如果要遵循MVC模式,则视图将定义可以在其中插入数据的插槽。 它还将提供单击按钮时注册回调的方法。 在这方面,视图将完全不了解Controller。 Controller将主动填充值并向View注册回调方法。 只有Controller会同时了解View和Model,并将两者链接在一起。
As you will see below, Angular takes a different approach, called the MVVM pattern. Here the Controller is replaced by a ViewModel. This will be the topic of the next section.
如下所示,Angular采用了另一种方法,称为MVVM模式。 在这里,Controller由ViewModel代替。 这将是下一节的主题。
The ViewModel lives in the .ts
files of the components. Open src/app/notes/notes.component.ts
and fill it with the code below.
ViewModel位于组件的.ts
文件中。 打开src/app/notes/notes.component.ts
并用下面的代码填充。
import { Component, OnInit } from '@angular/core';
import { Note, NoteInfo, NotesService } from '../notes.service';
import { BehaviorSubject } from 'rxjs';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-notes',
templateUrl: './notes.component.html',
styleUrls: ['./notes.component.css']
})
export class NotesComponent implements OnInit {
notes = new BehaviorSubject<NoteInfo[]>([]);
currentNote: Note = {id:-1, title: '', text:''};
createNote = false;
editNote = false;
editNoteForm: FormGroup;
constructor(private formBuilder: FormBuilder,
private notesModel: NotesService) { }
ngOnInit() {
this.notesModel.subscribe(this.notes);
this.editNoteForm = this.formBuilder.group({
title: ['', Validators.required],
text: ['', Validators.required]
});
}
onSelectNote(id: number) {
this.currentNote = this.notesModel.getNote(id);
}
noteSelected(): boolean {
return this.currentNote.id >= 0;
}
onNewNote() {
this.editNoteForm.reset();
this.createNote = true;
this.editNote = true;
}
onEditNote() {
if (this.currentNote.id < 0) return;
this.editNoteForm.get('title').setValue(this.currentNote.title);
this.editNoteForm.get('text').setValue(this.currentNote.text);
this.createNote = false;
this.editNote = true;
}
onDeleteNote() {
if (this.currentNote.id < 0) return;
this.notesModel.deleteNote(this.currentNote.id);
this.currentNote = {id:-1, title: '', text:''};
this.editNote = false;
}
updateNote() {
if (!this.editNoteForm.valid) return;
const title = this.editNoteForm.get('title').value;
const text = this.editNoteForm.get('text').value;
if (this.createNote) {
this.currentNote = this.notesModel.addNote(title, text);
} else {
const id = this.currentNote.id;
this.notesModel.updateNote(id, title, text);
this.currentNote = {id, title, text};
}
this.editNote = false;
}
}
In the @Component
decorator of the class, you can see the reference to the View .html
and .css
files. In the rest of the class, on the other hand, there is no reference to the View whatsoever. Instead, the ViewModel, contained in the NotesComponent
class, exposes properties and methods that can be accessed by the View. This means that, compared to the MVC architecture, the dependency is reversed. The ViewModel has no knowledge of the View but provides a Model-like API that can be used by the View. If you take another look at src/app/notes/notes.component.html
you can see that the template interpolation, such as {{currentNote.text}}
directly accesses the properties of the NotesComponent
.
在该类的@Component
装饰器中,您可以看到对View .html
和.css
文件的引用。 另一方面,在课程的其余部分中,均未引用View。 而是, NotesComponent
类中包含的ViewModel公开了可由View访问的属性和方法。 这意味着,与MVC架构相比,依赖性是相反的。 ViewModel不了解View,但提供了可由View使用的类似于模型的API。 如果再看一下src/app/notes/notes.component.html
您会看到模板插值(例如{{currentNote.text}}
直接访问NotesComponent
的属性。
The last step to make your application work is to tell the router which components are responsible for the different routes. Open src/app/app-routing.module.ts
and edit the content to match the code below.
使应用程序正常工作的最后一步是告诉路由器哪些组件负责不同的路由。 打开src/app/app-routing.module.ts
并编辑内容以匹配下面的代码。
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/home.component';
import { NotesComponent } from './notes/notes.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'notes', component: NotesComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
This will link the HomeComponent
to the default route and the NotesComponent
to the notes
route.
这将链接HomeComponent
到默认路由和NotesComponent
到notes
路线。
For the main application component, I will define a few methods which will be implemented later on. Open src/app/app.component.ts
and update the content to look like the following.
对于主要的应用程序组件,我将定义一些方法,这些方法将在以后实现。 打开src/app/app.component.ts
并更新内容,如下所示。
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
public title = 'Angular Notes';
public isAuthenticated: boolean;
ngOnInit() {
this.isAuthenticated = false;
}
login() {
}
logout() {
}
}
The component contains two properties title
and isAuthenticated
. The second one of these is a flag that indicates whether the user has logged into the application. Right now, it is simply set to false
. Two empty methods act as callbacks to trigger logging in or logging out. For now, I have left them empty, but you will be filling them in later on.
该组件包含两个属性title
和isAuthenticated
。 其中的第二个是一个标志,指示用户是否已登录到应用程序。 现在,它只是设置为false
。 有两个空方法用作触发登录或注销的回调。 现在,我将其保留为空,但是稍后您将填写它们。
With this knowledge about the direction of dependency, you can update the View so that the buttons and forms perform actions on the ViewModel. Open src/app/notes/notes.component.html
again and change the code to look like this.
有了有关依赖性方向的知识,您就可以更新View,以便按钮和表单在ViewModel上执行操作。 再次打开src/app/notes/notes.component.html
并将代码更改为如下所示。
<div fxLayout="row" fxLayout.xs="column" fxLayoutAlign="center" class="notes">
<mat-list fxFlex="100%" fxFlex.gt-sm="20%">
<mat-list-item *ngFor='let note of notes | async'>
<a (click)="onSelectNote(note.id)">
{{note.title}}
</a>
</mat-list-item>
</mat-list>
<mat-divider fxShow="false" fxShow.gt-sm [vertical]="true"></mat-divider>
<mat-divider fxShow="true" fxShow.gt-sm="false" [vertical]="false"></mat-divider>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="!editNote" class="note-container">
<h3>{{currentNote.title}}</h3>
<p>
{{currentNote.text}}
</p>
<div fxLayout="row" fxLayoutAlign="space-between center" >
<button mat-raised-button color="primary" (click)="onEditNote()" *ngIf="noteSelected()">Edit</button>
<button mat-raised-button color="warn" (click)="onDeleteNote()" *ngIf="noteSelected()">Delete</button>
<button mat-raised-button color="primary" (click)="onNewNote()">New Note</button>
</div>
</div>
<div fxFlex="100%" fxFlex.gt-sm="70%" *ngIf="editNote" class="form-container">
<form [formGroup]="editNoteForm" (ngSubmit)="updateNote()">
<mat-form-field class="full-width">
<input matInput placeholder="Title" formControlName="title">
</mat-form-field>
<mat-form-field class="full-width">
<textarea matInput placeholder="Note text" formControlName="text"></textarea>
</mat-form-field>
<button mat-raised-button color="primary">Update</button>
</form>
</div>
</div>
You can see (click)
handlers in various places directly referring to the methods of the NotesComponent
class. This means that the View needs to know about the ViewModel and its methods. The reason for reversing the dependency is the reduction of boilerplate code. There is a two-way data binding between the View and the ViewModel. The data in the View is always in sync with the data in the ViewModel.
您可以在各个位置直接引用NotesComponent
类的方法来查看(click)
处理程序。 这意味着View需要了解ViewModel及其方法。 反转依赖关系的原因是减少了样板代码。 View和ViewModel之间存在双向数据绑定。 视图中的数据始终与ViewModel中的数据同步。
A good application is not complete without proper user authentication. In this section, you will learn how to quickly add authentication to your existing Angular application. Okta provides single sign-on authentication which can be plugged into the app with just a few lines of code.
没有正确的用户身份验证,好的应用程序是不完整的。 在本节中,您将学习如何快速向现有的Angular应用程序添加身份验证。 Okta提供了单点登录身份验证,只需几行代码即可将其插入到应用程序中。
You will need a free developer account with Okta. Simply fill in the form that appears with your details, accept the terms & conditions, and submit it by pressing Get Started. Once you have completed the registration you will be taken to the Okta dashboard. Here you can see an overview of all the applications registered with the Okta service.
您将需要使用Okta的免费开发者帐户 。 只需填写包含您的详细信息的表格,接受条款和条件,然后按“入门”将其提交。 完成注册后,您将被带到Okta仪表板。 在这里,您可以查看向Okta服务注册的所有应用程序的概述。
Click on Add Application to register a new application. On the next screen that appears you will be given a choice of the type of application. The Single-Page Application is the right choice for your Angular app. On the page that follows, you will be shown the application settings. You will need to change the port number to 4200 when you are testing your application with ng serve
.
单击添加应用程序以注册新应用程序。 在出现的下一个屏幕上,将为您提供应用程序类型的选择。 单页应用程序是您Angular应用程序的正确选择。 在随后的页面上,将显示应用程序设置。 使用ng serve
测试应用程序时,需要将端口号更改为4200。
That’s it. Now you should be seeing a Client ID which you will need later on. Now you are ready to include the authentication service into your code. Okta provides a convenient library for Angular. You can install it by running the following command in your application root directory.
而已。 现在,您应该会看到一个以后需要的客户ID。 现在,您可以将身份验证服务包含到您的代码中了。 Okta为Angular提供了一个方便的库。 您可以通过在应用程序根目录中运行以下命令来安装它。
npm install @okta/okta-angular@1.0.7 --save
Open app.module.ts
and import the OktaAuthModule
.
打开app.module.ts
并导入OktaAuthModule
。
import { OktaAuthModule } from '@okta/okta-angular';
Further down, in the same file add the following in the list of imports
.
再往下,在同一文件中,在imports
列表中添加以下内容。
OktaAuthModule.initAuth({
issuer: 'https://{yourOktaDomain}/oauth2/default',
redirectUri: 'http://localhost:4200/implicit/callback',
clientId: '{clientId}'
})
In this snippet, {clientId}
needs to be replaced with the client ID that you just obtained in the Okta developer dashboard.
在此代码段中, {clientId}
需要替换为刚在Okta开发人员仪表板中获得的客户端ID。
To protect specific routes from being accessed without a password you need to modify src/app/app-routing.module.ts
. Add an import for OktaCallbackComponent
and OktaAuthGuard
.
要保护特定的路由免遭密码访问,您需要修改src/app/app-routing.module.ts
。 为OktaCallbackComponent
和OktaAuthGuard
添加导入。
import { OktaCallbackComponent, OktaAuthGuard } from '@okta/okta-angular';
Next, add another route to the array of routes.
接下来,将另一条路由添加到路由数组中。
{ path: 'implicit/callback', component: OktaCallbackComponent }
The implicit/callback
route will be called by Okta when the user has completed the login process. The OktaCallbackComponent
handles the result and redirects the user to the page that requested the authentication process. To guard individual routes, you can now simply add OktaAuthGuard
to that route, like this.
用户完成登录过程后,Okta将调用implicit/callback
路由。 OktaCallbackComponent
处理结果并将用户重定向到请求身份验证过程的页面。 为了保护单个路由,您现在可以像这样简单地将OktaAuthGuard
添加到该路由。
{ path: 'notes', component: NotesComponent, canActivate: [OktaAuthGuard] }
Remember that you have left the main application ViewModel un-implemented. Open src/app/app.component.ts
again and add the following import to the top of the file.
请记住,您尚未执行主应用程序ViewModel。 再次打开src/app/app.component.ts
并将以下导入添加到文件顶部。
import { OktaAuthService } from '@okta/okta-angular';
Next, implement all the methods of the AppComponent
class.
接下来,实现AppComponent
类的所有方法。
constructor(public oktaAuth: OktaAuthService) {}
async ngOnInit() {
this.isAuthenticated = await this.oktaAuth.isAuthenticated();
}
login() {
this.oktaAuth.loginRedirect();
}
logout() {
this.oktaAuth.logout('/');
}
There is only one thing left to do. You can now add the Login and Logout buttons to the top bar. Open src/app/app.component.html
and add these two lines inside the <mat-toolbar>
element, after the closing </span>
.
只剩下一件事要做。 现在,您可以将“登录”和“注销”按钮添加到顶部栏中。 打开src/app/app.component.html
并在结束</span>
之后在<mat-toolbar>
元素内添加这两行。
<button mat-button *ngIf="!isAuthenticated" (click)="login()"> Login </button>
<button mat-button *ngIf="isAuthenticated" (click)="logout()"> Logout </button>
The Login and Logout buttons are linked to the login()
and logout()
methods in the app.component.ts
ViewModel. The visibility of these two buttons is determined by the isAuthenticated
flag in the ViewModel.
Login和Logout按钮链接到app.component.ts
ViewModel中的login()
和logout()
方法。 这两个按钮的可见性由ViewModel中的isAuthenticated
标志确定。
That’s all there is to it! Now you have a complete application based on the MVVM architecture, complete with authentication. You can test it out by firing up the Angular test server in the application root directory.
这里的所有都是它的! 现在,您将拥有一个基于MVVM体系结构的完整应用程序,并带有身份验证。 您可以通过在应用程序根目录中启动Angular测试服务器来对其进行测试。
ng serve
Open your browser and navigate to http://localhost:4200
. You should see something like this.
打开浏览器并导航到http://localhost:4200
。 您应该会看到类似这样的内容。
In this tutorial, I have shown you how Angular is based on the MVVM design pattern and how this pattern is different from the better known MVC pattern. In the MVC pattern, the Controller simply links up the View with the Model by using Observers and Observables provided by the other two components. Once the Controller has set up the connection, the View and the Model communicate directly, but without knowing who they are communicating with. Specifically, the Controller holds no application state of its own. It is simply a facilitator to make the connection between the View and the Model. In the MVVM pattern, the Controller is replaced by the ViewModel. The View and the ViewModel are linked via a two-way data-binding. They share the same state.
在本教程中,我向您展示了Angular如何基于MVVM设计模式,以及该模式与众所周知的MVC模式有何不同。 在MVC模式中,控制器仅使用其他两个组件提供的Observers和Observables将视图与模型链接起来。 Controller建立连接后,View和Model将直接通信,但不知道与谁通信。 具体来说,控制器不拥有自己的应用程序状态。 它只是在视图和模型之间建立连接的辅助工具。 在MVVM模式中,Controller被ViewModel取代。 View和ViewModel通过双向数据绑定链接。 它们共享相同的状态。
To learn more about the MVC and MVVM design patterns, you might be interested in the following links.
要了解有关MVC和MVVM设计模式的更多信息,您可能会对以下链接感兴趣。
The code for this tutorial is available at oktadeveloper/okta-angular-notes-app-example.
可以在oktadeveloper / okta-angular-notes-app-example上获得本教程的代码。
If you liked this post, chances are you’ll like others we publish. Follow @oktadev on Twitter and subscribe to our YouTube channel for more excellent tutorials.
如果您喜欢这篇文章,您可能会喜欢我们发布的其他文章。 在Twitter上关注@oktadev ,并订阅我们的YouTube频道以获取更多优秀的教程。
翻译自: https://www.digitalocean.com/community/tutorials/angular-angular-mvc-primer
angular mvc