表单是移动应用程序不可或缺的一部分。它们为用户交互提供了基础,例如登录、注册和搜索。因此,表单在增强用户体验方面可以发挥关键作用。
在本文中,我们将使用 NativeScript 和 Angular 框架 开发一个登录表单,并考虑到一些设计最佳实践。这些最佳实践可以扩展到设计其他类型的表单。我们稍后将扩展登录表单,以添加 Progress Kinvey 作为后端服务。
以下是我们将在这篇文章中构建的登录屏幕视图。图 1 显示了 iPhone 6 模拟器上的登录屏幕;图 2 显示了 iPad Pro(9.7 英寸)模拟器上的屏幕。
图 3 显示了应用程序文件夹结构的一部分。
此应用程序的完整版本已 在 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);
}
到目前为止,我们已经为手机应用程序构建了一个登录页面。现在,让我们为平板电脑进行自定义。更准确地说,我们需要
首先,应用程序需要识别它是否在平板电脑上。
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 作为后端。
Kinvey 是一个无服务器平台和后端即服务 (BaaS) 提供商,它使开发人员可以轻松地为其移动应用程序设置和使用云后端。在本节中,我们将配置我们的应用程序以使用 Kinvey 作为 BaaS。
要将 Kinvey 设置为 BaaS,我们需要
在 Kinvey 中,我们可以通过从应用程序仪表板导航到身份 -> 用户来手动注册我们应用程序的新用户。
当我们添加新用户时,必须在用户名字段中输入有效的电子邮件 ID。这是因为我们的 NativeScript 应用程序期望电子邮件 ID!
我们可以运行以下任一命令将 Kinvey NativeScript SDK 添加为插件到我们的移动应用程序
npm install --save kinvey-nativescript-sdk
或
tns plugin add kinvey-nativescript-sdk
现在,我们需要使用在任务 1,步骤 7 中获得的应用程序密钥和应用程序密钥来配置 NativeScript 应用程序。这涉及使用以下代码创建 app.config.ts
文件
从您先前创建的 Kinvey 应用程序开始。继续创建 src/app/app.config.ts
文件,如以下代码片段所示,并用您从 Kinvey 应用程序获得的值替换 appKey
和 appSecret
的值。
src/app/app.config.ts
// Configure Your Kinvey App Here
export const appConfig = {
appKey:'your_app_key',
appSecret:'your_app_secret'
}
将应用程序密钥和应用程序密钥添加为代码片段中 appKey
和 appSecret
属性的值。
接下来,我们需要使用 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。
最后一步是修改我们的登录和注销的后端逻辑。我们将使用 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 后端,并遵循表单设计的一些最佳实践,包括但不限于字段标签、密码显示隐藏功能、提供诊断错误信息。
我们希望您觉得这篇文章有用。如果您有任何改进建议或问题,请在下方留言。