diff --git a/Day001-Installation.md b/Day001-Installation.md
index 947d01a..13bc18b 100644
--- a/Day001-Installation.md
+++ b/Day001-Installation.md
@@ -74,7 +74,7 @@ Lúc này các bạn sẽ cần trả lời 1 số câu hỏi về routing, styl
## Youtube Video
-https://youtu.be/NS6P1fpU77o
+[](https://youtu.be/NS6P1fpU77o)
## HASHTAG
diff --git a/Day002-AngularApp.md b/Day002-AngularApp.md
index 3706999..c2494bc 100644
--- a/Day002-AngularApp.md
+++ b/Day002-AngularApp.md
@@ -27,9 +27,9 @@ export class HelloComponent {}
Nó là một TS class rất đơn giản phải không, bây giờ chúng ta sẽ gắn meta-data cho nó như sau.
```typescript
-import { Component } from "@angular/core";
+import { Component } from '@angular/core';
@Component({
- selector: "app-hello",
+ selector: 'app-hello',
template: `
Hello there!
`,
})
export class HelloComponent {}
@@ -59,8 +59,9 @@ error NG8001: 'app-hello' is not a known element:
Declarables must belong to exactly one module. The compiler emits an error if you try to declare the same class in more than one module. Be careful not to declare a class that is imported from another module.
Yeah, chính là nó đó, giờ chỉ việc import component lên đầu và thêm HelloComponent vào declarations là xong.
+
```typescript
-import { HelloComponent } from './hello.component'
+import { HelloComponent } from './hello.component';
```
```typescript
@@ -80,7 +81,7 @@ Các bạn hãy thử tìm hiểu cấu trúc ứng dụng và tạo thêm nhi
## Youtube Video
-https://youtu.be/jgFw8tAgKNs
+[](https://youtu.be/jgFw8tAgKNs)
## Link tham khảo
@@ -89,7 +90,7 @@ Link document các bạn cần tìm hiểu trong Day 2
- https://angular.io/guide/architecture
- https://angular.io/guide/architecture-modules
- https://angular.io/guide/architecture-components
-
+
Mục tiêu Day 3 sẽ là về **data binding**.
## Author
diff --git a/Day003-DataBinding.md b/Day003-DataBinding.md
index 4205670..3f70059 100644
--- a/Day003-DataBinding.md
+++ b/Day003-DataBinding.md
@@ -11,9 +11,9 @@ Nó có thể hiểu là hãy tính toán cái expression này, nếu có trả
Chỉ đơn giản thế. Giờ các bạn có thể phun data về tên tuổi của một người thành cái profile đơn giản như sau:
```typescript
-import { Component } from "@angular/core";
+import { Component } from '@angular/core';
@Component({
- selector: "app-hello",
+ selector: 'app-hello',
template: `
Hello there!
Your name: {{ user.name }}
@@ -22,7 +22,7 @@ import { Component } from "@angular/core";
})
export class HelloComponent {
user = {
- name: "Tiep Phan",
+ name: 'Tiep Phan',
age: 30,
};
}
@@ -37,11 +37,11 @@ Sau khi parse xong sẽ có một object (node) thuộc type HTMLInputElement đ
```typescript
obj = {
- type: 'text',
- value: 'something',
- attributes: [] // thuộc type NamedNodeMap, dạng như một array
- // … các thuộc tính, method khác
-}
+ type: 'text',
+ value: 'something',
+ attributes: [], // thuộc type NamedNodeMap, dạng như một array
+ // … các thuộc tính, method khác
+};
```
Như bạn có thể thấy, attribute được dùng để chỉ những gì các bạn đặt vào phần opening tag của một tag, còn lại là property của object (node).
@@ -67,7 +67,7 @@ Câu trả lời chính là Event binding. Để gắn event listener vào một
```typescript
@Component({
- selector: "app-hello",
+ selector: 'app-hello',
template: `
Hello there!
@@ -75,7 +75,7 @@ Câu trả lời chính là Event binding. Để gắn event listener vào một
})
export class HelloComponent {
showInfo() {
- alert("Inside Angular Component method");
+ alert('Inside Angular Component method');
}
}
```
@@ -119,13 +119,12 @@ Mục tiêu Day 4 sẽ là về cấu trúc `if else`
## Youtube Video
-https://youtu.be/WrMywdbnQfk
+[](https://youtu.be/WrMywdbnQfk)
## Author
[Tiep Phan](https://github.com/tieppt)
-
## HASHTAG
`#100DaysOfCodeAngular` `#100DaysOfCode` `#AngularVietNam100DoC_Day3`
diff --git a/Day004-Structure-Directive-If-Else.md b/Day004-Structure-Directive-If-Else.md
index 2c4e5d0..f80ec81 100644
--- a/Day004-Structure-Directive-If-Else.md
+++ b/Day004-Structure-Directive-If-Else.md
@@ -9,19 +9,17 @@ Trong Angular để thêm, xóa, thay đổi structure (structure HTML chẳng h
```typescript
@Component({
- selector: "app-hello",
+ selector: 'app-hello',
template: `
Hello there!
Your name: {{ user.name }}
Your name: {{ user.age }}
-
= 13">
- Bạn có thể xem nội dung PG-13
-
+
= 13">Bạn có thể xem nội dung PG-13
`,
})
export class HelloComponent {
user = {
- name: "Tiep Phan",
+ name: 'Tiep Phan',
age: 30,
};
}
@@ -32,24 +30,16 @@ Với những directive được cung cấp sẵn (built-in) bởi Angular, gi
Vậy nếu chúng ta muốn dùng IF-ELSE thì thế nào. Có thể các bạn sẽ nghĩ ngay đến phủ định mệnh đề IF là được ELSE thôi. Điều này hoàn toàn được.
```html
-
= 13">
- Bạn có thể xem nội dung PG-13
-
-
- Bạn không thể xem nội dung PG-13
-
+
= 13">Bạn có thể xem nội dung PG-13
+
Bạn không thể xem nội dung PG-13
```
Hoặc chúng ta có cách hay ho khác, đó là dùng đến ng-template. Tag ng-template là một tag được cung cấp bởi Angular, nó sẽ lưu trữ một Template được định nghĩa bên trong cặp thẻ mở/đóng của nó. Những gì được định nghĩa bên trong đó sẽ không được hiển thị ra view, nhưng chúng ta có thể sử dụng Template đó để render bằng code. Đoạn code phía trên có thể được chuyển đổi tương đương:
```html
-
= 13; else noPG13">
- Bạn có thể xem nội dung PG-13
-
+
= 13; else noPG13">Bạn có thể xem nội dung PG-13
-
- Bạn không thể xem nội dung PG-13
-
+
Bạn không thể xem nội dung PG-13
```
@@ -59,9 +49,7 @@ Với cú pháp sử dụng dấu `*` ở trên, có thể các bạn sẽ thấ
```html
= 13" [ngIfElse]="noPG13">
-
- Bạn có thể xem nội dung PG-13
-
+
Bạn có thể xem nội dung PG-13
```
@@ -77,7 +65,7 @@ Mục tiêu Day 5 sẽ là về cấu trúc lặp `ngForOf`
## Youtube Video
-https://youtu.be/Yujs6hi-l4w
+[](https://youtu.be/Yujs6hi-l4w)
## Author
diff --git a/Day005-Structure-Directive-NgFor.md b/Day005-Structure-Directive-NgFor.md
index 23f9f31..3826b96 100644
--- a/Day005-Structure-Directive-NgFor.md
+++ b/Day005-Structure-Directive-NgFor.md
@@ -9,19 +9,19 @@ Giả sử chúng ta có một danh sách các tác giả của các cuốn sác
authors = [
{
id: 1,
- firstName: "Flora",
- lastName: "Twell",
- email: "ftwell0@phoca.cz",
- gender: "Female",
- ipAddress: "99.180.237.33",
+ firstName: 'Flora',
+ lastName: 'Twell',
+ email: 'ftwell0@phoca.cz',
+ gender: 'Female',
+ ipAddress: '99.180.237.33',
},
{
id: 2,
- firstName: "Priscella",
- lastName: "Signe",
- email: "psigne1@berkeley.edu",
- gender: "Female",
- ipAddress: "183.243.228.65",
+ firstName: 'Priscella',
+ lastName: 'Signe',
+ email: 'psigne1@berkeley.edu',
+ gender: 'Female',
+ ipAddress: '183.243.228.65',
},
// more data
];
@@ -101,9 +101,7 @@ Ví dụ:
```html
-
- More code
-
+
More code
```
@@ -111,14 +109,10 @@ Giả sử nếu bạn không được phép hoặc không muốn sinh ra một
```html
-
- More code
-
+ More code
-
- More code
-
+ More code
```
@@ -137,7 +131,7 @@ Mục tiêu Day 6 sẽ là về `Attribute directive` để áp dụng style, cl
## Youtube Video
-https://youtu.be/q7CQPEPSkD0
+[](https://youtu.be/q7CQPEPSkD0)
## Author
diff --git a/Day006-Attribute-Directive-Class-Style.md b/Day006-Attribute-Directive-Class-Style.md
index 726e9c6..ea7d5ec 100644
--- a/Day006-Attribute-Directive-Class-Style.md
+++ b/Day006-Attribute-Directive-Class-Style.md
@@ -9,9 +9,7 @@ Trong các ứng dụng thực tế, có thể chúng ta cần thay đổi (thê
Ví dụ, nếu chúng ta đang chọn một tab nào đó để hiển thị, thì tab đó sẽ có thêm class tab-active, các tab khác sẽ không có. Lúc này chúng ta sẽ sử dụng cú pháp:
```html
-
- some content
-
+
some content
```
Nhìn qua thì nó chỉ là property binding, với giá trị của isTabActive trả về true thì classList của div đó sẽ tồn tại class tab-active, còn nếu trả về false thì sẽ không tồn tại.
@@ -78,7 +76,7 @@ Mục tiêu Day 7 sẽ là Input cho component.
## Youtube Video
-https://youtu.be/Zh36WRD3MMQ
+[](https://youtu.be/Zh36WRD3MMQ)
## Author
diff --git a/Day007-Component-Interaction-01.md b/Day007-Component-Interaction-01.md
index 7b1d411..b5650eb 100644
--- a/Day007-Component-Interaction-01.md
+++ b/Day007-Component-Interaction-01.md
@@ -61,10 +61,10 @@ Khi đã có component và khai báo input rồi thì làm sao để sử dụng
Đây là component progress-bar của chúng ta.
```typescript
-import { Component, OnInit, Input } from "@angular/core";
+import { Component, OnInit, Input } from '@angular/core';
@Component({
- selector: "app-progress-bar",
+ selector: 'app-progress-bar',
template: `
-
+
```
+
**app.component.ts**
+
```ts
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
- styleUrls: [ './app.component.css' ]
+ styleUrls: ['./app.component.css'],
})
-export class AppComponent {
+export class AppComponent {
name = 'Tiep Phan';
}
```
@@ -50,7 +52,7 @@ Giờ đây mỗi lần bạn thay đổi giá trị của input thì thẻ `p`
Thực chất cách viết trên là viết tắt của property binding và event binding như sau:
```html
-
+
```
Vậy nên để tạo custom Two-way binding thì bạn chỉ cần tạo @Input và @Output với @Output có suffix là change là được, ví dụ `value` và `valueChange`.
@@ -66,52 +68,57 @@ ng g c toggle
Phần code của component sẽ được implement như sau:
**toggle.component.ts**
+
```ts
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
@Component({
selector: 'app-toggle',
templateUrl: './toggle.component.html',
- styleUrls: ['./toggle.component.css']
+ styleUrls: ['./toggle.component.css'],
})
export class ToggleComponent implements OnInit {
@Input() checked = false;
@Output() checkedChange = new EventEmitter();
- constructor() { }
+ constructor() {}
- ngOnInit() {
- }
+ ngOnInit() {}
toggle() {
this.checked = !this.checked;
this.checkedChange.emit(this.checked);
}
-
}
```
**toggle.component.html**
+
```html
-
+
```
+
**toggle.component.css**
+
```css
.toggle-wrapper {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
- transition: all .2s;
+ transition: all 0.2s;
width: 100px;
height: 100px;
border-radius: 50%;
background-color: #fe4551;
box-shadow: 0 20px 20px 0 rgba(#fe4551, 0.3);
-
-
}
.toggle-wrapper:active {
@@ -124,7 +131,6 @@ export class ToggleComponent implements OnInit {
width: 17px;
}
-
.toggle {
transition: all 0.2s ease-in-out;
height: 20px;
@@ -133,19 +139,16 @@ export class ToggleComponent implements OnInit {
border: 10px solid #fff;
border-radius: 50%;
cursor: pointer;
- animation: red .7s linear forwards;
+ animation: red 0.7s linear forwards;
}
-
.toggle-wrapper.checked {
background-color: #48e98a;
box-shadow: 0 20px 20px 0 rgba(#48e98a, 0.3);
-
}
.toggle-wrapper.checked:active {
box-shadow: 0 15px 15px 0 rgba(#48e98a, 0.5);
-
}
.toggle-wrapper.checked .toggle {
@@ -153,17 +156,19 @@ export class ToggleComponent implements OnInit {
background-color: #fff;
border-color: transparent;
border-radius: 30px;
- animation: green .7s linear forwards !important;
+ animation: green 0.7s linear forwards !important;
}
```
+
Component trên của chúng ta không có quá khác biệt so với những component trước đây, nó đều sử dụng những concept cơ bản như @Input, @Output, class binding, style css.
Chỉ với việc sử dụng suffix `change` cho property `checked` chúng ta có thể sử dụng component này ở bất cứ đâu với cách dùng giống như `ngModel`:
+
```html
```
-
+
## SUMMARY
@@ -176,7 +181,7 @@ Như vậy trong Day 9, chúng ta sẽ phải tìm hiểu cách để tạo ra c
## Youtube Video
-https://youtu.be/U8UCOKInmu8
+[](https://youtu.be/U8UCOKInmu8)
## Code sample
diff --git a/Day010-template-variable-viewchild-viewchildren.md b/Day010-template-variable-viewchild-viewchildren.md
index ccf9abf..c0344e9 100644
--- a/Day010-template-variable-viewchild-viewchildren.md
+++ b/Day010-template-variable-viewchild-viewchildren.md
@@ -3,6 +3,7 @@
Nếu bạn cần trỏ tới một phần tử (HTMLElement/component/directive) ở trong template và thao tác trực tiếp lên nó thì sao. Có cách nào để chúng ta tạo ra một `variable` ở trong template và sử dụng nó không? Câu hỏi trên sẽ được trả lời trong ngày thứ 10 này.
## Parent interacts with child via local variable
+
Giả sử chúng ta có `AppComponent` có nhúng một phần template như sau:
```html
@@ -14,7 +15,7 @@ Nhưng thay vì click vào `ToggleComponent` để thay đổi trạng thái c
```html
-
+
```
@@ -26,7 +27,7 @@ Lúc này bạn sẽ có thể sử dụng đến Template variable như một g
```html
-
+
```
@@ -36,13 +37,9 @@ Cú pháp của Template variable chính là sử dụng `#varName` và bạn c
Như trước đây làm với ví dụ về `NgIf-Else` chúng ta cũng đã sử dụng Template variable để lấy về một instance của `ng-template` và truyền vào cho `NgIfElse` như sau:
```html
-
= 13; else noPG13">
- Bạn có thể xem nội dung PG-13
-
+
= 13; else noPG13">Bạn có thể xem nội dung PG-13
-
- Bạn không thể xem nội dung PG-13
-
+
Bạn không thể xem nội dung PG-13
```
@@ -56,16 +53,20 @@ Trong một số trường hợp bạn cần lấy chính xác một instance c
```html
```
Ở template trên chúng ta đã tạo ra 2 Template variable là:
+
- `nameForm`: mong muốn lấy instance của directive có `exportAs` là `ngForm`
- `name`: mong muốn lấy instance của directive có `exportAs` là `ngModel`
@@ -79,14 +80,14 @@ Lúc này chúng ta có thể query một Template variable ở trong Component
```html
-
-
+
+
```
```ts
-export class AppComponent {
+export class AppComponent {
@ViewChild('toggleComp') toggleComp: ToggleComponent;
toggleInside() {
this.toggleComp.toggle();
@@ -101,7 +102,7 @@ Nếu bạn sử dụng ViewChild cho một HTMLElement thì chúng ta sẽ nh
```
```ts
-export class AppComponent {
+export class AppComponent {
@ViewChild('chartContainer') container: ElementRef;
}
```
@@ -121,6 +122,7 @@ ViewChild(selector: string | Function | Type, opts?: {
```
Trong đó các `selector` có thể là:
+
- Any class with the @Component or @Directive decorator
- A template reference variable as a string (e.g. query with @ViewChild('cmp'))
- Any provider defined in the child component tree of the current component (e.g. @ViewChild(SomeService) someService: SomeService)
@@ -131,11 +133,14 @@ Trong đó các `selector` có thể là:
```html
```
@@ -143,25 +148,25 @@ Trong đó các `selector` có thể là:
```ts
export class NameFormComponent implements OnInit {
model = {
- name: 'Tiep Phan'
+ name: 'Tiep Phan',
};
@ViewChild('nameForm', {
read: ElementRef,
- static: true
- }) form: ElementRef;
- constructor() { }
+ static: true,
+ })
+ form: ElementRef;
+ constructor() {}
ngOnInit() {
- console.log(this.form)
+ console.log(this.form);
}
-
}
```
Ở trong trường hợp trên, nếu chúng ta không khai báo `read` thì sẽ lấy về NgForm instance, nhưng do khai báo là một ElementRef nên nó sẽ query khác với variable ở ngoài template.
-`opts.static` nếu `selector` không nằm trong if/else hay một structure directive nào thì chúng ta có thể gọi nó là `static: true`, tức là nó không thay đổi trong suốt thời gian sống của component. Lúc này Angular (v9 trở lên) sẽ chạy phần `resolve query result` (tiến trình) trước khi chạy *Change Detection* nên chúng ta có thể truy cập nó ở trong `ngOnInit` như ở trên, nếu `static: false` (giá trị mặc định) thì tiến trình trên sẽ chạy sau khi chạy *Change Detection* nên bạn không thể dùng nó ở `ngOnInit` mà phải chạy ở `ngAfterViewInit`.
+`opts.static` nếu `selector` không nằm trong if/else hay một structure directive nào thì chúng ta có thể gọi nó là `static: true`, tức là nó không thay đổi trong suốt thời gian sống của component. Lúc này Angular (v9 trở lên) sẽ chạy phần `resolve query result` (tiến trình) trước khi chạy _Change Detection_ nên chúng ta có thể truy cập nó ở trong `ngOnInit` như ở trên, nếu `static: false` (giá trị mặc định) thì tiến trình trên sẽ chạy sau khi chạy _Change Detection_ nên bạn không thể dùng nó ở `ngOnInit` mà phải chạy ở `ngAfterViewInit`.
## Parent class with ViewChildren
@@ -171,9 +176,10 @@ ViewChildren sẽ trả về một QueryList trước khi `ngAfterViewInit` đư
```html
-
+
```
+
```ts
@ViewChildren(ToggleComponent) toggleList: QueryList;
@@ -197,7 +203,7 @@ Cũng trong Day 10 chúng ta học thêm một component lifecycle khác là `ng
## Youtube Video
-https://youtu.be/Wd_644YBQUM
+[](https://youtu.be/Wd_644YBQUM)
## Code sample
diff --git a/Day011-typescript-data-type.md b/Day011-typescript-data-type.md
index ab9c031..6a95749 100644
--- a/Day011-typescript-data-type.md
+++ b/Day011-typescript-data-type.md
@@ -211,7 +211,7 @@ Mục tiêu của Day 12 là **Một số kỹ thuật TypeScript Nâng Cao**.
## Youtube Video
-https://youtu.be/ozHjDLuusVU
+[](https://youtu.be/ozHjDLuusVU)
## Author
diff --git a/Day012-typescript-advanced-type.md b/Day012-typescript-advanced-type.md
index effc8f9..3d4f1cf 100644
--- a/Day012-typescript-advanced-type.md
+++ b/Day012-typescript-advanced-type.md
@@ -286,7 +286,7 @@ Mục tiêu của Day 13 là **Content Projection trong Angular**.
## Youtube Video
-https://youtu.be/4tcajihANZQ
+[](https://youtu.be/4tcajihANZQ)
## Author
diff --git a/Day013-content-projection-in-angular.md b/Day013-content-projection-in-angular.md
index 77c8939..8ec4dde 100644
--- a/Day013-content-projection-in-angular.md
+++ b/Day013-content-projection-in-angular.md
@@ -168,7 +168,7 @@ Một số link mà các bạn cần tìm hiểu:
## Youtube Video
-https://youtu.be/-vN52YVbcgk
+[](https://youtu.be/-vN52YVbcgk)
## Code sample
diff --git a/Day014-ng-template-ng-template-outlet-ng-container.md b/Day014-ng-template-ng-template-outlet-ng-container.md
index af723ee..cd734cf 100644
--- a/Day014-ng-template-ng-template-outlet-ng-container.md
+++ b/Day014-ng-template-ng-template-outlet-ng-container.md
@@ -5,13 +5,9 @@
Trong [bài viết thứ tư][day4] của series, anh @tiepphan đã nói có nói đến một trường hợp dùng `ng-template`. Đó là khi dùng `*ngIf` với điều kiện else, chúng ta có thể truyền vào một template reference đc định nghĩa thông qua cú pháp `#templateReferenceName` để render lên UI.
```html
-
= 13; else noPG13">
- Bạn có thể xem nội dung PG-13
-
+
= 13; else noPG13">Bạn có thể xem nội dung PG-13
-
- Bạn không thể xem nội dung PG-13
-
+
Bạn không thể xem nội dung PG-13
```
@@ -20,7 +16,6 @@ Thông qua ví dụ trên, chắc các bạn cũng đã nhận ra được đôi
- Khi code HTML của bạn dc bao quanh bởi `ng-template`, phần HTML đó sẽ không dc render lên UI ngay lập tức. Mà chỉ dc render trong một số trường hợp, ví dụ như khi `*ngIf else tmpl` hoặc thông qua `ngTemplateOutlet` mà chúng ta sẽ đề cập đến ở phần sau của bài viết.
- Tên gọi của ng-template cũng phần nào nói lên đc ý nghĩa của nó. Template hiểu nôm na là mẫu, dạng. Dịch ra tiếng Việt hơi khó, tuy nhiên khi kết hợp nhiều template với nhau thì chúng ta có thể có một UI đầy đủ.
-
Từ những điểm trên, có thể định nghĩa `ng-template` là một thành phần của Angular để render HTML code. Và phần HTML code nằm trong `ng-template` không bao giờ được hiển thị trực tiếp ở nơi nó được định nghĩa
### Khi nào thì nên dùng ng-template?
@@ -31,21 +26,24 @@ Một số trường hợp hay cần dùng đến ng-template theo như kinh ngh
#### 2. Khi một số UI element trong một component bị lặp lại trong chính component đó, nhưng phần code đó quá nhỏ để tách ra làm một component riêng.
-Ví dụ như bạn có một component có chứa biến một biển tên là `counter`. Phần UI của counter này sẽ đc lặp lại ở trong component của bạn vài lần với UI giống nhau.
+Ví dụ như bạn có một component có chứa biến một biển tên là `counter`. Phần UI của counter này sẽ đc lặp lại ở trong component của bạn vài lần với UI giống nhau.
Đây là cách bình thường chúng ta hay làm. Copy and paste code.
```html
-
- You have selected {{ counter }} items.
-
-
- There are {{ counter }} items was selected.
-
-
+
+ You have selected
+ {{ counter }} items.
+
+
+ There are {{ counter }} items was
+ selected.
+
+
```
@@ -53,25 +51,28 @@ Và đây là cách chúng ta có thể viết lại bằng cách dùng `ng-temp
```html
-
- You have selected .
-
-
- There are was selected.
-
-
+
+ You have selected
+ .
+
+
+ There are was
+ selected.
+
+
- {{ counter }} items
+ {{ counter }} items
```
Khi viết lại code dùng `ng-template`, ưu điểm dễ nhận thấy đó là:
-- Nếu cần sửa lại UI cho counter. Thay vì phải sửa ở 3 nơi, bây giờ ta chỉ cần sửa ở một vị trí đó là `ng-template` của counter thôi. Tránh những lỗi typo hay find and replace bị thiếu.
+- Nếu cần sửa lại UI cho counter. Thay vì phải sửa ở 3 nơi, bây giờ ta chỉ cần sửa ở một vị trí đó là `ng-template` của counter thôi. Tránh những lỗi typo hay find and replace bị thiếu.
- Vì phần template này chỉ gói gọn trong đúng một dòng code nên dùng ng-template tiện hơn hẳn là phải tách phần counter ra một component mới.
#### 3. Dùng ng-template để pass vào component khác. Hỗ trợ override template có sẵn trong component.
@@ -83,16 +84,16 @@ Ví dụ mình có component `tab-container`, mặc định sẽ render tab vớ
selector: 'tab-container',
template: `
-
- ...
-
+
...
-
+
... rest of tab container component ...
- `
+ `,
})
export class TabContainerComponent {
- @Input() headerTemplate: TemplateRef; // Custom template provided by parent
+ @Input() headerTemplate: TemplateRef; // Custom template provided by parent
}
```
@@ -101,7 +102,7 @@ Tuy nhiên, khi dùng `tab-container` bạn hoàn toàn có thể pass vào temp
```ts
@Component({
selector: 'app-root',
- template: `
+ template: `
-
+
`
})
```
@@ -121,7 +122,7 @@ Tuy nhiên, khi dùng `tab-container` bạn hoàn toàn có thể pass vào temp
Qua ví dụ trên thì có thể thấy ngay `ngTemplateOutlet` là cách dùng để render một template được tạo ra bởi `ng-template` lên UI. Cú pháp như sau
-- *ngTemplateOutlet="templateRef"(chú ý dấu sao `*` nhé, không có là không chạy đâu đấy)
+- _ngTemplateOutlet="templateRef"(chú ý dấu sao `_` nhé, không có là không chạy đâu đấy)
- [ngTemplateOutlet]="templateRef"
Tuy nhiên, như component có `@Input()` để truyền data từ bên ngoài vào, thì ng-template cũng có cú pháp tương tự để truyền data. Đó chính là `ngTemplateOutletContext`
@@ -129,36 +130,39 @@ Tuy nhiên, như component có `@Input()` để truyền data từ bên ngoài v
Ví dụ như mình muốn reuse lại một button với những tên khác nhau, ngoài ra, một button mình muốn có icon, và một button mình ko muốn có icon. Thì bạn hoàn toàn cũng có thể dùng kết hợp giữa `ng-template`, `ngTemplateOutlet` và `ngTemplateOutletContext` để viết code.
```html
-
+
```
Hoàn toàn có thể được viết lại thành.
```html
-
-
+
+
-
+
-
+
```
@@ -167,24 +171,22 @@ Tuy nhiều code hơn, nhưng giờ chúng ta có thể reuse lại button này
Vài điểm chú ý:
- Khi define ra `ng-template`, bạn có thể config cho template đó nhận vào value bằng cách dùng cú pháp `let-name="name"`.
-Trong đó name ở bên trái dấu bằng là variable bạn có thể access được ở trong ng-template, còn name ở bên phải dấu bằng là tên của object property khi pass qua `ngTemplateOutletContext`. Hai cái name này hoàn toàn có thể khác nhau, không có vấn đề gì cả.
+ Trong đó name ở bên trái dấu bằng là variable bạn có thể access được ở trong ng-template, còn name ở bên phải dấu bằng là tên của object property khi pass qua `ngTemplateOutletContext`. Hai cái name này hoàn toàn có thể khác nhau, không có vấn đề gì cả.
- Nếu bạn không đặt tên cho variable ở vế phải của dấu bằng, chỉ viết là `let-name`, thì khi truyền data qua context, property của object truyền vào sẽ là `$implicit`. Ví dụ cụ thể:
```html
-
-
+
+
-
+
```
@@ -197,17 +199,15 @@ ng-container là một custom html tag để khi render trên UI sẽ không có
```html
```
Tuy nhiên khi render ra UI, sẽ bị thừa một thẻ div bao ngoài.
```html
-
+
```
@@ -221,14 +221,14 @@ Một số bài viết khác bạn có thể đọc thêm.
- https://alligator.io/angular/reusable-components-ngtemplateoutlet/
- https://angular.io/guide/structural-directives#the-ng-template
-- [Angular render recursive view using *ngFor and ng-template](https://trungk18.com/experience/angular-recursive-view-render/)
+- [Angular render recursive view using \*ngFor and ng-template](https://trungk18.com/experience/angular-recursive-view-render/)
- https://blog.angular-university.io/angular-ng-template-ng-container-ngtemplateoutlet/
Mục tiêu của Day 15 là **Giới thiệu Dependency Injection trong Angular**.
## Youtube Video
-https://youtu.be/3JM8pDR-MaU
+[](https://youtu.be/3JM8pDR-MaU)
## Author
@@ -236,4 +236,4 @@ https://youtu.be/3JM8pDR-MaU
`#100DaysOfCodeAngular` `#100DaysOfCode` `#AngularVietNam100DoC_Day14`
-[day4]: Day004-Structure-Directive-If-Else.md
\ No newline at end of file
+[day4]: Day004-Structure-Directive-If-Else.md
diff --git a/Day015-introduction-dependency-injection-in-angular.md b/Day015-introduction-dependency-injection-in-angular.md
index 6ed499e..3858861 100644
--- a/Day015-introduction-dependency-injection-in-angular.md
+++ b/Day015-introduction-dependency-injection-in-angular.md
@@ -121,7 +121,7 @@ Ví dụ về cách provide `CartService`:
```ts
@Injectable({
- providedIn: "root",
+ providedIn: 'root',
})
export class CartService {
// properties and methods
@@ -130,9 +130,9 @@ export class CartService {
```ts
@Component({
- selector: "app-product",
- templateUrl: "./product.component.html",
- styleUrls: ["./product.component.css"],
+ selector: 'app-product',
+ templateUrl: './product.component.html',
+ styleUrls: ['./product.component.css'],
})
export class ProductComponent implements OnInit {
constructor(private cartService: CartService) {}
@@ -182,7 +182,7 @@ Hoặc có thể override vào `@Injectable` của service
```ts
@Injectable({
- providedIn: "root",
+ providedIn: 'root',
useClass: CartExtService,
})
export class CartService {
@@ -203,7 +203,7 @@ Như vậy, trong Day 15 chúng ta đã làm quen về khái niệm cũng như c
## Youtube Video
-https://youtu.be/_JnUGhVhq_o
+[](https://youtu.be/_JnUGhVhq_o)
## Code sample
diff --git a/Day016-dependency-injection-in-angular-part-2.md b/Day016-dependency-injection-in-angular-part-2.md
index 395c941..cfc23ef 100644
--- a/Day016-dependency-injection-in-angular-part-2.md
+++ b/Day016-dependency-injection-in-angular-part-2.md
@@ -27,21 +27,21 @@ Và trong mỗi component của Angular chúng ta sẽ được cung cấp sẵn
Đến thời điểm hiện tại, bạn hoàn toàn có thể sử dụng `EventEmitter` để notify cho parent component biết được các thời điểm tương ứng. Nhưng cũng có một cách khác, đó chính là inject parent component vào child component và thực hiện các hành động tương ứng.
Giả sử bạn cài đặt **tab-group.component.ts** như sau:
+
```ts
@Component({
selector: 'app-tab-group',
templateUrl: './tab-group.component.html',
- styleUrls: ['./tab-group.component.css']
+ styleUrls: ['./tab-group.component.css'],
})
export class TabGroupComponent implements OnInit {
tabPanelList: TabPanelComponent[] = [];
@Input() tabActiveIndex = 0;
@Output() tabActiveChange = new EventEmitter();
- constructor() { }
+ constructor() {}
- ngOnInit() {
- }
+ ngOnInit() {}
selectItem(idx: number) {
this.tabActiveIndex = idx;
@@ -65,19 +65,22 @@ export class TabGroupComponent implements OnInit {
if (index !== -1) {
this.selectItem(0);
}
-
}
-
}
```
Và đây là phần UI cho **tab-group.component.html**:
+
```html
-
+
{{tab.title}}
-
@@ -88,22 +91,23 @@ Và đây là phần UI cho **tab-group.component.html**:
```
+
Việc của chúng ta bây giờ chỉ là inject và call các method để register và remove:
```ts
@Component({
selector: 'app-tab-panel',
template: `
-
-
-
+
+
+
`,
- styles: ['']
+ styles: [''],
})
export class TabPanelComponent implements OnInit, OnDestroy {
@Input() title: string;
- @ViewChild(TemplateRef, {static: true}) panelBody: TemplateRef;
- constructor(private tabGroup: TabGroupComponent) { }
+ @ViewChild(TemplateRef, { static: true }) panelBody: TemplateRef;
+ constructor(private tabGroup: TabGroupComponent) {}
ngOnInit() {
this.tabGroup.addTabPanel(this);
@@ -111,7 +115,6 @@ export class TabPanelComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.tabGroup.removeTabPanel(this);
}
-
}
```
@@ -122,6 +125,7 @@ Như bạn có thể thấy, tab group của chúng ta ở trên có UI cực k
Đây là nơi tỏa sáng của DI. Bạn chỉ cần đơn giản là provide một provider để override là được.
**bs-tab-group.component.ts**
+
```ts
@Component({
selector: 'app-bs-tab-group',
@@ -130,20 +134,31 @@ Như bạn có thể thấy, tab group của chúng ta ở trên có UI cực k
providers: [
{
provide: TabGroupComponent,
- useExisting: BsTabGroupComponent
- }
- ]
+ useExisting: BsTabGroupComponent,
+ },
+ ],
})
-export class BsTabGroupComponent extends TabGroupComponent {
-}
+export class BsTabGroupComponent extends TabGroupComponent {}
```
+
**bs-tab-group.component.html**
+
```html
@@ -156,6 +171,7 @@ export class BsTabGroupComponent extends TabGroupComponent {
```
Template khi sử dụng:
+
```html
content tab 1
@@ -179,22 +195,20 @@ Bây giờ giả sử bạn tách phần provider ở decorator ra một variabl
```ts
const BsTabGroupProvider = {
provide: TabGroupComponent,
- useExisting: BsTabGroupComponent
-}
+ useExisting: BsTabGroupComponent,
+};
@Component({
selector: 'app-bs-tab-group',
templateUrl: './bs-tab-group.component.html',
styleUrls: ['./bs-tab-group.component.css'],
- providers: [
- BsTabGroupProvider
- ]
+ providers: [BsTabGroupProvider],
})
-export class BsTabGroupComponent extends TabGroupComponent {
-}
+export class BsTabGroupComponent extends TabGroupComponent {}
```
Bạn sẽ nhận được một Error như sau:
+
> Error in src/app/bs-tab-group/bs-tab-group.component.ts (6:16)
>
> Class 'BsTabGroupComponent' used before its declaration.
@@ -204,19 +218,23 @@ Do đó chúng ta cần dùng đến closure, đó là tạo một function nó
```ts
const BsTabGroupProvider = {
provide: TabGroupComponent,
- useExisting: forwardRef(() => BsTabGroupComponent)
-}
+ useExisting: forwardRef(() => BsTabGroupComponent),
+};
```
+
Bạn có thể thắc mắc là tại sao sử dụng trực tiếp trong decorator thì lại không lỗi? Câu trả lời là vì bản thân Class Decorator sẽ được call sau khi mà bạn đã tạo xong class.
Bạn có thể tưởng tượng nó sẽ hoạt động giống như sau:
+
```ts
@SomeDecorator
class SomeClass {}
```
+
Sẽ tương đương với call một function như sau.
+
```ts
-let SomeClass = class SomeClass {}
+let SomeClass = class SomeClass {};
SomeClass = SomeDecorator(SomeClass);
```
@@ -227,6 +245,7 @@ Như chúng ta đã tìm hiểu qua thì chúng ta có các cách provide một
> Lưu ý: code phía dưới đây sẽ tương tự cho `@NgModule`, `@Component`, `@Directive`.
- useClass:
+
```ts
@NgModule({
providers: [SomeClass]
@@ -234,6 +253,7 @@ Như chúng ta đã tìm hiểu qua thì chúng ta có các cách provide một
```
Tương đương với cú pháp:
+
```ts
@NgModule({
providers: [{ provide: SomeClass, useClass: SomeClass}]
@@ -241,6 +261,7 @@ Tương đương với cú pháp:
```
- useExisting:
+
```ts
@Component({
providers: [
@@ -253,6 +274,7 @@ Tương đương với cú pháp:
```
- useFactory:
+
```ts
@Component({
providers: [
@@ -267,6 +289,7 @@ Tương đương với cú pháp:
```
- useValue:
+
```ts
@Component({
providers: [
@@ -277,6 +300,7 @@ Tương đương với cú pháp:
]
})
```
+
## Summary
Như vậy, trong Day 16 này bạn sẽ cần tìm hiểu một số kỹ thuật sử dụng DI trong Angular, như thế sẽ giúp bạn hiểu sâu hơn về DI trong Angular và có thể tạo những phần code dễ reuse, flexible hơn.
@@ -294,6 +318,10 @@ https://stackblitz.com/edit/angular-ivy-100-days-of-code-day-16?file=src%2Fapp%2
Mục tiêu của Day 17 là **ContentChild & ContentChildren**.
+## Youtube Video
+
+[](https://youtu.be/hTsn6L8vcVg)
+
## Author
[Tiep Phan](https://github.com/tieppt)
diff --git a/Day017-contentchild-contentchildren.md b/Day017-contentchild-contentchildren.md
index 44f6e1a..8e0cfe5 100644
--- a/Day017-contentchild-contentchildren.md
+++ b/Day017-contentchild-contentchildren.md
@@ -23,6 +23,7 @@ Giả sử chúng ta sẽ sử dụng Tab Component đã được tạo từ Day
```
+
Thật bất ngờ, chúng ta hi vọng rằng chỉ có 1 instance của counter, nhưng thực thế chúng ta đang có đến 4 instances, chỉ là có 1 instances được hiển thị.
Vậy nếu trong trường hợp các tabs của chúng ta có những component có phần phức tạp, và chúng ta mong muốn chúng được lazy initialize thì làm thế nào?
@@ -40,28 +41,31 @@ ContentChild(selector: string | Function | Type, opts?: {
```
Trước tiên, chúng ta sẽ tạo một directive đã.
+
```ts
import { Directive } from '@angular/core';
@Directive({
- selector: 'ng-template[tabPanelContent]'
+ selector: 'ng-template[tabPanelContent]',
})
export class TabPanelContentDirective {
- constructor() { }
+ constructor() {}
}
```
Directive sẽ giúp chúng ta thêm các tính năng lên một phần tử (DOM Node, Component chẳng hạn), chúng ta có thể thấy directive ở trên muốn target đến bất kỳ thẻ `ng-template` nào có kèm thêm attribute `[tabPanelContent]`.
Và đây là cách sử dụng `ContentChild` để lấy về directive đó.
+
```ts
export class TabPanelComponent implements OnInit, OnDestroy {
@Input() title: string;
- @ViewChild(TemplateRef, {static: true}) panelBody: TemplateRef;
+ @ViewChild(TemplateRef, { static: true }) panelBody: TemplateRef;
- @ContentChild(TabPanelContentDirective, {static: true}) explicitBody: TemplateRef;
+ @ContentChild(TabPanelContentDirective, { static: true })
+ explicitBody: TemplateRef;
- constructor(private tabGroup: TabGroupComponent) { }
+ constructor(private tabGroup: TabGroupComponent) {}
ngOnInit() {
this.tabGroup.addTabPanel(this);
@@ -69,11 +73,11 @@ export class TabPanelComponent implements OnInit, OnDestroy {
ngOnDestroy() {
this.tabGroup.removeTabPanel(this);
}
-
}
```
Và chúng ta có thể thay đổi cách sử dụng `TabPanelComponent` như sau:
+
```html
@@ -98,14 +102,14 @@ Bây giờ chúng ta chỉ cần thêm thắt một chút là Tab component sẽ
```ts
export class TabPanelComponent implements OnInit, OnDestroy {
@Input() title: string;
- @ViewChild(TemplateRef, {static: true}) implicitBody: TemplateRef;
+ @ViewChild(TemplateRef, { static: true }) implicitBody: TemplateRef;
- @ContentChild(TabPanelContentDirective, {static: true, read: TemplateRef}) explicitBody: TemplateRef;
+ @ContentChild(TabPanelContentDirective, { static: true, read: TemplateRef })
+ explicitBody: TemplateRef;
get panelBody(): TemplateRef {
return this.explicitBody || this.implicitBody;
}
-
}
```
@@ -116,7 +120,6 @@ export class TabPanelComponent implements OnInit, OnDestroy {
content tab 1
-
@@ -147,38 +150,37 @@ Quay trở lại với Tab component ở trên, nếu chúng ta không sử dụ
```ts
export class TabGroupComponent implements OnInit {
-
@Input() tabActiveIndex = 0;
@Output() tabActiveChange = new EventEmitter();
- @ContentChildren(TabPanelComponent) tabPanelList: QueryList
+ @ContentChildren(TabPanelComponent)
+ tabPanelList: QueryList;
- constructor() { }
+ constructor() {}
- ngOnInit() {
- }
+ ngOnInit() {}
selectItem(idx: number) {
this.tabActiveIndex = idx;
this.tabActiveChange.emit(idx);
}
-
}
```
```ts
export class TabPanelComponent {
@Input() title: string;
- @ViewChild(TemplateRef, {static: true}) implicitBody: TemplateRef;
+ @ViewChild(TemplateRef, { static: true }) implicitBody: TemplateRef;
- @ContentChild(TabPanelContentDirective, {static: true, read: TemplateRef}) explicitBody: TemplateRef;
+ @ContentChild(TabPanelContentDirective, { static: true, read: TemplateRef })
+ explicitBody: TemplateRef;
get panelBody(): TemplateRef {
return this.explicitBody || this.implicitBody;
}
-
}
```
+
Chỉ cần có thế là chúng ta đã có thể query được tất cả theo yêu cầu.
> Lưu ý: `ContentChildren` does not retrieve elements or directives that are in other components' templates, since a component's template is always a black box to its ancestors.
@@ -189,16 +191,15 @@ Chỉ cần có thế là chúng ta đã có thể query được tất cả the
```ts
export class TabGroupComponent implements OnInit, AfterContentInit {
-
@Input() tabActiveIndex = 0;
@Output() tabActiveChange = new EventEmitter();
- @ContentChildren(TabPanelComponent) tabPanelList: QueryList
+ @ContentChildren(TabPanelComponent)
+ tabPanelList: QueryList;
- constructor() { }
+ constructor() {}
- ngOnInit() {
- }
+ ngOnInit() {}
ngAfterContentInit() {
this.tabPanelList.changes.subscribe(() => {
@@ -212,7 +213,6 @@ export class TabGroupComponent implements OnInit, AfterContentInit {
this.tabActiveIndex = idx;
this.tabActiveChange.emit(idx);
}
-
}
```
@@ -225,7 +225,6 @@ Câu trả lời cho bạn đây:
- Content: là phần template được project vào thông qua cặp thẻ mở/đóng của một component/directive. Nó không trực tiếp quản lý. (Nó còn được gọi với tên light DOM).
-
## Summary
Như vậy, trong Day 17 bạn sẽ cần tìm hiểu sự khác biệt giữa view và content, làm thế nào để query một hoặc một số element được project vào component/directive đó.
@@ -246,6 +245,10 @@ Như vậy, trong Day 17 bạn sẽ cần tìm hiểu sự khác biệt giữa v
Mục tiêu của Day 18 là **Pipe trong Angular**.
+## Youtube Video
+
+[](https://youtu.be/m3ZgeVGLZag)
+
## Author
[Tiep Phan](https://github.com/tieppt)
diff --git a/Day018-pipes.md b/Day018-pipes.md
index 7402c97..f2fbee8 100644
--- a/Day018-pipes.md
+++ b/Day018-pipes.md
@@ -33,16 +33,17 @@ Mình có một biến tên là `now` ở trong component.
```ts
export class PipeExampleComponent implements OnInit {
- now = "2020-06-24T09:00:00.000Z";
+ now = '2020-06-24T09:00:00.000Z';
}
```
Và đây là cách mình hiển thị với built in pipe [Date][date] trong Angular
```html
-
{{ now | date }}
//Jun 24, 2020
-
{{ now | date:'medium'}}
//Jun 24, 2020, 5:00:00
-PM
+
{{ now | date }}
+//Jun 24, 2020
+
{{ now | date:'medium'}}
+//Jun 24, 2020, 5:00:00 PM
```
Chú ý phần giữa hai dấu ngoặc nhọn `{{ }}`, ngoài việc truyền vào variable bạn muốn hiển thì thì có thêm dấu xổ dọc `|`. Đó là pipe operator, sau đó là tên của pipe bạn đã định nghĩa. Tất cả pipe đều hoạt động theo cách này.
@@ -131,7 +132,7 @@ interface PipeTransform {
```ts
export class AppTitlePipe implements PipeTransform {
transform(resourceId: string): string {
- return resourceId ? "Edit" : "Add";
+ return resourceId ? 'Edit' : 'Add';
}
}
```
@@ -144,11 +145,11 @@ Giống như component có decorator `@Component`. Pipe cũng có decorator `@Pi
```ts
@Pipe({
- name: 'appTitle'
+ name: 'appTitle',
})
export class AppTitlePipe implements PipeTransform {
transform(resourceId: string): string {
- return resourceId ? "Edit" : "Add";
+ return resourceId ? 'Edit' : 'Add';
}
}
```
@@ -208,12 +209,12 @@ Với pipe `appTitle` ở trên, vì mình truyền vào giá trị string cho a
```ts
export class PipeExampleComponent implements OnInit {
- userIdChangeAfterFiveSeconds = "14324";
+ userIdChangeAfterFiveSeconds = '14324';
time$: Observable = timer(0, 1000).pipe(
map((val) => 5 - (val + 1)),
startWith(5),
finalize(() => {
- this.userIdChangeAfterFiveSeconds = "";
+ this.userIdChangeAfterFiveSeconds = '';
}),
takeWhile((val) => val >= 0)
);
@@ -262,7 +263,7 @@ Mình có một pipe tên là `isAdult`, để filter ra những user lớn hơn
```ts
@Pipe({
- name: "isAdult",
+ name: 'isAdult',
})
export class IsAdultPipe implements PipeTransform {
transform(arr: User[]): User[] {
@@ -331,6 +332,7 @@ Bây giờ thì bạn thấy list người lớn cũng đã được update khi
### 2. Set impure Pipe
Nếu bạn muốn trigger pipe khi có thay đổi value của một phần tử trong array, hay khi một property của object bị thay đổi. Bạn có thể cấu hình pipe của bạn với thuộc tính `pure` với giá trị `false` trong decorator. Mặc định, `pure` luôn có giá trị true.
+
```ts
@Pipe({
name: 'isAdult',
@@ -358,6 +360,10 @@ https://stackblitz.com/edit/angular-100-days-of-code-day-18-pipes
Mục tiêu của Day 19 là **Giới thiệu RxJS và Observable**.
+## Youtube Video
+
+[](https://youtu.be/4BJ2Vk67f6A)
+
## Author
[Trung Vo](https://github.com/trungk18)
diff --git a/Day019-intro-rxjs-observable.md b/Day019-intro-rxjs-observable.md
index ab25b5e..5f224e9 100644
--- a/Day019-intro-rxjs-observable.md
+++ b/Day019-intro-rxjs-observable.md
@@ -4,9 +4,9 @@ Khi bạn tìm hiểu về Angular, bạn sẽ thấy rằng nó có phụ thu
Đây vừa là một điểm mạnh, cũng vừa là điểm yếu của Angular. Vì RxJS xử lý asynchronous rất mạnh, nhưng bù lại bạn sẽ phải học thêm một số các concept khác xoay quanh stream. Thinking in streams.
-> RxJS is a library for composing asynchronous and event-based programs by using observable sequences. [RxJS Overview][RxJSOverview]
+> RxJS is a library for composing asynchronous and event-based programs by using observable sequences. [RxJS Overview][rxjsoverview]
-> In RxJS and in reactive programming in general, the fundamental unit of work is the stream. Think in terms of streams (think reactively) and design code in a way that, instead of holding on to data, you allow it to flow through and apply transformations along the way until it reaches your desired state. [RxJS in Action][RxJSinAction]
+> In RxJS and in reactive programming in general, the fundamental unit of work is the stream. Think in terms of streams (think reactively) and design code in a way that, instead of holding on to data, you allow it to flow through and apply transformations along the way until it reaches your desired state. [RxJS in Action][rxjsinaction]

@@ -30,15 +30,15 @@ Observable có thể coi là một Array của các value theo thời gian:
Hiện tại Observable chưa chính thức tồn tại trong JS, nhưng bạn có thể sử dụng RxJS để có thể có những thành phần chính như `Observable`, `Observer`, `Subject`, etc. Và một loạt các `operators` đi kèm để xử lý stream được dễ dàng hơn.
-
## Use-case throttle
Thông thường để xử lý một số event xảy ra quá nhanh và nhiều, trong khi chúng ta có thể bỏ qua một số value ở trung gian. Chẳng hạn bạn tạo một ứng dụng, ứng dụng đó có một button và bạn không muốn người dùng click vào button đấy nhanh quá (e.g: 500ms), nếu họ click quá nhanh thì bỏ qua và chỉ tương tác khi lần click gần nhất đã cách đó hơn khoảng thời gian quy định ở trên.
Bây giờ chúng ta thử implement giải pháp thông thường và giải pháp với RxJS xem sao. Các bạn hãy khoan đi vào chi tiết những phần code RxJS dưới đây:
+
```ts
-const btnjsThrottle = document.querySelector("#jsThrottle");
-const btnrxjsThrottle = document.querySelector("#rxjsThrottle");
+const btnjsThrottle = document.querySelector('#jsThrottle');
+const btnrxjsThrottle = document.querySelector('#rxjsThrottle');
// PURE JS version
let count = 0;
let rate = 500;
@@ -57,9 +57,9 @@ import { throttleTime, scan } from 'rxjs/operators';
fromEvent(btnrxjsThrottle, 'click')
.pipe(
throttleTime(500),
- scan(count => count + 1, 0)
+ scan((count) => count + 1, 0)
)
- .subscribe(count => console.log(`RxJS: Clicked ${count} times`));
+ .subscribe((count) => console.log(`RxJS: Clicked ${count} times`));
```
Với cách sử dụng RxJS cho bài toán trên quả không tệ, và nếu cần transform thêm nữa thì chúng ta hoàn toàn có thể handle được.
@@ -67,24 +67,33 @@ Với cách sử dụng RxJS cho bài toán trên quả không tệ, và nếu c
## RxJS core concepts
### Observable
+
- Observable: đại diện cho ý tưởng về một tập hợp các giá trị hoặc các sự kiện trong tương lai. Khi các giá trị hoặc sự kiện phát sinh trong tương lai, Observable sẽ điều phối nó đến Observer.
- Observable chỉ là một function (class) mà nó có một số yêu cầu đặc biệt. Nó nhận đầu vào là một Function, mà Function này nhận đầu vào là một Observer và trả về một function để có thể thực hiện việc cancel quá trình xử lý. Thông thường (RxJS 5 trở lên) chúng ta đặt tên function đó là unsubscribe.
-> Observables are functions that tie an observer to a producer. That’s it. They don’t necessarily set up the producer, they just set up an observer to listen to the producer, and generally return a teardown mechanism to remove that listener. The act of subscription is the act of “calling” the observable like a function, and passing it an observer. [Ben Lesh: Hot vs Cold Observables][BenLeshHotandCold]
+ > Observables are functions that tie an observer to a producer. That’s it. They don’t necessarily set up the producer, they just set up an observer to listen to the producer, and generally return a teardown mechanism to remove that listener. The act of subscription is the act of “calling” the observable like a function, and passing it an observer. [Ben Lesh: Hot vs Cold Observables][benleshhotandcold]
### Observer
+
- Observer là một tập hợp các callbacks tương ứng cho việc lắng nghe các giá trị (next, error, hay complete) được gửi đến bởi Observable.
### Subscription
+
- Subscription là kết quả có được sau khi thực hiện một Observable, nó thường dùng cho việc hủy việc tiếp tục xử lý.
### Operators
+
- Operators là các pure functions cho phép lập trình functional với Observable.
+
### Subject
+
- Subject để thực hiện việc gửi dữ liệu đến nhiều Observers (multicasting).
+
### Schedulers
+
- Một scheduler sẽ điều khiển khi nào một subscription bắt đầu thực thi, và khi nào sẽ gửi tín hiệu đi.
## Working with Observables
+
### Creating Observables
Để create một Observable chúng ta chỉ cần gọi constructor và truyền vào một function (gọi là **subscribe**), trong đó **subscribe function** sẽ nhận đầu vào là một Observer.
@@ -108,11 +117,12 @@ const observable = new Observable(function subscribe(observer) {
}, 1000);
return function unsubscribe() {
clearTimeout(id);
- }
+ };
});
```
### Invoking Observable
+
Các Observable hầu hết sẽ giống như một function, tức là nếu bạn có một Observable thì nó chỉ như khai báo một function, do đó những gì bên trong function sẽ không được chạy cho đến khi bạn invoke function đó (lazy computation).
Để invoke một Observable bạn chỉ cần `subscribe` vào nó là được. Và sau khi subscribe thì nó sẽ trả về một Subscription.
@@ -127,7 +137,7 @@ const subscription = observable.subscribe({
},
complete: () => {
console.log('Done');
- }
+ },
});
```
@@ -147,18 +157,17 @@ Có ba kiểu giá trị mà một Observable Execution có thể gửi đi:
Next notifications thường được sử dụng rộng rãi, nó cực kỳ quan trọng, vì nó gửi đi dữ liệu cần thiết cho một Observer.
-Error và Complete notifications có thể chỉ xảy ra duy nhất một lần trong một Observable Execution.
+Error và Complete notifications có thể chỉ xảy ra duy nhất một lần trong một Observable Execution.
> Lưu ý rằng, chỉ có 1 trong 2 loại tín hiệu trên được gửi đi, nếu đã complete thì không có error, nếu có error thì không có complete. (Chúng không thuộc về nhau :D). Và nếu đã gửi đi complete, hoặc error signal, thì sau đó không có dữ liệu nào được gửi đi nữa. Tức là stream đã close.
> In an Observable Execution, zero to infinite Next notifications may be delivered. If either an Error or Complete notification is delivered, then nothing else can be delivered afterwards.
-
### Disposing Observable Executions
-Bởi vì quá trình thực thi Observable có thể lặp vô hạn, hoặc trong trường hợp nào đó bạn muốn thực hiện hủy việc thực thi vì việc này không còn cần thiết nữa - dữ liệu đã lỗi thời, có dữ liệu khác thay thế. Các bạn có thể liên tưởng tới việc *close websocket stream*, *removeEvenListener* cho một element nào đó đã bị loại bỏ khỏi DOM chẳng hạn.
+Bởi vì quá trình thực thi Observable có thể lặp vô hạn, hoặc trong trường hợp nào đó bạn muốn thực hiện hủy việc thực thi vì việc này không còn cần thiết nữa - dữ liệu đã lỗi thời, có dữ liệu khác thay thế. Các bạn có thể liên tưởng tới việc _close websocket stream_, _removeEvenListener_ cho một element nào đó đã bị loại bỏ khỏi DOM chẳng hạn.
-Observable có cơ chế tương ứng, cho phép chúng ta hủy việc thực thi. Đó là khi subscribe được gọi, một Observer sẽ bị gắn với một *Observable execution* mới được tạo, sau đó nó sẽ trả về một object thuộc type Subscription. Kiểu dữ liệu này có một method `unsubscribe` khi chúng ta gọi đến, nó sẽ thực hiện cơ chế để hủy việc thực thi.
+Observable có cơ chế tương ứng, cho phép chúng ta hủy việc thực thi. Đó là khi subscribe được gọi, một Observer sẽ bị gắn với một _Observable execution_ mới được tạo, sau đó nó sẽ trả về một object thuộc type Subscription. Kiểu dữ liệu này có một method `unsubscribe` khi chúng ta gọi đến, nó sẽ thực hiện cơ chế để hủy việc thực thi.
> Lưu ý: nếu bạn tự tạo Observable (bằng new Observable chẳng hạn) thì bạn phải tự thiết lập cơ chế để hủy.
@@ -174,31 +183,34 @@ const subscription = observable.subscribe({
},
complete: () => {
console.log('Done');
- }
+ },
});
setTimeout(() => {
subscription.unsubscribe();
-}, 500)
+}, 500);
```
-
## Observers
+
Observer là một Consumer những dữ liệu được gửi bởi Observable. Observer là một object chứa một tập 3 callbacks tương ứng cho mỗi loại notification được gửi từ Observable: `next`, `error`, `complete`.
Một Observer có dạng như sau:
+
```ts
const observer = {
- next: x => console.log('Observer got a next value: ' + x),
- error: err => console.error('Observer got an error: ' + err),
+ next: (x) => console.log('Observer got a next value: ' + x),
+ error: (err) => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
```
Observer được cung cấp là tham số đầu vào của subscribe để kích hoạt Observable execution.
+
```ts
observable.subscribe(observer);
```
+
> Observers are just objects with three callbacks, one for each type of notification that an Observable may deliver.
Observe có thể chỉ có một số callbacks trong bộ 3 callbacks kể trên (có thể là một object không có callback nào trong bộ kể trên, trường hợp này ít dùng đến).
@@ -206,18 +218,19 @@ Observe có thể chỉ có một số callbacks trong bộ 3 callbacks kể tr
Ngoài cách dùng như trên, `observable.subscribe` sẽ chuẩn hóa các callbacks thành Observer object tương ứng, bạn có thể truyền vào các hàm rời rạc nhau, nhưng cần lưu ý truyền đúng thứ tự callback.
Cách dùng này hiện tại không khuyến cáo sử dụng, chỉ dùng cách truyền function nếu bạn có một hàm để handle `Next` notification.
+
```ts
observable.subscribe(
- x => console.log('Observer got a next value: ' + x),
- err => console.error('Observer got an error: ' + err),
+ (x) => console.log('Observer got a next value: ' + x),
+ (err) => console.error('Observer got an error: ' + err),
() => console.log('Observer got a complete notification')
);
// tương đương với
const observer = {
- next: x => console.log('Observer got a next value: ' + x),
- error: err => console.error('Observer got an error: ' + err),
+ next: (x) => console.log('Observer got a next value: ' + x),
+ error: (err) => console.error('Observer got an error: ' + err),
complete: () => console.log('Observer got a complete notification'),
};
@@ -225,27 +238,28 @@ observable.subscribe(observer);
```
> Lưu ý: Nếu bạn không muốn truyền error handler function vào, hãy truyền `null`/`undefined`:
+
```ts
observable.subscribe(
- x => console.log('Observer got a next value: ' + x),
+ (x) => console.log('Observer got a next value: ' + x),
null,
() => console.log('Observer got a complete notification')
);
```
# Subscription
+
Subscription là một object đại diện cho một nguồn tài nguyên có khả năng hủy được, thông thường trong Rxjs là hủy Observable execution. Subscription có chứa một method quan trọng unsubscribe (từ Rxjs 5 trở lên), khi method này được gọi, execution sẽ bị hủy.
Ví dụ: chúng ta có một đồng hồ đếm thời gian, mỗi giây sẽ gửi đi một giá trị, giả sử sau khi chạy 5s chúng ta cần hủy phần thực thi này.
```ts
const observable = interval(1000);
-const subscription = observable.subscribe(x => console.log(x));
+const subscription = observable.subscribe((x) => console.log(x));
setTimeout(() => {
subscription.unsubscribe();
}, 5000);
-
```
> A Subscription essentially just has an unsubscribe() function to release resources or cancel Observable executions.
@@ -258,8 +272,8 @@ Một Subscription có thể chứa trong nó nhiều Subscriptions con, khi Sub
const foo = interval(500);
const bar = interval(700);
-const subscription = foo.subscribe(x => console.log('first: ' + x));
-const childSub = bar.subscribe(x => console.log('second: ' + x));
+const subscription = foo.subscribe((x) => console.log('first: ' + x));
+const childSub = bar.subscribe((x) => console.log('second: ' + x));
subscription.add(childSub);
@@ -268,6 +282,7 @@ setTimeout(() => {
subscription.unsubscribe();
}, 2000);
```
+
## Summary
Vậy là qua Day 19 hy vọng các bạn đã hiểu được cơ bản về RxJS và Observable.
@@ -286,14 +301,16 @@ https://stackblitz.com/edit/rxjs-racgao?file=index.ts
Mục tiêu của Day 20 là **RxJS Creation Operators**.
+## Youtube Video
+
+[](https://youtu.be/lRfyUh4ex38)
+
## Author
[Tiep Phan](https://github.com/tieppt)
`#100DaysOfCodeAngular` `#100DaysOfCode` `#AngularVietNam100DoC_Day19`
-[RxJSOverview]: https://rxjs.dev/guide/overview
-
-[RxJSinAction]: https://freecontent.manning.com/reactive-fundamentals-thinking-in-streams/#:~:text=In%20RxJS%20and%20in%20reactive,it%20reaches%20your%20desired%20state.
-
-[BenLeshHotandCold]: https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339#:~:text=Observables%20are%20functions%20that%20tie,mechanism%20to%20remove%20that%20listener.
\ No newline at end of file
+[rxjsoverview]: https://rxjs.dev/guide/overview
+[rxjsinaction]: https://freecontent.manning.com/reactive-fundamentals-thinking-in-streams/#:~:text=In%20RxJS%20and%20in%20reactive,it%20reaches%20your%20desired%20state.
+[benleshhotandcold]: https://medium.com/@benlesh/hot-vs-cold-observables-f8094ed53339#:~:text=Observables%20are%20functions%20that%20tie,mechanism%20to%20remove%20that%20listener.
diff --git a/Day020-rxjs-creation.md b/Day020-rxjs-creation.md
index f8ac1d0..f641b5e 100644
--- a/Day020-rxjs-creation.md
+++ b/Day020-rxjs-creation.md
@@ -294,6 +294,10 @@ Với `defer()`, chúng ta đã có 3 giá trị khác nhau cho mỗi lần subs
Mục tiêu của Day 21 là **RxJS Transformation Operators**.
+## Youtube Video
+
+[](https://youtu.be/OWvK8ZB_Wrc)
+
## Author
[Chau Tran](https://github.com/nartc)
diff --git a/Day021-rxjs-transformation.md b/Day021-rxjs-transformation.md
index a88aebf..65a1763 100644
--- a/Day021-rxjs-transformation.md
+++ b/Day021-rxjs-transformation.md
@@ -9,24 +9,17 @@ Một Pipeable Operator là một function nó nhận đầu vào là một Obse
Cú pháp:
```ts
-observableInstance.pipe(
- operator1(),
- operator2(),
-)
+observableInstance.pipe(operator1(), operator2());
```
-Với cú pháp trên thì `observableInstance` có *pipe* bao nhiêu operator đi nữa thì nó vẫn không đổi, và cuối cùng chúng ta sẽ nhận lại một Observable nên để có thể sử dụng thì chúng ta cần gán lại, hoặc thực hiện subscribe ngay sau khi *pipe*:
+Với cú pháp trên thì `observableInstance` có _pipe_ bao nhiêu operator đi nữa thì nó vẫn không đổi, và cuối cùng chúng ta sẽ nhận lại một Observable nên để có thể sử dụng thì chúng ta cần gán lại, hoặc thực hiện subscribe ngay sau khi _pipe_:
```ts
-const returnObservable = observableInstance.pipe(
- operator1(),
- operator2(),
-)
+const returnObservable = observableInstance.pipe(operator1(), operator2());
```
Nếu bạn dùng với RxJS version < 5.5 thì có thể các bạn sẽ thấy cú pháp sử dụng khác là prototype method chain, nhưng nếu bạn dùng từ version 5.5 trở lên thì nên dùng pipe operators, dựa theo một số giải thích ở đây: [pipeable operators](https://rxjs.dev/guide/v6/pipeable-operators)
-
Pipeable Operators có thể chia thành nhiều category khác nhau, trong ngày hôm nay chúng ta sẽ tìm hiểu về **Transformation Operators**.
## Transformation Operators
@@ -35,18 +28,30 @@ Chắc hẳn các bạn đã quá quen với làm việc cùng Array trong JS, c
```ts
const users = [
- {id: 'ddfe3653-1569-4f2f-b57f-bf9bae542662', username: 'tiepphan', firstname: 'tiep', lastname: 'phan'},
- {id: '34784716-019b-4868-86cd-02287e49c2d3', username: 'nartc', firstname: 'chau', lastname: 'tran'},
+ {
+ id: 'ddfe3653-1569-4f2f-b57f-bf9bae542662',
+ username: 'tiepphan',
+ firstname: 'tiep',
+ lastname: 'phan',
+ },
+ {
+ id: '34784716-019b-4868-86cd-02287e49c2d3',
+ username: 'nartc',
+ firstname: 'chau',
+ lastname: 'tran',
+ },
];
-const usersVm = users.map(user => {
+const usersVm = users.map((user) => {
return {
...user,
- fullname: `${user.firstname} ${user.lastname}`
- }
+ fullname: `${user.firstname} ${user.lastname}`,
+ };
});
```
+
Kết quả có được sẽ có dạng như sau:
+
```ts
usersVm = [
{
@@ -54,25 +59,24 @@ usersVm = [
username: 'tiepphan',
firstname: 'tiep',
lastname: 'phan',
- fullname: 'tiep phan'
+ fullname: 'tiep phan',
},
{
id: '34784716-019b-4868-86cd-02287e49c2d3',
username: 'nartc',
firstname: 'chau',
lastname: 'tran',
- fullname: 'chau tran'
- }
-]
+ fullname: 'chau tran',
+ },
+];
```
Như vậy qua một lần biến đổi, chúng ta sẽ có được dữ liệu như ý muốn.
Vậy với Observable thì sao. Giả sử chúng ta đang có một hệ thống tracking xem những ai đăng nhập vào hệ thống. Do đó ở một số thời điểm sẽ có một/một vài người đăng nhập, và mỗi lần như thế hệ thống sẽ gửi cho chúng ta một event để biết. Bây giờ chúng ta cũng làm nhiệm vụ tương tự như `map` ở trên thì sao.
-
```ts
-import { Observable } from 'rxjs';
+import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
interface User {
@@ -84,8 +88,18 @@ interface User {
const source = new Observable((observer) => {
const users = [
- {id: 'ddfe3653-1569-4f2f-b57f-bf9bae542662', username: 'tiepphan', firstname: 'tiep', lastname: 'phan'},
- {id: '34784716-019b-4868-86cd-02287e49c2d3', username: 'nartc', firstname: 'chau', lastname: 'tran'},
+ {
+ id: 'ddfe3653-1569-4f2f-b57f-bf9bae542662',
+ username: 'tiepphan',
+ firstname: 'tiep',
+ lastname: 'phan',
+ },
+ {
+ id: '34784716-019b-4868-86cd-02287e49c2d3',
+ username: 'nartc',
+ firstname: 'chau',
+ lastname: 'tran',
+ },
];
setTimeout(() => {
@@ -98,12 +112,11 @@ const source = new Observable((observer) => {
});
const observer = {
- next: value => console.log(value),
- error: err => console.error(err),
+ next: (value) => console.log(value),
+ error: (err) => console.error(err),
complete: () => console.log('completed'),
};
source.subscribe(observer);
-
```
Khi chạy chương trình bạn sẽ thấy rằng, sau 1 giây thì sẽ emit ra user đầu tiên, và sau đó 2 giây thì sẽ emit ra user thứ hai kèm theo complete signal.
@@ -121,23 +134,25 @@ Cách đơn giản nhất là bạn sẽ vào hàm next để thực hiện tín
```ts
import { map } from 'rxjs/operators';
-source.pipe(
- map(user => {
- return {
- ...user,
- fullname: `${user.firstname} ${user.lastname}`
- };
- })
-).subscribe(observer);
+source
+ .pipe(
+ map((user) => {
+ return {
+ ...user,
+ fullname: `${user.firstname} ${user.lastname}`,
+ };
+ })
+ )
+ .subscribe(observer);
```
Hoặc giả sử yêu cầu của chúng ta giờ đây thay đổi, chỉ cần trả về id của user mỗi khi được emit.
+
```ts
-source.pipe(
- map(user => user.id)
-).subscribe(observer);
+source.pipe(map((user) => user.id)).subscribe(observer);
```
-Cách dùng map này *khá giống* cách dùng map của array ở trên phải không???
+
+Cách dùng map này _khá giống_ cách dùng map của array ở trên phải không???

@@ -146,13 +161,13 @@ Cách dùng map này *khá giống* cách dùng map của array ở trên phải
`pluck(...properties: string[]): OperatorFunction`
Đối với yêu cầu map ra một property trong một object như vừa rồi, bạn có thể sử dụng một cách khác là `pluck`:
+
```ts
import { pluck } from 'rxjs/operators';
-source.pipe(
- pluck('id')
-).subscribe(observer);
+source.pipe(pluck('id')).subscribe(observer);
```
+

### mapTo
@@ -174,12 +189,8 @@ const mouseover$ = fromEvent(element, 'mouseover');
const mouseleave$ = fromEvent(element, 'mouseleave');
const hover$ = merge(
- mouseover$.pipe(
- mapTo(true),
- ),
- mouseleave$.pipe(
- mapTo(false),
- )
+ mouseover$.pipe(mapTo(true)),
+ mouseleave$.pipe(mapTo(false))
);
hover$.subscribe(observer);
@@ -202,17 +213,28 @@ const button = document.querySelector('#add');
const click$ = fromEvent(button, 'click');
-click$.pipe(
- scan((acc, curr) => acc + 1, 0)
-).subscribe(observer);
+click$.pipe(scan((acc, curr) => acc + 1, 0)).subscribe(observer);
```
+
Count số bài đăng của những người dùng đăng nhập theo thời gian:
```ts
const users$ = new Observable((observer) => {
const users = [
- {id: 'ddfe3653-1569-4f2f-b57f-bf9bae542662', username: 'tiepphan', firstname: 'tiep', lastname: 'phan', postCount: 5},
- {id: '34784716-019b-4868-86cd-02287e49c2d3', username: 'nartc', firstname: 'chau', lastname: 'tran', postCount: 22},
+ {
+ id: 'ddfe3653-1569-4f2f-b57f-bf9bae542662',
+ username: 'tiepphan',
+ firstname: 'tiep',
+ lastname: 'phan',
+ postCount: 5,
+ },
+ {
+ id: '34784716-019b-4868-86cd-02287e49c2d3',
+ username: 'nartc',
+ firstname: 'chau',
+ lastname: 'tran',
+ postCount: 22,
+ },
];
setTimeout(() => {
@@ -224,10 +246,9 @@ const users$ = new Observable((observer) => {
}, 3000);
});
-users$.pipe(
- scan((acc, curr) => acc + curr.postCount, 0)
-).subscribe(observer);
+users$.pipe(scan((acc, curr) => acc + curr.postCount, 0)).subscribe(observer);
```
+

### reduce
@@ -237,9 +258,7 @@ users$.pipe(
Operator này khá giống `scan` là nó sẽ reduce value overtime, nhưng nó sẽ đợi đến khi source complete rồi thì nó mới emit một giá trị cuối cùng và gửi đi `complete`.
```ts
-users$.pipe(
- reduce((acc, curr) => acc + curr.postCount, 0)
-).subscribe(observer);
+users$.pipe(reduce((acc, curr) => acc + curr.postCount, 0)).subscribe(observer);
```

@@ -251,18 +270,15 @@ users$.pipe(
Giả sử bạn cần collect toàn bộ các value emit bởi stream rồi lưu trữ thành một array, sau đó đợi đến khi stream complete thì emit một array và complete. Lúc này bạn hoàn toàn có thể sử dụng `reduce`:
```ts
-users$.pipe(
- reduce((acc, curr) => [...acc, curr], [])
-).subscribe(observer);
+users$.pipe(reduce((acc, curr) => [...acc, curr], [])).subscribe(observer);
```
Nhưng có một cách viết khác ngắn gọn hơn đó là dùng `toArray`.
```ts
-users$.pipe(
- toArray()
-).subscribe(observer);
+users$.pipe(toArray()).subscribe(observer);
```
+
### buffer
`buffer(closingNotifier: Observable): OperatorFunction`
@@ -274,20 +290,15 @@ const interval$ = interval(1000);
const click$ = fromEvent(document, 'click');
-const buffer$ = interval$.pipe(
- buffer(click$)
-);
-
+const buffer$ = interval$.pipe(buffer(click$));
-const subscribe = buffer$.subscribe(
- val => console.log('Buffered Values: ', val)
+const subscribe = buffer$.subscribe((val) =>
+ console.log('Buffered Values: ', val)
);
// output có dạng
-"Buffered Values: "
-[0, 1]
-"Buffered Values: "
-[2, 3, 4, 5, 6]
+'Buffered Values: '[(0, 1)];
+'Buffered Values: '[(2, 3, 4, 5, 6)];
```

@@ -317,6 +328,7 @@ const bufferTimeSub = bufferTime.subscribe(
[4, 5]
...
```
+

## Summary
@@ -330,6 +342,10 @@ Như vậy trong Day 21 chúng ta đã tìm hiểu cơ bản về một số **T
Mục tiêu của Day 22 là **RxJS Filtering Operators**.
+## Youtube Video
+
+[](https://youtu.be/AG97A7_NCLE)
+
## Author
[Tiep Phan](https://github.com/tieppt)
diff --git a/Day022-rxjs-filtering.md b/Day022-rxjs-filtering.md
index c666e50..7cb8056 100644
--- a/Day022-rxjs-filtering.md
+++ b/Day022-rxjs-filtering.md
@@ -377,6 +377,10 @@ Mục tiêu ngày 23 sẽ là **RxJS Combination Operators**
- [RxJS Overview](https://rxjs.dev/guide/overview)
- [LearnRxJS](https://www.learnrxjs.io/)
+## Youtube Video
+
+[](https://youtu.be/KEBpdRL11Nw)
+
## Author
[Chau Tran](https://github.com/nartc)
diff --git a/Day023-rxjs-combination.md b/Day023-rxjs-combination.md
index cb9098a..5200d6b 100644
--- a/Day023-rxjs-combination.md
+++ b/Day023-rxjs-combination.md
@@ -564,6 +564,10 @@ Mục tiêu ngày 24 sẽ là **RxJS Error handling/conditional Operators**
- [RxJS Overview](https://rxjs.dev/guide/overview)
- [LearnRxJS](https://www.learnrxjs.io/)
+## Youtube Video
+
+[](https://youtu.be/qChj6nScvl0)
+
## Author
[Chau Tran](https://github.com/nartc)
diff --git a/Day024-rxjs-error-handling-conditional.md b/Day024-rxjs-error-handling-conditional.md
index b6cb804..239c70e 100644
--- a/Day024-rxjs-error-handling-conditional.md
+++ b/Day024-rxjs-error-handling-conditional.md
@@ -27,14 +27,14 @@ const observer = {
`catchError>(selector: (err: any, caught: Observable) => O): OperatorFunction>`
```ts
-import { of } from "rxjs";
-import { map, catchError } from "rxjs/operators";
+import { of } from 'rxjs';
+import { map, catchError } from 'rxjs/operators';
const cached = [4, 5];
of(1, 2, 3, 4, 5)
.pipe(
- map(n => {
+ map((n) => {
if (cached.includes(n)) {
- throw new Error("Duplicated: " + n);
+ throw new Error('Duplicated: ' + n);
}
return n;
}),
@@ -43,10 +43,9 @@ of(1, 2, 3, 4, 5)
.subscribe(observer);
/**
-* Output:
-* --1--2--3--(next: Error)--|
-*/
-
+ * Output:
+ * --1--2--3--(next: Error)--|
+ */
```
Trong trường hợp trên nếu chúng ta không bắt error thì `observer.error` sẽ là nơi đón Error, nhưng vì chúng ta trả về là một `next: Error` nên error này đã được handle bởi `observer.next`.
@@ -54,31 +53,24 @@ Trong trường hợp trên nếu chúng ta không bắt error thì `observer.er
Một ví dụ trong ứng dụng là khi các bạn làm việc với `forkJoin` [Day 23](Day023-rxjs-combination.md), lúc này nếu một stream nào đó emit error thì toàn bộ stream sẽ bị văng ra error. Trong trường hợp các bạn muốn nó vẫn tiếp tục chạy hết và chúng ta sẽ tách Error ra ở pipe tiếp theo thì chỉ cần `catchError` lại như trên là được.
```ts
-forkJoin([
- of(1),
- of(2),
- throwError(new Error('401')),
-]).subscribe(observer);
+forkJoin([of(1), of(2), throwError(new Error('401'))]).subscribe(observer);
/**
-* Output:
-* --(x: Error 401)--
-*/
-
+ * Output:
+ * --(x: Error 401)--
+ */
// with catchError
forkJoin([
of(1),
of(2),
- throwError(new Error('401')).pipe(
- catchError(err => of(err))
- ),
+ throwError(new Error('401')).pipe(catchError((err) => of(err))),
]).subscribe(observer);
/**
-* Output:
-* --(next: [1, 2, Error 401])|--
-*/
+ * Output:
+ * --(next: [1, 2, Error 401])|--
+ */
```

@@ -89,9 +81,9 @@ Nếu bạn muốn retry kèm theo giới hạn về số lần, chúng ta có t
```ts
of(1, 2, 3, 4, 5)
.pipe(
- map(n => {
+ map((n) => {
if (cached.includes(n)) {
- throw new Error("Duplicated: " + n);
+ throw new Error('Duplicated: ' + n);
}
return n;
}),
@@ -101,9 +93,9 @@ of(1, 2, 3, 4, 5)
.subscribe(observer);
/**
-* Output:
-* --1--2--3--1--2--3--1|
-*/
+ * Output:
+ * --1--2--3--1--2--3--1|
+ */
```
Ngoài ra, trong catchError bạn hoàn toàn có thể throw về một error để pipe phía sau có thể handle tiếp.
@@ -122,9 +114,9 @@ Nó khá hữu ích khi bạn muốn retry HTTP request chẳng hạn. Lưu ý c
const cached = [4, 5];
of(1, 2, 3, 4, 5)
.pipe(
- map(n => {
+ map((n) => {
if (cached.includes(n)) {
- throw new Error("Duplicated: " + n);
+ throw new Error('Duplicated: ' + n);
}
return n;
}),
@@ -133,9 +125,9 @@ of(1, 2, 3, 4, 5)
.subscribe(observer);
/**
-* Output:
-* --1--2--3--1--2--3--1--2--3--1--2--3--(x: Error)
-*/
+ * Output:
+ * --1--2--3--1--2--3--1--2--3--1--2--3--(x: Error)
+ */
```

@@ -162,9 +154,9 @@ export function retryBackoff(
defer(() => {
let index = 0;
return source.pipe(
- retryWhen(errors =>
+ retryWhen((errors) =>
errors.pipe(
- concatMap(error => {
+ concatMap((error) => {
const attempt = index++;
return iif(
() => attempt < maxRetries && shouldRetry(error),
@@ -189,6 +181,7 @@ export function retryBackoff(
## RxJS Error Conditional Operators
### defaultIfEmpty/throwIfEmpty
+
`defaultIfEmpty(defaultValue: R = null): OperatorFunction`
`throwIfEmpty(errorFactory: () => any = defaultErrorFactory): MonoTypeOperatorFunction`
@@ -200,16 +193,17 @@ Giả sử, chúng ta cần làm yêu cầu nếu người dùng không click v
```ts
import { fromEvent, timer } from 'rxjs';
import { throwIfEmpty, takeUntil } from 'rxjs/operators';
-
+
const click$ = fromEvent(document, 'click');
-
-click$.pipe(
- takeUntil(timer(1000)),
- throwIfEmpty(
- () => new Error('the document was not clicked within 1 second')
- ),
-)
-.subscribe(observer);
+
+click$
+ .pipe(
+ takeUntil(timer(1000)),
+ throwIfEmpty(
+ () => new Error('the document was not clicked within 1 second')
+ )
+ )
+ .subscribe(observer);
```

@@ -225,31 +219,32 @@ Opeator này sẽ trả về true nếu tất cả các value emit của source
> Lưu ý nếu source không complete thì sẽ không có gì emit ra cả.
```ts
-of(1, 2, 3, 4, 5, 6).pipe(
- every(x => x < 5),
-)
-.subscribe(observer);
+of(1, 2, 3, 4, 5, 6)
+ .pipe(every((x) => x < 5))
+ .subscribe(observer);
/**
-* Output:
-* ------false|
-*/
+ * Output:
+ * ------false|
+ */
```
+

Các method của Array trong JS, có cả `every` và `some`, nếu các bạn muốn có một operator giống some ở trong RxJS thì có thể dùng `first` kèm theo predicate function. Giống như trong phần [Router của Angular](https://github.com/angular/angular/blob/10.0.x/packages/router/src/operators/check_guards.ts#L74-L76)
```ts
-of(1, 2, 3, 14, 5, 6).pipe(
- first(x => x > 10, false),
- map(v => Boolean(v))
-)
-.subscribe(observer);
+of(1, 2, 3, 14, 5, 6)
+ .pipe(
+ first((x) => x > 10, false),
+ map((v) => Boolean(v))
+ )
+ .subscribe(observer);
/**
-* Output:
-* ------true|
-*/
+ * Output:
+ * ------true|
+ */
```
### iif
@@ -266,31 +261,25 @@ Opeartor này cho phép chúng ta lựa chọn Observable tương ứng với h
> If you have more complex logic that requires decision between more than two Observables, `defer` will probably be a better choice. Actually `iif` can be easily implemented with `defer` and exists only for convenience and readability reasons.
-
```ts
import { iif, of } from 'rxjs';
-
+
let subscribeToFirst;
-const firstOrSecond = iif(
- () => subscribeToFirst,
- of('first'),
- of('second'),
-);
-
+const firstOrSecond = iif(() => subscribeToFirst, of('first'), of('second'));
+
subscribeToFirst = true;
-firstOrSecond.subscribe(value => console.log(value));
-
+firstOrSecond.subscribe((value) => console.log(value));
+
// Logs:
// "first"
-
+
subscribeToFirst = false;
-firstOrSecond.subscribe(value => console.log(value));
-
+firstOrSecond.subscribe((value) => console.log(value));
+
// Logs:
// "second"
```
-
## Summary
Như vậy ngày hôm nay chúng ta đã tăng cường thêm nội lực (💪) về RxJS qua một số operators: Error Handling và Conditional. Hẹn gặp lại các bạn vào ngày mai.
@@ -303,9 +292,12 @@ Mục tiêu ngày 25 sẽ là **RxJS Higher-order Observable & Utility**
- [LearnRxJS](https://www.learnrxjs.io/)
- [rxmarbles](https://rxmarbles.com/)
+## Youtube Video
+
+[](https://youtu.be/UnfiFpY5VtQ)
+
## Author
[Tiep Phan](https://github.com/tieppt)
-
`#100DaysOfCodeAngular` `#100DaysOfCode` `#AngularVietNam100DoC_Day24`
diff --git a/Day025-rxjs-hoo-utility.md b/Day025-rxjs-hoo-utility.md
index e9c98e7..430f78c 100644
--- a/Day025-rxjs-hoo-utility.md
+++ b/Day025-rxjs-hoo-utility.md
@@ -451,6 +451,10 @@ Mục tiêu của ngày 26 sẽ là **RxJS Subject và Multicasting**
- [RxJS Overview](https://rxjs.dev/guide/overview)
- [LearnRxJS](https://www.learnrxjs.io/)
+## Youtube Video
+
+[](https://youtu.be/5SD2YIxMBBM)
+
## Author
[Chau Tran](https://github.com/nartc)
diff --git a/Day026-rxjs-subject-multicast.md b/Day026-rxjs-subject-multicast.md
index a878f05..dfdea72 100644
--- a/Day026-rxjs-subject-multicast.md
+++ b/Day026-rxjs-subject-multicast.md
@@ -9,9 +9,7 @@ Như chúng ta đã biết, đối với các **Observable** thông thường, m
Ví dụ, chúng ta có một observable như sau:
```ts
-const observable = interval(500).pipe(
- take(5)
-);
+const observable = interval(500).pipe(take(5));
const observerA = {
next: (val) => console.log(`Observer A: ${val}`),
@@ -36,9 +34,7 @@ Observer A complete
Giả sử chúng ta subscribe thêm một observer mới sau một khoảng thời gian (ví dụ: 2s)
```ts
-const observable = interval(500).pipe(
- take(5)
-);
+const observable = interval(500).pipe(take(5));
const observerA = {
next: (val) => console.log(`Observer A: ${val}`),
@@ -136,15 +132,15 @@ const hybridObserver = {
this.observers.push(observer);
},
next(value) {
- this.observers.forEach(observer => observer.next(value));
+ this.observers.forEach((observer) => observer.next(value));
},
error(err) {
- this.observers.forEach(observer => observer.error(err));
+ this.observers.forEach((observer) => observer.error(err));
},
complete() {
- this.observers.forEach(observer => observer.complete());
- }
-}
+ this.observers.forEach((observer) => observer.complete());
+ },
+};
hybridObserver.subscribe(observerA);
@@ -181,8 +177,8 @@ Với phương pháp kể trên, chúng ta đã cơ bản chuyển đổi từ m
- multicast: cũng là hai người (có thể nhiều hơn) vào xem video ở Youtube, nhưng video đó đang phát Live (theo dõi một show truyền hình, hay một trận bóng đá Live chẳng hạn). Lúc này Youtube sẽ phát video Live, và những người vào xem video đó sẽ có cùng một thời điểm của video đó (cùng thời điểm của trận đấu đang diễn ra chẳng hạn).
-
## Subject
+
Do Subject vừa là một Observable (chúng ta có thể subscribe vào nó), vừa là một Observer (có các method để chúng ta tự control khi nào gửi notification). Nên nó khá hay được sử dụng trong ứng dụng, ví dụ để làm Event Bus chẳng hạn.
Dưới đây là một ví dụ về type ahead:
@@ -191,22 +187,24 @@ Dưới đây là một ví dụ về type ahead:
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
- styleUrls: [ './app.component.css' ]
+ styleUrls: ['./app.component.css'],
})
-export class AppComponent implements OnInit {
-
+export class AppComponent implements OnInit {
searchTerm$ = new Subject();
ngOnInit() {
- this.searchTerm$.asObservable().pipe(
- throttleTime(250, undefined, {
- leading: true,
- trailing: true,
- }),
- distinctUntilChanged(),
- ).subscribe({
- next: value => console.log(value)
- });
+ this.searchTerm$
+ .asObservable()
+ .pipe(
+ throttleTime(250, undefined, {
+ leading: true,
+ trailing: true,
+ }),
+ distinctUntilChanged()
+ )
+ .subscribe({
+ next: (value) => console.log(value),
+ });
}
onInput(event: Event) {
@@ -228,14 +226,14 @@ Một trong những vấn đề khi làm việc với Subject đó là tình hu
const subject = new Subject();
subject.subscribe({
- next: v => console.log("observerA: " + v)
+ next: (v) => console.log('observerA: ' + v),
});
subject.next(1);
subject.next(2);
subject.subscribe({
- next: v => console.log("observerB: " + v)
+ next: (v) => console.log('observerB: ' + v),
});
subject.next(3);
@@ -249,6 +247,7 @@ observerA: 3
observerB: 3
*/
```
+
Để giải quyết vấn đề này, chúng ta có một trong các biến thể của **Subject** đó là **BehaviorSubject**, nó là biến thế có khái niệm về "the current value". **BehaviorSubject** lưu trữ lại giá trị mới emit gần nhất để khi một Observer mới subscribe vào, nó sẽ emit giá trị đó ngay lập tức cho Observer vừa rồi.
> A variant of Subject that requires an initial value and emits its current value whenever it is subscribed to. [BehaviorSubject](https://rxjs.dev/api/index/class/BehaviorSubject)
@@ -263,14 +262,14 @@ Lưu ý: BehaviorSubject yêu cầu phải có giá trị khởi tạo khi tạo
const subject = new BehaviorSubject(0); // 0 is the initial value
subject.subscribe({
- next: (v) => console.log('observerA: ' + v)
+ next: (v) => console.log('observerA: ' + v),
});
subject.next(1);
subject.next(2);
subject.subscribe({
- next: (v) => console.log('observerB: ' + v)
+ next: (v) => console.log('observerB: ' + v),
});
subject.next(3);
@@ -288,6 +287,7 @@ observerB: 3
```
### ReplaySubject
+
Một ReplaySubject tương tự như một BehaviorSubject khi nó có thể gửi những dữ liệu trước đó cho Observer mới subscribe, nhưng nó có thể lưu giữ nhiều giá trị (có thể là toàn bộ giá trị của stream từ thời điểm ban đầu).
Tham số đầu vào của ReplaySubject có thể là:
@@ -301,7 +301,7 @@ Tham số đầu vào của ReplaySubject có thể là:
const subject = new ReplaySubject(3); // buffer 3 values for new subscribers
subject.subscribe({
- next: (v) => console.log('observerA: ' + v)
+ next: (v) => console.log('observerA: ' + v),
});
subject.next(1);
@@ -310,7 +310,7 @@ subject.next(3);
subject.next(4);
subject.subscribe({
- next: (v) => console.log('observerB: ' + v)
+ next: (v) => console.log('observerB: ' + v),
});
subject.next(5);
@@ -329,13 +329,14 @@ observerA: 5
observerB: 5
*/
```
+
Hoặc kết hợp buffer với `windowTime`:
```ts
const subject = new ReplaySubject(100, 500 /* windowTime */);
subject.subscribe({
- next: (v) => console.log('observerA: ' + v)
+ next: (v) => console.log('observerA: ' + v),
});
let i = 1;
@@ -343,7 +344,7 @@ const id = setInterval(() => subject.next(i++), 200);
setTimeout(() => {
subject.subscribe({
- next: (v) => console.log('observerB: ' + v)
+ next: (v) => console.log('observerB: ' + v),
});
}, 1000);
@@ -385,7 +386,7 @@ AsyncSubject khá giống Promise đấy chứ.
const subject = new AsyncSubject();
subject.subscribe({
- next: (v) => console.log('observerA: ' + v)
+ next: (v) => console.log('observerA: ' + v),
});
subject.next(1);
@@ -394,7 +395,7 @@ subject.next(3);
subject.next(4);
subject.subscribe({
- next: (v) => console.log('observerB: ' + v)
+ next: (v) => console.log('observerB: ' + v),
});
subject.next(5);
@@ -409,6 +410,7 @@ observerB: 5
```
### Subject Completion
+
Khi BehaviorSubject complete, thì các Observers subscribe vào sau đó sẽ chỉ nhận được complete signal.
Khi ReplaySubject complete, thì các Observers subscribe vào sau đó sẽ được emit tất cả các giá trị lưu trữ trong buffer, sau đó mới thực thi complete của Observer.
@@ -420,7 +422,7 @@ const subject = new BehaviorSubject(0); // 0 is the initial value
subject.subscribe({
next: (v) => console.log('observerA: ' + v),
- complete: () => console.log('observerA: done')
+ complete: () => console.log('observerA: done'),
});
subject.next(1);
@@ -428,7 +430,7 @@ subject.next(2);
subject.subscribe({
next: (v) => console.log('observerB: ' + v),
- complete: () => console.log('observerB: done')
+ complete: () => console.log('observerB: done'),
});
subject.next(3);
@@ -437,7 +439,7 @@ subject.complete();
subject.subscribe({
next: (v) => console.log('observerC: ' + v),
- complete: () => console.log('observerC: done')
+ complete: () => console.log('observerC: done'),
});
/**
@@ -454,12 +456,13 @@ observerB: done
observerC: done
*/
```
+
```ts
const subject = new ReplaySubject(3);
subject.subscribe({
next: (v) => console.log('observerA: ' + v),
- complete: () => console.log('observerA: done')
+ complete: () => console.log('observerA: done'),
});
let i = 1;
@@ -470,7 +473,7 @@ setTimeout(() => {
clearInterval(id);
subject.subscribe({
next: (v) => console.log('observerB: ' + v),
- complete: () => console.log('observerB: done')
+ complete: () => console.log('observerB: done'),
});
}, 1000);
@@ -489,12 +492,13 @@ observerB: 5
observerB: done
*/
```
+
```ts
const subject = new AsyncSubject();
subject.subscribe({
next: (v) => console.log('observerA: ' + v),
- complete: () => console.log('observerA: done')
+ complete: () => console.log('observerA: done'),
});
subject.next(1);
@@ -507,7 +511,7 @@ subject.complete();
subject.subscribe({
next: (v) => console.log('observerB: ' + v),
- complete: () => console.log('observerB: done')
+ complete: () => console.log('observerB: done'),
});
/**
Output:
@@ -520,12 +524,11 @@ observerB: done
```
## Multicasting
+
Quay trở lại vấn đề ban đầu khi chúng ta mong muốn multicast. Chúng ta mong muốn cả hai observer đều chạy cùng một execution.
```ts
-const observable = interval(500).pipe(
- take(5)
-);
+const observable = interval(500).pipe(take(5));
const subject = new Subject();
@@ -608,7 +611,9 @@ Multicast nhận vào một `subjectOrSubjectFactory`, như trong ví dụ trên
```ts
connectableObservable.connect();
```
+
Nó tương đương với việc subscribe internal Subject vào Observable như ban đầu:
+
```ts
observable.subscribe(subject);
```
@@ -687,6 +692,7 @@ setTimeout(() => {
connectSub.unsubscribe();
}, 3000);
```
+
Lúc này bạn chỉ cần `connectSub.unsubscribe()` là sẽ unsubscribe internal Subject do đó không cần chạy `sub.unsubscribe()` cũng được.
#### refCount
@@ -697,7 +703,7 @@ Việc phải connect và disconnect manually khá là low level. Do đó `Conne
const subject = new Subject();
const connectableObservable = interval(500).pipe(
- tap(x => console.log('log.info: ' + x)),
+ tap((x) => console.log('log.info: ' + x)),
multicast(subject)
) as ConnectableObservable;
@@ -738,7 +744,7 @@ Trong trường hợp Subject bị complete, nó sẽ không thể next thêm m
```ts
const connectableObservable = interval(500).pipe(
take(10),
- tap(x => console.log('log.info: ' + x)),
+ tap((x) => console.log('log.info: ' + x)),
multicast(new Subject())
) as ConnectableObservable;
@@ -766,7 +772,6 @@ setTimeout(() => {
setTimeout(() => {
const subA2 = sharedObservable.subscribe(observerA);
}, 6000);
-
```
Sau 5s `sharedObservable` đã emit complete nên khi ở thời điểm 6s chúng ta tiếp tục subscribe thì nó không thể nhận được các value được nữa. Lúc này để có thể tiếp tục trigger chúng ta cần phải tạo ra một Subject mới, đó chính là lúc bạn sẽ có thể dùng đến SubjectFactory.
@@ -774,7 +779,7 @@ Sau 5s `sharedObservable` đã emit complete nên khi ở thời điểm 6s chú
```ts
const connectableObservable = interval(500).pipe(
take(10),
- tap(x => console.log('log.info: ' + x)),
+ tap((x) => console.log('log.info: ' + x)),
multicast(() => new Subject())
) as ConnectableObservable;
```
@@ -810,8 +815,8 @@ Việc sử dụng `multicast(new Subject())` có thể được viết gọn l
```ts
const connectableObservable = interval(500).pipe(
- tap(x => console.log('log.info: ' + x)),
- publish(),
+ tap((x) => console.log('log.info: ' + x)),
+ publish()
) as ConnectableObservable;
const observerA = {
@@ -843,6 +848,7 @@ setTimeout(() => {
subB.unsubscribe(); // ref from 1 => 0
}, 5000);
```
+

Ngoài ra, giống như Subject có các biến thể thì publish cũng có các biến thể tương ứng với một số loại Subject.
@@ -861,8 +867,8 @@ Việc sử dụng `multicast(() => new Subject()) + refCount` khá phổ biến
```ts
const sharedObservable = interval(500).pipe(
- tap(x => console.log('log.info: ' + x)),
- share(),
+ tap((x) => console.log('log.info: ' + x)),
+ share()
);
const observerA = {
@@ -939,19 +945,19 @@ export class JokeService {
private cache$: Observable>;
private reload$ = new Subject();
- constructor(private http: HttpClient) { }
+ constructor(private http: HttpClient) {}
// This method is responsible for fetching the data.
- // The first one who calls this function will initiate
+ // The first one who calls this function will initiate
// the process of fetching data.
get jokes() {
if (!this.cache$) {
// Set up timer that ticks every X milliseconds
const timer$ = timer(0, REFRESH_INTERVAL);
-
+
// For each timer tick make an http request to fetch new data
- // We use shareReplay(X) to multicast the cache so that all
- // subscribers share one underlying source and don't re-create
+ // We use shareReplay(X) to multicast the cache so that all
+ // subscribers share one underlying source and don't re-create
// the source over and over again. We use takeUntil to complete
// this stream when the user forces an update.
this.cache$ = timer$.pipe(
@@ -972,9 +978,9 @@ export class JokeService {
// Helper method to actually fetch the jokes
private requestJokes() {
- return this.http.get(API_ENDPOINT).pipe(
- map(response => response.value)
- );
+ return this.http
+ .get(API_ENDPOINT)
+ .pipe(map((response) => response.value));
}
}
```
@@ -989,6 +995,10 @@ Mục tiêu của ngày 27 sẽ là **Giới thiệu Router trong Angular**
https://stackblitz.com/edit/angular-ivy-wbyobn?file=src%2Fapp%2Fapp.component.ts
+## Youtube Video
+
+[](https://youtu.be/8nWosjgcI5k)
+
## References
- [RxJS Overview](https://rxjs.dev/guide/overview)
diff --git a/Day027-router.md b/Day027-router.md
index d0c7308..9524915 100644
--- a/Day027-router.md
+++ b/Day027-router.md
@@ -84,17 +84,15 @@ Khi tạo ứng dụng mới theo step ở trên thì CLI đã mặc định t
```ts
@NgModule({
- declarations: [
- AppComponent
- ],
+ declarations: [AppComponent],
imports: [
BrowserModule,
- AppRoutingModule // Đây chính là AppRoutingModule được tạo tự động bằng CLI
+ AppRoutingModule, // Đây chính là AppRoutingModule được tạo tự động bằng CLI
],
providers: [],
- bootstrap: [AppComponent]
+ bootstrap: [AppComponent],
})
-export class AppModule { }
+export class AppModule {}
```
Có ba thành phần chính khi làm việc với Router
@@ -114,6 +112,7 @@ export class AppRoutingModule {}
Chú ý là `AppRoutingModule` vừa import và export `RouterModule`. Điều này có nghĩa là khi bạn import `AppRoutingModule` vào các module khác, bạn ko cần import lại `RouterModule` để sử dụng nữa vì nó đã được đc re-export từ `AppRoutingModule`.
RouterModule mặc định sẽ provide hai method là `forRoot` và `forChild`. Hai method này đều dùng để config routes, tuy nhiên.
+
- `forRoot`, dc gọi một lần duy nhất khi bạn config route trong `AppRoutingModule`. forRoot cũng dùng để configures/initializes router.
- `forChild`, dc gọi trong các module khác để config routes.
@@ -128,11 +127,11 @@ Với yêu cầu của mình và hai component vừa được tạo, thì cấu
```ts
const routes: Routes = [
{
- path: "detail",
+ path: 'detail',
component: ArticleDetailComponent,
},
{
- path: "",
+ path: '',
component: ArticleListComponent,
},
];
@@ -178,22 +177,22 @@ Bây giờ mình sẽ render ra một danh sách các bài viết dựa vào dat
```ts
const Articles: Article[] = [
{
- id: "1",
- slug: "bai-viet-1",
- title: "Bai viet 1",
- content: "Day la noi dung bai viet 1",
- updateAt: "2020-07-06T13:26:31.785Z",
+ id: '1',
+ slug: 'bai-viet-1',
+ title: 'Bai viet 1',
+ content: 'Day la noi dung bai viet 1',
+ updateAt: '2020-07-06T13:26:31.785Z',
},
{
- id: "2",
- slug: "bai-viet-2",
- title: "Bai viet 2",
- content: "Day la noi dung bai viet 2 nhe",
- updateAt: "2020-07-15:00:00.000Z",
+ id: '2',
+ slug: 'bai-viet-2',
+ title: 'Bai viet 2',
+ content: 'Day la noi dung bai viet 2 nhe',
+ updateAt: '2020-07-15:00:00.000Z',
},
];
@Injectable({
- providedIn: "root",
+ providedIn: 'root',
})
export class ArticleService {
getArticles(): Observable {
@@ -219,9 +218,7 @@ export class ArticleListComponent implements OnInit {