用户应该能够创建和编辑和自己的文章。在这里我们要构建出这个功能,但在创建文章编辑器之前,我们首先要创建文章本身的模型。
下面的Article模型是基于Api规范的。
创建Article模型
src/app/shared/models/article.model.ts
import { Profile } from './profile.model';export class Article {slug: string;title: string = '';description: string = '';body: string = '';tagList: Array<string> = [];createdAt: string;updatedAt: string;favorited: boolean;favoritesCount: number;author: Profile;}
src/app/shared/models/index.ts
+export * from './article.model';export * from './errors.model';export * from './profile.model';export * from './user.model';
接下来,我们需要创建一个服务,负责根据API规范创建和检索文章。
创建ArticlesService
src/app/shared/services/articles.service.ts
import { Injectable } from '@angular/core';import { URLSearchParams } from '@angular/http';import { Observable } from 'rxjs/Rx';import 'rxjs/add/operator/map';import 'rxjs/add/operator/catch';import { ApiService } from './api.service';import { Article } from '../models';@Injectable()export class ArticlesService {constructor (private apiService: ApiService) {}get(slug): Observable<Article> {return this.apiService.get('/articles/' + slug).map(data => data.article);}save(article): Observable<Article> {// If we're updating an existing articleif (article.slug) {return this.apiService.put('/articles/' + article.slug, {article: article}).map(data => data.article);// Otherwise, create a new article} else {return this.apiService.post('/articles/', {article: article}).map(data => data.article);}}}
src/app/shared/services/index.ts
export * from './api.service';+export * from './articles.service';export * from './auth-guard.service';export * from './jwt.service';export * from './profiles.service';[...]
…我们需要将它包含在AppModule中,以便在我们的应用程序中使用它。
src/app/app.module.ts
[...]import { AppComponent } from './app.component';import { AuthModule } from './auth/auth.module';import { HomeModule } from './home/home.module';import { ProfileModule } from './profile/profile.module';import { SettingsModule } from './settings/settings.module';import {ApiService,+ ArticlesService,AuthGuard,FooterComponent,HeaderComponent,[...]],providers: [ApiService,+ ArticlesService,AuthGuard,JwtService,ProfilesService,[...]
厉害!现在,在编辑页面上,如果有文章的slug存在,我们要预取文章。我们将创建一个resolution来自动完成这个任务。
创建EditableArticleResolver
src/app/editor/editable-article-resolver.service.ts
import { Injectable } from '@angular/core';import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';import { Observable } from 'rxjs/Rx';import { Article, ArticlesService, UserService } from '../shared';@Injectable()export class EditableArticleResolver implements Resolve<Article> {constructor(private articlesService: ArticlesService,private router: Router,private userService: UserService) {}resolve(route: ActivatedRouteSnapshot,state: RouterStateSnapshot): Observable<any> {return this.articlesService.get(route.params['slug']).map( article => {if (this.userService.getCurrentUser().username === article.author.username) {return article;} else {this.router.navigateByUrl('/');}}).catch((err) => this.router.navigateByUrl('/'));}}
现在我们可以创建编辑器页面本身。
创建EditorComponent
src/app/editor/editor.component.html
Create the EditorComponentsrc/app/editor/editor.component.html<div class="editor-page"><div class="container page"><div class="row"><div class="col-md-10 offset-md-1 col-xs-12"><list-errors [errors]="errors"></list-errors><form [formGroup]="articleForm"><fieldset [disabled]="isSubmitting"><fieldset class="form-group"><input class="form-control form-control-lg"formControlName="title"type="text"placeholder="Article Title" /></fieldset><fieldset class="form-group"><input class="form-control"formControlName="description"type="text"placeholder="What's this article about?" /></fieldset><fieldset class="form-group"><textarea class="form-control"formControlName="body"rows="8"placeholder="Write your article (in markdown)"></textarea></fieldset><fieldset class="form-group"><input class="form-control"type="text"placeholder="Enter tags"[formControl]="tagField"(keyup.enter)="addTag()" /><div class="tag-list"><span *ngFor="let tag of article.tagList"class="tag-default tag-pill"><i class="ion-close-round" (click)="removeTag(tag)"></i>{{ tag }}</span></div></fieldset><button class="btn btn-lg pull-xs-right btn-primary" type="button" (click)="submitForm()">Publish Article</button></fieldset></form></div></div></div></div>
src/app/editor/editor.component.ts
import { Component, OnInit } from '@angular/core';import { FormBuilder, FormGroup, FormControl } from '@angular/forms';import { ActivatedRoute, Router } from '@angular/router';import { Article, ArticlesService } from '../shared';@Component({selector: 'editor-page',templateUrl: './editor.component.html'})export class EditorComponent implements OnInit {article: Article = new Article();articleForm: FormGroup;tagField = new FormControl();errors: Object = {};isSubmitting: boolean = false;constructor(private articlesService: ArticlesService,private route: ActivatedRoute,private router: Router,private fb: FormBuilder) {// use the FormBuilder to create a form groupthis.articleForm = this.fb.group({title: '',description: '',body: '',});// Optional: subscribe to value changes on the form// this.articleForm.valueChanges.subscribe(value => this.updateArticle(value));}ngOnInit() {// If there's an article prefetched, load itthis.route.data.subscribe((data: {article: Article}) => {if (data.article) {this.article = data.article;this.articleForm.patchValue(data.article);}});}addTag() {// retrieve tag controllet tag = this.tagField.value;// only add tag if it does not exist yetif (this.article.tagList.indexOf(tag) < 0) {this.article.tagList.push(tag);}// clear the inputthis.tagField.reset('');}removeTag(tagName: string) {this.article.tagList = this.article.tagList.filter((tag) => tag !== tagName);}updateArticle(values: Object) {(<any>Object).assign(this.article, values);}submitForm() {this.isSubmitting = true;// update the modelthis.updateArticle(this.articleForm.value);// post the changesthis.articlesService.save(this.article).subscribe(article => this.router.navigateByUrl('/editor/' + article.slug),err => {this.errors = err;this.isSubmitting = false;});}}
现在我们可以创建编辑器模块了。请注意,我们有两条路线:一条用于新建文章,另一条用于编辑文章(API规范)(并使用EditableArticleResolver自动获取)。
创建EditorModule
src/app/editor/editor.module.ts
import { ModuleWithProviders, NgModule } from '@angular/core';import { RouterModule } from '@angular/router';import { EditorComponent } from './editor.component';import { EditableArticleResolver } from './editable-article-resolver.service';import { AuthGuard, SharedModule } from '../shared';const editorRouting: ModuleWithProviders = RouterModule.forChild([{path: 'editor',component: EditorComponent,canActivate: [AuthGuard]},{path: 'editor/:slug',component: EditorComponent,canActivate: [AuthGuard],resolve: {article: EditableArticleResolver}}]);@NgModule({imports: [editorRouting,SharedModule],declarations: [EditorComponent],providers: [EditableArticleResolver]})export class EditorModule {}
最后,将EditorModule包含在AppModule中。
将EditorModule包含在AppModule中
src/app/app.module.ts
[...]import { AppComponent } from './app.component';import { AuthModule } from './auth/auth.module';+import { EditorModule } from './editor/editor.module';import { HomeModule } from './home/home.module';import { ProfileModule } from './profile/profile.module';import { SettingsModule } from './settings/settings.module';import {ApiService,ArticlesService,AuthGuard,FooterComponent,HeaderComponent,[...]imports: [BrowserModule,AuthModule,+ EditorModule,HomeModule,ProfileModule,rootRouting,[...]
我们的编辑器现在能用了!为了更方便地访问它,让我们在标题中添加一个链接。
在HeaderComponent中添加一个链接到编辑器
src/app/shared/layout/header.component.html
[...]</li><li class="nav-item">+ <a class="nav-link"+ routerLink="/editor"+ routerLinkActive="active"><i class="ion-compose"></i> New Article</a></li>[...]
你可以将你的代码与Github上的有效代码进行比较,或者在本地检查分支:
git checkout m-2
