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 应用程序时,如果需要支持移动设备特有的操作(例如滑动、捏放、多点触控等),需要选择的情况。

スクリーンショット 2016-12-21 22.56.16.png

如同 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)

我可以制作这样的东西。

img_3_trimmed.png

运用方式

可以使用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 版本的规范。

種別セレクタコンポーネントクラス *1コンポーネントファクトリクラス *2ページマネージャons-navigatorOnsNavigatorなし

ページマネージャons-splitterなし *3なしページマネージャons-splitter-sideOnsSplitterSideなしページマネージャons-splitter-contentOnsSplitterContentなし

ページマネージャons-tabbarなしなしページマネージャons-tabOnsTabなし

ページ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-hookOnsPullHookなし

ウィジェットons-carouselなしなしウィジェットons-carousel-itemなしなし

ウィジェットons-listなしなしウィジェットons-list-headerなしなしウィジェットons-list-itemなしなしウィジェットons-lazy-repeatOnsLazyRepeatなし

ウィジェットons-inputOnsInputなし

ウィジェットons-buttonなし
ウィジェットons-rangeOnsRangeなしウィジェットons-switchOnsSwitchなし

ウィジェット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

スクリーンショット 2016-12-22 2.22.28.png

方法二:页面管理器

使用页面管理器组件的方法有点复杂。
以下是使用 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

3466c1cfa0fed7622006cc862e6ec9fa.gif

选项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

75b785084c8d0956734ce599c7546b91.gif

总结

我介绍了4种Angular应用程序开发模式,其中UI组件分为样式和UI逻辑两部分进行定义。

此外,我简要介绍了我的作品 angular2-onsenui 的规格和使用方法。

我想談談別的話題。

由於經常被問及關於 Onsen UI 的開發目標,所以我們提前回答一下,Onsen UI 的開發並不是為了直接帶來利益(或者說,我認為這樣的開源軟體都是如此)。
最初是因為我們內部自家服務的需要,所以我們決定將其公開為開源軟體。

关于详细经过,请查看Onsen UI项目负责人@massie所写的文章。

Onsen UI的起源:亚锡尔博客
http://blog.asial.co.jp/1490

bannerAds