Urigo / angular2-meteor

Angular 2.0 and Meteor - the perfect stack
http://www.angular-meteor.com/
298 stars 61 forks source link

Accounts.createUser and angular2-meteor not syncing data correctly #297

Closed DurhamSmith closed 8 years ago

DurhamSmith commented 8 years ago

I am trying to build my own user authentication and have run into some strange behaviour when trying to get the errors returned from Accounts.createUser().

When I try to render the errors from createUser() they dont appear immediately (even if I put errors in the array that holds the errors both before and after calling createUser(), however logging the errors shows that all the errors are in the array).

influencer-signup-form.ts

import 'reflect-metadata';
import { Component } from '@angular/core';
import {  Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
import { FormBuilder, ControlGroup, Validators, AbstractControl } from '@angular/common';
import {MeteorComponent} from 'angular2-meteor';

export interface SignupCredentials {
  email: string;
  password: string;
}

@Component({
  selector: 'influencer-signup-form',
  templateUrl: '/client/imports/influencer-signup-form/influencer-signup-form.html'
})
export class InfluencerSignupForm extends MeteorComponent {

  //DataMembers
  errors: Array<any>;
  message: string;
  credentials: SignupCredentials;
  //Form Bulder
  influencerSignupForm: ControlGroup;

  //Workaround for refreshed forms
  active = true;

  //Constructor
  constructor(){
    super();
    let fb = new FormBuilder();

    this.influencerSignupForm = fb.group({
       email: ['', Validators.required],
       password: ['', Validators.required]
     })

    this.resetErrors();
    this._resetCredentialsFields();

  }
  //MemberFunctionss
  _resetCredentialsFields() {
    this.credentials = { email: '', password: '' };
  }

  resetErrors() {
    this.errors = [];
    this.message = "";
  }

  influencerSignUp()
  {
    this.resetErrors();
    this.errors.push("start");

    if(this.influencerSignupForm.valid)
    {
      this.credentials = { email: this.influencerSignupForm.value['email'],
          password: this.influencerSignupForm.value['password'] }

      Accounts.createUser(this.credentials, (error) => {
        if (error) {
          this.errors.push(error.reason || "Unknown error");
          console.log(this.errors);
        }
        else {
          console.log("User created");
        }
      });
    }
    else
    {
      console.log("Form not valid");
    }
    this.errors.push("end");
  }

  _refresh_form(){
    this.active = false;
    setTimeout(()=> this.active=true, 0);
  }

}

influencer-signup-form.html

<form *ngIf="active" (ngSubmit)="influencerSignUp()" [ngFormModel]="influencerSignupForm">

      <ul [hidden]="!errors || errors.length == 0">
        <li *ngFor="let error of errors">
          {{ error }}
        </li>
      </ul>

  <label for="email">Email</label>
  <input type="text" id="email" placeholder="Email" required
    [ngFormControl]="influencerSignupForm.controls['email']">

    <div [hidden]="influencerSignupForm.controls['email'].valid ||
    influencerSignupForm.controls['email'].pristine" class="alert alert-danger">
      Email is required
    </div>

  <label for="password">Password</label>
  <input id="password" type="password" placeholder="password" required
    [ngFormControl]="influencerSignupForm.controls['password']">

  <div [hidden]="influencerSignupForm.controls['password'].valid ||
   influencerSignupForm.controls['password'].pristine" class="alert alert-danger">
    Password is required
  </div>

  <button type="submit" class="btn btn-default"
  [disabled]="!influencerSignupForm.valid">Submit</button>
</form>
kokokenada commented 8 years ago

Try a calling ngZone.run when your array changes. (DI NgZone in your constructor.) This would be done for you if you were using the functionality provided by MeteorComponent, but it looks like you are managing the array to display yourself, so you need to tell Angular that the data has changed (roughly speaking).

DurhamSmith commented 8 years ago

Thanks that worked 👍

It seems a little inconvenient to call ngZone.run do that everytime I want something like this updated.

Could you perhaps explain what the correct way it using MeteorComponent to have this done automatically? From the docs i'm not seeing how this would be done in this situation.

kokokenada commented 8 years ago

To have MeteorComponent do it, you would need to do a this.subscribe(). This sets up a watch on the Meteor cursor that the subscription returns and does something akin to calling ngZone.run. (I think.)

DurhamSmith commented 8 years ago

Thanks for the help. From what I can tell in my situation this might not be possible as what I'm interested in is the errors when trying to create a user as opposed to just subscribing to an already created created db. If creating a user fails it there won't be a new user in the db and nothing will run, I think, unless createUser() triggers the onStop callback function given to subscribe()?

If this is correct let me know otherwise ill give it a go once I get back to my pc.

kokokenada commented 8 years ago

I think you are correct in that if you are interested in displaying a client only array, then the angular2-meteor functionality is not intended to help. (Someone with more expertise can correct me if I'm off track.) We're broaching the more general topic of Angular 2 change detection. Your question made me realize I'm foggy on the details so I did a bit of digging and found this article. http://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html ). The pattern that might suit what you're trying to do is creating an observable. Not sure if that is more or less convenient form a coding perspective. It feels like it would me more efficient from an execution perspective (compared to calling NgZone.run), but I'm not sure and it doesn't sound like that part of your app would be all the performance sensitive.

barbatus commented 8 years ago

@DurhamSmith Accounts is not part of the Meteor core, so it won't be patched to run callbacks in the zone as part of this package (modules/patch.ts). But it can be done as part of https://github.com/Urigo/angular2-meteor-accounts-ui. Anyways, I don't see a big problem to inject ngZone and use it wheever you need. this is how Angular2 works and it's a must be aware of it.