🇬🇧 English Version

A quick-reference guide to the core concepts of Angular: Observables, RxJS, NgModules, Dependency Injection, HttpClient, Router, and Animations.

1. Observables

Observables provide support for passing messages between publishers and subscribers in your application. They offer significant benefits over other techniques for event handling, asynchronous programming, and handling multiple values.

  • Declarative: you define a function for publishing values, but it is not executed until a consumer subscribes. The consumer then receives notifications until the function completes or the consumer unsubscribes.
  • Multiple values of any type: literals, messages, or events. The API for receiving values is the same whether they arrive synchronously or asynchronously.
  • Uniform interface: setup and teardown are handled by the observable, so your code only needs to subscribe and, when done, unsubscribe, whether the stream is keystrokes, an HTTP response, or an interval timer.

Because of these advantages, observables are used extensively within Angular and are recommended for app development as well.

Basic usage and terms

As a publisher, you create an Observable instance that defines a subscriber function, which runs when a consumer calls subscribe(). It defines how to obtain or generate the values or messages to publish.

To execute the observable and start receiving notifications, call subscribe() and pass an observer, a JavaScript object that defines the handlers for the notifications you receive. subscribe() returns a Subscription object with an unsubscribe() method that stops notifications.

Defining observers

A handler for observable notifications implements the Observer interface: an object with callback methods for the three types of notifications an observable can send (next, error, complete).

Subscribing

An Observable begins publishing values only when someone subscribes. You subscribe by calling subscribe() and passing an observer object.

To demonstrate subscribing, we need an observable. There is a constructor for that, but for illustration we can use static methods on the Observable class that create simple observables:

  • Observable.of(...items): returns an Observable that synchronously delivers the values provided as arguments.
  • Observable.from(iterable): converts its argument to an Observable; commonly used to convert an array to an observable.
Subscribing to an observable

Creating observables

Use the Observable constructor to create a stream of any type. It takes the subscriber function to run when subscribe() executes. The subscriber function receives an Observer object and can publish values to its next() method.

Multicasting

A typical observable creates a new, independent execution for each subscribed observer: the first observer gets its own event handler, and a second observer gets another one in a separate execution.

Sometimes you want every subscription to get the same values, even if values have already started emitting. For example, an observable of clicks on the document object. Multicasting broadcasts to a list of subscribers in a single execution: instead of registering multiple listeners on the document, you re-use the first listener and send values to each subscriber.

When creating an observable, decide how it will be used and whether you want to multicast its values.

Error handling

Because observables produce values asynchronously, try/catch will not effectively catch errors. Instead, specify an error callback on the observer. Producing an error also causes the observable to clean up subscriptions and stop producing values. An observable either produces values (calling next) or completes, calling either complete or error.

Observer error callback

2. The RxJS library

Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. RxJS (Reactive Extensions for JavaScript) is a library for reactive programming using observables that makes it easier to compose asynchronous or callback-based code.

RxJS provides an implementation of the Observable type (needed until it becomes part of the language and browsers support it) plus utility functions for creating and working with observables. These can be used for:

  • Converting existing async code into observables
  • Iterating through the values in a stream
  • Mapping values to different types
  • Filtering streams
  • Composing multiple streams

Observable creation functions

RxJS offers many functions that create observables from events, timers, promises, and more.

Operators

Operators are functions built on the observable foundation that enable sophisticated manipulation of collections, such as map(), filter(), concat(), and flatMap(). They take configuration options and return a function that takes a source observable; when executed, it observes the source's emitted values.

Use pipes to link operators together. The pipe() function takes the functions you want to combine and returns a new function that runs them in sequence.

A set of operators applied to an observable is a recipe, a set of instructions for producing the values you want. By itself it does nothing; you must call subscribe() to produce a result.

Common operators

RxJS provides many operators, but only a handful are used frequently. See the RxJS API documentation for the full list.

For Angular apps, we prefer combining operators with pipes rather than chaining. Chaining is used in many RxJS examples.
Combining operators with pipe

Error handling with RxJS

In addition to the error() handler on subscription, RxJS provides the catchError operator for handling known errors in the observable recipe. For instance, if an API request errors or the value doesn't exist, you can catch the error and supply a default value so the stream continues instead of erroring out.

Naming conventions for observables

Since Angular apps are mostly TypeScript, you typically know when a variable is an observable. Angular doesn't enforce a naming convention, but you will often see observables named with a trailing $. This helps when scanning code, and if you want a property that stores the most recent value from an observable, you can use the same name with or without the $.

Naming convention with trailing $

3. Observables in Angular

Angular uses observables as an interface for many common asynchronous operations:

  • The EventEmitter class extends Observable.
  • The HTTP module uses observables to handle AJAX requests and responses.
  • The Router and Forms modules use observables to listen for and respond to user-input events.

Event emitter

EventEmitter is used when publishing values from a component through the @Output() decorator. It extends Observable and adds an emit() method to send arbitrary values; calling emit() passes the value to the next() method of any subscribed observer. Example of a component that listens for open and close events:

<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>

HTTP

HttpClient returns observables from HTTP method calls; for instance, http.get('/api') returns an observable. This has several advantages over promise-based HTTP APIs:

  • Observables do not mutate the server response (as chained .then() calls can). Instead, use a series of operators to transform values as needed.
  • HTTP requests are cancellable through unsubscribe().
  • Requests can be configured to get progress event updates.
  • Failed requests can be retried easily.

Router

Router.events provides events as observables. Use the RxJS filter() operator to look for events of interest and subscribe to them to make decisions based on the navigation sequence.

Reactive forms

Reactive forms use observables to monitor form control values. The FormControl properties valueChanges and statusChanges contain observables that raise change events. Subscribing to them is a way of triggering application logic within the component class.

4. Observables compared to other techniques

You can often use observables instead of promises to deliver values asynchronously, in place of event handlers, and, because they deliver multiple values, where you might otherwise build and operate on arrays.

Observables vs. promises

ObservablesPromises
Declarative: computation does not start until subscription. Useful for defining recipes to run whenever you need the result.Execute immediately on creation.
Provide many values over time.Provide one value.
Differentiate between chaining and subscription, so you can build complex transformation recipes for other parts of the system without executing the work.Only have .then() clauses.
subscribe() is responsible for handling errors, giving centralized and predictable error handling.Push errors to child promises.

Observables vs. the events API

Observables are very similar to event handlers: both define notification handlers and process multiple values delivered over time. Subscribing to an observable is equivalent to adding an event listener. A key difference is that you can configure an observable to transform an event before passing it to the handler. Using observables for events and async operations can also bring greater consistency in contexts such as HTTP requests.

Observables vs. arrays

An observable produces values over time, while an array is a static set of values. In a sense, observables are asynchronous where arrays are synchronous. In the following examples, ➞ implies asynchronous value delivery.

Observables vs arrays (1)
Observables vs arrays (2)

5. Bootstrapping

Prerequisite: JavaScript Modules vs. NgModules.

An NgModule describes how the application parts fit together. Every application has at least one Angular module, the root module that you bootstrap to launch the application. By convention it is called AppModule.

AppModule example

After the import statements is a class with the @NgModule decorator, which identifies AppModule as an NgModule class and takes a metadata object telling Angular how to compile and launch the application:

  • declarations: this application's lone component.
  • imports: import BrowserModule to get browser-specific services such as DOM rendering, sanitization, and location.
  • providers: the service providers.
  • bootstrap: the root component that Angular creates and inserts into the index.html host page.

The default CLI application has only one component, AppComponent, so it is in both the declarations and bootstrap arrays.

The declarations array

Tells Angular which components belong to the module. As you create more components, add them here. You must declare every component in exactly one NgModule class; using an undeclared component produces an error.

The same array is used for directives and pipes. To use a directive, component, or pipe in a module:

  1. Export it from the file where you wrote it.
  2. Import it into the appropriate module.
  3. Declare it in the @NgModule declarations array.

The bootstrap array

The application launches by bootstrapping the root AppModule (also referred to as an entryComponent). Among other things, bootstrapping creates the component(s) listed in the bootstrap array and inserts each into the browser DOM.

6. NgModules

Prerequisites: Bootstrapping and JavaScript Modules vs. NgModules.

NgModules configure the injector and the compiler and help organize related things together. An NgModule is a class marked by @NgModule, which takes a metadata object describing how to compile a component's template and how to create an injector at runtime. It identifies the module's own components, directives, and pipes, makes some public through exports, and can add service providers to the application's dependency injectors.

Angular modularity

Modules are a great way to organize an application and extend it with capabilities from external libraries. Angular libraries are NgModules, such as FormsModule, HttpClientModule, and RouterModule. Many third-party libraries are also available as NgModules, such as Material Design, Ionic, and AngularFire2.

NgModules consolidate components, directives, and pipes into cohesive blocks of functionality, each focused on a feature area, business domain, workflow, or common set of utilities. They can also add services, whether internally developed or from outside sources such as the Angular router and HTTP client. Modules can be loaded eagerly at startup or lazy loaded asynchronously by the router.

NgModule metadata does the following:

  • Declares which components, directives, and pipes belong to the module.
  • Makes some of them public so other modules' component templates can use them.
  • Imports other modules whose components, directives, and pipes this module needs.
  • Provides services that other application components can use.

The root module is all you need in a simple application. As the app grows, refactor it into feature modules that represent collections of related functionality, then import them into the root module.

7. Entry Components

An entry component is any component that Angular loads imperatively (not referenced in a template), by type. You specify one by bootstrapping it in an NgModule or including it in a routing definition. This contrasts with declarative components, which are included in templates.

There are two main kinds of entry components:

  • The bootstrapped root component.
  • A component you specify in a route definition.

Example of specifying a bootstrapped component, AppComponent, in a basic app.module.ts:

Bootstrapped entry component

8. Providers

A provider is an instruction to the DI system on how to obtain a value for a dependency. Most of the time, these dependencies are services that you create and provide.

Providing a service

In a CLI-generated app, create a service from the project root (replace User with your service name):

ng generate service User

This creates the following UserService skeleton (src/app/user.service.ts):

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

You can now inject UserService anywhere in your application. The class is decorated with @Injectable, which by default is configured with providedIn, creating a provider for the service. Here providedIn: 'root' specifies that the service is provided in the root injector.

Provider scope

When you add a provider to the root application injector, it is available throughout the app, and to all classes that have the lookup token. You should always provide your service in the root injector unless you want it available only when the consumer imports a particular @NgModule.

9. Singleton services

There are two ways to make a service a singleton in Angular:

  • Declare that the service should be provided in the application root.
  • Include the service in AppModule or in a module that is only imported by AppModule.

Beginning with Angular 6.0, the preferred way is to specify on the service that it should be provided in the application root.

10. Lazy loading feature modules

There are three main steps to set up a lazy loaded feature module:

  1. Create the feature module.
  2. Create the feature module's routing module.
  3. Configure the routes.
Lazy loading relationship

forRoot() and forChild()

The CLI adds RouterModule.forRoot(routes) to the imports array of app-routing.module.ts. This tells Angular that AppRoutingModule is the root routing module: it configures all the routes you pass to it, gives you access to router directives, and registers the router service. Use forRoot() only once, at the root level.

The CLI adds RouterModule.forChild(routes) to feature routing modules, so Angular knows the route list only provides additional routes. You can use forChild() in multiple modules.

forRoot() contains global injector configuration, such as configuring the Router. forChild() has no injector configuration, only directives such as RouterOutlet and RouterLink.

11. Dependency Injection in Angular

Dependency injection (DI) is an important application design pattern. Angular has its own DI framework, typically used in the design of Angular applications to increase their efficiency and modularity.

Dependencies are services or objects that a class needs to perform its function. DI is a coding pattern in which a class asks for dependencies from external sources rather than creating them itself. In Angular, the DI framework provides declared dependencies to a class when that class is instantiated, making apps flexible, efficient, robust, testable, and maintainable.

Create and register an injectable service

The DI framework lets you supply data to a component from an injectable service class defined in its own file. For example, an injectable service class that provides a list of heroes, registered as a provider of that service.

Having multiple classes in the same file can be confusing; we generally recommend defining components and services in separate files.

If you do combine them in one file, define the service first, then the component. Defining the component first causes a run-time null reference error. You can define the component first with forwardRef(), and forward references can also break circular dependencies (see the DI Cookbook).

Hello service

Configure an injector with a service provider

The @Injectable() decorator marks a class as injectable, but Angular can't inject it anywhere until you configure a dependency injector with a provider. The injector creates service instances and injects them into classes like HeroListComponent. You rarely create an injector yourself; Angular creates them as the app runs, starting with the root injector created during bootstrap.

A provider tells an injector how to create the service. You can configure providers at different levels by setting metadata in one of three places:

  • The @Injectable() decorator of the service itself.
  • The @NgModule() decorator of an NgModule.
  • The @Component() decorator of a component.

@Injectable() has the providedIn option, where you can specify the root injector or the injector of a specific NgModule. @NgModule() and @Component() have the providers option for module-level or component-level injectors.

Component injectors

Each component sets up its own injector with zero, one, or more providers. When you resolve an instance of Car at the deepest component (C), its injector produces a Car resolved by injector C, with an Engine resolved by injector B and Tires resolved by the root injector A.

Injector tree

12. HttpClient

Most front-end apps communicate with backend services over HTTP. Modern browsers support two APIs for this: XMLHttpRequest and fetch(). HttpClient in @angular/common/http offers a simplified client HTTP API that rests on XMLHttpRequest. Additional benefits include testability, typed request and response objects, request and response interception, Observable APIs, and streamlined error handling.

The sample app doesn't require a data server. It relies on the Angular in-memory-web-api, which replaces HttpClient's HttpBackend and simulates a REST-like backend. See AppModule imports for the configuration.
HttpClient GET example

Making a POST request

Apps often POST data to a server, for example when submitting a form. Here HeroesService posts when adding a hero:

/** POST: add a new hero to the database */
addHero (hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
    .pipe(
      catchError(this.handleError('addHero', hero))
    );
}

HttpEvents

You might expect intercept() and handle() to return observables of HttpResponse<any> like most HttpClient methods. Instead they return observables of HttpEvent<any>, because interceptors work at a lower level. A single request can generate multiple events, including upload and download progress events. HttpResponse is itself an event, of type HttpEventType.HttpResponseEvent.

Many interceptors only care about the outgoing request and simply return the stream from next.handle() unmodified. But interceptors that examine or modify the response will see all events. Your interceptor should return every event untouched unless it has a compelling reason to do otherwise.

Return a multi-valued Observable

HttpClient.get() normally returns an observable that either emits the data or an error, a "one and done" observable. An interceptor can change this into an observable that emits more than once.

Listening to progress events

Large transfers, such as file uploads, can take a long time, so give users feedback on progress. To enable it, create an HttpRequest with the reportProgress option set to true.

13. Routing & Navigation

The Angular Router enables navigation from one view to the next as users perform application tasks. It borrows from the familiar browser model:

  • Enter a URL in the address bar and the browser navigates to the corresponding page.
  • Click links on the page and the browser navigates to a new page.
  • Click back and forward and the browser moves through the history of pages.

The router interprets a browser URL as an instruction to navigate to a client-generated view, can pass optional parameters to the view component, can be bound to links, can navigate imperatively (button, dropdown, and so on), and logs activity in the browser's history so back and forward work.

<base href>

Most routing apps should add a <base> element as the first child of <head> in index.html so the router knows how to compose URLs. If the app folder is the application root, set it exactly as shown:

<base href="/">

Router imports

The Router is an optional service that presents a component view for a given URL. It is not part of Angular core; it lives in its own package, @angular/router.

import { RouterModule, Routes } from '@angular/router';

Configuration

A routed app has one singleton Router service. When the URL changes, the router looks for a matching Route to decide which component to display. A router has no routes until you configure it. The example below creates five route definitions, configures the router via RouterModule.forRoot, and adds the result to AppModule's imports.

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'hero/:id',      component: HeroDetailComponent },
  {
    path: 'heroes',
    component: HeroListComponent,
    data: { title: 'Heroes List' }
  },
  { path: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  { path: '**', component: PageNotFoundComponent }
];

Each Route maps a URL path to a component. There are no leading slashes in the path; the router parses and builds the final URL for you, so you can use relative and absolute paths. The :id in the second route is a route parameter token: in /hero/42, "42" is the value of id, which HeroDetailComponent uses to find and present that hero.

Router outlet

RouterOutlet is a directive used like a component. It is a placeholder marking where the router should display components.

<router-outlet></router-outlet>
<!-- Routed components go here -->

Router links

Navigation usually results from a user action such as clicking an anchor tag.

<nav>
  <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
  <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>

Active router links

The RouterLinkActive directive toggles CSS classes for active RouterLink bindings based on the current RouterState. The value is a space-delimited string of CSS classes added when the link is active and removed when inactive, for example [routerLinkActive]="'active fluffy'", or you can bind it to a component property that returns such a string.

Router state

After each successful navigation, the router builds a tree of ActivatedRoute objects that make up the current router state. Access it anywhere through the Router service's routerState property. Each ActivatedRoute provides methods to traverse up and down the tree to get information from parent, child, and sibling routes.

Activated route

The route path and parameters are available through the injected ActivatedRoute service.

Router events

During each navigation, the router emits events through Router.events, ranging from the start and end of navigation to many points in between.

A crisis center with child routes

Recommended pattern for Angular apps:

  • Each feature area resides in its own folder.
  • Each feature has its own Angular feature module.
  • Each area has its own area root component.
  • Each area root component has its own router outlet and child routes.
  • Feature area routes rarely (if ever) cross with routes of other features.

If your app had many feature areas, the component tree might look like this:

Component tree

14. Angular animations

Introduction

Animation provides the illusion of motion: HTML elements change styling over time. Well-designed animations make your app more fun and easier to use, and they are not just cosmetic:

  • Without animations, page transitions can seem abrupt and jarring.
  • Motion gives users a chance to detect the app's response to their actions.
  • Good animations intuitively call attention to where it is needed.

Animations typically involve multiple style transformations over time: an element can move, change color, grow or shrink, fade, or slide off the page, simultaneously or sequentially. Angular's animation system is built on CSS, so you can animate any property the browser considers animatable (positions, sizes, transforms, colors, borders, and more). See the W3C's CSS Transitions list.

Animation state and styles

Use state() to define states to call at the end of each transition. It takes a unique name such as open or closed and a style() function. Use style() to define the set of styles for a state; style attributes must be in camelCase. Multiple style attributes can be set at once for a state.

Transitions and timing

Without further refinement, a state change happens instantly with no fade or shrink. To soften it, define a transition(). It accepts two arguments: an expression defining the direction between two states, and an animate() function.

animate() defines the length, delay, and easing of a transition, and designates the style function for the transition period. It can also define keyframes() for multi-step animations.

Duration, delay, and easing

animate() takes a timings string in three parts: animate('duration delay easing').

  • duration (required): milliseconds as a plain number (100), a string in ms ('100ms'), or a string in seconds ('0.1s').
  • delay: same syntax as duration. Example: wait 100ms and run for 200ms: '0.2s 100ms'.
  • easing: controls acceleration and deceleration, for example ease-in starts slowly and speeds up.
  • Wait 100ms, run 200ms, decelerating to rest: '0.2s 100ms ease-out'
  • Run 200ms with no delay, slow-fast-slow: '0.2s ease-in-out'
  • Start immediately, run 200ms, end at full velocity: '0.2s ease-in'

Triggering the animation

An animation needs a trigger to know when to start. trigger() collects the states and transitions and gives the animation a name so you can attach it to an element in the template. It describes the property name to watch; when it changes, the trigger starts the actions in its definition. In this example the trigger is named openClose and attached to a button, describing the open and closed states and the timings for the two transitions.

Triggering the animation

Defining animations and attaching them to the template

Animations are defined in the metadata of the component that controls the element. Put the code under the animations: property within @Component().

Summary

You added animation to a simple two-state transition using style(), state(), and animate(). See the transitions and triggers guide for more.

Transitions and triggers

Wildcard state. An asterisk * matches any animation state, useful for transitions regardless of start or end state. For example, open => * applies when the state changes from open to anything else. The special void state is used for elements entering and leaving a view. This area also covers multiple triggers, animation callbacks, and keyframe-based sequences.

Wildcard state

Complex animation sequences

Beyond single elements, Angular can animate coordinated sequences, such as an entire grid or list entering and leaving a page, in parallel or one after another. The relevant functions:

  • query(): finds one or more inner HTML elements.
  • stagger(): applies a cascading delay to animations for multiple elements.
  • group(): runs multiple animation steps in parallel.
  • sequence(): runs animation steps one after another.

Reusable animations

The AnimationOptions interface lets you create animations reusable across components. Use animation() to define an animation in a separate .ts file and export it as a const, then import and reuse it in any component via useAnimation().

Route transition animations

When a user navigates between routes, the router maps the URL to a component and displays its view; animating this transition can greatly enhance the experience. The router comes with high-level animation functions; you define nested animation sequences, starting with the top-level component that hosts the view and nesting more in the components that host embedded views. To enable it:

  1. Import the routing module and create a routing configuration defining the possible routes.
  2. Add a router outlet to tell the router where to place activated components in the DOM.
  3. Define the animation.

15. Setup, deployment & upgrading

Setup for local development

The QuickStart live-coding example is an Angular playground, not where you'd develop a real app. You should develop locally on your own machine. Setting up a new project is quick with the QuickStart seed, maintained on GitHub. Make sure you have Node.js® and npm installed.

Deployment

Techniques for deploying your Angular application to a remote server.

Upgrading from AngularJS to Angular

Angular is the name for the Angular of today and tomorrow; AngularJS is the name for all 1.x versions. AngularJS apps are great, so always consider the business case (including time and effort) before moving to Angular.

Some apps are easier to upgrade than others, and you can prepare AngularJS apps before starting: make code more decoupled, more maintainable, and better aligned with modern tooling. This eases the upgrade and improves the existing app.

A key to success is upgrading incrementally: run both frameworks side by side in the same app and port AngularJS components to Angular one by one. Large, complex apps can be upgraded without disrupting other business, with work spread over time. The upgrade module is designed to make this seamless.

AngularJS to Angular concepts: quick reference

This guide helps you move from AngularJS to Angular by mapping AngularJS syntax to the equivalent Angular syntax.

Source: https://angular.io/guide/observables


🇻🇳 Bản tiếng Việt

Tài liệu tra cứu nhanh các khái niệm cốt lõi của Angular: Observables, RxJS, NgModules, Dependency Injection, HttpClient, Router và Animations.

1. Observables

Observables hỗ trợ việc truyền thông điệp giữa bên phát (publisher) và bên nhận (subscriber) trong ứng dụng. So với các kỹ thuật khác, chúng mang lại nhiều lợi ích đáng kể trong xử lý sự kiện, lập trình bất đồng bộ và xử lý nhiều giá trị.

  • Tính khai báo (declarative): bạn định nghĩa một hàm để phát giá trị, nhưng hàm này chỉ chạy khi có consumer đăng ký (subscribe). Sau đó consumer nhận thông báo cho đến khi hàm hoàn tất hoặc consumer hủy đăng ký.
  • Nhiều giá trị, mọi kiểu dữ liệu: giá trị thô, thông điệp hoặc sự kiện. API nhận giá trị là như nhau dù dữ liệu đến đồng bộ hay bất đồng bộ.
  • Giao diện thống nhất: việc thiết lập và dọn dẹp do observable đảm nhiệm, nên code của bạn chỉ cần subscribe và khi xong thì unsubscribe, dù luồng dữ liệu là phím gõ, phản hồi HTTP hay bộ đếm thời gian.

Nhờ các ưu điểm này, observables được dùng rộng rãi trong Angular và cũng được khuyến nghị cho việc phát triển ứng dụng.

Cách dùng cơ bản và thuật ngữ

Với vai trò bên phát, bạn tạo một đối tượng Observable định nghĩa một hàm subscriber. Hàm này chạy khi consumer gọi subscribe() và quy định cách lấy hoặc tạo ra các giá trị/thông điệp cần phát.

Để thực thi observable và bắt đầu nhận thông báo, hãy gọi subscribe() và truyền vào một observer, tức một đối tượng JavaScript chứa các hàm xử lý thông báo. subscribe() trả về một đối tượng Subscription có phương thức unsubscribe() để ngừng nhận thông báo.

Định nghĩa observer

Bộ xử lý thông báo của observable cài đặt interface Observer: một đối tượng có các callback cho ba loại thông báo mà observable có thể gửi (next, error, complete).

Đăng ký (Subscribing)

Một Observable chỉ bắt đầu phát giá trị khi có ai đó subscribe. Bạn subscribe bằng cách gọi subscribe() và truyền vào một observer.

Để minh họa việc subscribe, ta cần một observable. Có constructor để tạo mới, nhưng để dễ hiểu, ta có thể dùng các phương thức tĩnh của lớp Observable tạo ra những observable đơn giản:

  • Observable.of(...items): trả về Observable phát đồng bộ các giá trị truyền vào.
  • Observable.from(iterable): chuyển đối số thành Observable; thường dùng để chuyển một mảng thành observable.
Đăng ký một observable

Tạo observable

Dùng constructor Observable để tạo luồng dữ liệu thuộc bất kỳ kiểu nào. Constructor nhận hàm subscriber sẽ chạy khi subscribe() được gọi. Hàm này nhận một đối tượng Observer và có thể phát giá trị qua phương thức next() của nó.

Multicasting (phát đa hướng)

Một observable thông thường tạo ra một lần thực thi độc lập cho mỗi observer đăng ký: observer đầu tiên có trình xử lý sự kiện riêng, observer thứ hai lại có một trình xử lý khác trong lần thực thi riêng.

Đôi khi bạn muốn mọi subscription nhận cùng các giá trị, kể cả khi giá trị đã bắt đầu được phát, ví dụ observable của các lần click trên document. Multicasting là phát tới nhiều subscriber trong một lần thực thi duy nhất: thay vì đăng ký nhiều listener trên document, ta dùng lại listener đầu tiên và gửi giá trị cho từng subscriber.

Khi tạo observable, hãy xác định cách dùng và có cần multicast hay không.

Xử lý lỗi

Vì observable tạo giá trị bất đồng bộ nên try/catch không bắt lỗi hiệu quả. Thay vào đó, hãy khai báo callback error trên observer. Khi có lỗi, observable cũng dọn dẹp các subscription và ngừng phát giá trị. Một observable hoặc phát giá trị (gọi next), hoặc kết thúc bằng complete hoặc error.

Callback error của observer

2. Thư viện RxJS

Lập trình phản ứng (reactive programming) là mô hình lập trình bất đồng bộ tập trung vào các luồng dữ liệu và sự lan truyền thay đổi. RxJS (Reactive Extensions for JavaScript) là thư viện lập trình reactive dùng observables, giúp việc kết hợp code bất đồng bộ hoặc dựa trên callback dễ dàng hơn.

RxJS cung cấp bản cài đặt kiểu Observable (cần thiết cho đến khi kiểu này trở thành một phần của ngôn ngữ và được trình duyệt hỗ trợ), cùng các hàm tiện ích để tạo và làm việc với observable. Các hàm này dùng để:

  • Chuyển code bất đồng bộ có sẵn thành observable
  • Duyệt qua các giá trị trong luồng
  • Ánh xạ giá trị sang kiểu khác
  • Lọc luồng
  • Kết hợp nhiều luồng

Các hàm tạo observable

RxJS có nhiều hàm tạo observable từ sự kiện, bộ đếm thời gian, promise, v.v.

Operators (toán tử)

Operators là các hàm xây dựng trên nền observable để thao tác tinh vi với tập hợp dữ liệu, ví dụ map(), filter(), concat(), flatMap(). Chúng nhận các tùy chọn cấu hình và trả về một hàm nhận observable nguồn; khi chạy, hàm đó quan sát các giá trị mà nguồn phát ra.

Dùng pipe để nối các operator. Hàm pipe() nhận các hàm bạn muốn kết hợp và trả về một hàm mới, khi chạy sẽ thực thi lần lượt các hàm đó.

Tập hợp các operator áp dụng lên một observable là một công thức (recipe), tức một bộ chỉ dẫn để tạo ra giá trị bạn cần. Bản thân nó không làm gì cả; bạn phải gọi subscribe() để có kết quả.

Các operator phổ biến

RxJS có rất nhiều operator nhưng chỉ một số ít được dùng thường xuyên. Xem danh sách đầy đủ tại tài liệu API RxJS.

Với ứng dụng Angular, nên kết hợp operator bằng pipe thay vì nối chuỗi (chaining). Chaining xuất hiện trong nhiều ví dụ RxJS.
Kết hợp operator bằng pipe

Xử lý lỗi với RxJS

Ngoài handler error() khi subscribe, RxJS có operator catchError để xử lý các lỗi đã biết ngay trong công thức observable. Ví dụ, nếu một API request lỗi hoặc giá trị không tồn tại, bạn có thể bắt lỗi và cung cấp giá trị mặc định để luồng tiếp tục chạy thay vì dừng vì lỗi.

Quy ước đặt tên cho observable

Vì ứng dụng Angular chủ yếu viết bằng TypeScript, bạn thường biết biến nào là observable. Angular không bắt buộc quy ước đặt tên, nhưng bạn sẽ hay thấy observable có hậu tố $. Điều này giúp dễ nhận diện khi đọc code, và nếu muốn một thuộc tính lưu giá trị gần nhất của observable thì có thể dùng cùng tên, có hoặc không có $.

Quy ước hậu tố $

3. Observables trong Angular

Angular dùng observables làm giao diện cho nhiều thao tác bất đồng bộ phổ biến:

  • Lớp EventEmitter kế thừa từ Observable.
  • Module HTTP dùng observables để xử lý request và response AJAX.
  • Module Router và Forms dùng observables để lắng nghe và phản hồi sự kiện nhập liệu của người dùng.

Event emitter

EventEmitter được dùng khi component phát giá trị ra ngoài qua decorator @Output(). Nó kế thừa Observable và thêm phương thức emit() để gửi giá trị tùy ý; khi gọi emit(), giá trị được chuyển tới next() của mọi observer đang subscribe. Ví dụ component lắng nghe sự kiện open và close:

<zippy (open)="onOpen($event)" (close)="onClose($event)"></zippy>

HTTP

HttpClient trả về observable từ các lời gọi phương thức HTTP; ví dụ http.get('/api') trả về một observable. Cách này có nhiều ưu điểm so với API HTTP dựa trên promise:

  • Observable không làm thay đổi phản hồi từ server (như các lời gọi .then() nối tiếp có thể làm). Thay vào đó, bạn dùng một chuỗi operator để biến đổi giá trị khi cần.
  • Có thể hủy HTTP request bằng unsubscribe().
  • Có thể cấu hình request để nhận cập nhật tiến trình (progress).
  • Request thất bại có thể được thử lại dễ dàng.

Router

Router.events cung cấp các sự kiện dưới dạng observable. Dùng operator filter() của RxJS để tìm sự kiện cần quan tâm và subscribe để đưa ra quyết định dựa trên chuỗi sự kiện trong quá trình điều hướng.

Reactive forms

Reactive forms dùng observables để theo dõi giá trị của form control. Các thuộc tính valueChanges và statusChanges của FormControl chứa observable phát sự kiện thay đổi. Subscribe vào chúng là một cách kích hoạt logic ứng dụng trong class của component.

4. So sánh Observables với các kỹ thuật khác

Bạn thường có thể dùng observable thay cho promise để trả giá trị bất đồng bộ, thay cho trình xử lý sự kiện, và vì observable phát nhiều giá trị nên có thể dùng ở nơi bạn định tạo và thao tác trên mảng.

Observables và Promises

ObservablesPromises
Khai báo: chỉ tính toán khi có subscribe. Hữu ích để định nghĩa các công thức chạy bất cứ khi nào cần kết quả.Chạy ngay khi được tạo.
Cung cấp nhiều giá trị theo thời gian.Chỉ cung cấp một giá trị.
Phân biệt giữa chaining và subscription, nên có thể tạo công thức biến đổi phức tạp cho phần khác của hệ thống dùng mà chưa thực thi công việc.Chỉ có mệnh đề .then().
subscribe() chịu trách nhiệm xử lý lỗi, giúp xử lý lỗi tập trung và dễ dự đoán.Đẩy lỗi xuống các promise con.

Observables và events API

Observables rất giống trình xử lý sự kiện: cả hai đều định nghĩa handler thông báo và xử lý nhiều giá trị được gửi theo thời gian. Subscribe vào observable tương đương với thêm một event listener. Điểm khác biệt quan trọng là bạn có thể cấu hình observable để biến đổi sự kiện trước khi chuyển cho handler. Dùng observable cho sự kiện và thao tác bất đồng bộ cũng giúp code nhất quán hơn, chẳng hạn trong các HTTP request.

Observables và mảng

Observable tạo giá trị theo thời gian, còn mảng là một tập giá trị tĩnh. Có thể nói observable là bất đồng bộ còn mảng là đồng bộ. Trong các ví dụ sau, ký hiệu ➞ biểu thị việc giá trị được gửi bất đồng bộ.

Observable so với mảng (1)
Observable so với mảng (2)

5. Bootstrapping (khởi chạy ứng dụng)

Kiến thức cần có: JavaScript Modules và NgModules.

NgModule mô tả cách các phần của ứng dụng khớp với nhau. Mọi ứng dụng có ít nhất một module Angular, là module gốc (root) mà bạn bootstrap để khởi chạy ứng dụng. Theo quy ước, nó thường được đặt tên là AppModule.

Ví dụ AppModule

Sau các câu lệnh import là một class có decorator @NgModule, xác định AppModule là một NgModule và nhận một đối tượng metadata cho Angular biết cách biên dịch và khởi chạy ứng dụng:

  • declarations: component duy nhất của ứng dụng này.
  • imports: import BrowserModule để có các dịch vụ dành riêng cho trình duyệt như render DOM, làm sạch dữ liệu (sanitization) và location.
  • providers: các service provider.
  • bootstrap: component gốc mà Angular tạo và chèn vào trang host index.html.

Ứng dụng CLI mặc định chỉ có một component là AppComponent, nên nó nằm ở cả mảng declarations và bootstrap.

Mảng declarations

Cho Angular biết component nào thuộc module. Khi tạo thêm component, hãy thêm vào đây. Mỗi component phải được khai báo trong đúng một class NgModule; dùng component chưa khai báo sẽ gây lỗi.

Mảng này cũng dùng cho directive và pipe. Để dùng một directive, component hoặc pipe trong module:

  1. Export nó từ file nơi bạn viết.
  2. Import nó vào module phù hợp.
  3. Khai báo nó trong mảng declarations của @NgModule.

Mảng bootstrap

Ứng dụng khởi chạy bằng cách bootstrap AppModule gốc (còn gọi là entryComponent). Trong quá trình này, Angular tạo các component trong mảng bootstrap và chèn từng cái vào DOM của trình duyệt.

6. NgModules

Kiến thức cần có: Bootstrapping và JavaScript Modules so với NgModules.

NgModules cấu hình injector và compiler, đồng thời giúp tổ chức các thành phần liên quan vào cùng một nơi. NgModule là một class được đánh dấu bằng @NgModule, nhận đối tượng metadata mô tả cách biên dịch template của component và cách tạo injector lúc chạy. Nó xác định component, directive, pipe của chính module, công khai một số thành phần qua thuộc tính exports, và có thể thêm service provider vào các dependency injector của ứng dụng.

Tính module hóa của Angular

Module là cách tuyệt vời để tổ chức ứng dụng và mở rộng bằng các thư viện bên ngoài. Các thư viện Angular đều là NgModule, như FormsModule, HttpClientModule và RouterModule. Nhiều thư viện bên thứ ba cũng có dạng NgModule, như Material Design, Ionic và AngularFire2.

NgModules gom component, directive và pipe thành các khối chức năng gắn kết, mỗi khối tập trung vào một mảng tính năng, nghiệp vụ, quy trình hoặc tập tiện ích chung. Module cũng có thể thêm service, dù do bạn tự phát triển hay đến từ bên ngoài như router và HTTP client của Angular. Module có thể được tải ngay khi khởi động (eager) hoặc tải bất đồng bộ theo yêu cầu bởi router (lazy).

Metadata của NgModule thực hiện các việc sau:

  • Khai báo component, directive và pipe nào thuộc module.
  • Công khai một số thành phần để template của component ở module khác có thể dùng.
  • Import các module khác chứa component, directive, pipe mà module hiện tại cần.
  • Cung cấp các service để các component khác trong ứng dụng sử dụng.

Module gốc là đủ cho một ứng dụng đơn giản. Khi ứng dụng lớn dần, hãy tách module gốc thành các feature module đại diện cho các nhóm chức năng liên quan, rồi import chúng vào module gốc.

7. Entry Components

Entry component là bất kỳ component nào Angular tải theo kiểu mệnh lệnh (imperative), tức không được tham chiếu trong template mà được nạp theo kiểu (type). Bạn chỉ định entry component bằng cách bootstrap nó trong NgModule hoặc đưa vào định nghĩa route. Điều này trái với các component khai báo (declarative) được đưa vào template.

Có hai loại entry component chính:

  • Component gốc được bootstrap.
  • Component được chỉ định trong định nghĩa route.

Ví dụ chỉ định component được bootstrap, AppComponent, trong app.module.ts cơ bản:

Entry component được bootstrap

8. Providers

Provider là một chỉ dẫn cho hệ thống DI về cách lấy giá trị cho một dependency. Phần lớn thời gian, các dependency này là service do bạn tạo và cung cấp.

Cung cấp một service

Trong ứng dụng do CLI tạo, hãy tạo service từ thư mục gốc dự án (thay User bằng tên service của bạn):

ng generate service User

Lệnh này tạo bộ khung UserService sau (src/app/user.service.ts):

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

@Injectable({
  providedIn: 'root',
})
export class UserService {
}

Giờ bạn có thể inject UserService ở bất cứ đâu trong ứng dụng. Class này được gắn decorator @Injectable, mặc định được cấu hình với providedIn để tạo provider cho service. Ở đây providedIn: 'root' nghĩa là service được cung cấp trong root injector.

Phạm vi của provider

Khi thêm provider vào root injector của ứng dụng, nó khả dụng trên toàn ứng dụng, và với mọi class có token để tra cứu. Bạn nên luôn cung cấp service ở root injector, trừ khi muốn service chỉ khả dụng khi nơi dùng import một @NgModule cụ thể.

9. Singleton services

Có hai cách để biến service thành singleton trong Angular:

  • Khai báo service được cung cấp ở root của ứng dụng.
  • Đưa service vào AppModule hoặc vào một module chỉ được import bởi AppModule.

Từ Angular 6.0, cách ưu tiên là chỉ định trên service rằng nó được cung cấp ở root của ứng dụng.

10. Lazy loading feature modules

Có ba bước chính để thiết lập một feature module tải lười (lazy loaded):

  1. Tạo feature module.
  2. Tạo routing module cho feature module.
  3. Cấu hình các route.
Quan hệ trong lazy loading

forRoot() và forChild()

CLI thêm RouterModule.forRoot(routes) vào mảng imports của app-routing.module.ts. Điều này cho Angular biết AppRoutingModule là routing module gốc: nó cấu hình mọi route bạn truyền vào, cho phép dùng các router directive và đăng ký router service. Chỉ dùng forRoot() một lần, ở cấp gốc.

CLI thêm RouterModule.forChild(routes) vào các routing module của feature, để Angular biết danh sách route này chỉ cung cấp thêm route. Bạn có thể dùng forChild() ở nhiều module.

forRoot() chứa cấu hình injector toàn cục, như cấu hình Router. forChild() không có cấu hình injector, chỉ có các directive như RouterOutlet và RouterLink.

11. Dependency Injection trong Angular

Dependency injection (DI) là một mẫu thiết kế ứng dụng quan trọng. Angular có framework DI riêng, thường dùng khi thiết kế ứng dụng Angular để tăng hiệu quả và tính module hóa.

Dependency là các service hoặc đối tượng mà một class cần để thực hiện chức năng. DI là mẫu lập trình trong đó class yêu cầu dependency từ nguồn bên ngoài thay vì tự tạo. Trong Angular, framework DI cung cấp các dependency đã khai báo cho class khi class được khởi tạo, giúp ứng dụng linh hoạt, hiệu quả, vững chắc, dễ kiểm thử và dễ bảo trì.

Tạo và đăng ký một injectable service

Framework DI cho phép cung cấp dữ liệu cho component từ một class service có thể inject, được định nghĩa trong file riêng. Ví dụ: một service cung cấp danh sách hero, được đăng ký làm provider.

Đặt nhiều class trong cùng một file có thể gây rối; nhìn chung nên định nghĩa component và service ở các file riêng.

Nếu vẫn gộp chung một file, hãy định nghĩa service trước, rồi mới đến component. Nếu định nghĩa component trước sẽ gặp lỗi null reference khi chạy. Bạn có thể định nghĩa component trước nhờ forwardRef(), và forward reference cũng giúp phá vỡ phụ thuộc vòng (xem DI Cookbook).

Hello service

Cấu hình injector với service provider

Decorator @Injectable() đánh dấu một class là có thể inject, nhưng Angular chưa thể inject nó đi đâu cho đến khi bạn cấu hình một dependency injector với một provider. Injector chịu trách nhiệm tạo instance của service và inject vào các class như HeroListComponent. Bạn hiếm khi tự tạo injector; Angular tự tạo khi chạy ứng dụng, bắt đầu từ root injector được tạo lúc bootstrap.

Provider cho injector biết cách tạo service. Bạn có thể cấu hình provider ở nhiều cấp bằng cách đặt metadata ở một trong ba nơi:

  • Decorator @Injectable() của chính service.
  • Decorator @NgModule() của một NgModule.
  • Decorator @Component() của một component.

@Injectable() có tùy chọn providedIn, cho phép chỉ định injector root hoặc injector của một NgModule cụ thể. @NgModule() và @Component() có tùy chọn providers để cấu hình injector cấp module hoặc cấp component.

Injector theo component

Mỗi component tự thiết lập injector riêng với không, một hoặc nhiều provider. Khi resolve một instance của Car ở component sâu nhất (C), injector của nó tạo ra Car được resolve bởi injector C, với Engine được resolve bởi injector B và Tires được resolve bởi root injector A.

Cây injector

12. HttpClient

Hầu hết ứng dụng front-end giao tiếp với dịch vụ backend qua giao thức HTTP. Trình duyệt hiện đại hỗ trợ hai API cho việc này: XMLHttpRequest và fetch(). HttpClient trong @angular/common/http cung cấp một API HTTP phía client đơn giản hóa, dựa trên XMLHttpRequest. Các lợi ích khác gồm khả năng kiểm thử, đối tượng request/response có kiểu, chặn (intercept) request và response, API Observable và xử lý lỗi gọn gàng.

Ứng dụng mẫu không cần máy chủ dữ liệu. Nó dùng in-memory-web-api của Angular, thay thế HttpBackend của HttpClient và mô phỏng backend kiểu REST. Xem phần imports của AppModule để biết cách cấu hình.
Ví dụ HttpClient GET

Gửi POST request

Ứng dụng thường POST dữ liệu lên server, ví dụ khi gửi form. Ở đây HeroesService thực hiện POST khi thêm một hero:

/** POST: add a new hero to the database */
addHero (hero: Hero): Observable<Hero> {
  return this.http.post<Hero>(this.heroesUrl, hero, httpOptions)
    .pipe(
      catchError(this.handleError('addHero', hero))
    );
}

HttpEvents

Bạn có thể kỳ vọng intercept() và handle() trả về observable của HttpResponse<any> như hầu hết phương thức của HttpClient. Thực tế chúng trả về observable của HttpEvent<any>, vì interceptor hoạt động ở mức thấp hơn. Một request có thể sinh ra nhiều sự kiện, gồm cả sự kiện tiến trình upload và download. Bản thân HttpResponse cũng là một sự kiện, kiểu HttpEventType.HttpResponseEvent.

Nhiều interceptor chỉ quan tâm request gửi đi và đơn giản trả về luồng từ next.handle() mà không sửa đổi. Nhưng interceptor kiểm tra hoặc sửa response sẽ thấy tất cả sự kiện. Interceptor của bạn nên trả về mọi sự kiện nguyên vẹn trừ khi có lý do thuyết phục để làm khác.

Trả về Observable nhiều giá trị

HttpClient.get() thường trả về observable chỉ phát dữ liệu hoặc lỗi, kiểu "một lần là xong". Interceptor có thể biến nó thành observable phát nhiều lần.

Lắng nghe sự kiện tiến trình

Việc truyền lượng dữ liệu lớn, như upload file, có thể mất nhiều thời gian, nên hãy cho người dùng biết tiến trình. Để bật tính năng này, tạo HttpRequest với tùy chọn reportProgress đặt là true.

13. Routing & Navigation

Router của Angular cho phép điều hướng từ view này sang view khác khi người dùng thực hiện các tác vụ trong ứng dụng. Nó mượn mô hình quen thuộc của trình duyệt:

  • Nhập URL vào thanh địa chỉ và trình duyệt chuyển tới trang tương ứng.
  • Nhấp vào liên kết trên trang và trình duyệt chuyển sang trang mới.
  • Nhấn nút back/forward và trình duyệt đi lùi/tiến trong lịch sử các trang đã xem.

Router diễn giải URL của trình duyệt thành chỉ dẫn điều hướng tới một view do phía client tạo, có thể truyền tham số tùy chọn cho component của view, có thể gắn vào các liên kết, có thể điều hướng bằng code (khi nhấn nút, chọn dropdown, v.v.) và ghi lại hoạt động vào lịch sử trình duyệt để nút back/forward hoạt động.

<base href>

Hầu hết ứng dụng có routing nên thêm phần tử <base> làm con đầu tiên của <head> trong index.html để router biết cách ghép URL điều hướng. Nếu thư mục app là gốc ứng dụng, hãy đặt đúng như sau:

<base href="/">

Import Router

Router là một service tùy chọn, hiển thị view của component cho một URL nhất định. Nó không thuộc phần lõi của Angular mà nằm trong package riêng, @angular/router.

import { RouterModule, Routes } from '@angular/router';

Cấu hình

Ứng dụng có routing có một instance singleton của service Router. Khi URL thay đổi, router tìm Route tương ứng để xác định component cần hiển thị. Router không có route nào cho đến khi bạn cấu hình. Ví dụ dưới đây tạo năm định nghĩa route, cấu hình router bằng RouterModule.forRoot và thêm kết quả vào mảng imports của AppModule.

const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'hero/:id',      component: HeroDetailComponent },
  {
    path: 'heroes',
    component: HeroListComponent,
    data: { title: 'Heroes List' }
  },
  { path: '',
    redirectTo: '/heroes',
    pathMatch: 'full'
  },
  { path: '**', component: PageNotFoundComponent }
];

Mỗi Route ánh xạ một path URL tới một component. Không có dấu gạch chéo ở đầu path; router tự phân tích và dựng URL cuối cùng, nên bạn có thể dùng cả đường dẫn tương đối và tuyệt đối. :id ở route thứ hai là token của tham số route: trong /hero/42, "42" là giá trị của id, và HeroDetailComponent dùng giá trị này để tìm và hiển thị hero tương ứng.

Router outlet

RouterOutlet là một directive dùng như component. Nó là chỗ giữ chỗ, đánh dấu vị trí trong template nơi router hiển thị component.

<router-outlet></router-outlet>
<!-- Routed components go here -->

Router links

Điều hướng thường là kết quả của hành động người dùng, chẳng hạn nhấp vào thẻ anchor.

<nav>
  <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
  <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
</nav>
<router-outlet></router-outlet>

Active router links

Directive RouterLinkActive bật/tắt các class CSS cho các RouterLink đang active dựa trên RouterState hiện tại. Giá trị là một chuỗi các class CSS cách nhau bằng khoảng trắng, được thêm khi liên kết active và gỡ khi không active, ví dụ [routerLinkActive]="'active fluffy'", hoặc bạn có thể bind nó với thuộc tính của component trả về chuỗi như vậy.

Router state

Sau mỗi lần điều hướng thành công, router dựng một cây các đối tượng ActivatedRoute tạo nên trạng thái hiện tại của router. Bạn có thể truy cập nó ở bất cứ đâu qua thuộc tính routerState của service Router. Mỗi ActivatedRoute cung cấp các phương thức để đi lên xuống trong cây nhằm lấy thông tin từ route cha, con và anh em.

Activated route

Đường dẫn và tham số của route có sẵn thông qua service ActivatedRoute được inject.

Router events

Trong mỗi lần điều hướng, router phát các sự kiện qua Router.events, từ lúc điều hướng bắt đầu, kết thúc cho đến nhiều thời điểm ở giữa.

Crisis center với các child route

Mẫu tổ chức được khuyến nghị cho ứng dụng Angular:

  • Mỗi khu vực tính năng nằm trong thư mục riêng.
  • Mỗi tính năng có feature module Angular riêng.
  • Mỗi khu vực có component gốc riêng.
  • Mỗi component gốc của khu vực có router outlet và child route riêng.
  • Route của các khu vực tính năng hiếm khi (nếu có) giao nhau với route của tính năng khác.

Nếu ứng dụng có nhiều khu vực tính năng, cây component có thể trông như sau:

Cây component

14. Angular animations

Giới thiệu

Animation tạo ảo giác chuyển động: các phần tử HTML thay đổi kiểu dáng theo thời gian. Animation được thiết kế tốt giúp ứng dụng thú vị và dễ dùng hơn, và không chỉ mang tính trang trí:

  • Không có animation, việc chuyển trang có thể đột ngột và gây khó chịu.
  • Chuyển động giúp người dùng nhận biết phản hồi của ứng dụng với thao tác của họ.
  • Animation tốt thu hút sự chú ý một cách trực quan vào nơi cần thiết.

Animation thường gồm nhiều phép biến đổi kiểu dáng theo thời gian: một phần tử có thể di chuyển, đổi màu, phóng to thu nhỏ, mờ dần hoặc trượt khỏi trang, đồng thời hoặc tuần tự. Hệ thống animation của Angular được xây dựng trên CSS, nên bạn có thể animate mọi thuộc tính mà trình duyệt cho là animate được (vị trí, kích thước, transform, màu sắc, viền, v.v.). Xem danh sách của W3C tại CSS Transitions.

Trạng thái và kiểu dáng của animation

Dùng state() để định nghĩa các trạng thái được gọi ở cuối mỗi transition. Hàm nhận một tên duy nhất như open hoặc closed và một hàm style(). Dùng style() để định nghĩa tập kiểu dáng gắn với trạng thái; tên thuộc tính style phải ở dạng camelCase. Có thể đặt nhiều thuộc tính style cùng lúc cho một trạng thái.

Transition và thời gian

Nếu không tinh chỉnh thêm, thay đổi trạng thái diễn ra tức thì, không có hiệu ứng mờ hay co lại. Để mượt hơn, hãy định nghĩa transition(). Hàm nhận hai đối số: biểu thức xác định hướng giữa hai trạng thái, và một hàm animate().

animate() định nghĩa thời lượng, độ trễ và easing của transition, và chỉ định hàm style áp dụng trong lúc chuyển. Nó cũng có thể định nghĩa keyframes() cho animation nhiều bước.

Thời lượng, độ trễ và easing

animate() nhận chuỗi timings gồm ba phần: animate('duration delay easing').

  • duration (bắt buộc): mili giây dạng số thường (100), chuỗi mili giây ('100ms') hoặc chuỗi giây ('0.1s').
  • delay: cú pháp giống duration. Ví dụ: chờ 100ms rồi chạy 200ms: '0.2s 100ms'.
  • easing: điều khiển cách animation tăng/giảm tốc, ví dụ ease-in bắt đầu chậm rồi tăng tốc.
  • Chờ 100ms, chạy 200ms, giảm tốc dần đến lúc dừng: '0.2s 100ms ease-out'
  • Chạy 200ms không trễ, chậm-nhanh-chậm: '0.2s ease-in-out'
  • Bắt đầu ngay, chạy 200ms, kết thúc ở tốc độ tối đa: '0.2s ease-in'

Kích hoạt animation

Animation cần một trigger để biết khi nào bắt đầu. trigger() gom các state và transition lại, đặt tên cho animation để bạn gắn vào phần tử trong template. Nó mô tả tên thuộc tính cần theo dõi; khi thuộc tính thay đổi, trigger bắt đầu các hành động trong định nghĩa. Trong ví dụ này, trigger tên openClose được gắn vào một button, mô tả hai trạng thái open và closed cùng thời gian của hai transition.

Kích hoạt animation

Định nghĩa animation và gắn vào template

Animation được định nghĩa trong metadata của component điều khiển phần tử cần animate. Đặt code định nghĩa animation dưới thuộc tính animations: trong @Component().

Tóm tắt

Bạn đã thêm animation cho một transition đơn giản giữa hai trạng thái bằng style(), state() và animate(). Xem hướng dẫn transitions and triggers để tìm hiểu thêm.

Transitions và triggers

Trạng thái wildcard. Dấu sao * khớp với mọi trạng thái animation, hữu ích cho transition áp dụng bất kể trạng thái đầu hay cuối. Ví dụ, open => * áp dụng khi trạng thái đổi từ open sang bất kỳ trạng thái nào khác. Trạng thái đặc biệt void dùng cho phần tử đi vào và rời khỏi view. Phần này còn đề cập nhiều trigger, callback của animation và chuỗi animation dựa trên keyframe.

Trạng thái wildcard

Chuỗi animation phức tạp

Ngoài từng phần tử đơn lẻ, Angular còn animate các chuỗi phối hợp, như cả một lưới hoặc danh sách phần tử xuất hiện và biến mất khỏi trang, chạy song song hoặc nối tiếp. Các hàm liên quan:

  • query(): tìm một hoặc nhiều phần tử HTML bên trong.
  • stagger(): áp dụng độ trễ lần lượt (cascading) cho animation của nhiều phần tử.
  • group(): chạy song song nhiều bước animation.
  • sequence(): chạy các bước animation lần lượt từng bước.

Animation tái sử dụng

Interface AnimationOptions cho phép tạo animation dùng lại ở nhiều component. Dùng animation() để định nghĩa animation trong file .ts riêng và export dạng const, sau đó import và dùng lại ở bất kỳ component nào qua useAnimation().

Animation chuyển route

Khi người dùng điều hướng giữa các route, router ánh xạ URL sang component tương ứng và hiển thị view của nó; animate quá trình chuyển này có thể cải thiện đáng kể trải nghiệm. Router có sẵn các hàm animation cấp cao; bạn định nghĩa các chuỗi animation lồng nhau, bắt đầu từ component cấp cao nhất chứa view và lồng thêm animation trong các component chứa view nhúng. Để bật tính năng này:

  1. Import routing module và tạo cấu hình routing định nghĩa các route khả dĩ.
  2. Thêm router outlet để cho router biết đặt component được kích hoạt ở đâu trong DOM.
  3. Định nghĩa animation.

15. Cài đặt, triển khai & nâng cấp

Cài đặt môi trường phát triển cục bộ

Ví dụ live-coding QuickStart chỉ là một sân chơi Angular, không phải nơi để phát triển ứng dụng thật. Bạn nên phát triển trên máy của mình. Việc tạo dự án mới rất nhanh với QuickStart seed, được duy trì trên GitHub. Hãy chắc chắn bạn đã cài Node.js® và npm.

Triển khai (Deployment)

Các kỹ thuật triển khai ứng dụng Angular lên máy chủ từ xa.

Nâng cấp từ AngularJS lên Angular

Angular là tên của Angular hiện tại và tương lai; AngularJS là tên của tất cả các phiên bản 1.x. Ứng dụng AngularJS vẫn rất tốt, vì vậy hãy luôn cân nhắc bài toán kinh doanh (gồm thời gian và công sức) trước khi chuyển sang Angular.

Có ứng dụng dễ nâng cấp hơn ứng dụng khác, và bạn có thể chuẩn bị cho ứng dụng AngularJS trước khi bắt đầu: làm code tách biệt hơn, dễ bảo trì hơn và phù hợp hơn với công cụ hiện đại. Việc này vừa giúp nâng cấp dễ hơn vừa cải thiện ứng dụng hiện có.

Chìa khóa thành công là nâng cấp từng bước: chạy hai framework song song trong cùng ứng dụng và chuyển từng component AngularJS sang Angular. Ứng dụng lớn, phức tạp cũng có thể nâng cấp mà không làm gián đoạn công việc khác, với khối lượng việc được dàn trải theo thời gian. Module upgrade được thiết kế để việc này diễn ra liền mạch.

AngularJS sang Angular: bảng tra nhanh khái niệm

Hướng dẫn này giúp bạn chuyển từ AngularJS sang Angular bằng cách ánh xạ cú pháp AngularJS sang cú pháp tương đương trong Angular.

Nguồn: https://angular.io/guide/observables