So let's continue! now we will migrate the list of todo, so far we use an existing Blaze Template called Lists_show_page inside a new Angular 2 Component called ListShowComponent.
First, let's modify the template, we use the same techniques we learned in the previous steps - we will use the existing template and just change the events, bindings and directives:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<div class="page lists-show"> <nav class="js-title-nav" *ngIf="list"> <form *ngIf="editing" class="js-edit-form list-edit-form"> <input type="text" name="name" [value]="editModel.name"> <div class="nav-group right"> <a href="#" class="js-cancel nav-item"> <span class="icon-close js-cancel" title="Cancel"></span> </a> </div> </form> <div *ngIf="!editing"> <div class="nav-group"> <a href="#" class="js-menu nav-item"> <span class="icon-list-unordered" title="Show Menu"></span> </a> </div> <h1 class="js-edit-list title-page"> <span class="title-wrapper">{{list.name}}</span> <span class="count-list">{{list.incompleteCount}}</span> </h1> <div class="nav-group right"> <div class="nav-item options-mobile"> <select class="list-edit"> <option disabled selected>Select an action</option> <option *ngIf="list.userId" value="public">Make Public</option> <option *ngIf="!list.userId" value="private">Make Private</option> <option value="delete">Delete</option> </select> <span class="icon-cog"></span> </div> <div class="options-web"> <a class="js-toggle-list-privacy nav-item"> <span *ngIf="list.userId" class="icon-lock" title="Make list public"></span> <span *ngIf="!list.userId" class="icon-unlock" title="Make list private"></span> </a> <a class="js-delete-list nav-item"> <span class="icon-trash" title="Delete list"></span> </a> </div> </div> </div> <form class="js-todo-new todo-new input-symbol"> <input type="text" placeholder="Type to add new tasks"> <span class="icon-add js-todo-add"></span> </form> </nav> <div class="content-scrollable list-items"> <div *ngIf="todosReady"> <div *ngFor="let todo of todos"> <blaze-template name="Todos_item" [context]="getContextForItem(todo)"></blaze-template> </div> <div class="wrapper-message" *ngIf="!todos || todos.length == 0"> <div class="title-message">No tasks here</div> <div class="subtitle-message">Add new tasks using the field above</div> </div> </div> <div *ngIf="!todosReady" class="wrapper-message"> <div class="title-message">Loading tasks...</div> </div> </div></div>And because we are using RxJS Observable as a wrapper for our data, we need to add async Pipe in our view, and let's migrate the Template code into a Component
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import {Component, OnInit} from "@angular/core";import {ActivatedRoute, Params} from "@angular/router";import {MeteorObservable} from "meteor-rxjs";import {Lists} from "../../../imports/api/lists/lists";@Component({ template: '<blaze-template *ngIf="templateContext" name="Lists_show_page" [context]="templateContext"></blaze-template>'})export class ListShowComponent implements OnInit { private list : any; private todosReady : boolean = false; private todos : Array<any>; private editing : boolean = false; constructor(private currentRoute: ActivatedRoute) {...some lines skipped... ngOnInit() { this.currentRoute.params.subscribe((params: Params) => { const listId = params['_id']; MeteorObservable.subscribe('todos.inList', listId).subscribe(); MeteorObservable.autorun().zone().subscribe(() => { if (listId && Lists.findOne(listId)) { this.list = Lists.findOne(listId); this.todosReady = true; this.todos = this.list.todos(); } }) }); } getContextForItem(todo) { return {todo: todo,
editing: false, onEditingChange(editing) { }, } }}50
51
52
53
54
55
56
<div class="content-scrollable list-items"> <div *ngIf="todosReady"> <div *ngFor="let todo of todos | async"> <blaze-template name="Todos_item" [context]="getContextForItem(todo)"></blaze-template> </div> <div class="wrapper-message" *ngIf="!todos || todos.length == 0">At the moment, we will use the exiting
Todo_itemtemplate to show the items - we will later migrate it too - so we just pass the required params usinggetContextForItem.
And now let's implement and migrate the code into the Component's class, and let's add the events in the view
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
import {Component, OnInit} from "@angular/core";import {ActivatedRoute, Params, Router} from "@angular/router";import {MeteorObservable} from "meteor-rxjs";import {Lists} from "../../../imports/api/lists/lists";import {updateName,
makePublic,
makePrivate,
remove,
insert,
} from '../../../imports/api/todos/methods';import { displayError } from '../../../imports/ui/lib/errors';import {Observable} from "rxjs";@Component({ templateUrl: '/client/imports/components/list-show.html'})export class ListShowComponent implements OnInit { private list : any; private todosReady : boolean = false; private todos : Observable<any>; private editing : boolean = false; private editModel : any; private newItemModel : string = ''; constructor(private currentRoute: ActivatedRoute, private router: Router) { this.editModel = { name: '' } } ngOnInit() { this.currentRoute.params.subscribe((params: Params) => { const listId = params['_id']; MeteorObservable.subscribe('todos.inList', listId).zone().subscribe(); MeteorObservable.autorun().zone().subscribe(() => { if (listId && Lists.findOne(listId)) { this.list = Lists.findOne(listId); this.todosReady = true; this.todos = this.list.todos().zone(); } }) }); } deleteList() { const list = this.list; const message = `Are you sure you want to delete the list?`; if (confirm(message)) { remove.call({listId: list._id,
}, displayError); this.router.navigate(['Home']); return true; } return false; } editList(toggle) { this.editModel.name = this.list.name; this.editing = toggle; } toggleListPrivacy() { const list = this.list; if (list.userId) {makePublic.call({ listId: list._id }, displayError);
} else {makePrivate.call({ listId: list._id }, displayError);
} } addNew() { if (this.newItemModel == '') { return; } insert.call({ listId: this.list._id, text: this.newItemModel, }, displayError); this.newItemModel = ''; } saveList() { if (this.editing) { updateName.call({ listId: this.list._id, newName: this.editModel.name, }, displayError); this.editing = false; } } getContextForItem(todo) { return {todo: todo,
editing: false, onEditingChange(editing) { }, } }}1
2
3
4
5
6
7
8
9
15
16
17
18
19
20
21
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<div class="page lists-show"> <nav class="js-title-nav" *ngIf="list"> <form *ngIf="editing" class="js-edit-form list-edit-form" (ngSubmit)="saveList()" #editForm="ngForm"> <input type="text" [(ngModel)]="editModel.name" (blur)="saveList()" name="editNameInput"> <div class="nav-group right"> <a href="#" class="js-cancel nav-item" (click)="editList(false)"> <span class="icon-close js-cancel" title="Cancel"></span> </a> </div>...some lines skipped... </a> </div> <h1 class="js-edit-list title-page" (click)="editList(true)"> <span class="title-wrapper">{{list.name}}</span> <span class="count-list">{{list.incompleteCount}}</span> </h1>...some lines skipped... <span class="icon-cog"></span> </div> <div class="options-web"> <a class="js-toggle-list-privacy nav-item" (click)="toggleListPrivacy()"> <span *ngIf="list.userId" class="icon-lock" title="Make list public"></span> <span *ngIf="!list.userId" class="icon-unlock" title="Make list private"></span> </a> <a class="js-delete-list nav-item" (click)="deleteList()"> <span class="icon-trash" title="Delete list"></span> </a> </div> </div> </div> <form class="js-todo-new todo-new input-symbol" (ngSubmit)="addNew()" #newForm="ngForm"> <input type="text" placeholder="Type to add new tasks" [(ngModel)]="newItemModel" name="newItemInput"> <span class="icon-add js-todo-add"></span> </form> </nav>And remember we wrapped the Collection? we need to do the same for Todo Collection:
1
2
3
4
5
6
7
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
57
58
59
60
61
62
63
64
65
66
67
68
78
79
80
81
82
83
84
import { Mongo } from 'meteor/mongo';import { Factory } from 'meteor/factory';import faker from 'faker';import {MongoObservable} from "meteor-rxjs";import incompleteCountDenormalizer from './incompleteCountDenormalizer.js';import { SimpleSchema } from 'meteor/aldeed:simple-schema';...some lines skipped... }}export const Todos = new MongoObservable.fromExisting(new TodosCollection('Todos'));// Deny all client-side updates since we will be using methods to manage this collectionTodos.collection.deny({ insert() { return true; }, update() { return true; }, remove() { return true; },});let schema = new SimpleSchema({ listId: { type: String,regEx: SimpleSchema.RegEx.Id,
...some lines skipped... },});Todos.collection.attachSchema(schema);
// This represents the keys from Lists objects that should be published// to the client. If we add secret properties to List objects, don't list// them here to keep them private to the server.Todos.collection.publicFields = { listId: 1, text: 1, createdAt: 1,...some lines skipped... createdAt: () => new Date(),});Todos.collection.helpers({ list() { return Lists.findOne(this.listId); },That's it! we can now remove the old files of this Template (imports/ui/components/lists-show.html, imports/ui/components/lists-show.js, imports/ui/pages/lists-show-page.js, imports/ui/pages/lists-show-page.html), and we can removed the imports for those files from the routes file (imports/startup/client/routes.js).