LoginSignup
23
22

More than 5 years have passed since last update.

Angular2 DOC GUIDEを翻訳する[ARCHITECTURE OVERVIEW]

Last updated at Posted at 2016-10-04

トピック

Angular2 CORE DOCUMENTATIONのGUIDEの翻訳です。

注意1)ここに掲載されていない項目は、Angular2 CORE DOCUMENTATIONのGUIDEを直接参照してください。

注意2)2016年10月4日時点の翻訳です。翻訳者はTOEICで700点くらいの英語力なので、英訳が間違っている可能性があります。しかもかなり意訳している箇所もあります。もし意訳を通り越して、誤訳になっているような箇所がありましたらご指摘ください。

ARCHITECTURE OVERVIEW - 構文概要 -

Angularアプリケーションの基礎となる構成要素

AngularはHTMLとJavaScript、もしくはJavaScriptをコンパイルする言語(DartやTypeScript)といった、クライアントサイドのアプリケーションを構築するためのフレームワークです。

Angularはいくつかのコアとなるライブラリと、オプション的なライブラリから構成されています。Angularアプリケーションを作るときは、まずAngular化したマークアップによるHTML テンプレートを構成し、それらのテンプレートを管理するコンポーネントクラスを書きます。それからサービスにアプリケーションロジックを追加して、モジュールにコンポーネントとサービスを追加します。

そしてルートモジュール起動させ、アプリが動き出せば、ブラウザで保持していた状態をあなたが発したユーザーインタラクションに引き継ぐようになります。

もちろん、Angularには抑えるべきことがまだまだあります。それをこれから、このページで詳しく学んでいきましょう。まずは全体像をお見せします。

overview

この構造図は、Angularアプリケーションのもつ8つの主要な構成要素からなっています。

これらの構成要素を、あなたなりのやり方で学んでみてください。

このページで参照されているコードは、live exampleで利用できます。

Modules - モジュール -

Component

Angularアプリはモジュール(組み立てられるもの)であり、Angularそれ自体もAngular modulesもしくはNgModulesと呼ばれるモジュラリティシステムです。

Angularモジュールはかなり大きなトピックなので、このページではモジュールの紹介に留めておきます。
Angularモジュールのページで、もっと深く掘り下げます。

すべてのAngularアプリは、最低でも一つ、慣習的にAppModuleと名付けられたルートモジュールを持ちます。

小さなアプリケーションであれば、ルートモジュールがアプリ内で唯一のモジュールとなりますが、多くのアプリでは、それ以外にも多くの機能モジュールを持っています。機能モジュールは、それぞれがアプリケーション領域やワークフロー、または性能が密接に関連した機能に注力したコードの集合体となっています。

Angularモジュールは、ルートモジュールであれ機能モジュールであれ、@NgModuleデコレータをもったクラスとなります。

デコレータは、JavaScript のクラスを修飾する機能です。Angularには複数のデコレータが用意されていますが、それらはクラスが何を意味するのか、どんな働きをするのかがわかるように、メタデータをクラスに付随させます。このサイトでデコレータについて学習してください。

NgModuleはデコレータであり、モジュールであることを説明するプロパティをもった、たった一つのメタデータオブジェクトです。最も重要なプロパティを次にあげます。

  • declarations - このモジュールに属するビュークラスです。Angularはcomponentsdirectivespipesという3種類のビュークラスを持ちます。
  • exports - ほかのモジュールにあるコンポーネントテンプレートの中で、閲覧および使用を可能にするdeclarationsのサブセットです。
  • imports - exportされたクラスを持ち、このモジュール内で宣言されたコンポーネントテンプレートに必要とされるほかのモジュールです。
  • providers - オリジナルで作成したサービスを、このモジュールのグローバルコレクションサービスに追加します。これにより、そのサービスはアプリ内のどこからでもアクセスできるようになります。
  • bootstrap - ルートコンポーネントと呼ばれるメインアプリケーションビューが、他のすべてのアプリケーションビューの基礎となります。bootstrapプロパティは、ルートモジュールでのみ設定するようにしてください。

以下は簡単なルートモジュールの例です。

app/app.module.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@NgModule({
  imports:      [ BrowserModule ],
  providers:    [ Logger ],
  declarations: [ AppComponent ],
  exports:      [ AppComponent ],
  bootstrap:    [ AppComponent ]
})
export class AppModule { }

AppComponentexportは、ただexportのやり方を見せるためだけの例なので、実際のところ、この例で使う必要はありません。
他のコンポーネントではルートモジュールをimportする必要がないので、ルートモジュールは何もexportすることがないのです。

ルートモジュールを起動し、アプリケーションを動かしてみてください。開発中にAppModuleを起動させるなら、main.tsファイルを次のようにするといいかもしれません。

app/main.ts
COPY CODE
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);

Angular modules vs. JavaScript modules

Angular moduleは、@NgModuleというデコレータをもつクラスであり、Angularの土台となる機能を担っています。

また、JavaScriptはそれ自体が、JavaScriptオブジェクトの集合体を管理するモジュールの仕組みをもっています
これはAngularのモジュールシステムとはまったく異なるものであり、関連性もありません。

JavaScriptは、それぞれのファイルが1つのモジュールとなっており、全てのオブジェクトはそのモジュールをもつファイルの中で定義されています。
JavaScriptモジュールはexportキーワードを使って、オブジェクトがpublicであることを宣言し、importステートメントを使って他のモジュールでpublicとなっているオブジェクトにアクセスします。

import { NgModule }     from '@angular/core';
import { AppComponent } from './app.component';
export class AppModule { }

このサイトでJavaScriptモジュールの仕組みについて学習してください。

Angularには、2つの異なる相互補完的なモジュールシステムがあります。あなたがアプリを作るときは、それらを使ってください。

Angular libraries

Component

AngularはJavaScriptモジュールの集合体を積んでおり、それらをライブラリモジュールとして扱っています。

Angularライブラリの名称は、@angularという接頭辞で始まります。

Angularライブラリはnpmパッケージマネージャーでインストールされ、JavaScriptのimportステートメントによってimportされます。

例として、@angular/coreからAngularのComponentデコレータをimportしてみます。

import { Component } from '@angular/core';

また、JavaScriptのimportステートメントを使って、Angular ライブラリからAngular モジュールをimportすることもできます。

import { BrowserModule } from '@angular/platform-browser';

上記の簡易的なルートモジュールの例では、アプリケーションモジュールはBrowserModuleからのデータを必要としています。
そのデータにアクセスするため、次のようにして@NgModuleのメタデータであるimportBrowserModuleを加えます。

imports:      [ BrowserModule ],

このようにして、AngularとJavaScriptのモジュールシステムは、同時に使用することが可能です。

しかし、それらは「imports」と「exports」という同じ語彙を共有しているので、2つのシステムに対し混乱を招きやすくしています。
頑張ってください。これを理解するためには、時間と経験が必要です。

Angular modulesのページでより深く学習してください。

Components - コンポーネント -

Component

コンポーネントは、ビューと呼ばれる画面部分を管理します。

たとえば、次のような画面がコンポーネントによってコントロールされています。

  • ナビゲーションリンクを伴うアプリのルート画面
  • heroesのリスト
  • heroの編集

ビューへのサポート内容を含んだコンポーネントのアプリケーションロジックを、クラスに定義する必要があります。そのクラスはAPIのプロパティとメソッドに対し、影響を与えるようになります。

たとえば、このHeroListComponentはサービスから要求されたときにheroesの配列を返すheroesプロパティを持っています。また、HeroListComponentは、ユーザーがheroListから1つheroを選んでクリックしたときに、selectedHeroプロパティを設定するselectHero()メソッドも持っています。

app/hero-list.component.ts(class)
export class HeroListComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(private service: HeroService) { }

  ngOnInit() {
    this.heroes = this.service.getHeroes();
  }

  selectHero(hero: Hero) { this.selectedHero = hero; }
}

Angularはアプリケーション上のユーザーの動きに合わせて、コンポーネントを作成し、更新し、破棄します。上記で宣言されているngOnInit()のように、任意のライフサイクルフックを使って、ライフサイクルのその時々にアクションを起こすことができます。

Templates - テンプレート-

Template

コンポーネントのビューを定義するときは、それに対応するテンプレートの定義も必要です。
テンプレートは、コンポーネントのレンダリング方法をHTML形式でAngularに伝えます。

テンプレートは多少の違いを除けば、普通のHTMLと同じように見えます。
HeroListComponentでは、次のようなテンプレートになっています。

app/hero-list.component.html
<h2>Hero List</h2>
<p><i>Pick a hero from the list</i></p>
<ul>
  <li *ngFor="let hero of heroes" (click)="selectHero(hero)">
    {{hero.name}}
  </li>
</ul>
<hero-detail *ngIf="selectedHero" [hero]="selectedHero"></hero-detail>

このテンプレートでは、典型的なHTML要素である<h2><p>を使っていますが、いくつかの違いも含んでいます。*ngFor{{hero.name}}(click)[hero]<hero-detail>のようなコードは、@Angularのテンプレート文法を使っています。

このテンプレートの最後の行では、新しいコンポーネントであるHeroDetailComponentを表示するために、<hero-detail>タグというカスタム要素を使っています。

HeroDetailComponentは、先にとりあげたHeroListComponentとは異なるコンポーネントです。HeroDetailComponent(コード出しません)は、HeroListComponentに表示されていたリストで、ユーザーが選択した特定のheroの情報を表示します。HeroDetailComponentは、HeroListComponentになります。

Metadata

<hero-detail>が、ネイティブなHTML要素とすごく馴染んでいることに注目してください。カスタムコンポーネントは、同じレイアウト上でネイティブなHTMLとシームレスに混在しています。

Metadata - メタデータ-

Metadata

メタデータはAngularにおけるクラスの処理方法を示します。

HeroListComponentのコードを振り返ってみてると、HeroListComponentがただのクラスであることがわかると思います。それがフレームワークであるという証拠も、「Angular」であるということも、その中にはまったく記述されていません。

事実として、HeroListComponentは本当にただのクラスです。Angularにコンポーネントであると伝えるまでは、コンポーネントではありません。

HeroListComponentがコンポーネントであるとAngularに伝えるためには、クラスにメタデータをつける必要があります。

TypeScriptでは、デコレータを使ってメタデータをつけることになります。HeroListComponentのメタデータは、次のようになります。

app/hero-list.component.ts(metadata)
@Component({
  moduleId: module.id,
  selector:    'hero-list',
  templateUrl: 'hero-list.component.html',
  providers:  [ HeroService ]
})
export class HeroListComponent implements OnInit {
/* . . . */
}

ここでは@Componentデコレータが、その下にあるクラスをすぐにコンポーネントクラスであると特定しています。

@Componentデコレータは、Angularがコンポーネントおよびビューを作成し、表示させるために必要な情報として、必須となる構成オブジェクトです。

@Componentで使用可能な、数少ない構成オプションは次の通りです。

  • moduleId: templateUrlのようなモジュールに関連するURLのために、ベースとなるアドレス(module.id)のソースを設定します。
  • selector: コンポーネントのインスタンスを作成し、挿入することをAngularに伝えるCSSセレクタです。となるHTMLの中で<hero-list>タグを見つけるためには、ここで明示しておく必要があります。たとえば、もしアプリ内にあるHTMLが<hero-list></hero-list>を含んでいた場合、Angularはこのタグの間にHeroListComponentのビューインスタンスを挿入します。
  • templateUrl: 上記のような、コンポーネントのHTMLテンプレートを紐づける、モジュールに関連するアドレスです。
  • providers: コンポーネントが必要とするサービスを使うために、Dependency Injectionプロバイダを列挙します。これはコンポーネントのコンストラクタがHeroServiceを要求することをAngularに伝える方法の1つであり、それによってheroesを表示するためのリストを取得できます。

Metadata

@Componentにあるメタデータは、コンポーネントで明示されている重要な構成要素が、どこで取得できるかをAngularに伝えます。

テンプレート、メタデータ、コンポーネントは連動してビューを描画します。

Angularの振る舞いを決めるために、同じような形をとっている場合、他のメタデータのデコレータを使ってください。
@Injectable@Input@Outputは、あまり多くはない、よく使われているデコレータです。

構築について総括すると、Angularに役割を伝えるためには、あなたの書いたコードにメタデータを加えなければならない、ということです。

Data binding - データバインディング-

フレームワークがなかった場合、HTMLにデータの値を流し込み、ユーザー操作をアクションと値の更新に変換するといったコントロールを、あなたが担保する必要があります。
pushやpullのようなロジックを自分で書くのはひどく退屈で間違いやすく、どんな熟練したjQueryプログラマーであったとしても、それを読むことは悪夢だと証言するでしょう。

Data Binding

Angularはデータバインディングという、コンポーネント部分とテンプレート部分を同調させるメカニズムをサポートしています。互いの接続方法をAngularに伝えるため、テンプレートHTMLにバインディングマークアップを加えてください。

図が示すように、データバインディングの文法には4つの記法があります。それぞれの記法が、「DOMへ」、「DOMから」、「両方向へ」という方向性を持っています。

HeroListComponentテンプレートの例では、3つの記法を使っています。

app/hero-list.component.html(binding)
<li>{{hero.name}}</li>
<hero-detail [hero]="selectedHero"></hero-detail>
<li (click)="selectHero(hero)"></li>
  • {{hero.name}}は、<li>タグの中で、コンポーネントにあるhero.nameプロパティの値と置き換えて表示されます。
  • [hero] プロパティバインディングは、親であるHeroListComponentから、子であるHeroDetailComponentheroプロパティにselectedHeroの値を渡します。
  • (click)イベントバインディングは、ユーザーがheroの名前をクリックしたときに、コンポーネントにあるselectHeroメソッドを呼び出します。

双方向バインディングは、ngModelディレクティブを使うときに、単一記法でプロパティとイベントのバインディングを結びつける重要な4つ目の記法です。
HeroDetailComponentテンプレートには、次のような例があります。

app/hero-detail.component.html(ngModel)
<input [(ngModel)]="hero.name">

双方向バインディングでは、プロパティバインディングによって、コンポーネントからインプットボックスにデータプロパティの値を渡しています。また、ユーザーが値を変更すると、イベントバインディングによってその情報がコンポーネントへと伝わり、プロパティを最新の値へと再設定します。

Angularはアプリケーションコンポーネントツリーにあるルートから、すべての子コンポーネントを通して、1回のJavaScriptイベントサイクルにつき、すべてのデータバインディングを処理しています。

Data Binding

データバインディングは、テンプレートとコンポーネントが相互に対話するにあたって、重要な役割を担っています。

Parent/Child binding

データバインディングは親と子のコンポーネントが対話するときにも重要です。

Directives - ディレクティブ-

Parent child

Angularテンプレートは動的なものです。Angularがテンプレートをレンダリングするとき、ディレクティブによって与えられた指示によってDOMを変更します。

ディレクティブはディレクティブのメタデータをもったクラスです。TypeScriptでは、クラスにメタデータを付随させる@Directiveデコレータを使ってください。

コンポーネントは、テンプレートを持ったディレクティブです。@Componentデコレータは、事実、テンプレートに関する機能を使って@Directiveデコレータを拡張したものです。

コンポーネントは技術的にはディレクティブですが、Angularアプリケーションの特徴的かつ中心的な機能であるため、このarchitectural overviewでは、ディレクティブとコンポーネントを区別して扱っています。

それ以外にも、2種類の異なるディレクティブが存在します。構造ディレクティブと、属性ディレクティブです。

それらのディレクティブは、タグ要素の属性や、たまにタグ名称としても使われますが、代入やバインディングの対象となる場合がほとんどです。

構造ディレクティブは、DOMの要素を追加、削除、交換することで、レイアウトを変更します。

次のテンプレートの例では、2つのビルトイン構造ディレクティブを使っています。

app/hero-list.component.html(structural)
<li *ngFor="let hero of heroes"></li>
<hero-detail *ngIf="selectedHero"></hero-detail>
  • *ngForはheroesリストをまわして、heroごとに1つの<li>を出力するようAngularに伝えています。
  • *ngIfは、選択されたheroが存在している場合のみ、HeroDetailコンポーネントを表示させます。

属性ディレクティブは、表示されている要素の振る舞い、もしくは表現を変更します。テンプレートでは、通常のHTML属性に似ているので、このように呼ばれています。

双方向バインディングを使用する手段として用いられるngModelディレクティブは、属性ディレクティブの1例です。ngModelは、表示している値のプロパティを設定したり、イベントを変更するよう指示したりすることで、表示されている要素の振る舞い(典型的な例としては<input>)を変更します。

app/hero-detail.component.html(ngModel)
<input [(ngModel)]="hero.name">

それほど多くはないですが、Angularにはレイアウト構造を変更するディレクティブ(例:ngSwitch)や、DOM要素やコンポーネントの様相を修正するディレクティブ(例:ngStylengClass)もあります。

もちろん、自分用のディレクティブを書くこともできます。HeroListComponentのようなコンポーネントは、カスタムディレクティブの1例です。

Services - サービス-

Service

サービスは幅広く対応しているカテゴリーで、アプリケーションに求められる値、関数、機能のすべてを包括しています。

どんなものでも、サービスとなることができます。サービスは規模が小さく、よく使われる目的をもったクラスであることが多いですが、その目的はわかりやすく、よく練られたものであった方がよいでしょう。

サービスの例

  • ログサービス
  • データサービス
  • バグ報告
  • 税計算
  • アプリケーション環境設定

Angularのサービスに、特別なことは何もありません。
サービスは何も定義されておらず、サービスの元となるクラスも、設置すべき場所も決まっていません。

しかし、サービスは全てのAngularアプリケーションの基礎となっています。それでいうと、コンポーネントはサービスのお得意様、ということになります。

ここで、ブラウザコンソールにログを表示させるサービスクラスの例を示します。

app/logger.service.ts(class)
export class Logger {
  log(msg: any)   { console.log(msg); }
  error(msg: any) { console.error(msg); }
  warn(msg: any)  { console.warn(msg); }
}

次に、起動したプロミスで、herosにアクセスして返すHeroServiceをお見せします。HeroServiceはLogger serviceともうひとつ、サーバーとの対話というあまりやりたくない仕事をするBackendServiceに依存しています。

app/hero.service.ts(class)
export class HeroService {
  private heroes: Hero[] = [];

  constructor(
    private backend: BackendService,
    private logger: Logger) { }

  getHeroes() {
    this.backend.getAll(Hero).then( (heroes: Hero[]) => {
      this.logger.log(`Fetched ${heroes.length} heroes.`);
      this.heroes.push(...heroes); // fill cache
    });
    return this.heroes;
  }
}

サービスはどこにあっても構いません。

コンポーネントクラスの記述は少なくあるべきです。コンポーネントクラスで、サーバーのデータにアクセスしたり、ユーザー入力のバリデーションを行ったり、コンソールに直接ログを吐かせたりはせず、そういった作業はサービスにやらせてください。

コンポーネントがすべき仕事は、UXを可能たらしめること以外の何者でもなく、ビュー(テンプレートによってレンダリングされるもの)とアプリケーションロジック(モデルの概念としてよく使われるもの)の調整につとめるべきです。
コンポーネントは、データバインディングに使うプロパティとメソッドを持つものがよく、それによって重要なものをすべてサービスに任せることができます。

Angularはこういった原則を強制しません。あなたが3000行の「キッチンシンク」(なんでも入った多機能なものの暗喩)のようなコンポーネントを書いたとしても、とがめることはありません。

Angularは、アプリケーションロジックをサービスに取り入れやすくすることで、上記のような原則に準拠しやすくしたり、Dependency injectionを使って、サービスをコンポーネントで利用できるようにしたりする支援を行います。

Dependency injection - 依存性注入-

Service

Dependency injectionは、クラスが必要とする依存性を完全な形で保ちつつ、クラスから新しいインスタンスをつくる手法です。クラスが依存するものの多くはサービスです。Angularは必要なサービスを持ったまま、新しいコンポーネントを作るためにDependency injectionを利用しています。

Angularでは、コンストラクタにあるパラメーターの型を見ることで、どのサービスが必要とされているか判別することができます。
以下の例では、HeroListComponentのコンストラクタはHeroServiceを要求しています。

app/hero-list.component.ts(constructor)
constructor(private service: HeroService) { }

Angularでコンポーネントが作られるとき、コンポーネントが要求するサービスのインジェクタが最初に求められます。

インジェクタは、事前に作られたサービスインスタンスのコンテナを保持します。もし要求されたサービスインスタンスがコンテナになかった場合、インジェクタは新しくサービスを作って、Angularに返す前にコンテナに加えます。
要求されたサービスが全て実行され、返されたとき、それらのサービスを引数として受け取ったコンポーネントのコンストラクタを呼び出すことができます。これがDependency injectionです。

HeroService を注入する過程は、次のようになります。

Service

もしインジェクタがHeroServiceを持っていなかった場合、新しいHeroServiceの作り方はどのようにしてわかるのでしょうか。

簡単に言ってしまえば、そのインジェクタを持ったHeroServiceプロバイダをあらかじめ登録しておく必要があります。プロバイダはサービス(大抵の場合は、そのサービス自身)を作るか、返す機能を持っています。

プロバイダはモジュール、もしくはコンポーネントに登録することができます。

同じサービスのインスタンスをどこからでも利用できるよう、プロバイダは基本的にルートモジュールに加えるようにしてください。

app/app.module.ts(module_providers)
providers: [
  BackendService,
  HeroService,
  Logger
],

その代り、コンポーネントレベルでは@Componentメタデータにあるprovidersプロパティの中に加えるようにしてください。

app/hero-list.component.ts(component_providers)
@Component({
  moduleId: module.id,
  selector:    'hero-list',
  templateUrl: 'hero-list.component.html',
  providers:  [ HeroService ]
})

コンポーネントレベルにプロバイダを登録することで、新しく作られたコンポーネントのインスタンスごとに、新しいサービスのインスタンスを取得することができるようになります。

忘れないように、Dependency injectionのポイントを挙げておきます。

  • Dependency injectionはAngularフレームワークの中に張り巡らされていて、どこからでも利用てきる。
  • インジェクタが核となるメカニズムとなる。
    • インジェクタは作成したサービスインスタンスのコンテナを保持する。
    • インジェクタは、プロバイダから新しいサービスのインスタンスを作成できる。
  • プロバイダはサービスを作るときのレシピになる。
  • インジェクタを持ったプロバイダを登録する。

Wrap up - 要約 -

Angularアプリケーションのもつ8つの主要な構成要素について、基礎的な部分を学習しました。

これらはAngularアプリケーションにおけるすべての基礎であり、とっかかりとしては十分すぎる内容です。ただし、これであなたが学習すべきことをすべて終えたわけではありません。

ここで簡単に、Angularの主要な機能、サービスのアルファベット順リストを掲載します。
大事なもののほとんどは、次のドキュメントで網羅されています(もしくは近日中に網羅します)。

  • Animations: Angularのアニメーションライブラリを使えば、アニメーション技術やCSSの深い知識がなくてもコンポーネントの振る舞いに動きをつけられます。
  • Change detection: Change detection(変更検知)のドキュメントでは、Angularがコンポーネントのもつプロパティの値の変化をどのようにして決め、どのタイミングで画面を更新しているかといったトピックを扱います。また、非同期で動いているものを中断し、その変更検知がもつ戦略を実行するゾーンの使用方法についても扱います。
  • Events: イベントのドキュメントでは、イベントを発行、購読するメカニズムを使って、コンポーネントやサービスでイベントを発火させる仕組みを扱っています。
  • Forms: HTMLベースのバリデーションとdirtyチェックで、複雑なデータ入力のシナリオをサポートします。
  • HTTP: HTTPクライアントを使ってサーバーサイドのアクションを引き起こし、データの取得、保存といったやり取りをサーバーと行います。
  • Lifecycle hooks: ライフサイクルフックのインターフェースを実行すると、コンポーネントが生成されてから破壊されるまでの存続期間中に、キー入力のタイミングが利用できます。
  • Pipes: 画面上にある値の変化によってUXを変更させるには、テンプレートでパイプを使うとよいです。次の例で、currencyパイプの書き方を見てみましょう。
    • price | currency:'USD':true
    • これによって、"42.33"という値は $42.33と表示されます。
  • Router: ブラウザを離れることなく、クライアントアプリケーション上でページからページへ遷移できます。
  • Testing: Angularのテストプラットフォームを使って、あなたの作ったアプリケーションの部品が、Angularフレームワーク上でうまく機能しているかどうか、ユニットテストテストを行って試すことができます。

Next Step
Displaying Data- データ表示 -

23
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
22