如何在JHipster Angular应用程序中使用自动完成组件

JHipster是一种引导你的应用程序的好方法。你的应用程序可以是一个单体,也可以分成几个微服务,使用JWT或OAuth2,用Docker打包,部署在云供应商上......JHipster可以处理沉重的技术复杂性。很好!但是......当涉及到从组合框中选择一个项目时,JHipster就不是那么好了。

在这篇文章中,我将告诉你如何改进生成的JHipster Angular代码,这样你就可以有一个(优化的)自动完成,而不仅仅是一个使用PrimeNG的普通组合框。

使用案例

让我们来看看一个简单的用例:一个联系人(在一个组织内可以联系的人)有一种偏好的交流语言。例如,"保罗喜欢说英语"和 "保罗喜欢说葡萄牙语"。由于JDL Studio的存在,我们可以对这种商业模式有一个可视化的表述:

对于那些了解JHipster及其JDL语言的人来说,以下是这种关系的JDL语法:

\[sourcecode language="shell"\]  
entity Contact {  
firstName String required,  
lastName String required,  
email String  
}

实体 Language {  
alpha3b String required maxlength(3),  
alpha2 String required maxlength(2)  
name String required,  
flag32 String,  
flag128 String,  
activated Boolean  
}

关系 ManyToOne {  
Contact{language(name) required} to Language  
}  
\[/sourcecode\]

如果你想到处理这种要求的用户界面,你可以看到,当创建一个联系人时,你需要从一个组合框中选择一种语言。而这正是这篇博文的主题。

裸露的JHipster生成的应用程序

当我们使用JHipster生成一个简单的应用程序,在联系人和语言之间有多对一的关系时,我们得到了一个限制在20项的组合框。因为在现实生活中,大约有180种 "官方"语言,一个20项的组合框是不够的。所以,在这里,当我试图创建一个新的联系人时,JHipster只给了我20种第一语言(这在现实生活中是没有用的)。

使用默认的JHipster生成的代码,没有办法得到 "英语 "作为语言(在字母表中太远了)。

在代码方面,生成的Angular组件由两个文件组成:一个HTML和一个TypeScript文件。这就是它的模样。HTML文件与联系人的语言绑定([(ngModel)]="contact.language"]),并通过ngFor从后端获取语言列表。

\[sourcecode language="html" title="contact-update.component.html"\]  
<div class="form-group">  
<label class="form-control-label" jhiTranslate="noautocompleteApp.contact.language" for="field\_language">Language</label>  
<select class="form-control" id="field\_language" name="language" \[(ngModel)\]=" contact.language" required>  
<option \*ngIf="!editForm.value.language" \[ngValue\]="null" selected></option>  
<option \[ngValue\]="languageOption.id== contact.language?.id ?contact.language : languageOption" \*ngFor="let languageOption of languages; trackBy: trackLanguageById">{{languageOption.name}}</option>  
</select>  
</div>  
\[/sourcecode\]

\[sourcecode language="javascript" title="contact-update.component.ts"\]  
ngOnInit() {  
// ...  
this.languageService.query()subscribe(  
(res: HttpResponse<ILanguage\[\]>) => {  
this.languages = res.body;  
},  
(res: HttpErrorResponse) => this.onError(res.message)  
) ;  
}  
\[/sourcecode\]

带有实体的PrimeNG自动完成组件

这时你去PrimeNG,拿起一个更聪明的组件:自动完成组件。因此,不要使用默认的JHipster组合框,只需设置PrimeNG并修改几行代码,就可以得到一个组合框,在你输入时提示语言。最终的结果是这样的。

正如你所看到的,我们不再有一个愚蠢的组合框,而是一个自动完成的组件,在你每次输入几个字符时调用后端(例如,输入en会带回亚美尼亚语、孟加拉语、车臣语...)。为了让它工作,我们需要用以下NPM命令安装PrimeNG。

\[sourcecode language="shell"\]  
$ npm install primeng -save  
$ npm install primeicons -save  
$ npm install @angular/animations -save  
\[/sourcecode\]

然后,在JHipstervendor.scss文件中添加PrimeNG的CSS。一旦PrimeNG设置完毕,我们需要修改contact-update.component.html文件并添加p-autoComplete组件。

注意这个组件的属性。首先,直接在 contact.language 变量中进行绑定。这个字段属性很重要,因为它在自动完成组件中显示了language.name值(亚美尼亚语、孟加拉语、车臣语......)(否则你会得到[Object对象])。completeMethod调用searchLanguages方法,该方法负责调用后端并获得建议的语言列表(建议属性)。

\[sourcecode language="html" title="contact-update.component.html" \]  
<div class="form-group">  
<label class="form-control-label" jhiTranslate="autocompleteentityApp.contact.language" for="field\_language">Language</label>  
<div class="form-group">  
<p-autoComplete id="field\_language" name="language" \[(ngModel)\]=" contact.language" field="name" \[sugges\]="suggedLanguages" (completeMethod)="searchLanguages($event)" required>  
</p-autoComplete>  
</div>  
</div>  
\[/sourcecode]

现在只需要对组件的Typescript部分进行编码。多亏了JHipster的过滤功能,我们没有什么可做的。事实上,我们重新使用生成的方法查询,并传递正确的参数'name.contains'和我们在自动完成组件中输入的值($event.query)。这就是contact-update.component.ts的样子。

\[sourcecode language="javascript" title=" contact-update.component.ts" \]  
suggestedLanguages:ILanguage\[\];

searchLanguages($event) {  
this.languageService.query({'name.contains': $event.query}).subscribe(  
(res: HttpResponse<ILanguage\[\]>) => {  
this.suggestedLanguages = res.body;  
},  
(res: HttpErrorResponse) => this.onError(res.message)  
);  
}  
\[/sourcecode\]

带有DTOs的PrimeNG自动完成组件

前面的例子直接使用了生成的实体(Contact和Language)。事实上,那是默认的。JHipster在其REST端点中直接使用域对象。但相反,最好要求JHipster生成一个DTO层(实体<->DTO的映射由MapStruct完成)。这是一个更好的做法,因为在后端和前端之间交换的JSON中必须精确(如果可能的话,不要有太多的数据)。因此,当我们用DTOs生成应用程序时,我们不使用Contact和Language之间的联系(我们不再有contact.language关系),而是ContactDTO有一个languageId和一个languageName属性。这看起来像这样。

这改变了我们在Angular中进行双向绑定的方式。在上面的例子中,直接使用实体,我们的自动完成组件被绑定到 contact.language([(ngModel)]="contact.language")。但是现在,我们需要将我们的组件绑定到ContactDTO上,而ContactDTO并没有与LanguageDTO的链接,而是一个languageId和languageName属性。

\[sourcecode language="html" title="Contact-update.component.html"\]  
<p-autoComplete id="field\_language" name="language" \[(ngModel)\]="selectedLanguage" field="name" \[sugges\]="suggestLanguages" (completeMethod)="searchLanguages($event)" (onSelect)="captureSelectedLanguage($event) " required>  
</p-autoComplete>  
\[/sourcecode]

所以诀窍是在外部selectedLanguage属性上进行双向绑定,然后使用onSelect来捕获在自动完成组件中选择的languageId和languageName。这个手稿看起来像这样。

\[sourcecode language="javascript" title="Contact-update.component.ts"\]  
selectedLanguage:ILanguage;

ngOnInit() {  
this.isSaving = false;  
this.activatedRoute.data.subscribe((contact:IContact) => {  
this.contact = contact;  
if (contact.languageId) {  
this.selectedLanguage = new Language();  
this.selectedLanguage.id = contact.languageId;  
this.selectedLanguage.name = contact.languageName;  
}  
}) ;  
}

captureSelectedLanguage($event) {  
this.selectedLanguage = $event;  
this.contact.languageId = $event.id;  
this.contact.languageName = $event.name;  
}  
\[/sourcecode\]

其他方法不会改变(如searchLanguages)。

用杰克逊视图优化网络流量

使用DTOs的一个好处是,你可以对你想要从后端到前端来回的数据非常具体。如果你检查网络日志,你会注意到当你在自动完成组件中输入数据时,会在JSON中返回一个完整的语言列表。因此,如果你输入en,自动完成组件将在URIhttp://localhost:9000/api/languages?name.contains=en调用后端API,这将返回。

{
  "id" : 19,
  "alpha3b" : "ben",
  "alpha2" : "bn",
  "name" : "Bengali",
  "flag32" : "",
  "flag128" : "",
  "activated" : false
}, {
  "id" : 39,
  "alpha3b" : "eng",
  "alpha2" : "en",
  "name" : "English",
  "flag32" : "GB-32.png",
  "flag128" : "GB-128.png",
  "activated" : true
}, {
  "id" : 46,
  "alpha3b" : "fre",
  "alpha2" : "fr",
  "name" : "French",
  "flag32" : "FR-32.png",
  "flag128" : "FR-128.png",
  "activated" : true
}, {
  ...
}

这是因为LanguageDTO拥有Language实体的所有属性。这是一个遗憾,因为我们在自动完成组件中真正需要的只是语言的ID和名称。因此,我们希望从后端返回以下的JSon结构。

{
  "id" : 19,
  "name" : "Bengali"
}, {
  "id" : 39,
  "name" : "English"
}, {
  "id" : 46,
  "name" : "French"
}, {
  ...
}

这是因为调用api/languages?name.contains=en会执行一个服务,其代码看起来像这样。

\[sourcecode language="java" title="LanguageQueryService.java"\]  
public Page<LanguageDTO> findByCriteria(LanguageCriteria criteria, Pageable page) {  
final Specification<Language> specification = createSpecification(c criteria);  
return languageRepository.findAll(specification, page)  
.map(languageMapper::toDto);  
}  
\[/sourcecode]

注意实体和DTO之间的映射是如何实现的 LanguageRepository.findAll返回一个Language实体的列表,由于MapStruct,它们被映射到LanguageDTO的列表。

有几种方法可以只返回语言的ID和名称(例如,创建新的DTO,定制映射器,自己建立JSON),但我喜欢使用Jackson视图。这个想法是在一个 "视图 "中分组某些属性,并告诉Jackson只序列化一个特定的视图。这样,你可以对同一个DTO有几个 "视图"(最小、正常、扩展......)。下面的代码定义了一个Minimal格式。

\[sourcecode language="java" title="Format.java"\]  
public class Format {  
public static class Minimal {  
}  
}  
\[/sourcecode\]

然后,我们在Minimal格式下对id和name进行分组:

\[sourcecode language="java" title="LanguageDTO.java"\]  
public class LanguageDTO implements Serializable {

@JsonView(Format.Minimal.class)  
private Long id;

@NotNull  
@JsonView(Format.Minimal.class)  
private String name;

@NotNull  
@Size(max = 3)  
private String alpha3b;

// ...  
\[/sourcecode\]

然后就只需要用Minimal视图注释REST端点,Jackson就会只序列化id和name了:

\[sourcecode language="java" title="LanguageResource.java"\]  
@GetMapping("/languages")  
@JsonView(Format.Minimal.class)  
public ResponseEntity<List<LanguageDTO>> getAllLanguages(LanguageCriteria criteria, Pageable pageable) {  
Page<LanguageDTO> page = languageQueryService.findByCriteria(criteria, pageable);  
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/languages");  
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);  
}  
\[/sourcecode]

需要CSS帮助

BTW如果你们中有人知道Boostrap、JHipster、PrimeNG并且是CSS大师,你能告诉我为什么Language前面的垂直红条没有其他的那么大?如果可以,请给我发一个PR;o)#Thanks

总结

JHipster的GitHub问题上有很多关于创建一个类似家庭自动完成的组件的讨论。我理解JHipster希望与任何组件库都不相干。作为一个曾经的PrimeFaces用户,我很高兴发现PrimeNG周围有同样的组件、同样的团队、同样的社区。我是一个非常快乐的PrimeNG用户!

我希望这篇文章能帮助你在你的JHipster Angular应用程序中使用自动完成组件。你也应该看看其他的PrimeNG组件,它们都非常棒。

猜你喜欢

转载自juejin.im/post/7126031188463976461