107 câu hỏi phỏng vấn Angular

Bản tiếng Việt được biên soạn lại từ các chủ đề phổ biến trong repo sudheerj/angular-interview-questions và cập nhật theo Angular hiện đại: standalone APIs, Signals, control flow, SSR, hydration, security, testing và kiến trúc thực tế.

8Entry
35Junior
40Middle
18Senior
6Expert

Câu trả lời được viết theo kiểu phỏng vấn thật: nói rõ ý chính, khi nào dùng, trade-off và lỗi thường gặp. Một số câu có ví dụ code ngắn để ứng viên dễ hình dung.

Nền tảng

20 câu

001Entry

Angular là gì và nên dùng khi nào?

Angular là framework front-end dựa trên TypeScript để xây dựng ứng dụng web có cấu trúc rõ ràng. Nó hợp khi sản phẩm có nhiều màn hình, form, phân quyền, routing, gọi API và cần maintain lâu dài bởi nhiều người.

Điểm mạnh của Angular không chỉ là render UI. Nó đi kèm router, HttpClient, forms, dependency injection, testing utilities và CLI, nên team ít phải tự ráp quá nhiều mảnh rời.

002Entry

Angular khác AngularJS như thế nào?

AngularJS là dòng 1.x, dựa nhiều vào scope, controller và digest cycle. Angular hiện đại là bản viết lại, dùng TypeScript, component tree, dependency injection rõ ràng và build tooling tốt hơn.

Khi phỏng vấn, câu trả lời tốt nên nói thêm về migration: AngularJS thường là legacy, còn Angular hiện đại ưu tiên standalone component, signals, strict typing và SSR/hydration.

003Entry

TypeScript đem lại lợi ích gì cho Angular?

TypeScript giúp phát hiện lỗi sớm bằng kiểu dữ liệu, interface, generic và strict mode. Với Angular, lợi ích này rất rõ ở input/output, service contract, reactive form, route data và response từ API.

Nói đơn giản: TypeScript làm code dễ refactor hơn. Khi đổi model hoặc API, compiler sẽ chỉ ra nhiều chỗ cần sửa trước khi lỗi tới production.

004Entry

Component trong Angular gồm những phần nào?

Một component thường gồm class TypeScript, template HTML, style và metadata trong decorator hoặc API tương đương. Class giữ state và hành vi, template hiển thị UI, style giới hạn phần trình bày.

Một component tốt nên có trách nhiệm rõ. Ví dụ `UserCardComponent` hiển thị user, không nên ôm luôn logic gọi API, phân quyền và xử lý cache phức tạp.

005Entry

Template trong Angular dùng để làm gì?

Template là nơi mô tả UI bằng HTML cộng thêm cú pháp Angular như binding, event, control flow, pipe và projection. Template nên đọc được như giao diện, không phải nơi nhồi business logic.

Nếu biểu thức trong template bắt đầu dài và khó hiểu, nên đưa logic sang computed signal, method đơn giản, pipe hoặc property đã chuẩn hóa trong component.

006Entry

Directive là gì?

Directive là cách Angular gắn thêm hành vi vào element hoặc thay đổi cấu trúc DOM. Component cũng là một loại directive có template riêng.

Có hai nhóm hay gặp: attribute directive như đổi style/hành vi của element, và structural directive như tạo/xóa block giao diện. Với Angular mới, nhiều nhu cầu structural đã được thay bằng control flow `@if`, `@for`, `@switch`.

007Entry

Service là gì?

Service là class chứa logic dùng chung: gọi API, format dữ liệu, quản lý state, kiểm tra quyền, logging hoặc tích hợp browser API. Component dùng service để nhẹ hơn và dễ test hơn.

Không phải logic nào cũng đưa vào service. Nếu logic chỉ phục vụ view nhỏ và không tái sử dụng, giữ trong component vẫn ổn.

008Entry

Dependency Injection giải quyết vấn đề gì?

Dependency Injection giúp class nhận dependency từ Angular injector thay vì tự tạo bằng `new`. Nhờ vậy code dễ test, dễ thay implementation và dễ kiểm soát scope.

Ví dụ component cần `UserApi`, trong test ta có thể thay bằng mock. Trong production Angular cung cấp instance thật theo provider đã khai báo.

009Junior

Standalone component là gì?

Standalone component là component tự khai báo dependency trong `imports` và không cần được khai báo trong NgModule. Đây là hướng mặc định của Angular hiện đại.

Nó làm feature nhỏ gọn hơn: route có thể lazy load trực tiếp component, test setup bớt vòng vèo, và dependency của component nhìn thấy ngay tại chỗ.

@Component({
  standalone: true,
  selector: 'app-user-card',
  imports: [DatePipe],
  template: '<p>{{ user.createdAt | date }}</p>'
})
export class UserCardComponent {
  user = { createdAt: new Date() };
}
010Junior

NgModule còn cần thiết không?

NgModule vẫn tồn tại và còn gặp nhiều trong codebase cũ hoặc thư viện. Tuy nhiên với app mới, standalone APIs thường đơn giản hơn.

Một câu trả lời thực tế: không nên rewrite toàn bộ chỉ để bỏ NgModule. Hãy migrate dần theo route/feature, ưu tiên nơi đang thay đổi hoặc nơi NgModule gây phức tạp rõ ràng.

011Junior

Metadata trong Angular là gì?

Metadata là thông tin Angular dùng để hiểu class nên hoạt động ra sao: selector, template, imports, providers, changeDetection, host binding... Nó biến một class TypeScript bình thường thành component, directive, pipe hoặc injectable.

Metadata càng rõ thì component càng dễ đọc. Ví dụ nhìn vào `imports` của standalone component, ta biết ngay template có thể dùng directive/pipe nào.

012Junior

Angular CLI dùng để làm gì?

Angular CLI giúp tạo project, generate component/service/route, chạy dev server, build production, test và update version. Giá trị lớn nhất là chuẩn hóa workflow cho cả team.

Với dự án lớn, CLI còn giúp giữ cấu hình build nhất quán, dùng schematic/migration khi nâng version và giảm rủi ro cấu hình thủ công.

013Junior

AOT và JIT khác nhau thế nào?

JIT biên dịch template khi chạy app, thường phù hợp cho development cũ. AOT biên dịch trước trong quá trình build, giúp phát hiện lỗi template sớm và production chạy nhẹ hơn.

Trong Angular hiện đại, production nên dùng AOT. Khi phỏng vấn, đừng chỉ nói “AOT nhanh hơn”; hãy nói thêm nó giúp type-check template và giảm việc biên dịch ở runtime.

014Junior

Ivy là gì?

Ivy là compiler và runtime engine hiện đại của Angular. Nó giúp build output tối ưu hơn, template type checking tốt hơn, hỗ trợ locality và là nền cho nhiều API mới.

Thông thường không cần dùng Ivy internal API. Senior nên hiểu Ivy để giải thích build, tree shaking, debug stack và migration từ View Engine, nhưng code app nên bám public API.

015Junior

Angular package version nên hiểu thế nào?

Angular dùng semantic versioning: major có thể có thay đổi lớn, minor thêm tính năng tương thích ngược, patch sửa lỗi. Angular core và CLI nên cùng major version.

Theo tài liệu Angular chính thức, Angular phát hành major khoảng mỗi 6 tháng và các major thường được hỗ trợ khoảng 18 tháng. Vì vậy chiến lược update đều đặn quan trọng hơn chờ nhiều năm rồi nhảy version.

016Junior

Strict mode trong Angular có đáng bật không?

Có. Strict mode giúp template type checking, TypeScript strictness và cấu hình build chặt hơn. Nó làm code ban đầu hơi khó tính, nhưng giảm lỗi âm thầm.

Với project mới nên bật strict từ đầu. Với project cũ, nên bật từng phần, sửa theo feature để tránh một PR khổng lồ khó review.

017Middle

Angular application bootstrapping diễn ra như thế nào?

Bootstrapping là quá trình Angular khởi động app, tạo injector gốc, đăng ký provider và render root component. Với standalone app, điểm vào thường là `bootstrapApplication`.

Điều đáng chú ý là provider cấp app nằm ở đây: router, HttpClient, animations, hydration, error handler hoặc config toàn cục.

bootstrapApplication(AppComponent, {
  providers: [
    provideRouter(routes),
    provideHttpClient(withInterceptors([authInterceptor]))
  ]
});
018Middle

Provider scope ảnh hưởng gì tới state?

Provider đặt ở root thường là singleton toàn app. Provider đặt ở route hoặc component có thể tạo instance riêng theo phạm vi đó.

Sai scope là lỗi khá khó chịu: service tưởng là shared nhưng bị tạo nhiều instance, hoặc state đáng ra reset theo feature lại sống quá lâu. Cần đặt provider theo vòng đời state mong muốn.

019Middle

Injector hierarchy là gì?

Angular có hệ thống injector phân cấp. Khi cần dependency, Angular tìm từ injector gần nhất rồi đi lên cha cho tới root.

Hiểu injector hierarchy giúp giải thích vì sao cùng một service có thể là singleton ở root nhưng lại có instance riêng khi khai báo trong route/component providers.

020Middle

Khi nào nên dùng InjectionToken?

Dùng `InjectionToken` khi dependency không phải class cụ thể, ví dụ config object, base URL, feature flag hoặc interface abstraction.

Nó giúp code rõ contract hơn và test dễ hơn. Thay vì import hằng số khắp nơi, ta inject token và có thể override trong test hoặc theo môi trường.

export const API_URL = new InjectionToken<string>('API_URL');

bootstrapApplication(AppComponent, {
  providers: [{ provide: API_URL, useValue: 'https://api.example.com' }]
});

Component & Template

20 câu

021Junior

Data binding trong Angular gồm những loại nào?

Các loại phổ biến là interpolation `{{ value }}`, property binding `[disabled]`, event binding `(click)` và two-way binding `[(ngModel)]` hoặc model input.

Nên chọn binding theo hướng dữ liệu. Dữ liệu đi từ class ra UI dùng interpolation/property binding; hành động người dùng đi vào class dùng event binding.

022Junior

Interpolation hoạt động thế nào?

Interpolation lấy giá trị expression trong component và render ra text. Angular tự escape nội dung, nên hiển thị chuỗi người dùng nhập vào thường an toàn hơn gán thẳng HTML.

Không nên gọi hàm nặng trong interpolation vì change detection có thể chạy nhiều lần. Tính trước bằng computed signal hoặc property sẽ dễ kiểm soát hơn.

023Junior

Property binding khác attribute binding thế nào?

Property binding gán vào DOM property, ví dụ `[disabled]="isSaving"`. Attribute binding gán HTML attribute, ví dụ `[attr.aria-label]="label"`.

Dùng property binding cho trạng thái runtime của element. Dùng attribute binding khi cần attribute không có property tương ứng hoặc cần hỗ trợ accessibility như ARIA.

024Junior

Event binding nên viết thế nào cho sạch?

Event binding nên gọi method ngắn, thể hiện ý định rõ: `(click)="save()"`, `(input)="onSearch($event)"`. Đừng nhồi nhiều logic vào template.

Nếu handler cần dùng event, hãy type rõ ở class. Với form phức tạp, ưu tiên reactive forms để tránh parse event thủ công ở nhiều nơi.

025Junior

Two-way binding có nên dùng nhiều không?

Two-way binding tiện cho form đơn giản, nhưng nếu dùng quá nhiều có thể làm data flow khó lần theo. Với UI phức tạp, tách input và output rõ ràng thường dễ debug hơn.

Trong component tự viết, model input giúp tạo API two-way gọn, nhưng vẫn nên đặt tên rõ và tránh làm component tự sửa state ngoài ý muốn.

026Junior

Input và Output dùng để làm gì?

Input truyền dữ liệu từ component cha xuống component con. Output phát event từ con lên cha khi có hành động hoặc thay đổi.

Component con tốt nên nhận dữ liệu đủ để render và phát event theo ý nghĩa nghiệp vụ, ví dụ `userSelected`, không nên phát event quá thấp kiểu `buttonClicked` nếu cha không cần biết chi tiết đó.

027Junior

Host binding và host listener là gì?

Host binding gắn property/class/style lên chính host element của directive/component. Host listener lắng nghe event trên host.

Chúng hữu ích khi viết directive tái sử dụng, ví dụ directive tự thêm class khi focus hoặc tự xử lý phím Escape. Với logic UI nhỏ, host metadata thường gọn hơn đụng DOM trực tiếp.

028Junior

Pipe dùng để làm gì?

Pipe biến đổi dữ liệu hiển thị trong template: date, currency, uppercase, json hoặc custom format. Pipe nên thuần và nhẹ.

Không nên đưa side effect vào pipe. Nếu pipe gọi API, mutate dữ liệu hoặc phụ thuộc trạng thái ngoài, UI sẽ khó đoán và hiệu năng dễ xấu.

029Junior

Pure pipe và impure pipe khác nhau thế nào?

Pure pipe chỉ chạy lại khi input reference thay đổi. Impure pipe có thể chạy thường xuyên hơn trong mỗi chu kỳ kiểm tra, nên dễ tốn CPU.

Mặc định hãy dùng pure pipe. Nếu dữ liệu là array/object, hãy đổi reference khi update thay vì mutate tại chỗ để pure pipe và OnPush hoạt động tốt.

030Junior

Control flow `@if` có lợi gì so với `*ngIf`?

`@if` là cú pháp control flow mới, đọc gần với JavaScript hơn và không cần import directive trong standalone component. Nó cũng giúp template rõ hơn khi có `@else if`.

Về tư duy, `@if` vẫn là điều kiện render. Nếu block bị false, phần DOM tương ứng không tồn tại, khác với chỉ ẩn bằng CSS.

031Junior

`@for` và `track` quan trọng thế nào?

`@for` render danh sách. `track` giúp Angular nhận diện item ổn định, tránh phá và tạo lại DOM không cần thiết.

Không nên track bằng index nếu danh sách có insert/delete/sort. Hãy track bằng id ổn định như `user.id` để giữ state của từng row tốt hơn.

@for (user of users(); track user.id) {
  <app-user-row [user]="user" />
} @empty {
  <p>Chưa có người dùng.</p>
}
032Junior

`ngIf` khác `[hidden]` thế nào?

`ngIf` hoặc `@if` thêm/xóa DOM. `[hidden]` chỉ ẩn element, element vẫn nằm trong DOM và component con vẫn có thể còn sống.

Dùng conditional render khi phần UI nặng hoặc không nên tồn tại. Dùng hidden khi cần giữ trạng thái DOM, ví dụ tab đơn giản muốn giữ input người dùng đã nhập.

033Middle

Content projection là gì?

Content projection cho phép component nhận nội dung từ bên ngoài qua `ng-content`. Nó phù hợp để tạo layout component như card, modal, panel mà caller quyết định nội dung bên trong.

Điểm cần nhớ: projected content thuộc context của component cha, không phải component chứa `ng-content`. Vì vậy binding trong content vẫn đọc biến của cha.

034Middle

ViewChild và ContentChild khác nhau thế nào?

`ViewChild` query phần tử nằm trong template nội bộ của component. `ContentChild` query nội dung được project từ bên ngoài qua `ng-content`.

Dùng sai loại query thường dẫn tới undefined hoặc timing sai. Nếu cần query sau render view, kiểm tra lifecycle phù hợp như `ngAfterViewInit` hoặc API signal query hiện đại.

035Middle

Dynamic component rendering dùng khi nào?

Dùng khi component cần được quyết định ở runtime: dashboard widget, modal động, CMS block, plugin UI hoặc form field theo schema.

Không nên dùng dynamic component nếu chỉ cần `@if` chọn vài trường hợp cố định. Dynamic rendering mạnh hơn nhưng khó type, test và trace hơn.

036Middle

Angular Elements là gì?

Angular Elements đóng gói Angular component thành custom element theo chuẩn Web Components. Nó hữu ích khi muốn nhúng một phần Angular vào hệ thống không phải Angular.

Cần cân nhắc bundle size, style isolation, input/output mapping và việc bootstrap. Với app Angular thuần, Angular Elements thường không cần thiết.

037Middle

Template reference variable dùng để làm gì?

Template reference variable như `#input` cho phép tham chiếu element, directive hoặc component instance trong template. Nó tiện cho tương tác nhỏ, ví dụ đọc giá trị input khi submit.

Không nên lạm dụng để biến template thành nơi điều phối logic. Nếu trạng thái cần quản lý nghiêm túc, đưa vào form control hoặc component class.

038Middle

Template type checking giúp gì?

Template type checking giúp compiler bắt lỗi như sai tên property, truyền sai kiểu input hoặc dùng biến có thể null. Đây là một lợi thế lớn của Angular so với template lỏng lẻo.

Khi bật strict template, nhiều lỗi hiện ra lúc build thay vì lúc người dùng bấm vào màn hình. Với team lớn, nó đáng giá hơn chút khó chịu ban đầu.

039Middle

Làm sao tránh template quá nặng?

Tránh gọi hàm tính toán nặng, filter/sort trực tiếp trong template và nested condition khó đọc. Hãy chuẩn hóa view model trong class, computed signal hoặc selector.

Template tốt nên trả lời câu hỏi “UI hiển thị gì”, không phải “dữ liệu được xử lý qua 7 bước ra sao”.

040Middle

Component API thế nào là tốt?

Component API tốt có input ít nhưng đủ nghĩa, output theo ngôn ngữ nghiệp vụ, default hợp lý và không lộ chi tiết implement. Tên input nên rõ: `selectedUserId` tốt hơn `value` nếu context không hiển nhiên.

Một dấu hiệu xấu là component có quá nhiều boolean input để bật/tắt mọi thứ. Khi đó có thể cần tách component hoặc dùng composition.

State, Signals & RxJS

20 câu

041Junior

Signals trong Angular là gì?

Signal là primitive reactive state. Khi đọc signal trong template hoặc computed, Angular biết dependency nào đang được dùng và cập nhật khi giá trị đổi.

Signal hợp với state đồng bộ trong component/service: selected item, filter, counter, derived state. Nó làm data flow dễ nhìn hơn so với nhiều biến thường rời rạc.

const count = signal(0);
const doubled = computed(() => count() * 2);

count.update(value => value + 1);
042Junior

`signal`, `computed`, `effect` khác nhau thế nào?

`signal` lưu state, `computed` tạo giá trị dẫn xuất thuần, `effect` chạy side effect khi dependency thay đổi.

Quy tắc dễ nhớ: dùng computed để tính ra dữ liệu mới; dùng effect cho việc ngoài render như log, sync storage hoặc gọi API có kiểm soát. Đừng dùng effect để vá data flow vòng vòng.

043Junior

Observable là gì?

Observable là stream có thể phát nhiều giá trị theo thời gian. Trong Angular, HttpClient, router events, form value changes và nhiều API RxJS đều dùng Observable.

Observable lazy: thường chỉ chạy khi có subscribe. Điều này giúp compose pipeline trước, rồi quyết định nơi tiêu thụ bằng `async` pipe, `toSignal` hoặc subscribe có cleanup.

044Junior

Promise khác Observable thế nào?

Promise đại diện cho một kết quả bất đồng bộ, chạy ngay và không có cơ chế cancel chuẩn. Observable có thể phát nhiều giá trị, lazy và có thể unsubscribe.

Gọi HTTP một lần có thể dùng Promise, nhưng với search input, route param, websocket, polling hoặc cancellation, Observable thường phù hợp hơn.

045Junior

Async pipe dùng để làm gì?

`async` pipe subscribe Observable/Promise trong template, render giá trị mới và tự unsubscribe khi view bị hủy. Nó giảm rất nhiều lỗi memory leak.

Nếu dữ liệu chỉ dùng để hiển thị, ưu tiên `async` pipe hoặc `toSignal` thay vì subscribe thủ công trong component.

046Middle

Signals và RxJS nên phối hợp ra sao?

Signals hợp cho state hiện tại và derived state trong UI. RxJS hợp cho stream bất đồng bộ, cancellation, retry, debounce và combine nhiều nguồn event.

Một pattern thực tế: dùng RxJS xử lý search stream và HTTP, sau đó đưa kết quả sang signal để template đọc gọn. Đừng cố biến mọi thứ thành signal hoặc mọi thứ thành RxJS.

047Middle

`toSignal` dùng khi nào?

`toSignal` chuyển Observable thành Signal để template hoặc computed đọc được. Nó hữu ích khi nguồn dữ liệu vẫn là Observable, ví dụ route param hoặc HTTP stream.

Cần chú ý initial value, error handling và lifecycle. Nếu Observable có side effect, hãy chắc chắn không tạo lại pipeline nhiều lần ngoài ý muốn.

readonly user = toSignal(
  this.route.paramMap.pipe(
    switchMap(params => this.api.getUser(params.get('id')!))
  ),
  { initialValue: null }
);
048Middle

`switchMap` dùng khi nào?

`switchMap` hủy request/stream cũ khi source phát giá trị mới. Nó rất hợp với search box, route param thay đổi hoặc autocomplete.

Nếu người dùng gõ “a”, rồi “ab”, rồi “abc”, ta thường chỉ cần kết quả mới nhất. `switchMap` giúp tránh response cũ ghi đè response mới.

049Middle

`mergeMap` dùng khi nào?

`mergeMap` cho phép nhiều inner Observable chạy song song. Nó hợp khi mọi tác vụ đều cần hoàn thành, ví dụ upload nhiều file hoặc gửi nhiều analytics event.

Không dùng `mergeMap` cho search nếu response cũ có thể về sau và làm UI hiển thị sai. Khi thứ tự hoặc cancellation quan trọng, chọn operator khác.

050Middle

`concatMap` dùng khi nào?

`concatMap` xếp hàng tác vụ và chạy tuần tự. Nó hợp khi thứ tự quan trọng, ví dụ lưu từng bước theo sequence hoặc xử lý command không được chạy song song.

Trade-off là tác vụ sau phải chờ tác vụ trước xong. Nếu API chậm và không cần thứ tự, `mergeMap` có thể tốt hơn.

051Middle

`exhaustMap` dùng khi nào?

`exhaustMap` bỏ qua event mới trong khi tác vụ hiện tại chưa xong. Nó hợp với nút submit để tránh double click gửi nhiều request.

Ví dụ người dùng bấm “Thanh toán” liên tục, `exhaustMap` giữ request đầu và bỏ qua các click sau cho tới khi hoàn thành.

052Middle

`shareReplay` có bẫy gì?

`shareReplay` cache giá trị cuối và share subscription. Nó hữu ích để tránh gọi API lặp lại, nhưng cấu hình sai có thể giữ subscription quá lâu hoặc cache dữ liệu cũ.

Với HTTP cache trong service, hãy suy nghĩ về thời điểm invalidate. “Không gọi lại API” chưa chắc đúng nếu dữ liệu cần cập nhật.

053Middle

Subject, BehaviorSubject và ReplaySubject khác nhau thế nào?

Subject phát giá trị cho subscriber hiện tại. BehaviorSubject giữ giá trị hiện tại và phát ngay cho subscriber mới. ReplaySubject phát lại một số giá trị cũ.

Trong app mới, nhiều state đơn giản có thể dùng signal thay BehaviorSubject. Nhưng Subject vẫn rất hợp cho event stream và tích hợp RxJS phức tạp.

054Middle

State nên đặt ở component hay service?

State chỉ phục vụ một màn hình nhỏ thì đặt trong component. State dùng chung nhiều component hoặc cần sống qua route thì đưa vào service với scope phù hợp.

Đừng mặc định đưa mọi thứ lên global store. State càng xa nơi dùng, chi phí hiểu và debug càng cao.

055Middle

Khi nào cần NgRx?

NgRx hữu ích khi state phức tạp, nhiều nguồn cập nhật, cần audit action, time-travel/debug hoặc team lớn cần convention mạnh. Nó đặc biệt hợp cho domain state chia sẻ rộng.

Không nên dùng NgRx chỉ vì “dự án Angular thì phải có store”. Với feature nhỏ, service + signals/RxJS thường đủ và ít ceremony hơn.

056SeniorSenior cần nắm

Race condition trong Angular thường xảy ra ở đâu?

Hay gặp ở autocomplete, route param thay đổi nhanh, nhiều request song song và response cũ ghi đè state mới. RxJS operator chọn sai là nguyên nhân phổ biến.

Cách xử lý là làm rõ semantics: cần latest thì `switchMap`, cần tuần tự thì `concatMap`, cần song song thì `mergeMap`, cần chặn double submit thì `exhaustMap`.

057SeniorSenior cần nắm

Memory leak do subscription xử lý thế nào?

Ưu tiên `async` pipe, `toSignal`, hoặc `takeUntilDestroyed`. Nếu phải subscribe thủ công, luôn biết subscription sống theo vòng đời nào.

Leak thường đến từ stream dài như interval, websocket, router events, form valueChanges hoặc service subject. HTTP một lần thường tự complete, nhưng pipeline bọc ngoài có thể không.

058SeniorSenior cần nắm

Computed signal nên tránh điều gì?

Computed nên thuần: chỉ tính toán từ signal khác và không tạo side effect. Không nên gọi API, ghi storage hoặc update signal khác bên trong computed.

Nếu computed có side effect, dependency graph trở nên khó đoán. Hãy chuyển side effect sang effect có cleanup hoặc đưa vào flow RxJS rõ ràng.

059SeniorSenior cần nắm

Effect trong signals dùng sao cho an toàn?

Effect nên dùng cho side effect có kiểm soát: log, sync document title, ghi local storage, bridge ra API ngoài Angular. Nó không nên là nơi điều phối business flow chính nếu có thể dùng computed hoặc explicit method.

Cần chú ý vòng lặp: effect đọc signal rồi update signal phụ thuộc có thể gây chạy lại liên tục. Nếu phải làm, hãy tách dependency hoặc thiết kế lại state.

060SeniorSenior cần nắm

View model pattern trong Angular là gì?

View model là object/shape đã chuẩn bị sẵn cho template: label, trạng thái loading, quyền hiển thị, danh sách đã sort/filter. Template chỉ render, không tự lắp ghép logic phức tạp.

Pattern này giúp test dễ hơn và giảm template noise. View model có thể đến từ computed signal, Observable pipeline hoặc selector.

Router, Forms & HTTP

20 câu

061Junior

Angular Router dùng để làm gì?

Router ánh xạ URL tới component/route tree, quản lý navigation, params, query params, lazy loading, guard và resolver. Với SPA, URL không chỉ là địa chỉ mà còn là state có thể chia sẻ.

Một app tốt nên để route phản ánh màn hình quan trọng: người dùng refresh hoặc gửi link vẫn vào đúng ngữ cảnh.

062Junior

`router-outlet` là gì?

`router-outlet` là vị trí Angular render component tương ứng với route hiện tại. App có thể có outlet chính và cả named outlet nếu cần layout phức tạp.

Trong thực tế, root layout thường có header/sidebar và `router-outlet` ở vùng nội dung. Route con sẽ render lồng trong outlet của component cha.

063Junior

Route params và query params khác nhau thế nào?

Route params là một phần của path, ví dụ `/users/42`, thường định danh resource. Query params nằm sau dấu hỏi, ví dụ `/users?tab=active&page=2`, thường mô tả filter, sort hoặc UI state.

Đừng nhét mọi thứ vào route params. Nếu giá trị là tùy chọn hoặc có thể thay đổi độc lập, query params thường hợp hơn.

064Junior

Lazy loading route có lợi gì?

Lazy loading tải code của feature khi người dùng vào route đó, giúp bundle ban đầu nhỏ hơn và first load nhanh hơn.

Tuy vậy lazy loading không miễn phí: cần loading state, error boundary và chia route hợp lý. Chia quá nhỏ có thể tạo nhiều request và làm navigation lắt nhắt.

065Junior

Guard dùng để làm gì?

Guard quyết định một route có được activate, match hoặc deactivate hay không. Nó hay dùng cho auth, permission, unsaved changes hoặc redirect theo trạng thái user.

Guard không nên chứa UI logic phức tạp. Nếu cần hỏi người dùng xác nhận rời trang, hãy tách service/dialog rõ ràng để test được.

066Middle

Resolver dùng khi nào?

Resolver lấy dữ liệu trước khi route hoàn tất activation. Nó hợp khi màn hình cần data bắt buộc để render đúng.

Nhưng resolver có thể làm navigation bị chờ. Với data không bắt buộc, render skeleton trong component và load sau có thể cho trải nghiệm tốt hơn.

067Middle

Functional guard khác class guard thế nào?

Functional guard là function dùng `inject()` để lấy dependency, gọn hơn class guard và hợp với standalone APIs. Class guard vẫn gặp trong code cũ.

Functional guard thường dễ đọc vì logic nằm ngay trong function. Với guard phức tạp, tách phần nghiệp vụ sang service để guard chỉ điều phối.

068Middle

Route-level providers dùng khi nào?

Route-level providers tạo dependency scope theo route/feature. Nó hợp cho state chỉ sống trong feature lazy loaded hoặc config riêng theo route.

Ví dụ `CartFacade` chỉ cần sống trong checkout route. Khi rời checkout, instance có thể bị hủy, giảm rò rỉ state giữa feature.

069Junior

Template-driven forms hợp trường hợp nào?

Template-driven forms hợp form đơn giản, ít logic validation phức tạp và cần code nhanh. Phần lớn cấu hình nằm trong template qua `ngModel`.

Với form lớn, dynamic fields, validation phụ thuộc nhau hoặc test nghiêm túc, reactive forms thường kiểm soát tốt hơn.

070Junior

Reactive forms có lợi gì?

Reactive forms đặt form model trong TypeScript, dễ test, dễ compose validator và dễ xử lý form động. Nó cũng phù hợp với typed forms để tránh nhầm kiểu dữ liệu.

Khi form là phần quan trọng của domain, reactive forms giúp code rõ hơn: field nào có validator gì, update ra sao, submit đọc dữ liệu ở đâu.

readonly form = new FormGroup({
  email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }),
  age: new FormControl(18, { nonNullable: true, validators: [Validators.min(18)] })
});
071Middle

Typed forms giúp gì?

Typed forms giúp TypeScript biết shape và kiểu giá trị của form. Khi đọc `form.value` hoặc patchValue, compiler hỗ trợ bắt lỗi tốt hơn.

Nó đặc biệt hữu ích với form lớn. Nếu rename field hoặc đổi kiểu từ string sang number, nhiều lỗi sẽ lộ ra ở compile time thay vì lúc submit.

072Middle

Custom validator viết thế nào cho tốt?

Validator nên là function thuần: nhận control, trả về error object hoặc null. Không mutate control bên trong validator nếu không thật sự cần.

Tên error nên có nghĩa để UI hiển thị chính xác. Ví dụ `{ strongPassword: true }` rõ hơn `{ invalid: true }`.

073Middle

Async validator cần tối ưu gì?

Async validator thường gọi API, nên cần debounce/cancel hoặc chỉ chạy khi cần. Nếu gọi API mỗi phím bấm mà không kiểm soát, UX chậm và backend bị spam.

Nên đặt `updateOn: "blur"` cho field như username/email, hoặc xử lý stream cẩn thận để request cũ không ghi đè kết quả mới.

074Junior

HttpClient dùng để làm gì?

HttpClient là API Angular để gọi HTTP, trả về Observable và hỗ trợ typing, interceptor, params, headers, testing utilities.

Nên type response rõ ràng, nhưng nhớ TypeScript type không validate runtime. Với dữ liệu không tin cậy, cần schema validation hoặc kiểm tra thủ công.

075Middle

HttpInterceptor dùng khi nào?

Interceptor xử lý request/response ở một lớp chung: gắn token, thêm header, log, retry, map lỗi hoặc refresh token. Nó giúp component/service không lặp logic hạ tầng.

Không nên nhét business logic theo từng màn hình vào interceptor. Interceptor càng global càng phải đơn giản và dự đoán được.

export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = inject(AuthService).token();
  return next(token ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } }) : req);
};
076Middle

Xử lý lỗi HTTP nên đặt ở đâu?

Lỗi hạ tầng chung như 401, network offline, refresh token có thể xử lý ở interceptor. Lỗi nghiệp vụ cụ thể, ví dụ “mã giảm giá hết hạn”, nên xử lý ở feature vì UI cần thông điệp riêng.

Một lỗi thường gặp là interceptor nuốt hết error làm component không biết trạng thái thật. Hãy chuẩn hóa error nhưng đừng làm mất ngữ cảnh.

077SeniorSenior cần nắm

Cancellation cho HTTP trong Angular làm thế nào?

Với Observable từ HttpClient, unsubscribe sẽ hủy request ở mức phù hợp. Trong UI search hoặc route param, `switchMap` là cách phổ biến để tự hủy request cũ.

Cancellation không chỉ để tiết kiệm mạng. Nó còn tránh bug response cũ về sau và ghi đè state mới.

078SeniorSenior cần nắm

Caching API nên đặt ở service hay interceptor?

Cache theo nghiệp vụ thường đặt ở service/facade vì nó hiểu dữ liệu nào stale, invalidate khi nào và scope ra sao. Interceptor cache chỉ hợp cho rule rất chung và phải cực kỳ cẩn thận.

Ví dụ danh sách user có thể cache theo filter và invalidate sau khi tạo user. Logic này thuộc domain hơn là HTTP layer.

079SeniorSenior cần nắm

File upload trong Angular cần chú ý gì?

Cần dùng `FormData`, theo dõi progress nếu UX cần, validate size/type phía client và vẫn validate lại phía server. Đừng tin MIME type từ client tuyệt đối.

Với file lớn, cần cancellation, retry policy rõ ràng và thông báo lỗi dễ hiểu. Nếu upload nhiều file, chọn RxJS operator theo yêu cầu song song hay tuần tự.

080SeniorSenior cần nắm

SSR ảnh hưởng gì tới code dùng Router, HTTP và browser API?

Trong SSR, code có thể chạy trên server nên không phải lúc nào cũng có `window`, `document`, `localStorage`. Cần dùng platform check, injection abstraction hoặc chạy đoạn phụ thuộc browser sau khi vào client.

HTTP trong SSR cũng cần chú ý base URL, cookie forwarding và caching. Một app chạy tốt ở browser chưa chắc đã SSR-safe.

Kiến trúc, Hiệu năng & Testing

27 câu

081Junior

Change detection là gì?

Change detection là cơ chế Angular cập nhật view khi state thay đổi. Angular kiểm tra binding và render lại phần cần thiết theo chiến lược hiện tại.

Với signals và OnPush, Angular có thêm cách biết chính xác hơn nơi cần cập nhật. Nhưng tư duy chính vẫn là: state đổi thì UI phản ánh state mới.

082Middle

OnPush change detection hoạt động thế nào?

`OnPush` giúp component ít bị kiểm tra hơn. Component thường được check khi input reference đổi, event xảy ra trong component, signal/async pipe phát giá trị hoặc được đánh dấu thủ công.

Muốn OnPush hiệu quả, tránh mutate object/array tại chỗ. Hãy tạo reference mới khi state thay đổi.

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: '@for (item of items(); track item.id) { {{ item.name }} }'
})
export class ListComponent {
  readonly items = signal<Item[]>([]);
}
083Middle

NgZone là gì?

NgZone giúp Angular biết khi async task như timer, promise hoặc event hoàn tất để chạy change detection. Đây là nền của cách Angular truyền thống tự cập nhật UI.

Với ứng dụng hiệu năng cao, đôi khi chạy tác vụ ngoài zone để tránh check quá nhiều, rồi quay lại zone hoặc dùng signal để cập nhật có chủ đích.

084SeniorSenior cần nắm

Zoneless Angular là gì?

Zoneless là hướng chạy Angular không phụ thuộc Zone.js để tự động bắt mọi async task. Thay vào đó app cần state/reactivity rõ ràng hơn, đặc biệt qua signals và explicit notification.

Lợi ích là giảm overhead và tăng tính dự đoán. Rủi ro là code cũ dựa vào “tự magic update” có thể lộ bug timing khi migration.

085Middle

ExpressionChangedAfterItHasBeenCheckedError nghĩa là gì?

Lỗi này thường xảy ra khi một binding đổi sau khi Angular đã kiểm tra view trong cùng chu kỳ. Nó báo hiệu timing hoặc data flow không ổn.

Đừng chữa mặc định bằng `setTimeout` hoặc `detectChanges`. Hãy xem vì sao state bị đổi muộn: lifecycle sai, child update parent không đúng lúc, hoặc side effect trong template/computed.

086Middle

Làm sao tối ưu danh sách lớn?

Dùng `@for` với `track` ổn định, pagination hoặc virtual scroll, tránh pipe/filter/sort nặng trong template và chia row component hợp lý.

Nếu mỗi row có nhiều binding, OnPush/signals giúp giảm render thừa. Nhưng tối ưu tốt nhất thường là không render quá nhiều DOM cùng lúc.

087Middle

`@defer` dùng để làm gì?

`@defer` trì hoãn render/tải một phần template cho tới khi có trigger như viewport, interaction hoặc idle. Nó giúp giảm công việc ban đầu của trang.

Dùng tốt cho phần dưới màn hình, chart nặng, panel phụ hoặc component không cần ngay. Luôn thêm loading/placeholder để UX không bị trống khó hiểu.

088Middle

SSR, prerender và hydration khác nhau thế nào?

SSR render HTML trên server theo request. Prerender tạo HTML tĩnh lúc build cho route biết trước. Hydration là quá trình client tái sử dụng HTML đã có và gắn tương tác lên đó.

Chọn theo sản phẩm: trang public cần SEO có thể SSR/prerender; màn hình sau login nhiều tương tác có thể client render là đủ.

089SeniorSenior cần nắm

Hydration mismatch thường do đâu?

Mismatch xảy ra khi HTML server render khác HTML client render. Nguyên nhân hay gặp: dùng random/time trực tiếp trong template, phụ thuộc browser-only API, data khác nhau giữa server và client.

Cách xử lý là làm render deterministic, transfer state đúng, hoặc hoãn phần chỉ chạy client sau hydration.

090Middle

Angular security model chống XSS thế nào?

Angular mặc định escape interpolation và sanitize một số binding nguy hiểm như HTML/URL trong context nhất định. Điều này giảm rủi ro XSS khi hiển thị dữ liệu người dùng.

Nhưng Angular không thay bạn thiết kế bảo mật. Nếu dùng `innerHTML`, `DomSanitizer.bypass...` hoặc đụng DOM trực tiếp, bạn phải hiểu nguồn dữ liệu có đáng tin không.

091SeniorSenior cần nắm

DomSanitizer bypass có nguy hiểm không?

Có. Các hàm bypass nói với Angular rằng “tôi tin dữ liệu này”. Nếu dữ liệu đến từ user hoặc CMS không kiểm soát chặt, bypass có thể mở cửa XSS.

Chỉ dùng bypass ở boundary rất rõ, có sanitize phía server hoặc whitelist nội dung. Đừng dùng nó để làm mất warning cho nhanh.

092SeniorSenior cần nắm

Token nên lưu ở LocalStorage hay cookie?

Không có câu trả lời tuyệt đối. LocalStorage dễ dùng nhưng dễ bị đọc nếu XSS. HttpOnly cookie giảm rủi ro bị JS đọc, nhưng cần xử lý CSRF, SameSite, domain và refresh flow.

Ứng viên senior nên nói theo threat model: loại app, backend hỗ trợ gì, rủi ro XSS/CSRF, yêu cầu mobile/webview và chính sách logout.

093Middle

CSP và Trusted Types liên quan gì tới Angular?

CSP giúp giới hạn nguồn script/style/resource được phép chạy. Trusted Types giúp giảm rủi ro DOM XSS bằng cách kiểm soát sink nguy hiểm.

Angular hỗ trợ hướng bảo mật này, nhưng app vẫn phải tránh inline script tùy tiện, thư viện không rõ nguồn và bypass sanitizer thiếu kiểm soát.

094Junior

TestBed dùng để làm gì?

TestBed tạo môi trường test Angular: render component, cung cấp provider, import dependency và inject service. Nó giúp test component gần với cách Angular chạy thật.

Với standalone component, test thường import thẳng component vào TestBed. Điều này gọn hơn nhiều so với khai báo NgModule giả.

095Middle

Unit test service nên kiểm tra gì?

Service test nên kiểm tra business logic, interaction với dependency và xử lý edge case. Nếu service gọi HTTP, dùng HttpTestingController hoặc mock API layer.

Đừng test implementation quá sâu. Test nên nói service trả kết quả gì khi input/dependency ở trạng thái nào.

096Middle

Component test nên tập trung vào đâu?

Component test nên kiểm tra UI render theo input/state, event người dùng, output phát ra và tương tác với service mock. Không cần test từng dòng HTML không có ý nghĩa nghiệp vụ.

Với component phức tạp, hãy test qua hành vi người dùng nhìn thấy: text xuất hiện, button disabled, error message, dialog mở.

097SeniorSenior cần nắm

E2E test trong Angular nên dùng gì?

Hiện nay Playwright hoặc Cypress thường thay Protractor. E2E test nên phủ luồng quan trọng: login, checkout, CRUD chính, permission và lỗi API.

Không nên biến E2E thành nơi test mọi edge case nhỏ. Unit/integration test rẻ hơn; E2E đắt nhưng bắt lỗi tích hợp tốt.

098SeniorSenior cần nắm

Protractor và Codelyzer hiện nên nhìn thế nào?

Protractor và Codelyzer là chủ đề legacy. Protractor thường được thay bằng Playwright/Cypress; Codelyzer được thay bằng ESLint ecosystem cho Angular.

Khi phỏng vấn, hỏi chúng để xem ứng viên biết migration và lịch sử dự án, không nên coi đó là kỹ năng chính cho Angular hiện đại.

099SeniorSenior cần nắm

Kiến trúc thư mục Angular nên tổ chức ra sao?

Tổ chức theo feature/domain thường dễ scale hơn tổ chức thuần theo loại file. Ví dụ `orders`, `users`, `billing` mỗi feature chứa component, route, service, model liên quan.

Shared chỉ nên chứa thứ thật sự dùng chung và ổn định. Nếu cái gì cũng cho vào shared, shared sẽ thành thùng đồ khó kiểm soát dependency.

100SeniorSenior cần nắm

Smart component và presentational component khác nhau thế nào?

Smart component biết dữ liệu đến từ đâu, gọi service/facade và xử lý flow. Presentational component nhận input, phát output và tập trung hiển thị.

Pattern này giúp UI component dễ tái sử dụng và test. Nhưng không cần cực đoan: với màn hình nhỏ, tách quá nhiều component có thể làm code rườm rà.

101SeniorSenior cần nắm

Facade pattern trong Angular dùng khi nào?

Facade che bớt chi tiết state/API khỏi component. Component gọi `loadUsers`, đọc `users`, `loading`, `error` thay vì biết store/RxJS/API bên dưới.

Facade tốt khi feature có nhiều nguồn dữ liệu hoặc cần đổi implementation. Với feature nhỏ, facade có thể là lớp thừa nếu chỉ pass-through service.

102ExpertSenior cần nắm

Micro frontend với Angular cần chú ý gì?

Micro frontend giải quyết bài toán tổ chức team/deploy độc lập, không phải thuốc tăng lực cho mọi app. Nó thêm chi phí về shared dependency, routing, auth, design system, observability và version drift.

Chỉ nên chọn khi có lý do tổ chức rõ. Nếu chỉ muốn chia code, lazy route hoặc workspace library thường đơn giản hơn.

103ExpertSenior cần nắm

Angular library nên thiết kế thế nào?

Library nên có public API nhỏ, ổn định, document rõ và tránh phụ thuộc ngược vào app. Component trong library cần input/output rõ, theming/accessibility tốt và không assume môi trường quá cụ thể.

Đừng export mọi file nội bộ. Public API càng rộng, breaking change càng khó kiểm soát.

104ExpertSenior cần nắm

Upgrade Angular version nên làm ra sao?

Nâng từng major theo Update Guide, chạy `ng update`, đọc breaking changes, chạy test và kiểm tra production build. Với app lớn, nên làm thường xuyên để mỗi lần upgrade nhỏ hơn.

Đừng gom 5 major vào một lần nếu không bắt buộc. Rủi ro không nằm ở lệnh update, mà ở dependency bên thứ ba, custom builder, code legacy và test thiếu.

105ExpertSenior cần nắm

Migration sang standalone nên đi theo chiến lược nào?

Đi theo route/feature, không cần big bang. Feature mới viết standalone trước; feature cũ migrate khi đang sửa hoặc khi NgModule gây cản trở rõ ràng.

Luôn giữ test và build xanh sau từng bước. Mục tiêu là giảm phức tạp thật, không phải đổi cú pháp để nhìn hiện đại.

106ExpertSenior cần nắm

Senior Angular cần nắm những chủ đề nào?

Senior cần nắm component architecture, DI scope, router, forms, HTTP, RxJS, signals, change detection, SSR/hydration, security, testing, performance và upgrade strategy.

Quan trọng hơn là biết trade-off. Cùng một bài toán có thể giải bằng service, signal store, NgRx hoặc route state; senior phải giải thích vì sao chọn cách này trong bối cảnh cụ thể.

107ExpertSenior cần nắm

Câu hỏi Angular tốt nên đánh giá điều gì?

Câu hỏi tốt không chỉ hỏi định nghĩa. Nó nên mở ra tình huống: bug production, form lớn, search bị race condition, SSR mismatch, token storage hoặc migration version.

Ứng viên mạnh thường trả lời bằng nguyên lý, ví dụ thực tế, trade-off và lỗi từng gặp. Câu trả lời “Angular là framework của Google” đúng nhưng chưa đủ để đánh giá năng lực làm việc.