Вопросы и ответы с собеседований по Angular
Вопросы и ответы для собеседования Angular-разработчика. Разница между Angular и AngularJS, выражения, шаблоны, модули, сервисы, внедрение зависимостей, AOT-компиляция, жизненный цикл компонента, директивы, реализация реактивности и многое другое.
Что такое Angular?
Angular — это фреймворк от компании Google для создания продвинутых бесшовных (одностраничных) веб-приложений (SPA) на языках программирования TypeScript, JavaScript, Dart. Название читается как «Ангуляр» и переводится на русский как «угловой». Фреймворк назвали в честь угловых скобок, которыми обрамляют HTML-теги. У фреймворка открытый исходный код. Продукт распространяется бесплатно. Найти исходные файлы и дополнительную информацию можно в официальном репозитории фреймворка на GitHub.
В чём разница между Angular и AngularJS?
Angular, первоначально называвшийся Angular 2, является полной переработкой AngularJS. По мере развития и появления новых технологий, ограничения AngularJS стали вызывать проблемы. В частности, его критиковали за медлительность и ресурсоемкость, а также за то, что он не обеспечивает масштабируемость и гибкость, необходимые для разработки более сложных приложений. В качестве ответа на эти жалобы команда Google решила полностью переработать Angular и создать новую версию под названием Angular 2, которая в итоге стала называться просто Angular. В отличие от AngularJS, Angular построен на TypeScript. Поскольку это полноценный фреймворк, он предоставляет полный набор инструментов для создания и тестирования приложений. Angular может предложить: - TypeScript - Компоненты и директивы - CLI (инструмент командной строки) - Two-way binding (двусторонняя привязка данных) - Динамический роутинг - Поддержка разработки мобильных приложений - Производительность AngularJS vs Angular 2: ключевые различия
Что такое выражения?
Выражения в Angular - это синтаксис, который позволяет прокидывать значения JavaScript-переменных в шаблоны.
Например:
<span>Hello, {{name}}!</span>
Что такое шаблоны?
Шаблоны в Angular - это html-подобный синтаксис, позволяющий описывать разметку компонента, встраивая в неё данные состояния и подписываясь на события.
Старый синтаксис шаблонов:
trackByFunction(index, item) {
return item.id;
}
<div *ngFor="let item of items; index as idx; trackBy: trackByFunction">
Item #{{ idx }}: {{ item.name }}
</div>
Новый синтаксис шаблонов:
{#for item of items; track item.id; let idx = $index, e = $even}
Item #{{ idx }}: {{ item.name }}
{/for}
Angular получил новый синтаксис шаблонов
Что такое бутстрэппинг?
Бутстрэппинг в Angular - это процесс инициализации и запуска приложения Angular. Во время бутстрэппинга Angular создает корневой компонент приложения и связывает его с DOM-элементом на странице. Затем Angular загружает и компилирует компоненты, устанавливает связи между компонентами и их шаблонами, и запускает приложение.
В процессе бутстрэппинга Angular также устанавливает связь между компонентами и сервисами, провайдерами и другими зависимостями, которые могут быть необходимы для работы приложения.
Бутстрэппинг в Angular обычно выполняется в файле main.ts, где вызывается функция platformBrowserDynamic().bootstrapModule(AppModule)
, где AppModule
- это модуль приложения, который содержит корневой компонент и другие компоненты, сервисы и зависимости.
Пример кода:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
В этом примере AppModule
- это модуль приложения, который содержит корневой компонент и другие компоненты, сервисы и зависимости. Функция platformBrowserDynamic().bootstrapModule(AppModule)
запускает процесс бутстрэппинга, и Angular начинает загружать и компилировать компоненты, устанавливать связи и запускать приложение.
Что такое AOT-компиляция?
AOT-компиляция (Ahead-of-Time компиляция) в Angular - это процесс преобразования кода Angular HTML и TypeScript в эффективный JavaScript-код во время этапа сборки перед запуском в браузере
Основное отличие AOT-компиляции от JIT-компиляции (Just-in-Time компиляции) заключается в том, что при AOT-компиляции код Angular преобразуется в JavaScript до запуска приложения, в то время как при JIT-компиляции преобразование происходит во время выполнения приложения в браузере.
Преимущества AOT-компиляции в Angular включают:
1. Улучшенную производительность: AOT-компиляция позволяет уменьшить размер и сложность кода, что приводит к более быстрой загрузке и выполнению приложения.
2. Более раннее обнаружение ошибок: AOT-компиляция позволяет обнаружить некоторые ошибки во время этапа сборки, что помогает предотвратить возможные проблемы во время выполнения приложения.
3. Улучшенную безопасность: AOT-компиляция позволяет обнаружить потенциальные уязвимости в коде на этапе сборки, что помогает улучшить безопасность приложения.
AOT-компиляция в Angular может быть выполнена с помощью Angular CLI добавлением флага --aot
при выполнении команды сборки, например: ng build --aot .
Что такое модули в Angular?
Модули в Angular - это способ организации и структурирования приложения на Angular. Они позволяют разделить функциональность приложения на отдельные блоки, называемые модулями. Каждый модуль содержит компоненты, сервисы, директивы и другие ресурсы, связанные с определенной функциональностью приложения.
Модули в Angular предоставляют следующие преимущества:
Логическая организация: модули позволяют разделить функциональность приложения на логические блоки, что делает код более понятным и поддерживаемым.
Изоляция: каждый модуль имеет свою собственную область видимости, что позволяет изолировать компоненты и сервисы от других частей приложения.
Ленивая загрузка: модули могут быть загружены только по требованию, что улучшает производительность приложения и уменьшает время загрузки.
Переиспользование: модули могут быть повторно использованы в разных приложениях или в разных частях одного приложения.
NgModule
- это декоратор, который используется для определения модуля в Angular. Он применяется к классу модуля и принимает объект конфигурации, который определяет компоненты, сервисы и другие ресурсы, связанные с модулем.
Пример использования декоратора NgModule
:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
В приведенном примере AppModule
является корневым модулем приложения. Он импортирует BrowserModule
, который предоставляет функциональность для работы с браузером, и объявляет AppComponent
в качестве компонента, который будет использоваться в модуле.
Архитектура приложения Angular. Используем NgModules
Подходы к управлению модулями в Angular (и не только)
Введение в модули Angular — корневой модуль (Root Module)
Что такое сервисы в Angular?
Сервисы в Angular - это классы, которые предоставляют функциональность и могут быть использованы в разных частях приложения. Они используются для разделения логики и общих функций между компонентами, директивами и другими классами Angular.
Сервисы в Angular обычно используются для выполнения следующих задач:
1. Получение данных с сервера или других источников данных.
2. Хранение и обработка данных, которые должны быть доступны в разных частях приложения.
3. Реализация общей функциональности, такой как аутентификация, логирование и обработка ошибок.
4. Взаимодействие с внешними библиотеками или сервисами.
Сервисы в Angular могут быть созданы с помощью ключевого слова @Injectable
и зарегистрированы в модуле приложения или компоненте с помощью массива providers
. Это позволяет Angular создавать и предоставлять экземпляр сервиса внедрения зависимостей (DI) при создании компонента или другого класса.
Пример регистрации сервиса в модуле Angular:
import { NgModule } from '@angular/core';
import { MyService } from './my.service';
@NgModule({
providers: [MyService]
})
export class AppModule { }
После регистрации сервиса в модуле, он может быть внедрен в компоненты или другие классы, используя DI. Например, в компоненте можно внедрить сервис следующим образом:
import { Component } from '@angular/core';
import { MyService } from './my.service';
@Component({
selector: 'app-my-component',
template: '...',
})
export class MyComponent {
constructor(private myService: MyService) { }
}
Важно отметить, что сервисы в Angular могут быть созданы в разных режимах жизненного цикла, таких как синглтон, когда создается только один экземпляр сервиса на всё приложение, или новый экземпляр сервиса для каждого компонента. Выбор режима жизненного цикла зависит от требований и особенностей приложения.
Как сервисы внедряются в приложение?
Два способа внедрения сервиса:
1. На уровне приложения
2. На уровне компонента (и всех его дочерних компонентов)
Внедрение сервиса на уровне приложения создает один единственный экземпляр этого сервиса. Это удобно когда нам нужно шарить данные между различными компонентами или модулями.
Внедрение сервиса на уровне компонента, создает новый экземпляр сервиса для каждого компонента в отдельности.
Чтобы начать использовать сервис, его нужно добавить в массив providers
модуля, а затем инжектировать его в директиву, используя constructor
. Чтобы инжектировать сервис в другой сервис, нужно использовать декоратор @Injectable
.
@Injectable()
export class MessageService {
//injected service
constructor(private errorService: ErrorService) {}
}
Как работает внедрение зависимостей в Angular?
Angular - это платформа для разработки веб-приложений, которая позволяет создавать масштабируемые и эффективные приложения с использованием компонентной архитектуры. Внедрение зависимостей (Dependency Injection, DI) является одним из ключевых концепций в Angular.
Под внедрением зависимостей в Angular подразумевается механизм, который позволяет компонентам и сервисам получать необходимые им зависимости извне, вместо того, чтобы создавать их самостоятельно. Это позволяет создавать слабосвязанные компоненты и сервисы, что упрощает тестирование, повторное использование кода и обеспечивает более гибкую архитектуру приложения.
Когда компонент или сервис требует определенную зависимость, Angular автоматически создает экземпляр этой зависимости и предоставляет его внутри компонента или сервиса. Это позволяет избежать необходимости явного создания и управления зависимостями вручную.
Пример использования внедрения зависимостей в Angular:
import { Component, Injectable } from '@angular/core';
@Injectable()
export class DataService {
getData(): string {
return 'Some data';
}
}
@Component({
selector: 'app-example',
template: `
<h1>{{ data }}</h1>
`,
})
export class ExampleComponent {
constructor(private dataService: DataService) {}
ngOnInit() {
this.data = this.dataService.getData();
}
}
В приведенном примере DataService
является сервисом, который предоставляет данные. Компонент ExampleComponent
требует эту зависимость и получает ее через конструктор. Angular автоматически создает экземпляр DataService
и предоставляет его внутри ExampleComponent
.
Angular: полное руководство для «Внедрения зависимостей»
Внедрение зависимостей в Angular простыми словами
Angular 2 и внедрение зависимостей
Что такое компоненты?
Компоненты - это самый базовый элемент пользовательского интерфейса приложения Angular, который формирует дерево компонентов Angular. Компоненты являются подмножеством директив.
В отличие от директив, компоненты всегда имеют шаблон, и для каждого элемента в шаблоне может быть создан только один компонент.
import { Component } from '@angular/core';
@Component ({
selector: 'my-app',
template: ` <div>
<h1>{{title}}</h1>
<div>Learn Angular6 with examples</div>
</div> `,
})
export class AppComponent {
title: string = 'Welcome to Angular world';
}
Какие минимальные требования к компоненту?
import { Component } from "@angular/core";
@Component({
templateUrl: "./minimum.component.html", // or template: ''
})
export class MinimumComponent {}
Нужно учесть:
1. constructor
не обязателен, он генерируется автоматически.
2. Селектор не обязателен, доступ к компоненту можно получить через роутер по названию класса.
3. Шаблон обязателен (сам шаблон или ссылка на отдельный файл).
Разница между умным и презентационным компонентом?
Умный компонент запрашивает данные у сервиса и знает, что это за данные и какой у них тип. Обрабатывает события, которые были посланы из презентационного компонента.
Презентационный компонент отображает данные, которые он получает через @Input
из умного компонента, модифицирует их и возвращает умному компоненту через @Output
. Не в курсе, откуда пришли эти данные. Любые события презентационного компонента эмитятся в умный компонент. При этом презентационный компонент не знает, кто получит эти события и кто будет их обрабатывать.
Пример:
Список пользователей - умный компонент, карточка пользователя - презентационный.
Преимущества:
1. Презентационные компоненты легко переиспользовать
2. Разделение ответственности (UI отдельно от логики)
3. Читаемость кода лучше
Что такое динамические компоненты?
Динамические компоненты в Angular позволяют создавать и добавлять компоненты в приложение во время выполнения. Они представляют собой способ генерации и управления компонентами динамически, без необходимости определения их статически в шаблоне.
Для создания динамических компонентов в Angular используются методы createComponent()
и ComponentFactoryResolver
.
Метод createComponent()
позволяет создавать экземпляры компонентов, а ComponentFactoryResolver
используется для получения фабрики компонента, которую можно использовать для создания экземпляров компонента.
Пример использования динамических компонентов в Angular:
import { Component, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core';
@Component({
template: `<div><ng-container #dynamicContent></ng-container></div>`,
})
export class AppComponent {
@ViewChild("dynamicContent", { read: ViewContainerRef })
public dynamicContainer: ViewContainerRef;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
createDynamicComponent() {
// Получение фабрики компонента
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
// Создание экземпляра компонента
const componentRef = this.dynamicContainer.createComponent(componentFactory);
}
}
В приведенном примере AppComponent
содержит ViewContainerRef
, который представляет контейнер для динамически создаваемых компонентов. Метод createDynamicComponent()
использует ComponentFactoryResolver
для получения фабрики компонента DynamicComponent
, а затем создает экземпляр компонента с помощью createComponent()
.
Важно отметить, что динамические компоненты в Angular могут быть полезны при создании динамических макетов, модальных окон, компонентов, которые должны быть добавлены или удалены в зависимости от условий, и других сценариев, требующих гибкости и динамического управления компонентами.
Динамический Angular или манипулируй правильно
Создать динамический компонент теперь проще: изменения в Angular 13
Как происходит взаимодействие компонентов?
Типы взаимодействия:
1. Родитель/дочерний компонент с помощью декоратора @Input
, декоратора @Output
и EventEmitter
2. Взаимодействие через сервисы и Angular dependency injection
3. Взаимодействие через store (redux), который тоже использует Angular dependency injection
Обмен данными между компонентами Angular
Жизненный цикл компонента Angular
Жизненный цикл компонента в Angular - это последовательность событий и методов, которые происходят при создании, обновлении и уничтожении компонента.
Методы жизненного цикла компонента предоставляют разработчикам возможность выполнять определенные действия на разных этапах жизненного цикла компонента, таких как инициализация, обновление и уничтожение.
В Angular есть несколько методов жизненного цикла компонента, вот некоторых из них:
ngOnChanges
: Этот метод вызывается, когда значения входных свойств компонента изменяются. Он принимает объект SimpleChanges, который содержит информацию о предыдущих и текущих значениях входных свойств..
ngOnInit
: Этот метод вызывается после того, как Angular инициализирует компонент и устанавливает входные свойства. Он используется для выполнения инициализационных действий, таких как получение данных с сервера или настройка компонента.
ngDoCheck
: Этот метод вызывается при каждом изменении в компоненте, включая изменения входных свойств, события и обнаружение изменений, которые Angular не может обнаружить самостоятельно. Он позволяет разработчикам выполнять дополнительные проверки и действия при изменении компонента.
ngAfterContentInit
: Этот метод вызывается после того, как Angular вставляет внешний контент в представление компонента. Он используется для выполнения действий, которые требуют доступа к внешнему контенту, например, инициализация дочерних компонентов.
ngAfterContentChecked
: Этот метод вызывается после каждой проверки внешнего контента компонента. Он используется для выполнения действий, которые требуют доступа к внешнему контенту и проверки его изменений.
ngAfterViewInit
: Этот метод вызывается после инициализации представления компонента и его дочерних компонентов. Он используется для выполнения действий, которые требуют доступа к представлению компонента, например, инициализация сторонних библиотек или установка обработчиков событий.
ngAfterViewChecked
: Этот метод вызывается после каждой проверки представления компонента и его дочерних компонентов. Он используется для выполнения действий, которые требуют доступа к представлению компонента и проверки его изменений.
ngOnDestroy
: Этот метод вызывается перед уничтожением компонента. Он используется для выполнения действий, таких как отписка от подписок, очистка ресурсов или отмена запущенных процессов.
Пример использования методов жизненного цикла компонента:
import { Component, OnInit, OnDestroy } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `
<h1>{{ title }}</h1>
`
})
export class MyComponent implements OnInit, OnDestroy {
title: string;
ngOnInit() {
this.title = 'Hello, Angular!';
console.log('Component initialized');
}
ngOnDestroy() {
console.log('Component destroyed');
}
}
В приведенном выше примере компонент MyComponent
реализует интерфейсы OnInit
и OnDestroy
, что позволяет использовать методы ngOnInit
и ngOnDestroy
. В методе ngOnInit
устанавливается значение переменной title
и выводится сообщение в консоль при инициализации компонента. В методе ngOnDestroy
выводится сообщение в консоль перед уничтожением компонента.
Обратите внимание: При использовании методов жизненного цикла компонента в Angular, важно следить за правильным использованием и избегать выполнения длительных операций в методах, которые могут замедлить работу приложения.
В чём разница между constructor() и ngOnInit?
constructor - это метод класса, который выполняется при создании экземпляра класса и обеспечивает правильную инициализацию полей в классе и его подклассах.
Механизм Dependency Injector (DI) в Angular анализирует параметры конструктора и при создании нового экземпляра он пытается найти поставщиков, которые соответствуют типам параметров конструктора, разрешает их и передает конструктору.
ngOnInit - это функция жизненного цикла, вызываемая Angular в момент, когда создание компонента завершилось.
В основном мы используем ngOnInit
для большей части инициализационных операций/объявлений и избегаем работы с конструктором. Конструктор должен использоваться только для инициализации членов класса, но не должен выполнять реальную "работу", называемую бизнес-логикой.
constructor()
используется для настройки внедрения зависимостей и инициализации членов класса.
ngOnInit()
- своего рода точка старта бизнес-логики в компоненте.
export class App implements OnInit{
constructor(private myService: MyService){
// Вызывается перед ngOnInit()
}
ngOnInit(){
// Вызывается после конструктора и после первого ngOnChanges()
}
}
Что такое директива?
Директива в Angular - это специальная конструкция, которая позволяет расширять функциональность HTML элементов или создавать собственные элементы с определенным поведением. Директивы позволяют добавлять новые атрибуты, классы или стили к элементам, а также реагировать на события и изменять их внешний вид или поведение.
Директивы в Angular могут быть двух типов: структурные и атрибутные.
Структурные директивы изменяют структуру DOM-дерева, добавляя или удаляя элементы из разметки. Примеры структурных директив в Angular: ngIf
, ngFor
, ngSwitch
. Структурные директивы начинаются с *
, например *ngFor
, *ngSwitch
и *ngIf
.
Атрибутные директивы изменяют внешний вид или поведение элемента, к которому они применяются. Примеры атрибутных директив в Angular: ngStyle
, ngClass
, ngModel
.
Директивы в Angular объявляются с помощью декоратора @Directive
и могут содержать различные хуки жизненного цикла, которые позволяют выполнять определенные действия при создании, изменении или удалении директивы.
Например, вот пример директивы в Angular:
import { Directive, ElementRef } from '@angular/core';
@Directive({
selector: '[myDirective]'
})
export class MyDirective {
constructor(private elementRef: ElementRef) {
// Используем ElementRef для доступа к элементу, к которому применяется директива
this.elementRef.nativeElement.style.backgroundColor = 'red';
}
}
В этом примере директива MyDirective
применяется к элементу с атрибутом myDirective
и устанавливает красный фон для этого элемента.
В чем разница между компонентом и директивой?
Три вида директив в Angular:
1. Компонент - директива с шаблоном. Непосредственно создает новое представление (DOM элементы) с определённым поведением.
2. Структурная директива - меняет DOM добавляя или удаляя элементы (*ngFor
)
3. Атрибутная директива - изменяет появление или поведение элемента, компонента или другой директивы (ngStyle
)
Можно ли использовать ngFor и ngIf на одном и том же элементе?
Не рекомендуется использовать директивы ngFor
и ngIf
на одном и том же элементе.
Директива ngFor
используется для итерации по коллекции элементов и создания HTML-кода для каждого из них, в то время как директива ngIf
используется для условного отображения элемента на основе логического выражения.
При использовании этих двух директив вместе на одном элементе, это может вызвать непредсказуемое поведение и ошибки. Например, если ngIf
удаляет элемент из DOM, ngFor
не сможет итерироваться по нему.
Вместо этого рекомендуется использовать элемент-обертку ng-container
и применять директивы ngFor
и ngIf
к отдельным дочерним элементам внутри ng-container.
Например:
<ng-container *ngFor="let item of items">
<div *ngIf="item.isVisible">
<!-- Контент для видимых элементов здесь -->
</div>
</ng-container>
В этом примере ngFor
применяется к элементу ng-container
, а ngIf
- к элементу div
внутри него. Таким образом, директива ngIf
не будет влиять на итерацию ngFor
.
Что такое Observable?
Observables обеспечивают поддержку передачи сообщений между частями приложения. Они часто используются в Angular и служат для обработки событий, асинхронного программирования и работы с несколькими значениями. Паттерн наблюдателя — это паттерн проектирования программного обеспечения, в котором объект, называемый субъектом, ведет список своих зависимых объектов, называемых наблюдателями, и автоматически уведомляет их об изменении состояния. Этот паттерн похож (но не идентичен) на паттерн publish/subscribe. Observables являются декларативными — то есть вы определяете функцию для публикации значений, но она не выполняется до тех пор, пока на нее не подпишется потребитель. Подписавшийся потребитель получает уведомления до тех пор, пока функция не завершится, или пока он не откажется от подписки. Observables может передавать несколько значений любого типа — литералы, сообщения или события, в зависимости от контекста. API для получения значений одинаково, независимо от того, синхронно или асинхронно они передаются. Поскольку логика установки и удаления обрабатывается observables, код приложения должен быть озабочен только подпиской на потребление значений и отменой подписки. Будь то поток нажатий клавиш, HTTP-ответ или интервальный таймер, интерфейс для прослушивания значений и прекращения прослушивания одинаков.
Что такое Subject?
Subject - это тип Observable, который, может передавать данные нескольким Observer'ам. Это означает, что Subject многоадресный (multicast), а Observable одноадресный (unicast).
Каждый Subject - это Observable, и на него можно подписаться, но подписка не означает выполнение. Происходит только регистрация нового Observer в списке Observers.
В то же время каждый Subject является Observer и у него есть методы next
, complete
и error
. Если мы хотим передать новое значение в Subject, то мы должны использовать метод next()
, и тогда значение будет передано всем подписанным на Subject Observers.
В чем разница между Observable и Subject?
В случае с Observable каждая новая подписка (subscribe) получает новую копию данных.
import { Observable } from "rxjs";
let obs = Observable.create((observer) => {
observer.next(Math.random());
});
obs.subscribe((res) => {
console.log("subscription a :", res); //subscription a :0.2859800202682865
});
obs.subscribe((res) => {
console.log("subscription b :", res); //subscription b :0.694302021731573
});
Когда мы используем Subject, все подписчики получают одни и те же данные.
import { Subject } from "rxjs";
let obs = new Subject();
obs.subscribe((res) => {
console.log("subscription a :", res); // subscription a : 0.91767565496093
});
obs.subscribe((res) => {
console.log("subscription b :", res); // subscription b : 0.91767565496093
});
obs.next(Math.random());
Изучаем мультикаст операторы RxJS
В чем разница между Observable и Promise?
Promise обрабатывает одно значение по завершению асинхронной операции, вне зависимости от ее исхода, и не поддерживают отмену операции. Observable же является потоком, и позволяет передавать как ноль, так и несколько событий, когда callback вызывается для каждого события. Выполнение Observable может быть отменено. Основы реактивного программирования с использованием RxJS. Часть 2. Операторы и пайпы
Как кэшировать данные из observable?
Для кэширования потока с состоянием можно использовать операторы publishReplay()
и refCount()
.
publishReplay()
создает внутри себя буфер сообщений и первым параметром принимает размер буфера. Каждый новый подписчик сразу же получит накопленные сообщения.
refCount()
реализует простой паттерн Ref Count, который используется для определения актуальности текущего потока. Если слушателей больше не останется, то он отпишется от потока, тем самым уничтожив его вместе с данными.
Также можно использовать BehaviorSubject
. Этот оператор хранит текущее значение и передаст его новому подписчику.
Как с помощью RxJS реализовать несколько последовательных запросов к API?
Для реализации следующий друг за другом запросов к API нужно использовать Observable высшего порядка concatMap
.
Данный оператор ждет, пока предыдущий запрос будет выполнен, а затем делает следующий.
В чем разница между switchMap, concatMap и mergeMap?
switchMap
подписывается на новый Observable и отменяет подписку на предыдущий. Эмитится только последний Observable.
concatMap
не будет подписываться на следующий Observable, пока не завершится текущий. Преимущество этого подхода в том, что порядок, в котором эмитятся Observable, поддерживается.
mergeMap
подписывается на новый Observable при этом не отписываясь от предыдущих. Порядок, в котором эмитятся Observable, не сохраняется.
В чем разница между scan() и reduce()?
scan
выведет все значения, которые эмитил Observable.
reduce
выведет только последнее значение.
let obsScan = Observable.from([1, 2, 3, 4, 5, 6]);
let count1 = obsScan.scan((acc, one) => acc + one, 0);
count1.subscribe((x) => {
console.log("scan shows incremental total", x);
});
let obsReduce = Observable.from([1, 2, 3, 4, 5, 6]);
let count2 = obsReduce.reduce((acc, one) => acc + one, 0);
count2.subscribe((x) => {
console.log("reduce shows only total", x);
});
В консоли:
scan shows incremental total 1
scan shows incremental total 3
scan shows incremental total 6
scan shows incremental total 10
scan shows incremental total 15
scan shows incremental total 21
reduce shows only total 21
В чем разница между BehaviorSubject, ReplySubject и AsyncSubject?
BehaviorSubject
BehaviorSubject хранит последнее значение, которое было передано подписчику. При подписке на BehaviorSubject, новый Observer сразу же получит значение, хранящиеся в BehaviorSubject. В момент объявления BehaviorSubject можно передавать начальное значение.
ReplySubject
У ReplySubject принцип действия похож на BehaviorSubject. Отличие в том, что ReplySubject может хранить несколько значений и передавать их новый Observer'ам. При создании ReplySubject необходимо указать количество последних значений, которое он должен запоминать. Кроме этого мы можем задать время в миллисекундах, которое определяет насколько "старым" может быть значение.
AsyncSubject
AsyncSubject хранит только последнее значение и передает его Observer'у только если был вызван метод complete()
.
Что такое Observable высшего порядка?
Observable высшего порядка - это Observable, значением которого является новый Observable.
Примеры: switchMap
, mergeMap
и concatMap
.
В чем разница между of и from?
Допустим у нас есть массив:
let fruits = ["orange", "apple", "banana"];
Оператор from вернет значения одно за другим:
from(fruits).subscribe(console.log); // 'orange','apple','banana'
Оператор of вернет весь массив целиком:
of(fruits).subscribe(console.log); // ['orange','apple','banana']
Оператор of
ведет себя так же, как from
при использовании спред оператора:
of(...fruits).subscribe(console.log); // 'orange','apple','banana'
Что такое multicasting?
Мультикастинг - рассылка данных списку из нескольких подписчиков, которая происходит за одно выполнение.
Например:
import { Subject } from "rxjs";
const subject = new Subject<number>();
subject.subscribe({
next: (v) => console.log(`observerA: ${v}`),
});
subject.subscribe({
next: (v) => console.log(`observerB: ${v}`),
});
subject.next(1);
subject.next(2);
// Logs:
// observerA: 1
// observerB: 1
// observerA: 2
// observerB: 2
Изучаем мультикаст операторы RxJS
Что такое поток в RxJS?
Поток RxJS - это последовательность текущих событий, упорядоченных во времени. Поток можно рассматривать как элементы на конвейерной ленте, обрабатываемые по одному, а не большими партиями. Основы реактивного программирования с использованием RxJS
В чем разница между combineLatest и forkJoin?
forkJoin
ждет, пока все Observable завершатся, а затем возвращает массив значений.
combineLatest
возвращает массив значений, как только один из Observable завершится.
Как анимировать переход между двумя состояниями в Angular?
Во время перехода между состояниями можно создавать простые анимации. Анимации определяются в @Component()
и состоят из множества сменяющих друг друга состояний конкретного элемента. Переходы определяются в функции state()
.
@Component({
selector: 'example-panel',
templateUrl: './example-panel.component.html',
animations: [
trigger('expandedPanel', [
state('initial', style({ height: 0 })),
state('expanded', style({ height: '*' })),
transition('initial <=> expanded', animate('0.3s')),
]),
],
})
Анимация в Angular-приложениях
Что такое состояние wildcard?
Звездочка *
или wildcard соответствует любому состоянию. Используется для того, чтобы определить анимацию, которая применяется независимо от начального или конечного состояния HTML-элемента.
Например, анимирование всех состояний:
transition("* => *", animate("0.3s"));
Если анимация должна срабатывать при переходе с initial
на любое другое состояние, то это указывается так:
transition("initial => *", animate("0.3s"));
Когда нужно использовать ngrx/store?
Когда в приложении мы имеем 5 или 10 уровней дерева компонентов. Мы передаем переменные через @Input
и конечный компоненты знают, что нужно делать переданными данными. Но для промежуточных компонентов эти данные кажутся лишними. Компоненты становится сложнее переиспользовать. В случае нескольких уровней вложенности компонентов реагировать на события также становится проблемой. Все это усложняет масштабирование приложения.
Когда данные могут изменятся как на клиенте так и на сервере. Store в таком случае будет являться единственным источником истины.
Angular: понятное введение в NGRX
Angular: пример использования NGRX
Что такое "race condition"?
Состояние гонки возникает, когда два или более потоков могут получить доступ к общим данным, и они пытаются изменить их одновременно.
Пример. У нас есть список фильмов. По клику на название показывается описание фильма. Мы кликаем на "Форест Гамп", а потом сразу на "Интерстеллар". В идеальном мире ответы от сервера приходят по очереди. Но если описание к "Интерстеллар" придет первым, то мы получим баг.
Решить эту проблему можно с помощью Subject и switchMap
. Вместо того, чтобы при каждом клике создавать новый независимый Observable мы создаем один Observable, который синхронизирует запросы. Оператор switchMap
подписывается только на последний запрос. Если происходит новый запрос, он подписывается на него и отписывается от предыдущего.
Race condition в веб-приложениях
Что такое Shared модуль?
Shared модуль позволяет организовать код. В нем находятся директивы, пайпы и компоненты, которые используются в других модулях приложения. Мы импортируем один Shared модуль вместо нескольких импортов директив, пайпов и компонентов.
Как сделать двухстороннее связывание данных?
Двухстороннее связывание можно сделать как с помощью ()
так и с []
.
<component [(title)]="name"></component>
<component [title]="name" (titleChange)="name=$event"></component>
<input [(ngModel)]="userName" />
Последний способ - частный случай для форм.
Двустороннее связывание Angular, чуть больше понимания
Что такое View Encapsulation?
View Encapsulation определяет, могут ли CSS и HTML, определенные в компоненте, влиять на все приложение или наоборот. Angular предоставляет три стратегии инкапсуляции: Emulated (по умолчанию) - стили из основного HTML распространяются на компонент. Стили, определенные в декораторе @Component этого компонента, относятся только к этому компоненту. ShadowDom - стили из основного HTML не распространяются на компонент. Стили, определенные в этом компоненте ограничены только этим компонентом. None - стили из компонента распространяются обратно на основной HTML и, следовательно, видны всем компонентам на странице.
Как выбрать подходящий тип формы?
Template-driven формы можно использовать в тех случаях, когда нужно сделать небольшую форму или если приложение переходит с AngularJS на Angular (ngModel
работает так же как ng-model
в AngularJS).
Во всех остальных случаях лучше использовать реактивные формы.
Преимущества реактивных форм:
1. Лучше масштабируются
2. Разделение бизнес и презентационной логики
3. Повышается читабельность кода
4. Проще поддерживать
5. Проще применять кастомную валидацию
Работа с формами в Angular — модуль работы с формами и обертки полей
Как отправить форму?
Нужно использовать ngSubmit
чтобы слушать событие отправки формы.
При сабмите формы будет вызываться соответствующий метод.
<form [formGroup]="checkoutForm" (ngSubmit)="onSubmit()"></form>
В чем разница между NgForm, FormGroup, и FormControl?
Директива NgForm
создает новую сущность FormGroup
и привязывает ее к элементу <form>
шаблона, собирает все значения полей формы и следит за их изменениями и валидацией. При импорте Forms Module директива применяется по умолчанию ко всем тегам <form>
. Применяется на уровне шаблона.
FormGroup
собирает значения каждой дочерней FormControl
в один объект, где ключами будут имена контроллеров полей формы.
FormControl
следит за значением и валидацией одного конкретного контроллера формы.
В чем преимущество использования FormBuilder?
FormBuilder
упрощает создание новых сущностей FormControl
, FormGroup
или FormArray
. Сокращает количество кода, необходимого для создания сложных форм.
Как добавить валидацию к форме, сделанной с помощью FormBuilder?
Для валидации формы, созданной с помощью FormBuilder
нужно:
1. Импортировать валидаторы:
import { Validators } from "@angular/forms";
2. Добавить массив с валидаторами к контроллеру поля формы:
this.contactForm = this.formBuilder.group({
name: ["", [Validators.required, Validators.minLength(10)]],
email: ["", [Validators.required, Validators.email]],
country: ["", [Validators.required]],
});
В чем разница между состояниями dirty, touched и pristine?
dirty
имеет значение true
, если пользователь поменял значение поля формы.
touched
имеет значение true
, если пользователь сделал активным (blur) поле ввода, но не менял значение поля формы.
pristine
имеет значение true
, если пользователь вообще не трогал это поле формы.
Как отобразить ошибки валидации в шаблоне?
У каждого поля формы в Angular есть свойство errors
.
К примеру, если мы задали валидацию Validators.required
, то мы можем показать наш текст ошибки с помощью *ngIf
:
<div *ngIf="name.errors?.required">Обязательное поле</div>
Как кэшировать данные в Angular?
Способы кэширования данных в Angular: 1. Сохранять данные в localStorage/sessionStorage 2. Сохранять данные в переменной в сервисе 3. Симулировать стейт с помощью BehaviorSubject и получать из него последнее значение 4. Использовать кэширование в помощью RxJs 5. Использовать хранилище NgRx
Зачем нужен NgModule?
NgModule
помогает организовать компоненты, директивы и сервисы в логический блок, который подразумевает под собой определенный раздел сайта.
Приложения среднего или большого размера включают в себя разные разделы. Админка, панель управления, заказы, магазин, настройки. Для каждого раздела мы создаем новый NgModule
.
Как используется свойство providedIn?
ProvidedIn
указывает на модуль, в котором будет использоваться сервис.
Если указать providedIn: 'root'
то сервис можно использовать по всему приложению.
Что бы вы поместили в shared модуль?
В Shared модуль помещаются директивы, пайпы, компоненты, которые используются по всему приложению и экспортируются из этого модуля. Shared модуль можно импортировать туда, где нам нужны эти компоненты/пайпы/директивы и, таким образом, нам не нужно будет импортировать всё по отдельности.
Что бы вы не поместили в shared модуль?
В Shared модуль не стоит помещать сервисы, которые могут быть импортированы в lazy loaded модуль. Когда lazy loaded модуль импортирует модуль, в котором есть сервис, angular создает новый экземпляр этого сервиса.
В какой модуль вы бы поместили сервис, использующийся по всему приложению?
Нужно создать Core модуль и поместить в него все использующиеся по всему приложению сервисы. Затем импортировать этот модуль в app.module, чтобы все модули, даже lazy loaded, использовали один и тот же экземпляр сервиса.
Как улучшить производительность приложения?
1. Изменить ChangeDetectionStrategy на OnPush.
2. Отключение Change Detection.
3. Обнаружение локальных изменений.
4. Запуск вне Angular.
5. Использование чистых пайпов.
6. Использование опции trackBy
для директивы *ngFor
.
7. Использование Web Worker.
8. Ленивая загрузка.
9. Предварительная загрузка.
Что такое и как работает ChangeDetection?
ChangeDetection - это механизм, который следит за изменениями в приложении. Когда происходит изменение, Angular обновляет DOM. ChangeDetection работает в двух режимах: Default
и OnPush
.
По умолчанию, Angular следит за всеми изменениями в приложении. Это означает, что Angular следит за изменениями во всех компонентах, даже если они не используются. Это может привести к низкой производительности приложения.
Angular поставляет отдельный changeDetector в каждый компонент. Когда происходит изменение, Angular запускает changeDetector для каждого компонента. Если changeDetector обнаруживает изменение, он обновляет DOM.
ChangeDetectorRef
- это ссылка на changeDetector. Есть два метода, которые можно использовать для запуска changeDetector: detectChanges()
и markForCheck()
. detectChanges()
запускает changeDetector для текущего компонента. markForCheck()
помечает текущий компонент и все его родительские компоненты как измененные. Это означает, что Angular будет запускать changeDetector для всех измененных компонентов.
Другие методы ChangeDetectorRef:
1. detach
- отключает changeDetector для текущего компонента.
2. reattach
- включает changeDetector для текущего компонента.
3. checkNoChanges
- проверяет, были ли изменения в текущем компоненте и выдает ошибку если изменения были обнаружены.
События, которые запускают changeDetector:
@Input
- когда значение @Input
изменяется, Angular запускает changeDetector для текущего компонента.
EventEmitter
- когда значение EventEmitter изменяется, Angular запускает changeDetector для текущего компонента.
setTimeout
- когда setTimeout вызывается, Angular запускает changeDetector для текущего компонента.
Promise
- когда Promise разрешается, Angular запускает changeDetector для текущего компонента.
Observable
- когда Observable разрешается, Angular запускает changeDetector для текущего компонента.
Изменения в компоненте, которые используют ChangeDetectionStrategy.OnPush
, будут обнаружены только в том случае, если изменения произошли в компоненте или в его дочерних компонентах. Это означает, что Angular будет следить только за изменениями в компонентах, которые используют ChangeDetectionStrategy.OnPush
.
Полное руководство по стратегии обнаружения изменений Angular onPush
Что такое ChangeDetectionStrategy.onPush?
Когда компонент получает данные из своего родителя, Angular анализирует дерево компонентов и проверяет входные данные на наличие любых отличий от его предыдущего значения этих данных.
ChangeDetectionStrategy.onPush
отключает такую проверку change detection (CD) на компоненте и его дочерних элементах. При первом запуске приложения Angular запускает CD на компоненте с onPush и отключает слежение. В последующих запусках CD этот компонент и все его дочерние компоненты будут пропущены. Далее CD на этом компоненте будет запущено только в случае, если входные данные были изменены. Если компонент имеет @Input()
свойства, то CD будет запущен только в случае, если ссылка на объект изменилась.
Что такое отключение Change Detection?
У каждого компонента в Angular есть детектор изменений. Мы можем инжектировать этот детектор изменений (ChangeDetectorRef) чтобы отключить или включить change detection на этом компоненте (и его дочерних компонентах).
У класса ChangeDetectorRef есть методы:
1. markForCheck()
: помечает родительские компоненты как нуждающиеся в проверке (и ререндеринге).
2. detach()
: отключает change detection на этом компоненте и его дочерних компонентах.
3. detectChanges()
: немедленно запустит change detection на этом компоненте и его дочерних компонентах
4. checkNoChanges()
: проверяет change detection и его дочерние элементы и выдает их при обнаружении любых изменений. Используется в режиме разработки, чтобы убедиться, что запущенное обнаружение изменений не вносит других изменений.
5. reattach()
: снова подключает к change detection ранее отключенные компоненты.
Что такое Local Change Detection?
Отключив компонент от change detection (CD) мы можем запускать CD локально, по поддереву этого компонента.
Например, мы можем создать метод clickHandler()
, который будет изменять данные (data) и запускать локальное CD.
clickHandler() {
data++;
changeDetectorRef.detectChanges();
}
При этом DOM будет обновлен, а глобального запуска CD от root компонента не произойдет. Это даст огромную производительность, особенно если данные обновляются каждую секунду.
Что такое запуск вне зоны Angular?
Angular использует NgZone/Zone чтобы следить за асинхронными событиями, чтобы знать когда запускать change detection. Весь код приложения на Angular выполняется в зоне Angular, которая создается Zone.js. Zone.js слушает асинхронные события и сообщает их Angular.
У Angular есть особенность, которая позволяет выполнять часть кода вне зоны Angular. В этом случае change detection не будет запущено и UI не будет обновлен.
Это может быть полезно если у нас есть код, который меняет UI каждую секунду. Мы можем вывести обновление UI из зоны Angular, подождать пока нам снова нужно будет обновить UI и ввести обновление UI в зону.
@Component({
...
template: `
<div>
{{data}}
{{done}}
</div>
`
})
class TestComponent {
data = 0
done
constructor(private ngZone: NgZone) {}
processInsideZone() {
if (data >= 100) {
done = "Done"
} else {
data += 1
}
}
processOutsideZone() {
this.ngZone.runOutsideAngular(()=> {
if (data >= 100) {
this.ngZone.run(()=> {data = "Done"})
} else {
data += 1
}
})
}
}
processInsideZone
выполняется в зоне Angular и UI обновляется по ходу выполнения.
processOutsideZone
выполняется вне ngZone
и UI не будет обновляться. Когда data
станет больше 100 и мы хотим показать "Done", мы возвращаемся в ngZone
и показываем "Done".
Как работает trackBy для директивы *ngFor?
Мы можем сократить количество запросов к серверу, отслеживая изменения на каждом элементе списка. Используя свойство trackBy
для *ngFor
Angular, вместо перерисовки всего списка, будет изменять и перерисовывать только те элементы, которые были изменены.
В компоненте:
trackByItems(index: number, item: Item): number {
return item.id;
}
В шаблоне:
<div *ngFor="let item of items; trackBy: trackByItems">
({{item.id}}) {{item.name}}
</div>
Что такое Lazy Loading в Angular?
По умолчанию все NgModule
в тот момент, когда загружается приложение вне зависимости от того, нужны сейчас этим модули или нет. Для больших приложений необходимо использовать lazy loading чтобы модули загружались по мере необходимости. Lazy loading помогает уменьшить размер бандла, что уменьшит время загрузки приложения.
const routes: Routes = [
{
path: "items",
loadChildren: () => import("./items/items.module").then((m) => m.ItemsModule),
},
];
Какие есть стратегии предварительной загрузки?
У Angular router
есть свойство preloadingStrategy
, которое определяет логику предзагрузки и выполняет ленивую загрузку Angular модулей.
1. NoPreloading
- нет предзагрузки
2. PreloadAllModules
, который предзагрузает все all lazy-loaded роуты
3. QuicklinkStrategy
, который предзагружает только те роуты, ссылки на которые есть на текущей странице (необходимо установить библиотеку ngx-quicklink).
Что такое пайп в Angular?
Пайп - это декоратор, который позволяет форматировать отображаемое значение. Например, нам надо вывести определенную дату:
import { Component } from "@angular/core";
@Component({
selector: "my-app",
template: `
<div>Без форматирования: {{ myDate }}</div>
<div>С форматированием: {{ myDate | date }}</div>
`,
})
export class AppComponent {
myDate = new Date(1961, 3, 12);
}
Мы получим результат:
Без форматирования: Wed Apr 12 1961 00:00:00 GMT +0400 (GMT +04 00)
С форматированием: Apr 12, 1961
Что такое пайп async?
Пайп async подписывается на Observable или Promise и возвращает их последнее значение. Когда появляется новое значение, пайп async помечает компонент как нуждающийся в проверке. Когда компонент уничтожается, async пайп автоматически отписывается от Observable или Promise для избежания утечек памяти.
Как async pipe предотвращает утечку памяти?
В момент уничтожения компонента (NgOnDestroy
) async пайп автоматически отписывается от Observable или Promise на которые подписан.
В чем разница между чистыми и нечистыми пайпами?
Чистые (pure) пайпы: 1. Входные параметры определяют выходные. Если входные параметры не поменялись, то выходные тоже не поменяются. 2. Легко переиспользовать, так как результат предсказуем. 3. Чистые пайпы это чистые функции и их легко тестировать. Нечистые (impure) пайпы: 1. Входные параметры не определяют выходные. 2. Не могут быть переиспользованы.
В чем разница между RouterModule.forRoot() и RouterModule.forChild()?
Когда мы импортируем сервис в lazy loaded модуль, то Angular создает новый экземпляр этого сервиса.
RouterModule объявляет и экспортирует директивы (router-outlet
, routerLink
, routerLinkActive
и т.д.) и сервисы (Router
, ActivatedRoute
и т.д.). Для того, чтобы избежать дублирования экземпляров сервиса у RouterModule
есть два метода: forRoot
and forChild
.
Из названий методов ясно, что метод forRoot
должен быть вызван только в корневом модуле (app.module), а forChild
должен вызываться в других (feature) модулях. Таким образом все директивы, компоненты и пайпы всё также будут экспортироваться из модуля, но новый экземпляр сервиса не будет создан.
Как работает loadChildren?
Для ленивой загрузки модуля нужно использовать свойство loadChildren
объекта Route
.
loadChildren
определяет, какой модуль должен быть лениво загружен.
В примере ниже показано, что роутер лениво загрузит модуль, используя нативную браузерную систему импорта.
[
{
path: "lazy",
loadChildren: () => import("./lazy-route/lazy.module").then((mod) => mod.LazyModule),
},
];
Нужен ли отдельный Routing Module?
В Angular конфигурация роутов в отдельном файле считается лучшей практикой. При этом роуты можно прописать и в App модуле.
В чем разница между ActivatedRoute и RouterState?
ActivatedRoute
— сервис, предоставляемый каждому компоненту маршрута, который содержит информацию о маршруте, такую как параметры маршрута, статические данные, глобальные параметры запроса и глобальный фрагмент.
RouterState
— текущее состояние маршрутизатора, включая дерево активных маршрутов вместе с удобными методами обхода дерева маршрутов.
Зачем нужны guardes роутов?
Гарды позволяют ограничить навигацию по определенным маршрутам. Например, если для доступа к определенному ресурсу требуется наличие аутентификации или наличие каких-то других условий, в зависимости от которых мы можем предоставить пользователю доступ, а можем и не предоставить.
Что такое RouterOutlet?
Router-outlet в Angular работает как плейсхолдер для динамической загрузки компонентов в соответствии с текущим роутом.
При навигации контент компонента будет вставлен внутри <router-outlet></router-outlet>
.
Что такое CanActivate, CanActivateChild, CanDeactivate и CanLoad?
CanActivate
- разрешает/запрещает доступ к маршруту;
CanActivateChild
- разрешает/запрещает доступ к дочернему маршруту;
CanDeactivate
- разрешает/запрещает уход с текущего маршрута;
CanLoad
- разрешает/запрещает загрузку модуля, загружаемого асинхронно.