Migrating Authentication Templates

Note: If you skipped ahead to this section, click here to download a zip of the tutorial at this point.

So now we will take care of the authentication Blaze Templates, such as Join and Signup.

We already created a stub Angular 2 Components for them - we just need to implement them now.

This Todos project uses AccountTemplates package, which has a default style templates for signin and join pages - we do not want to use those and we want to implement it with Angular 2.

The style and template defined in imports/ui/accounts/accounts-templates.html and we will copy the thing we need and create a new Angular 2 template file that looks the same.

11.1 Take the layout of the join form client/imports/components/join.ng2.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<div class="page auth">
    <nav>
        <div class="nav-group">
            <a href="#" class="js-menu nav-item">
                <span class="icon-list-unordered"></span>
            </a>
        </div>
    </nav>
 
    <div class="content-scrollable">
        <div class="wrapper-auth">
            <div class="at-form">
                <h1 class="title-auth">Join</h1>
                <p class="subtitle-auth">Signing in allows you to have private lists</p>
                <div class="at-pwd-form">
                    <form role="form" id="at-pwd-form">
 
                    </form>
                </div>
            </div>
 
        </div>
    </div>
</div>

So this is the basic layout without the actual form fields, let's use it:

1
2
3
4
5
6
7
import {Component} from "@angular/core";
 
@Component({
  templateUrl: '/client/imports/components/join.html'
})
export class JoinComponent {
}

Now let's add the actual form:

11.3 Added the join form HTML client/imports/components/join.ng2.html
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
            </a>
        </div>
    </nav>
    <div class="content-scrollable">
        <div class="wrapper-auth">
            <div class="at-form">
                <h1 class="title-auth">Join</h1>
                <p class="subtitle-auth">Signing in allows you to have private lists</p>
                <div class="list-errors">
                    <div *ngFor="let errorText of errors" class="list-item">{{errorText}}</div>
                </div>
                <div class="at-pwd-form">
                    <form id="at-pwd-form" (ngSubmit)="join()" #joinForm="ngForm">
                        <div class="input">
                            <input [(ngModel)]="model.email" required type="text" id="email" name="email" placeholder="Email" class="form-control" autocapitalize="none" autocorrect="off">
                        </div>
                        <div class="input">
                            <input [(ngModel)]="model.password" required type="password" id="password" name="password" class="form-control" placeholder="Password" autocapitalize="none" autocorrect="off">
                        </div>
                        <div class="input">
                            <input [(ngModel)]="model.passwordVerify" required type="password" id="password_verify" class="form-control" name="password_verify" placeholder="Password (Again)" autocapitalize="none" autocorrect="off">
                        </div>
                        <button type="submit" class="btn-primary" [disabled]="!joinForm.form.valid">
                            REGISTER
                        </button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

Let's understand what do we have here:

  • A form, that registers an event ngSubmit to the Component method join, and we give it a name joinForm using variable reference (more info here)
  • 3 inputs for email, password and verify password, that declared as ngControl which indicate that this input related to the form and effect it's validation.
  • We also use two-way binding using ngModel for the inputs.
  • Button of type submit that disabled when the form is not valid.

Great, now we need to add some code to the form:

  • Handle errors using errors array.
  • Implement join() method and create the actual user when join.
  • Create a model object with our fields (email, password, verifyPassword) - note that this is optional and you can just use regular object.
  • Use router to navigate the user to the main page after joining.

So let's do it:

11.4 Added the join form logic client/imports/components/join.component.ts
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
import {Component, NgZone} from "@angular/core";
import {Router} from "@angular/router";
 
class JoinModel {
  constructor(public email : string, public password : string, public passwordVerify : string) {
 
  }
}
 
@Component({
  templateUrl: '/client/imports/components/join.html'
})
export class JoinComponent {
  private model : JoinModel;
  private errors : Array<string> = [];
 
  constructor(private router : Router, private zone: NgZone) {
    this.model = new JoinModel('', '', '');
  }
 
  resetErrors() {
    this.errors = [];
  }
 
  join() {
    this.resetErrors();
 
    if (this.model.password !== this.model.passwordVerify) {
      this.errors.push("Passwords does not match!");
 
      return;
    }
 
    Accounts.createUser({
      email: this.model.email,
      password: this.model.password
    }, (err) => {
      if (err) {
        this.zone.run(() => {
          this.errors.push(err.reason);
        });
 
        return;
      }
 
      this.router.navigate(['/']);
    });
  }
}

And we also need to add an import for Angular 2 Forms Module, so let's do it:

11.5 Add import to angular forms module client/imports/app.module.ts
7
8
9
10
11
12
13
 
28
29
30
31
32
33
34
35
import {Angular2BlazeTemplateModule} from "angular2-blaze-template";
import {JoinComponent} from "./components/join.component";
import {SigninComponent} from "./components/signin.component";
import {FormsModule} from "@angular/forms";
 
@NgModule({
    // Components, Pipes, Directive
...some lines skipped...
    imports: [
        BrowserModule,
        routing,
        Angular2BlazeTemplateModule,
        FormsModule
    ],
    // Main Component
    bootstrap: [MainComponent]

This Todo base project uses packages that intent to help developing Blaze Template with Meteor Accounts, and we no longer need it, and it is also "takes control" of sign-up, so we need to remove it.

So let's remove those packages, by running:

meteor remove useraccounts:unstyled useraccounts:flow-routing softwarerero:accounts-t9n

And we also perform some cleanup and remove some files that uses this packages - you can see those modifications in commit #7.6 (or here)

Great! now we need to make sure that there is an indication for the user that he's logged in, so let's go back to MainContainerComponent and and add currentUser field:

11.7 Added current user to the main page client/imports/main.component.ts
16
17
18
19
20
21
22
 
33
34
35
36
37
38
39
40
  private menuOpen : boolean = false;
  private userMenuOpen : boolean = false;
  private lists: Observable<any>;
  private currentUser : Meteor.User;
 
  constructor(private router: Router) {
    this.isCordova = Meteor.isCordova;
...some lines skipped...
          {userId: Meteor.userId()},
        ]
      }).zone();
 
      this.currentUser = Meteor.user();
    });
  }
 

We put that code inside autorun because we want it to update when the user login or logout.

Now we should be able to see the user's name if the main page - the only missing thing is to fix and add toggle for the user menu:

11.8 Fix the user menu toggle client/imports/main-component.ng2.html
1
2
3
4
5
6
7
<div id="container" [ngClass]="{'menu-open': menuOpen, 'cordova': isCordova}">
    <section id="menu">
        <div *ngIf="currentUser" class="btns-group-vertical">
            <a class="js-user-menu btn-secondary" (click)="userMenuOpen = !userMenuOpen">
                <span *ngIf="userMenuOpen" class="icon-arrow-up"></span>
                <span *ngIf="!userMenuOpen" class="icon-arrow-down"></span>
 

Now, let's do the same for the SigninComponent - it's very similar:

11.9 Added the signin view client/imports/components/signin.ng2.html
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
<div class="page auth">
    <nav>
        <div class="nav-group">
            <a href="#" class="js-menu nav-item">
                <span class="icon-list-unordered"></span>
            </a>
        </div>
    </nav>
    <div class="content-scrollable">
        <div class="wrapper-auth">
            <div class="at-form">
                <h1 class="title-auth">Signin</h1>
                <p class="subtitle-auth">Signing in allows you to have private lists</p>
                <div class="list-errors">
                    <div *ngFor="let errorText of errors" class="list-item">{{errorText}}</div>
                </div>
                <div class="at-pwd-form">
                    <form id="at-pwd-form" (ngSubmit)="join()" #joinForm="ngForm">
                        <div class="input">
                            <input [(ngModel)]="model.email" required type="text" id="email" name="email" placeholder="Email" class="form-control" autocapitalize="none" autocorrect="off">
                        </div>
                        <div class="input">
                            <input [(ngModel)]="model.password" required type="password" id="password" name="password" class="form-control" placeholder="Password" autocapitalize="none" autocorrect="off">
                        </div>
                        <button type="submit" class="btn-primary" [disabled]="!joinForm.form.valid">
                            SIGN IN
                        </button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

And the Component:

11.10 Implemented the signin logic client/imports/components/signin.component.ts
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
import {Component, NgZone} from "@angular/core";
import {Router} from "@angular/router";
 
class SigninModel {
  constructor(public email : string, public password : string) {
 
  }
}
 
@Component({
  templateUrl: '/client/imports/components/signin.html'
})
export class SigninComponent {
  private model : SigninModel;
  private errors : Array<string> = [];
 
  constructor(private router: Router, private ngZone: NgZone) {
    this.model = new SigninModel('', '');
  }
 
 
  resetErrors() {
    this.errors = [];
  }
 
  join() {
    this.resetErrors();
 
    Meteor.loginWithPassword(this.model.email, this.model.password, (err) => {
      if (err) {
        this.ngZone.run(() => {
          this.errors.push(err.reason);
        });
 
        return;
      }
 
      this.router.navigate(['/']);
    });
  }
}

That's it! we implemented the join/signin forms with Angular 2 !