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_item
template 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 collection
Todos.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
).