概述与模块说明
经过前面对共享模块,布局部分的剖析可以看出JHipster的页面模块划分了,剩余的机构子模块,都只是根据不同的路由,替换掉主页上<outlet>部分,已完成不同的具体功能。
account目录包含账户管理的功能,有JHipster实现了一套简单而标准的模型,用户可以实现(注册-激活)、(忘记密码-修改)、密码修改、用户信息修改功能。
按照前后端分离的策略,后端仅提供API接口的调用响应,用户界面的展现,逻辑都在前端进行。主题上可以分为API服务调用,路由控制,组件逻辑与展现3个部分。
在看具体的子功能前,先查看AccountModule的部分。
@NgModule({
//引入共享模块,并forChild加载accountState路由
imports: [
JhipsterSampleApplicationNg2SharedModule,
RouterModule.forChild(accountState)
],
//声明内部的组件(相对简单,没有指令)
declarations: [
ActivateComponent,
RegisterComponent,
PasswordComponent,
PasswordStrengthBarComponent,
PasswordResetInitComponent,
PasswordResetFinishComponent,
SettingsComponent
],
//声明内部的服务,都是与后端通讯的封装
providers: [
Register,
ActivateService,
PasswordService,
PasswordResetInitService,
PasswordResetFinishService
],
//已分析过,支持‘-‘命名
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class JhipsterSampleApplicationNg2AccountModule {}
路由配置
每个component组件都需占主体部分,因此path各不相同。
activate.route.ts
export const activateRoute: Route = {
//路径名我active
path: 'activate',
component: ActivateComponent,
data: {
//无需权限
authorities: [],
pageTitle: 'activate.title'
},
//路由守卫为UserRouteAccessService,由于authorities是空数组,始终是允许的。但是守卫会调用principal.identity()从后端获取用户信息。
canActivate: [UserRouteAccessService,]
};
password.route.ts
password-reset-finish.route.ts
password-reset-init.route.ts
settings.route.ts
这几个路由也类似,password和setting增加需要“'ROLE_USER'”权限
register.route.ts
export const registerRoute: Route = {
path: 'register',
component: RegisterComponent,
data: {
authorities: [],
pageTitle: 'register.title'
}
};
可以看到registerRoute并没有声明路由守卫,因为要注册,那么必然没有用户信息,也就无需获取用户信息了。
服务调用
activate.service.ts
@Injectable()
export class ActivateService {
//注入封装过的http客户端
constructor(private http: Http) {}
//设置params,调用通讯API,返回Observable
get(key: string): Observable<any> {
const params: URLSearchParams = new URLSearchParams();
params.set('key', key);
return this.http.get(SERVER_API_URL + 'api/activate', {
search: params
}).map((res: Response) => res);
}
}
password.service.ts
password-reset-finish.service.ts
password-reset-init.service.ts
register.service.ts.ts
代码也类似,十分简单。值得注意的是setting并没单独的service。用户信息保存是AccountService的一部分,直接调用即可。
功能组件
组件都使用模板表单的构造方式,通过在html中定义ngModel进行数据绑定,通过定义内部状态,控制表单的提示信息。以下仅分析register组件源码
register.component.ts
export class RegisterComponent implements OnInit, AfterViewInit {
confirmPassword: string;
doNotMatch: string;
error: string;
errorEmailExists: string;
errorUserExists: string;
registerAccount: any;
success: boolean;
modalRef: NgbModalRef;
//注入语言服务,登录框,注册服务,以及标签本身,绘制器
constructor(
private languageService: JhiLanguageService,
private loginModalService: LoginModalService,
private registerService: Register,
private elementRef: ElementRef,
private renderer: Renderer
) {
}
ngOnInit() {
this.success = false;
this.registerAccount = {};
}
//默认激活login按钮
ngAfterViewInit() {
this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#login'), 'focus', []);
}
register() {
//并非实时判断两个input的匹配程度,提交的时候才会判断,如果不匹配,就提示错误
if (this.registerAccount.password !== this.confirmPassword) {
this.doNotMatch = 'ERROR';
} else {
//两个密码框相符,则进行注册接口调用。
this.doNotMatch = null;
this.error = null;
this.errorUserExists = null;
this.errorEmailExists = null;
this.languageService.getCurrent().then((key) => {
this.registerAccount.langKey = key;
this.registerService.save(this.registerAccount).subscribe(() => {
this.success = true;
}, (response) => this.processError(response));
});
}
}
//通用的登录框显示
openLogin() {
this.modalRef = this.loginModalService.open();
}
//错误的response处理,出错时候返回type表示不同错误
private processError(response) {
this.success = null;
if (response.status === 400 && response.json().type === LOGIN_ALREADY_USED_TYPE) {
this.errorUserExists = 'ERROR';
} else if (response.status === 400 && response.json().type === EMAIL_ALREADY_USED_TYPE) {
this.errorEmailExists = 'ERROR';
} else {
this.error = 'ERROR';
}
}
}
register.component.html
<div>
<div class="row justify-content-center">
<div class="col-md-8">
<h1 jhiTranslate="register.title">Registration</h1>
<!-- 成功提示,仅当注册成功时显示 -->
<div class="alert alert-success" *ngIf="success" jhiTranslate="register.messages.success">
<strong>Registration saved!</strong> Please check your email for confirmation.
</div>
<!-- 失败提示,仅当注册失败时显示 -->
<div class="alert alert-danger" *ngIf="error" jhiTranslate="register.messages.error.fail">
<strong>Registration failed!</strong> Please try again later.
</div>
<!-- 失败提示,仅当注册失败,且服务器返回用户已存在时显示 -->
<div class="alert alert-danger" *ngIf="errorUserExists" jhiTranslate="register.messages.error.userexists">
<strong>Login name already registered!</strong> Please choose another one.
</div>
<!-- 失败提示,仅当注册失败,且服务器返回注册Email已存在时显示 -->
<div class="alert alert-danger" *ngIf="errorEmailExists" jhiTranslate="register.messages.error.emailexists">
<strong>Email is already in use!</strong> Please choose another one.
</div>
<!-- 失败提示,仅当两个密码输入框不符时显示 -->
<div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch">
The password and its confirmation do not match!
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<!-- 窗体form定义,提交方法为register,如果注册成功,就不显示了 -->
<form name="form" role="form" (ngSubmit)="register()" #registerForm="ngForm" *ngIf="!success">
<div class="form-group">
<label class="form-control-label" for="login" jhiTranslate="global.form.username">Username</label>
<!-- 标准的模板方式生成窗体方式,使用ngModel绑定值到login,后面定义了一系列angular的验证器 -->
<input type="text" class="form-control" [(ngModel)]="registerAccount.login" id="login" name="login" #login="ngModel" placeholder="{{'global.form.username.placeholder' | translate}}" required minlength="1" maxlength="50" pattern="^[_'.@A-Za-z0-9-]*$">
<!-- 统一的错误提示div,包含多个small提示,根据不同的错误类型,有不同的提示 -->
<div *ngIf="login.dirty && login.invalid">
<small class="form-text text-danger" *ngIf="login.errors.required" jhiTranslate="register.messages.validate.login.required">
Your username is required.
</small>
<small class="form-text text-danger" *ngIf="login.errors.minlength" jhiTranslate="register.messages.validate.login.minlength">
Your username is required to be at least 1 character.
</small>
<small class="form-text text-danger" *ngIf="login.errors.maxlength" jhiTranslate="register.messages.validate.login.maxlength">
Your username cannot be longer than 50 characters.
</small>
<small class="form-text text-danger" *ngIf="login.errors.pattern" jhiTranslate="register.messages.validate.login.pattern">
Your username can only contain lower-case letters and digits.
</small>
</div>
</div>
<!-- 省略email,密码,确认密码框 -->
<!-- 如果窗体不合法,就disable提交按钮 -->
<button type="submit" [disabled]="registerForm.form.invalid" class="btn btn-primary" jhiTranslate="register.form.button">Register</button>
</form>
<p></p>
<!-- 窗体之外,增加默认用户的说明 -->
<div class="alert alert-warning">
<span jhiTranslate="global.messages.info.authenticated.prefix">If you want to </span>
<a class="alert-link" (click)="openLogin()" jhiTranslate="global.messages.info.authenticated.link">sign in</a><span jhiTranslate="global.messages.info.authenticated.suffix">, you can try the default accounts:<br/>- Administrator (login="admin" and password="admin") <br/>- User (login="user" and password="user").</span>
</div>
</div>
</div>
</div>