Різниця між AngularJS та Angular
AngularJS (1.x) та Angular (2+) - два різних фреймворки, які поділяють назву, але майже нічого більше.
Теорія
TL;DR
- AngularJS (2010): JavaScript + контролери +
$scope; Angular (2016): TypeScript + компоненти + декоратори - Головна різниця: digest-цикл через
$scopeпроти Zone.js і дерева компонентів - Офіційна підтримка AngularJS завершилась у грудні 2021; Angular активно оновлюється, v18 вийшов у 2024
- Google переписав Angular з нуля у 2016 році, це не оновлення старого
- AngularJS - тільки для підтримки legacy; Angular - для всього нового
Швидкий приклад
<!-- AngularJS 1.x: двостороннє зв'язування через $scope -->
<div ng-app="app" ng-controller="MainCtrl">
<input ng-model="name">
<p>Привіт, {{ name }}!</p>
</div>angular.module('app', []).controller('MainCtrl', function($scope) {
$scope.name = 'Світ'; // $digest() стежить за кожною зміною
});// Angular: клас компонента, $scope не потрібен
@Component({
selector: 'app-hello',
template: `<p>Привіт, {{ name }}!</p>`
})
export class HelloComponent {
name = 'Світ'; // властивість TypeScript-класу
}В AngularJS $scope - спільний об'єкт між контролером і шаблоном. Angular замінює це класом, властивості якого напряму відповідають даним шаблону.
Ключова різниця
AngularJS прив'язує глобальне дерево $scope до шаблонів і запускає цикли $digest(), перевіряючи кожен watcher на зміни. На маленьких застосунках це працює, але зі зростанням кількості watchers починає гальмувати. Angular компілює TypeScript у JavaScript ще до запуску (AOT), будує дерево компонентів і використовує Zone.js, щоб знати про завершення асинхронних операцій. Після цього перевіряє тільки зачеплену гілку. З ChangeDetectionStrategy.OnPush Angular взагалі пропускає гілки, де @Input не змінювався. Результат: бандли менші в 5-10 разів і помітно швидший рендеринг на великих застосунках.
Я бачив команди, які підтримували AngularJS у 2023 році, бо міграція постійно відкладалась. Накладні витрати digest-циклу в тих застосунках були топ-1 скаргою з продуктивності, особливо на дашбордах із 500+ прив'язаними значеннями.
Коли що використовувати
- Старий внутрішній інструмент, бюджету на міграцію немає: AngularJS (не додавай нових фіч)
- Новий SPA, корпоративний застосунок або мобільний проект: Angular
- Команда з досвідом TypeScript: Angular (краща підтримка IDE і рефакторинг)
- Вже на AngularJS і треба масштабуватись:
ngUpgradeдля поступової міграції
Таблиця порівняння
| Характеристика | AngularJS (1.x) | Angular (2+) |
|---|---|---|
| Реліз | 2010 | 2016 (повне переписування) |
| Мова | JavaScript | TypeScript |
| Зв'язування даних | Двостороннє через $scope/digest | Одностороннє @Input/@Output |
| Архітектура | MVC, контролери, директиви | Компоненти, модулі, сервіси |
| CLI | Відсутній (bower/gulp вручну) | Angular CLI (ng generate, ng build) |
| Мобільні | Базово (PhoneGap) | Native-ready (Ionic, Cordova) |
| Продуктивність | Digest-цикл гальмує на 1000+ watchers | AOT + lazy loading, OnPush detection |
| Підтримка | Завершена у грудні 2021 | Активна, v18 у 2024 |
| Коли використовувати | Тільки підтримка legacy | Нові проекти, enterprise |
Як Angular визначає зміни
AngularJS парсить HTML на ng-* директиви, будує дерево $scope, потім запускає $digest() до 10 разів поспіль, поки жоден watcher не спрацює. Кожен прохід - O(n) по всіх watchers. Angular іде інакшим шляхом: компілятор ngc будує шаблони компонентів у JavaScript на етапі збірки. Zone.js патчить браузерні API (setTimeout, addEventListener), щоб знати про завершення асинхронних операцій. Тільки після цього Angular перевіряє потрібне піддерево компонентів.
Типові помилки
Мутація $scope всередині нативного таймера в AngularJS:
// Погано: $digest не запускається, UI залишається застарілим
setTimeout(function() {
$scope.items.push('новий елемент');
});
// Правильно: обгортаємо в $apply
$scope.$apply(function() {
$scope.items.push('новий елемент');
});Надмірне використання [(ngModel)] у формах Angular:
<!-- Запускає повну перевірку змін при кожному натисканні клавіші -->
<input [(ngModel)]="user.name">Для форм з багатьма полями краще використовувати реактивні форми з FormControl і patchValue. Менше перевірок, краща продуктивність.
Забутий імпорт модулів в Angular:
@NgModule({
declarations: [MyComponent]
// забули: imports: [CommonModule, FormsModule]
})*ngIf і *ngFor живуть у CommonModule. Без нього шаблон видає помилку парсингу. Класична пастка для початківців.
Де зустрічається в реальних проектах
- Netflix перемістив компоненти дашборду з AngularJS на Angular для зменшення розміру бандлів
- Google Workspace використовує Angular-модулі для ізоляції фіч у великих командах
- Delta Airlines побудував SPA для бронювання на Angular CLI
- IBM замінив promise-ланцюжки AngularJS на Angular + RxJS для реактивних форм
Питання на співбесіді
Q: Чому Google переписав Angular замість оновлення AngularJS?
A: Архітектура $scope/digest не підтримувала AOT-компіляцію та серверний рендеринг без повного редизайну. Написати з нуля виявилось простіше.
Q: Що таке $digest-цикл і чому він гальмує великі застосунки?
A: Він перебирає всі зареєстровані watchers, порівнюючи поточні й попередні значення. Якщо щось змінилось, починає заново, до 10 разів. На сотнях watchers це дорого коштує при кожній взаємодії.
Q: Як Zone.js замінює $scope.$apply?
A: Zone.js патчить setTimeout, Promise і DOM-події. Angular використовує ці патчі, щоб знати про завершення асинхронних операцій, і запускає перевірку змін автоматично. В AngularJS доводилось викликати $apply вручну для коду поза фреймворком.
Q: Як мігрувати великий AngularJS-застосунок на Angular?
A: Через ngUpgrade: запустити обидва фреймворки разом, потім мігрувати компоненти на Angular по одному. AngularJS і Angular-компоненти комунікують під час переходу через спільні сервіси.
Q: Що таке сигнали (signals) в Angular і навіщо вони?
A: Signals (стабільні в Angular 18) дають точну реактивність без Zone.js. Сигнал відстежує, які саме компоненти від нього залежать, і при зміні значення перерендерює тільки їх, а не все дерево.
Приклади
Hello World в обох фреймворках
<!-- AngularJS 1.x -->
<div ng-app="app" ng-controller="MainCtrl">
<p>{{ message }}</p>
</div>
<script>
angular.module('app', []).controller('MainCtrl', function($scope) {
$scope.message = 'Привіт від AngularJS'; // контролер прив'язується через $scope
});
</script>// Angular: властивість класу замінює $scope
@Component({
selector: 'app-root',
template: `<p>{{ message }}</p>`
})
export class AppComponent {
message = 'Привіт від Angular';
}Два синтаксиси, дві ментальні моделі. AngularJS з'єднує контролер і шаблон через $scope. Angular використовує TypeScript-клас, де властивості стають даними шаблону напряму, без спільного мутабельного об'єкта між ними.
Список задач: ng-repeat проти *ngFor
<!-- AngularJS: ng-repeat під наглядом $digest -->
<ul>
<li ng-repeat="todo in todos">{{ todo.text }}</li>
</ul>$scope.todos = [{text: 'Купити молоко'}];
$scope.todos.push({text: 'Вигуляти собаку'}); // $digest бачить зміну масиву, ре-рендер// Angular: trackBy запобігає зайвому відтворенню DOM
@Component({
template: `
<ul>
<li *ngFor="let todo of todos; trackBy: trackById">{{ todo.text }}</li>
</ul>
<input [(ngModel)]="newTodo" (keyup.enter)="add()">
`
})
export class TodosComponent {
todos = [{id: 1, text: 'Купити молоко'}];
newTodo = '';
trackById(index: number, todo: {id: number}) { return todo.id; }
add() {
this.todos.push({id: Date.now(), text: this.newTodo});
this.newTodo = '';
}
}trackBy підказує Angular, які <li> можна перевикористати при оновленні списку. Без неї Angular знищує і відтворює кожен елемент заново. В AngularJS будь-яка мутація масиву запускає повний ре-рендер списку через $digest.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.