Angular 应用程序开发的分类和创建 Angular UI 框架的故事
本文是2016年Angular Advent Calendar第21天的文章。
我在今年的下半年开发了一个名为angular2-onsenui的UI框架,下面我将简要介绍其规格。
首先,我将讨论在Angular应用开发中构建UI的模式,以及如何与每个模式中的CSS框架和UI框架进行配合。
首先
Angular于今年9月发布了稳定版(2.0.0),不再经历破坏性的改变。此外,现在已经有很多关于Angular周边概念的日语文章,如TypeScript、装饰器、层次依赖注入、动画效果、Zone.js、RxJS、Ahead-of-Time编译、Angular CLI、服务器端渲染(SSR)等。也有一些关于如何精通这些概念的技巧总结出来了。
我认为很多人可能已经达到了这个阶段,只要有心,就能制作任何组件和任何应用程序。
然而,完全从零开始实施所有内容是不明智的。我认为这些人会考虑使用库来轻松完成任务,这将成为下一步。我认为明年这类话题会增多。
在使用库来轻松处理的对象中,可以进行与后端(云服务、本地服务器等)的协作和Flux架构的实现等。但是,本文将讨论如何轻松构建用户界面。
用户界面的组件
可能な中国語翻译:构成UI的元素多种多样,但本文暂时仅讨论UI的构成要素。
スタイル: CSS だけで実装できる部分
UI ロジック: CSS だけでは実装できない部分(=複雑な視覚効果, UI 状態管理, …)
我决定将其抽象为两个概念来考虑(请注意,这两个概念都是为了这篇文章而创造的词语)。
根据对样式和UI逻辑的不同需求,Angular应用程序开发可以分为以下四种模式。
Angular 应用开发的模式
选项1:「我想自己准备样式」「我也想自己准备UI逻辑」
这是一个在需求对设计要求较高的PC网站的委托开发和自主开发中广泛应用的模式。
Angular通过CSS封装使得我们摆脱了冗长的CSS编码规范,相对容易地自行实现样式,并且只要不涉及复杂的多指触控等输入事件,自行实现UI逻辑也几乎没有什么困难。因此,许多人选择采用这种模式来确认Angular的表现力。
然而,自行实现复杂的UI逻辑往往是白费力气,容易成为重复造轮子,并且容易滋生错误。当需要一定程度的复杂UI逻辑时,最好采用后面提到的模式4,以便更轻松地构建UI。
选项1: “我想依赖库来处理样式,但是我想自己准备UI逻辑。”
在PC网站/移动网站中,应用逻辑比设计更重要。我认为在商业应用开发中,主要使用这种模式。
使用Angular时,根据上述第一种情况的原因,我会想要尝试用全新的方式编写所有的样式,但是如果对样式没有特别的执着,那么使用CSS框架是明智的选择。对于对CSS感到困惑的人来说,我认为暂时停止编写样式,并尝试使用CSS框架可能是个好主意。
听到CSS框架,你可能会想到Bootstrap,但现在有很多其他选择。因此,在Qiita上有很多推荐,请务必去查看。
选项1:模式3:“我想依赖库来设置样式”,“我也想依赖库来处理UI逻辑”。
这是在开发移动网站或 Cordova 应用程序时,如果需要支持移动设备特有的操作(例如滑动、捏放、多点触控等),需要选择的情况。

如同 angular.io 的首页上所写的 “One framework. Mobile & desktop.”,Angular 也被设计用于移动网站的使用。
然而,滑动事件和多点触控事件在本质上是复杂的,即使利用 Angular 的能力也很难自行进行实现。例如,要实现一个正常的轮播图或侧滑菜单,需要考虑到阈值和滑动加速度等因素来进行实现。
此外,在移动浏览器上实现60fps的动画就需要一些骚操作。
在开发这样的移动网站时,采用模式1或模式2是不明智的。(如果希望处理一定复杂的PC网站UI逻辑,使用模式1或模式2是不明智的)
在模式3中,将使用UI框架而不是CSS框架。CSS框架只提供样式,而UI框架提供样式和UI逻辑。也就是说,使用UI框架可以省去自己实现样式和UI逻辑的麻烦,更加轻松。
然而,与CSS框架不同的是,UI框架的选择非常有限。这是因为提供特定于某个JS框架的UI逻辑会导致维护成本非常高,相比CSS框架而言。
在Angular使用的UI框架中,有几个知名的选项。首先是适用于PC和移动设备的Material Design for Angular 2;另外,还有适用于PC和移动设备但更倾向于移动设备的Ionic2。此外还有我自己的作品Onsen UI 2。
请务必阅读第8天的@rdlabo的文章,其中详细整理了有关这方面的内容。
【初学者指南】一起看看与Angular2一起使用的知名第三方库 – Tech Tech 関西|科技科技関西
选项一:模式4:“我想自己准备风格”,“我想依赖库来处理用户界面逻辑”。
这种模式被用于当我们不想受到库的限制时,但是自己实现 UI 逻辑太难的情况下希望借助库的好处的情况下。
然而,目前尚未存在”只提供UI逻辑而不提供样式”的库,因此在这种情况下,需要修改和覆盖UI框架中附带的样式。
在这种情况下,如果进行超出UI框架接受范围的修改,将会遇到困扰于样式方面的错误,但与方案1相比,最终的工时会减少。
总结到这里
我們已經介紹了Angular應用開發的四種模式。
如果您正在選擇模式1,請務必考慮選擇模式2至模式4。
以下是附带话题。
我使用了Angular2-OnsenUI来创建。
由于今年下半年我创立了一个名为angular2-onsenui的软件包,所以我将简单介绍一下它的规格(补充说明:开发主要由@anatoo完成)。
angular2-onsenui是一个用于在Angular中使用Onsen UI 2的包。它是为模式3和4的使用而设计的。
可以制作的东西 (Koreareru mono)
我可以制作这样的东西。

运用方式
可以使用npm来安装onsenui和angular2-onsenui,并加载OnsenModule以供使用。关于详细步骤,可以参考@rdlabo的Angular CLI使用方法的总结。
放弃使用monaca-cli吧!为了使用OnsenUI2,最好从angular-cli开始的原因和使用方法 – Tech Tech Kansai|テクテク关西
http://tec-kansai.tech/technology-2584.php
文档存放在下面的位置。
-
- Angular 2 向け Onsen UI 2 ガイド – Onsen UI フレームワーク
- Angular 2 向け Onsen UI 2 ドキュメント – Onsen UI フレームワーク
可使用的组件
加载 OnsenModule 后,您可以使用以下选择器来使用 Onsen UI 2 的组件。
※ 这是 1.0.0-rc.3 版本的规范。
ons-navigator
OnsNavigator
なし
ページマネージャons-splitter
なし *3なしページマネージャons-splitter-side
OnsSplitterSide
なしページマネージャons-splitter-content
OnsSplitterContent
なし
ページマネージャons-tabbar
なしなしページマネージャons-tab
OnsTab
なし
ページons-page
なしなしページons-toolbar
なしなしページons-toolbar-button
なしなしページons-back-button
なしなしページons-bottom-toolbar
なしなし
モーダルウィンドウons-modal
なしModalFactory
ダイアログons-alert-dialog
なしAlertDialogFactory
ダイアログons-dialog
なしDialogFactory
ダイアログons-popover
なしPopoverFactory
アイコンons-icon
なしなし
ウィジェットons-pull-hook
OnsPullHook
なし
ウィジェットons-carousel
なしなしウィジェットons-carousel-item
なしなし
ウィジェットons-list
なしなしウィジェットons-list-header
なしなしウィジェットons-list-item
なしなしウィジェットons-lazy-repeat
OnsLazyRepeat
なし
ウィジェットons-input
OnsInput
なし
ウィジェットons-button
なし
ウィジェットons-range
OnsRange
なしウィジェットons-switch
OnsSwitch
なし
ウィジェットons-fab
なしなしウィジェットons-speed-dial
なしなしウィジェットons-speed-dial-item
なしなし
ウィジェットons-progress-bar
なしなしウィジェットons-progress-circular
なしなし
条件分岐ons-if
なしなし
グリッドレイアウトons-row
なしなしグリッドレイアウトons-col
なしなし
タッチイベントons-gesture-detector
なしなし
視覚効果ons-ripple
なしなし
严格来说,每个组件类实际上是使用 @Directive 而不是 @Component 进行实现的。因为 Onsen UI 在 template 上并没有使用 @Component,而是使用了 Custom Elements V1 的 connectedCallback 和 attributeChangedCallback 钩子来进行模板的附加,所以并不需要使用 @Component。
我们为需要动态生成的组件,如模态窗口和对话框,提供了组件工厂。
针对不需要输入、输出或方法的组件,我们没有准备组件类。
使用方法1:小部件
在模板中只需简单地放置 push 等标签即可使用小部件组件,无需特别考虑。
import {
Component,
OnsenModule,
NgModule,
CUSTOM_ELEMENTS_SCHEMA
} from 'angular2-onsenui';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
declare var alert: Function;
@Component({
selector: 'app',
template: `
<ons-button (click)="onClick()">push</ons-button>
`
})
export class AppComponent {
constructor() {
}
onClick() {
alert('Clicked!');
}
}
@NgModule({
imports: [OnsenModule],
declarations: [AppComponent],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
动作示例(Plunker):
https://plnkr.co/edit/rySiuIg8JF3bOn33Gn0S

方法二:页面管理器
使用页面管理器组件的方法有点复杂。
以下是使用 ons-navigator 的示例。
首先,在以下的代码中,我们通过在 DefaultPageComponent 类和 Page2Component 类中定义“页面”,并将其提供给 ons-navigator 元素的输入,来显示页面。
由于每个页面(实际上是组件)需要动态生成,因此我们在 NgModule 的 entryComponents 中预先注册了 DefaultPageComponent 类和 Page2Component 类。
页面组件的选择器也进行了属性指定,比如 ons-page[page2] 或 ons-page[default],这是因为使用 ons-page 时,当定义多个页面时选择器会发生冲突导致错误。
在每个页面中,通过调用使用DI获取的OnsNavigator实例的方法来实现页面的跳转。正如这段代码 this._navigator.element.popPage(); 所示,这个示例直接调用了与DOM元素相关联的方法,这是由于其内部使用了Custom Elements V1规范导致的。
import {
Component,
ViewChild,
Params,
OnsNavigator,
OnsenModule,
NgModule,
CUSTOM_ELEMENTS_SCHEMA
} from 'angular2-onsenui';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@Component({
selector: 'ons-page[page2]',
template: `
<ons-toolbar>
<div class="left"><ons-back-button>Back</ons-back-button></div>
<div class="center">Page2</div>
</ons-toolbar>
<div class="content">
<div style="text-align: center; margin: 10px">
<ons-button (click)="push()">push</ons-button>
<ons-button id="pop" (click)="pop()">pop</ons-button>
</div>
</div>
`
})
export class Page2Component {
constructor(private _navigator: OnsNavigator, private _params: Params) {
}
push() {
this._navigator.element.pushPage(Page2Component, {animation: 'slide');
}
pop() {
this._navigator.element.popPage();
}
}
@Component({
selector: 'ons-page[default]',
template: `
<ons-toolbar>
<div class="center">Page</div>
</ons-toolbar>
<div class="content">
<div style="text-align: center; margin: 10px">
<ons-button id="push" (click)="push(navi)">push</ons-button>
</div>
</div>
`
})
class DefaultPageComponent {
constructor(private _navigator: OnsNavigator) {
}
push() {
this._navigator.element.pushPage(Page2Component);
}
}
@Component({
selector: 'app',
template: `
<ons-navigator animation="slide" [page]="defaultPage"></ons-navigator>
`
})
export class AppComponent {
defaultPage = DefaultPageComponent
}
@NgModule({
imports: [OnsenModule],
declarations: [AppComponent, DefaultPageComponent, Page2Component],
entryComponents: [DefaultPageComponent, Page2Component],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
动作示例(Plunker):
请查看以下链接:https://plnkr.co/edit/MoLFcdTRTbD7C7v8A69C

选项3:模态窗口、对话框的用法。
应该使用组件工厂动态生成和使用类似模态窗口和对话框的组件,这些组件不应该属于应用程序的组件树内。
在下面的例子中,我们动态生成对话框,并通过点击按钮来触发显示对话框。
要进行动态生成对话框,首先需要在模板中定义一个没有选择器的 MyDialogComponent 类,它具有 ons-dialog 元素。然后将该类注册到NgModule的entryComponents中。接下来,执行由DI获取的DialogFactory实例的createDialog方法。然后,就会异步返回生成的ons-dialog元素(以及用于销毁对话框组件的函数)。最后,只需执行this._dialog.show(); 即可显示对话框。
angular2-onsenui提供了DialogFactory之外的AlertDialogFactory,PopoverFactory和ModalFactory。
使用组件工厂时,请在ngOnDestroy时不要忘记使用this._destroyDialog()等方法来销毁组件。
import {
Component,
DialogFactory,
AfterViewInit,
OnInit,
OnDestroy,
Params,
OnsenModule,
NgModule,
CUSTOM_ELEMENTS_SCHEMA
} from 'angular2-onsenui';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
@Component({
template: `
<ons-dialog animation="default" cancelable #dialog>
<div class="dialog-mask"></div>
<div class="dialog">
<div class="dialog-container" style="height: 200px;">
<ons-page>
<ons-toolbar>
<div class="center">Name</div>
</ons-toolbar>
<div class="content">
<div style="text-align: center">
<p>{{message}}</p>
<br>
<ons-button id="close" (click)="dialog.hide()">Close</ons-button>
</div>
</div>
</ons-page>
</div>
</div>
</ons-dialog>
`
})
class MyDialogComponent {
message = '';
constructor(params: Params) {
this.message = <string>params.at('message');
}
}
@Component({
selector: 'app',
template: `
<ons-page class="page">
<ons-toolbar>
<div class="center">Dialog</div>
</ons-toolbar>
<div class="content">
<div style="text-align: center;">
<br>
<ons-button id="open" (click)="show()">Open</ons-button>
</div>
</div>
</ons-page>
`
})
export class AppComponent implements OnInit, OnDestroy {
private _dialog: any;
private _destroyDialog: Function;
constructor(private _dialogFactory: DialogFactory) {
}
ngOnInit() {
this._dialogFactory
.createDialog(MyDialogComponent, {message: 'This is just an example.'})
.then(({dialog, destroy}) => {
this._dialog = dialog;
this._destroyDialog = destroy;
});
}
show() {
if (this._dialog) {
this._dialog.show();
}
}
ngOnDestroy() {
this._destroyDialog();
}
}
@NgModule({
imports: [OnsenModule],
declarations: [AppComponent, MyDialogComponent],
bootstrap: [AppComponent],
entryComponents: [MyDialogComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule);
动作示例(Plunker):
https://plnkr.co/edit/ZAu5BstNGklPIYliVBtt

总结
我介绍了4种Angular应用程序开发模式,其中UI组件分为样式和UI逻辑两部分进行定义。
此外,我简要介绍了我的作品 angular2-onsenui 的规格和使用方法。
我想談談別的話題。
由於經常被問及關於 Onsen UI 的開發目標,所以我們提前回答一下,Onsen UI 的開發並不是為了直接帶來利益(或者說,我認為這樣的開源軟體都是如此)。
最初是因為我們內部自家服務的需要,所以我們決定將其公開為開源軟體。
关于详细经过,请查看Onsen UI项目负责人@massie所写的文章。
Onsen UI的起源:亚锡尔博客
http://blog.asial.co.jp/1490