返回博客首页
← 所有文章

为 NativeScript 应用构建登录功能

2019 年 4 月 23 日 — 作者:Phani Sajja

表单是移动应用程序不可或缺的一部分。它们为用户交互提供了基础,例如登录、注册和搜索。因此,表单在增强用户体验方面可以发挥关键作用。

在本文中,我们将使用 NativeScriptAngular 框架 开发一个登录表单,并考虑到一些设计最佳实践。这些最佳实践可以扩展到设计其他类型的表单。我们稍后将扩展登录表单,以添加 Progress Kinvey 作为后端服务。

以下是我们将在这篇文章中构建的登录屏幕视图。图 1 显示了 iPhone 6 模拟器上的登录屏幕;图 2 显示了 iPad Pro(9.7 英寸)模拟器上的屏幕。

login screen on emulators

应用程序结构

图 3 显示了应用程序文件夹结构的一部分。

app file and folder structure

此应用程序的完整版本已 在 GitHub 上提供

以下代码片段定义了应用程序的路由。如您所见,默认路由 /home 受身份验证 (AuthGuard) 保护。我们将在本文后面回到 AuthGuard。

src/app/auth-guard.service.ts

import { NgModule } from "@angular/core";
import { NativeScriptRouterModule } from "nativescript-angular/router";
import { Routes } from "@angular/router";
import { LoginComponent } from "./login/login.component";
import { HomeComponent } from "./home/home.component";
import { AuthGuard } from "./auth-guard.service";

const routes: Routes = [
    { path:"", redirectTo:"/home", pathMatch:"full" },
    { path:"login", component:LoginComponent },
    { path:"home", component:HomeComponent, canActivate: [AuthGuard] },
];

@NgModule({
    imports: [NativeScriptRouterModule.forRoot(routes)],
    exports: [NativeScriptRouterModule]
})

export class AppRoutingModule { }

构建用户界面

在这里,我们将专注于创建表单字段和实现验证。

以下是我们的用户模型。我们将使用用户模型绑定登录表单的电子邮件和密码字段。

src/app/services/user.model.ts

export class User {
  email: string;
  password: string;
  hasEmail() {
    return this.email != '';
  }
}

我们的电子邮件字段包括一个标签(“电子邮件”)、一个用于接受用户电子邮件 ID 的文本字段和一个在出现错误情况下的消息。由于我们希望收集用户的电子邮件 ID,因此我们将键盘类型指定为 email,这是适当的输入方法。

添加电子邮件字段的代码片段如下:

src/app/login/login.component.html

<GridLayout row="0" rows="auto, auto, auto">

    <Label class="eloha-font-semibold login-field-label m-b-2 font-size-md" row="0" text="Email"></Label>

    <TextField row="1" class="eloha-font-semibold login-input-field font-size-md" hint="E.g. [email protected]" keyboardType=" email" [(ngModel)]="user.email" autocorrect="false" autocapitalizationType="none" (focus)="onEmailFocus()" [ngClass]="{'input-field-error': hasEmailErrors()}"></TextField>

    <Label *ngIf="hasEmailErrors()" class="eloha-font-semibold m-t-2 login-field-label color-danger font-size-md" row="2" [text]="getEmailError()"></Label>

</GridLayout>

除了显示错误消息外,我们还希望突出显示文本字段的边框,如果用户的输入有误。

以下子程序检查用户输入是否为空/空或语义上无效。在任何情况下,子程序都将返回 true。

src/app/login/login.component.ts

public hasEmailErrors() {
    const hasErrorMsg = !!this.emailError;

    if (!hasErrorMsg)
      return false;

    const isValidEmail = this.user.hasEmail() && this.utilityService.isValidEmail(this.user.email);

    let hasError = hasErrorMsg || !isValidEmail;

    if (isValidEmail) {
      this.emailError = ""
      return false;
    }

    return hasError;
}

以下是如何验证用户输入(电子邮件)——通过使用 现有的 npm 模块,而不是编写我们自己的验证代码。

src/app/services/utility.service.ts

import { Injectable } from "@angular/core";

// From https://stackoverflow.com/questions/201323/how-to-validate-an-email-address-using-a-regular-expression/201378#201378

const regex:any = /(?:[a-z0-9!#$%&'\*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'\*+/=?^_`{|}~-]+)\*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])\*")@(?:(?:[a-z0-9](?:[a-z0-9-]\*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]\*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]\*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/

@Injectable()

export class UtilityService {
    public isValidEmail(email: String) {
        if(!email)
            return false;

        return regex.test(email);
    }
}

添加密码字段类似于添加电子邮件字段,唯一的区别是显示/隐藏 (FontAwesome) 图标,以使用户能够看到他们输入的内容。

src/app/login/login.component.html

<GridLayoutrow="1"rows="auto, auto, auto"class="m-t-10">

    <Label class="eloha-font-semibold login-field-label m-b-2 font-size-md"row="0" text="Password"></Label>

    <GridLayout row="1" columns="\*, auto" class="login-password-container" [ngClass]="{'input-field-error': hasPasswordErrors()}">

        <TextField #passwordcol="0" class="eloha-font-semibold login-password-field font-size-md" hint="Password" secure="true" [(ngModel)]="user.password" (focus)="onPasswordFocus()"></TextField>

        <Label col="1" [text]="showHideIcon" class="fa show-hide-icon font-size-lg" (tap)="showHidePassword()" verticalAlignment="middle"></Label>

    </GridLayout>

    <Label *ngIf="hasPasswordErrors()" class="eloha-font-semibold m-t-2 login-field-label color-danger font-size-md" row="2" [text]="getPasswordError()"></Label>

</GridLayout>

以下子程序在用户点击显示/隐藏图标时调用。它切换 TextField 的安全属性的关闭或打开,并相应地更改图标。

src/app/login/login.component.ts

showHidePassword() {
    this.showPassword = !this.showPassword;
    this.showHideIcon = this.showPassword ? this.showIcon : this.hideIcon;
    let passField: TextField = this.passwordField.nativeElement;
    passField.secure = !passField.secure;
}

我们还为密码字段添加了焦点绑定。当此字段处于焦点时,我们可以检查输入电子邮件字段的有效性并更新任何错误。

src/app/login/login.component.ts

onPasswordFocus() {
    this.passHasFocus = true;
    this.updateErrors(false);
}

updateErrors(checkPass) {
    if (this.user.hasEmail()) {
        if (this.utilityService.isValidEmail(this.user.email)) {
            this.emailError = "";
        } else {
            this.emailError = "Invalid Email"
        }
    } else {
        this.emailError = "Email cannot be blank"
    }

    if (checkPass) {
        let length = this.user.password.length;
        if (length == 0) {
            this.passError = "Password cannot be blank";
        } else {
            this.passError = "";
        }
    }
}

接下来,我们将添加“提交”功能,并将其命名为“登录”。弹出窗口有时会很烦人,因此我们将显示任何错误消息,而不是在“登录”按钮下方显示。

src/app/login/login.component.html

<GridLayout row="2" rows="auto, auto" class="submit-container">

    <Button row="0" [isEnabled]="isSubmitEnabled()" class="eloha-font-semibold login-submit font-size-lg" text="Sign in"(tap)="login()"></Button>

    <Label *ngIf="hasLoginErrors()" class="eloha-font-semibold color-danger font-size-md" row="1" [text]="getLoginError()"textWrap="true"></Label>

</GridLayout>

登录按钮仅在用户输入有效的电子邮件 ID 时启用。

isSubmitEnabled() {
    return !this.isAuthenticating && this.utilityService.isValidEmail(this.user.email);
}

平板电脑支持

到目前为止,我们已经为手机应用程序构建了一个登录页面。现在,让我们为平板电脑进行自定义。更准确地说,我们需要

  1. 调整 UI 控件的宽度和字体大小。
  2. 动态应用这些调整。

首先,应用程序需要识别它是否在平板电脑上。

src/app/services/utility.service.ts

import { Injectable } from "@angular/core";
import { DeviceType } from "ui/enums";
import { device } from "platform";

@Injectable()

export class UtilityService {
    public isTablet() {
        return device.deviceType === DeviceType.Tablet;
    }
}

src/app/login/login.component.ts

isTablet() {
    return this.utilityService.isTablet();
}

默认情况下,控件会被拉伸以适应父级。您可以分别为每个控件或父级控件自定义宽度,具体取决于表单的设计。在我们的示例应用程序中,我们调整了包含这些控件的父容器的宽度。

src/app/login/login.component.html

<GridLayout row="1" rows="*, auto" [ngClass]="{'login-page-tablet': isTablet()}">

我们使用 NGClass 指令在 _app-common.scss 中动态配置我们的 CSS 类 login-page-tablet

src/scss/_app-common.scss

.login-page-tablet {
    width: 60%;
}

为了管理字体,我们在 _app-common.scss 中为我们的 CSS 类添加了特定于平板电脑的类。我们选择将平板电脑的每个字体大小规则增加 5 个点。

src/scss/_app-common.scss

.font-size-sm {
    font-size: 12;
}

.font-size-sm-tablet{
    font-size: 17;
}

.font-size-md {
    font-size: 14;
}

.font-size-md-tablet {
    font-size: 19;
}

.font-size-lg {
    font-size: 16;
}

.font-size-lg-tablet {
    font-size: 21;
}

如您所见,平板电脑的字体大小增加了 5 个点。接下来,我们必须找到一种动态应用这些差异的方法。

以下代码片段使用字体类 font-size-md(默认字体大小为 14)作为电子邮件标签。

src/app/login/login.component.html

<Label class="eloha-font-semibold login-field-label m-b-2 font-size-md" row="0" text="Email"></Label>

我们再次使用 NgClass 指令根据设备类型应用 CSS 类,如下所示。

src/app/login/login.component.html

<Label class="eloha-font-semibold login-field-label m-b-2" [ngClass]="{'font-size-md': !isTablet(), 'font-size-md-tablet': isTablet()}" row="0" text="Email"></Label>

登录页面的其他组件必须进行类似的更改。然后,我们将看到页面在平板电脑上的效果。

登录时间

到目前为止,我们已经实现了表单控件、用户验证和设备自定义。现在,我们将添加后端逻辑来执行登录过程。这涉及保留用户会话直到用户注销的逻辑。

在本节中,我们将添加后端服务,它模拟登录后端。在下一节中,我们将添加登录到 Progress Kinvey 后端的函数。

以下代码显示了我们的登录和注销功能。在登录子程序中,我们传递 1000 毫秒,然后检查电子邮件和密码是否相同。您还可以选择为电子邮件和密码硬编码值,然后检查给定的值。如果匹配,我们将存储这些详细信息,并将它们视为会话详细信息。注销也会等待 1000 毫秒,然后清理会话数据并返回。

src/app/services/backend.service.ts

import { Injectable } from "@angular/core";
import { getString, setString } from "application-settings";
import { User } from "./user.model";

const _CURRENT_USER = "_CURRENT_USER";

@Injectable()

export class BackendService {
  public isUserLoggedIn(): boolean {
    let loggedIn = !!this.user;
    return loggedIn;
  }

  public login(user: User) {
    let that = this;
    return newPromise(function (resolve, reject) {

      setTimeout(() => {
        if (user.email === user.password) {
          that.user = JSON.stringify(user)
          resolve();
        } else {
          reject({ message:'Invalid Email/Password, For this example both should be same.' })
        }
      }, 1000)
    });
  }

  logout() {
    let that = this;
    return newPromise(function (resolve, reject) {
      setTimeout(() => {
        that.user = "";
        resolve();
      }, 1000)
    });
  }

  private getuser(): string {
    return getString(_CURRENT_USER);
  }

  private setuser(theToken: string) {
    setString(_CURRENT_USER, theToken);
  }

}

接下来,让我们将此逻辑绑定到我们的登录,它在用户点击登录按钮时调用。

以下代码片段显示了执行登录的代码。登录成功后,用户将到达主页/屏幕 /home。请注意,我们正在清除路由导航历史记录。这样做是为了防止用户使用设备上的后退按钮返回登录屏幕。

login() {
    this.updateErrors(true);
    if (this.isValidForm()) {
      this.isAuthenticating = true;

      // Use the backend service to login
      this.backendService.login(this.user)
        .then(() => {
          this.isAuthenticating = false;
          this.routerExtensions.navigate(["/home"], { clearHistory:true });
        }).catch(error=> {
          this.isAuthenticating = false;
          this.loginError = error.message;
        });
    }
}

身份验证守卫

回想一下,我们已经保护了 /home 路由,因此只有在登录成功的情况下,用户才会被带到主页。

可以通过实现 CanActivate 接口来配置 Angular 路由CanActivate 属性,以决定是否可以激活路由。

以下代码行显示了我们对身份验证守卫的实现。实现依赖于我们后端服务中定义的 isUserLoggedIn() 方法。

如果用户已登录,则配置的路由将被激活。否则,用户将被重定向到登录屏幕。

src/app/auth-guard.service.ts

import { Injectable } from "@angular/core";
import { CanActivate } from "@angular/router";
import { BackendService } from "./services/backend.service";
import { RouterExtensions } from "nativescript-angular/router";

@Injectable()

export class AuthGuardimplementsCanActivate {
    constructor(privatebackendService: BackendService, privaterouterExtensions: RouterExtensions) { }

    canActivate() {
        if (this.backendService.isUserLoggedIn()) {
            return true;
        } else {
            this.routerExtensions.navigate(["/login"]);
            return false;
        }
    }
}

到目前为止,我们已经向我们的 NativeScript 应用程序添加了虚拟登录功能。您可以查看 master 分支以获取源代码。在下一节中,我们将设置 Progress Kinvey 作为后端。

配置 Progress Kinvey

Kinvey 是一个无服务器平台和后端即服务 (BaaS) 提供商,它使开发人员可以轻松地为其移动应用程序设置和使用云后端。在本节中,我们将配置我们的应用程序以使用 Kinvey 作为 BaaS。

要将 Kinvey 设置为 BaaS,我们需要

  1. 创建一个 Kinvey 应用程序。
  2. 为 Kinvey 应用程序添加一个用户。
  3. 将 Kinvey NativeScript SDK 添加到移动应用程序。
  4. 使用新创建的 Kinvey 应用程序详细信息配置移动应用程序。
  5. 修改后端逻辑。

任务 1 - 创建 Kinvey 应用程序

  1. 导航到 Kinvey 控制台
  2. 如果您是第一次使用,请注册 Kinvey 以创建您的用户帐户。
  3. 使用您的用户帐户详细信息登录 Kinvey 控制台。
  4. 创建 Kinvey 应用程序。
  5. 导航到应用程序仪表板。
  6. 右键单击仪表板左上角的用户名。
  7. 记下应用程序密钥和应用程序密钥。我们将需要这些详细信息来配置我们的 NativeScript 应用程序。

任务 2 - 为 Kinvey 应用程序添加一个用户

在 Kinvey 中,我们可以通过从应用程序仪表板导航到身份 -> 用户来手动注册我们应用程序的新用户。

当我们添加新用户时,必须在用户名字段中输入有效的电子邮件 ID。这是因为我们的 NativeScript 应用程序期望电子邮件 ID!

任务 3 - 将 Kinvey NativeScript SDK 添加到移动应用程序

我们可以运行以下任一命令将 Kinvey NativeScript SDK 添加为插件到我们的移动应用程序

npm install --save kinvey-nativescript-sdk

tns plugin add kinvey-nativescript-sdk

任务 4 - 使用 Kinvey 应用程序详细信息配置 NativeScript 应用程序

现在,我们需要使用在任务 1,步骤 7 中获得的应用程序密钥应用程序密钥来配置 NativeScript 应用程序。这涉及使用以下代码创建 app.config.ts 文件

从您先前创建的 Kinvey 应用程序开始。继续创建 src/app/app.config.ts 文件,如以下代码片段所示,并用您从 Kinvey 应用程序获得的值替换 appKeyappSecret 的值。

src/app/app.config.ts

// Configure Your Kinvey App Here

export const appConfig = {
    appKey:'your_app_key',
    appSecret:'your_app_secret'
}

应用程序密钥应用程序密钥添加为代码片段中 appKeyappSecret 属性的值。

接下来,我们需要使用 NativeScript 应用程序详细信息初始化 Kinvey SDK。在以下代码片段中,我们在 src/main.ts 文件中通过调用 Kinvey.init(appConfig) 来初始化 Kinvey SDK,这是我们传递应用程序配置的方式。

src/main.ts

import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { Kinvey } from'kinvey-nativescript-sdk';
import { AppModule } from "./app/app.module";
import { appConfig } from'./app/app.config';

Kinvey.init(appConfig);

platformNativeScriptDynamic().bootstrapModule(AppModule);

如您所见,我们在引导应用程序模块之前初始化了 SDK。

任务 5 - 修改后端逻辑

最后一步是修改我们的登录和注销的后端逻辑。我们将使用 Kinvey SDK 与 Kinvey 后端执行实际的登录和注销操作,如下所示

src/app/services/backend.service.ts

import { Kinvey } from'kinvey-nativescript-sdk';

public login(user: User): Promise<any> {
    let _user: Kinvey.User = Kinvey.User.getActiveUser();
    if (_user) {
      return_user.logout()
        .then(() => this.performLogin(user));
    } else {
      return this.performLogin(user);
    }
}

logout() {
    return Kinvey.User.logout().then(() => {
      this.user = "";
    });
}

private performLogin(user: User) {
    return Kinvey.User.login(user.email, user.password).then((_user: any) => {
      this.user = JSON.stringify(_user)
    });
}

注意:如果使用登录用户重新安装应用程序,尝试登录会导致出现错误“已存在活动用户”。这只是处理错误的一种方法。您可以使用您自己的错误处理机制。

忘记密码

如果您快速查看图 3 和图 4,您将看到“忘记密码?”标签位于“登录”按钮下方,该标签的目的是支持密码恢复。当用户点击该标签时,系统会提示用户输入电子邮件 ID。如果电子邮件 ID 有效,系统将发送包含密码重置说明的电子邮件。

src/app/login/login.component.ts 中添加以下代码片段,它会提示用户输入电子邮件。

src/app/login/login.component.ts

forgotPassword() {
    prompt({
      title: "Forgot Password",
      message: "Enter the email address you used to register to reset your password.",
      defaultText: "",
      okButtonText: "Ok",
      cancelButtonText: "Cancel",
      inputType:inputType.email
    }).then((data) => {
      if (data.result) {
        this.backendService.forgetPassword(data.text.trim())
          .then(() => {
            alert("An email has been sent to your email address. Please check your email for instructions on resetting your password.");
          }, () => {
            alert("Unfortunately, an error occurred resetting your password.");
          });
      }
    });
}

然后,我们可以使用电子邮件 ID 和 Kinvey SDK 重置密码

src/app/services/backend.service.ts

public forgetPassword(email: string) {
    return Kinvey.User.resetPassword(email)
      .then((data) => {})
      .catch((error: Kinvey.BaseError) => {});
}

注意:为了使密码恢复在 Kinvey 中正常工作,我们需要为用户添加一个电子邮件字段,并在 Kinvey 控制台中为每个用户添加一个电子邮件。导航到 Kinvey 控制台中的用户,添加一个名为“email”的新字段,然后通过为每个用户添加相应的电子邮件值来更新用户。

使用 Kinvey 作为后端服务的完整代码可以在 此分支 中找到。登录到 Kinvey 后端的示例代码可作为 NativeSript Playground 示例应用程序 使用。

总结

在这篇文章中,我们学习了如何在 NativeScript 中实现登录表单,并为平板电脑定制它。同时,我们还介绍了如何从 NativeScript 应用连接到 Kinvey 后端,并遵循表单设计的一些最佳实践,包括但不限于字段标签、密码显示隐藏功能、提供诊断错误信息。

我们希望您觉得这篇文章有用。如果您有任何改进建议或问题,请在下方留言。