Skip to content

AnnaSolovykh/angular-ru-interview-questions

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

89 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Вопросы на собеседовании по Angular Angular-RU

Вопросы подготовлены непосредственно для того, чтобы определить уровень разработчика, насколько глубоко, поверхностно или сносно он знает Angular. Вопросы для собеседования на знание JavaScript или Web-стека хорошо освещены в других местах, поэтому ниже будет добавлен список ресурсов по этой теме:

Fundamentals:

Frontend:

Angular:

Базовые вопросы для Junior/Middle
В чем отличие фреймворка от библиотеки (приведите примеры и отличия)?
Основные различия:
  1. Inversion of Control (IoC): - Фреймворк: Управляет потоком выполнения вашего кода. Например, Angular контролирует создание и жизненный цикл компонентов. - Библиотека: Вы управляете потоком выполнения и вызываете функции библиотеки по мере необходимости. Библиотека — это код, который написали другие разработчики. Код из библиотеки разработчик использует в своей программе, а код из фреймворка использует код разработчика в себе. Примеры: React. js — это библиотека для языка программирования JavaScript с открытым исходным кодом для разработки пользовательских интерфейсов. Также Lodash, Axios.

  2. Структура и архитектура: - Фреймворк: Определяет архитектуру вашего приложения и требует следования определенным паттернам и структурам. - Библиотека: Не навязывает конкретную архитектуру, и вы можете использовать её функции в любом месте вашего кода.

Какие популярные CSS, JS библиотеки вы знаете?

Популярные CSS библиотеки:

  1. Bootstrap: Один из самых популярных CSS-фреймворков, который помогает быстро создавать адаптивные и мобильные веб-сайты.
  2. Tailwind CSS: Утилитарный CSS-фреймворк, который позволяет создавать кастомные дизайны без написания пользовательских CSS.
  3. Chakra UI: Простой и модульный компонентный фреймворк для React, который позволяет быстро создавать красивый и адаптивный пользовательский интерфейс.
  4. MUI (Material-UI): Популярная библиотека для React, которая реализует Google's Material Design и предоставляет широкий набор готовых компонентов.

Популярные JS библиотеки:

  1. React: Библиотека для создания пользовательских интерфейсов, разработанная Facebook. Широко используется для построения одностраничных приложений.
  2. Vue.js: Прогрессивный JavaScript-фреймворк для создания пользовательских интерфейсов. Легко интегрируется в проекты.
  3. Angular: Фреймворк для создания динамичных веб-приложений, поддерживаемый Google. Включает в себя всё необходимое для разработки фронтенда.
  4. jQuery: Библиотека, упрощающая работу с DOM, событиями, анимацией и AJAX. Несмотря на свой возраст, всё ещё популярна в старых проектах.
  5. Lodash: Утилитарная библиотека, предоставляющая полезные функции для работы с массивами, объектами и другими типами данных.
Знаете ли вы как браузер обрабатывает index.html (расскажите про Critical Rendering Path)?

Обработка index.html и Critical Rendering Path

Когда браузер загружает веб-страницу, он проходит несколько этапов, чтобы отобразить содержимое на экране пользователя. Этот процесс называется Critical Rendering Path (Критический путь рендеринга).Основные шаги этого процесса:

  1. Парсинг HTML: - Браузер загружает HTML-документ и начинает его парсинг (анализ). Он строит DOM (Document Object Model) - дерево элементов, представляющее структуру HTML-документа. DOM позволяет программам и сценариям динамически изменять содержимое, структуру и стиль документа.
- **Дерево узлов**: DOM представляет документ как дерево узлов, где каждый элемент, атрибут и текстовый узел — это объекты. Корневой элемент дерева называется `document`.
- **Манипуляции с документом**: Используя DOM, можно изменять структуру документа, добавляя, удаляя или изменяя элементы. Например, JavaScript может использовать DOM для изменения содержимого страницы без перезагрузки.
- **События и методы**: DOM предоставляет методы и события для взаимодействия с элементами. Например, можно добавить обработчик события клика на кнопку.
  1. Парсинг CSS: - Браузер загружает и парсит CSS-файлы, создавая CSSOM (CSS Object Model). CSSOM и DOM объединяются для создания Render Tree (дерева рендеринга).

  2. Выполнение JavaScript: - Если в HTML-документе есть ссылки на внешние JavaScript-файлы или встроенные скрипты, браузер их загружает и выполняет. JavaScript может изменять DOM и CSSOM, поэтому выполнение скриптов может задерживать построение Render Tree.

  3. Построение Render Tree: - На основе DOM и CSSOM браузер строит Render Tree, которое представляет собой набор видимых элементов на странице.

  4. Расчет Layout (вычисление расположения): - Браузер рассчитывает точное расположение каждого элемента на странице, процесс известен как Layout или Reflow.

  5. Рендеринг (Painting): - Наконец, браузер рисует (paint) пиксели на экране, отображая видимые элементы.

Оптимизация Critical Rendering Path

Для ускорения загрузки и рендеринга страницы, можно использовать следующие методы оптимизации:

  • Минимизация размера HTML, CSS и JavaScript: Уменьшение размера файлов сокращает время их загрузки.
  • Отложенная загрузка скриптов: Использование атрибутов async и defer для скриптов, чтобы избежать блокировки рендеринга.
  • Критический CSS: Включение только необходимого CSS для первичного рендеринга в head секцию HTML, остальной CSS можно загружать асинхронно.
  • Использование CDN: Хранение файлов на Content Delivery Network (CDN) для быстрой загрузки из ближайшего географического расположения.

Подробнее о критическом пути рендеринга можно узнать из следующих источников:

Какие типы данных есть в JavaScript?

Типы данных в JavaScript

В JavaScript существует несколько типов данных, которые можно разделить на примитивные типы и объекты.

Примитивные типы:

  1. Number: - Представляет числовые значения, включая целые числа и числа с плавающей запятой. - Пример: let age = 30;, let price = 99.99;

  2. String: - Представляет текстовые данные. Строки могут быть заключены в одинарные, двойные или обратные кавычки. - Пример: let name = 'John';, let greeting = "Hello";, let message = Welcome!;

  3. Boolean: - Имеет два значения: true и false. - Пример: let isActive = true;, let hasAccess = false;

  4. Null: - Представляет "ничего" или "пустое значение". - Пример: let emptyValue = null;

  5. Undefined: - Переменная, которой не присвоено значение, имеет тип undefined. - Пример: let notAssigned;

  6. Symbol (введен в ES6): - Уникальный и неизменяемый тип данных, часто используемый для создания уникальных идентификаторов. - Пример: let uniqueId = Symbol('id');

  7. BigInt (введен в ES11): - BigInt позволяет работать с числами, которые превышают максимальное значение для типа Number: - Пример: let largeNumber = 1234567890123456789012345678901234567890n;

Объекты:

  1. Object: - Коллекция свойств, где каждое свойство имеет имя (ключ) и значение. - Пример: let user = { name: 'Alice', age: 25 };

  2. Array: - Специальный тип объекта для хранения упорядоченных коллекций значений. - Пример: let colors = ['red', 'green', 'blue'];

  3. Function: - Объект, который можно вызвать для выполнения определенного кода. - Пример: function greet() { console.log('Hello'); }

Ссылки:

Как устроена память в JavaScript (memory heap, memory stack)?

Память в JavaScript

В JavaScript память организована двумя основными структурами: stack (стек) и heap (куча).

Стек памяти (Memory Stack)

Стек памяти используется для хранения простых данных (примитивов) и выполнения функций. Он работает по принципу LIFO (Last In, First Out), то есть последний добавленный элемент извлекается первым.

Основные характеристики:

  • Быстрая аллокация и деаллокация памяти.
  • Используется для хранения контекста выполнения функций.
  • Хранит примитивные значения (например, number, string, boolean).

Куча памяти (Memory Heap)

Куча памяти используется для хранения объектов и более сложных данных. Куча менее организована по сравнению со стеком и позволяет гибкое распределение памяти.

Основные характеристики:

  • Гибкая структура, позволяющая динамическое распределение памяти.
  • Используется для хранения объектов и функций.
  • Может приводить к утечкам памяти, если объекты не удаляются должным образом (забытые таймеры, колбэки, глобальные переменные, которые не используются, события, связанные с удаленными DOM элементами и другое)

Пример кода

function greet() {
    var message = 'Hello, World!'; // Примитивное значение (строка) хранится в стеке
    var person = { name: 'Alice' }; // Объект хранится в куче, а ссылка на него в стеке
    console.log(message);
    console.log(person.name);
}

greet();

В JavaScript сборка мусора (Garbage Collection) используется для автоматического управления памятью. Основной алгоритм - это Mark-and-Sweep, который периодически проверяет объекты в куче, отмечает (mark) доступные и удаляет (sweep) недоступные.

Что такое this и расскажите про область видимости?

This в JavaScript

this — это ключевое слово в JavaScript, которое указывает на контекст выполнения функции. Контекст зависит от того, как вызвана функция.

Контекст вызова

  • Глобальный контекст: В глобальной области видимости this ссылается на глобальный объект (в браузерах это window).
  • В методе объекта: Когда функция вызывается как метод объекта, this указывает на объект, которому принадлежит метод.
  • В функции-конструкторе: При использовании функции как конструктора с оператором new, this ссылается на вновь созданный объект.
  • В стрелочных функциях: Стрелочные функции не имеют собственного this; они наследуют this из окружающего контекста.

Область видимости

Область видимости в JavaScript — это часть кода, где можно обращаться к переменным.

Виды областей видимости

  • Глобальная область видимости: Переменные, объявленные вне функции, доступны везде в коде.
  • Функциональная область видимости: Переменные, объявленные внутри функции, доступны только внутри этой функции.
  • Блочная область видимости: Переменные, объявленные с помощью let и const, внутри блока, доступны только внутри этого блока.

Замыкания

Замыкания позволяют функции запоминать и получать доступ к своей области видимости даже после того, как она была выполнена.

В чем отличие var от const, let?

Отличия var, let и const в JavaScript

var

  • Область видимости: var имеет функциональную область видимости. Переменная, объявленная с помощью var внутри функции, доступна во всей функции.

  • Поднятие (Hoisting): Переменные, объявленные с помощью var, поднимаются в начало своей области видимости. Это означает, что их объявление перемещается в верхнюю часть функции или глобального контекста, но инициализация остается на месте.

  • Повторное объявление: Переменные, объявленные с помощью var, могут быть повторно объявлены без ошибки.

let

  • Область видимости: let имеет блочную область видимости. Переменная, объявленная с помощью let внутри блока (например, внутри фигурных скобок {}), доступна только внутри этого блока.

  • Поднятие (Hoisting): Переменные, объявленные с помощью let, поднимаются, но остаются в "временной мертвой зоне" до тех пор, пока выполнение кода не достигнет строки их инициализации.

  • Повторное объявление: Переменные, объявленные с помощью let, не могут быть повторно объявлены в одной и той же области видимости.

const

  • Область видимости: const также имеет блочную область видимости, аналогично let.

  • Поднятие (Hoisting): Переменные, объявленные с помощью const, поднимаются, но остаются в "временной мертвой зоне" до тех пор, пока выполнение кода не достигнет строки их инициализации.

  • Повторное объявление и изменение: Переменные, объявленные с помощью const, не могут быть повторно объявлены или переинициализированы. Однако, если переменная const ссылается на объект, свойства этого объекта можно изменять.

Краткое резюме

  • var: функциональная область видимости, поднятие, повторное объявление.
  • let: блочная область видимости, поднятие с временной мертвой зоной, отсутствие повторного объявления.
  • const: блочная область видимости, поднятие с временной мертвой зоной, отсутствие повторного объявления и изменения значения.
Объясните, как работает наследование прототипов, что такое цепочка прототипов, и когда появилось ключевое слово class в JS?

Наследование прототипов в JavaScript

JavaScript использует прототипное наследование, что означает, что объекты могут наследовать свойства и методы от других объектов. Каждый объект имеет скрытую ссылку, которая указывает на другой объект, называемый его прототипом.

Прототип

  • Прототип — это объект, от которого другие объекты наследуют методы и свойства.
  • Когда к свойству или методу обращаются через объект, JavaScript сначала ищет это свойство в самом объекте. Если его нет, JavaScript продолжает поиск в прототипе объекта, и так далее, пока не достигнет конца цепочки прототипов.

Цепочка прототипов

Цепочка прототипов — это механизм, по которому объекты могут наследовать свойства и методы от других объектов. Когда объект не имеет свойство или метод, JavaScript ищет его в цепочке прототипов.

Пример

Если объект obj не имеет метода method, JavaScript попытается найти method в прототипе obj. Если прототип также не имеет method, JavaScript продолжит поиск в прототипе прототипа, и так далее, пока не найдет метод или не достигнет конца цепочки (обычно Object.prototype).

Ключевое слово class

Ключевое слово class было введено в ECMAScript 6 (ES6) в 2015 году для облегчения работы с объектно-ориентированным программированием в JavaScript. Хотя классы в JavaScript являются синтаксическим сахаром поверх прототипного наследования, они предоставляют более удобный и знакомый способ работы с объектами и наследованием.

Основные особенности ключевого слова class:

  • Синтаксический сахар для прототипного наследования.
  • Позволяет определять конструкторы и методы в рамках класса.
  • Поддерживает наследование с помощью ключевого слова extends.
  • Включает возможность создания статических методов с использованием ключевого слова static.

Краткое резюме

  • Прототипное наследование: Объекты наследуют свойства и методы от других объектов через прототипы.
  • Цепочка прототипов: Механизм поиска свойств и методов в цепочке прототипов объектов.
  • Ключевое слово class: Введено в ES6 (2015) для упрощения работы с наследованием и объектно-ориентированным программированием в JavaScript.
Что такое структура данных и какие виды вы знаете (Стек, etc)?

Структуры данных

Структура данных — это способ организации и хранения данных таким образом, чтобы они могли быть эффективно использованы. Структуры данных позволяют управлять данными и выполнять различные операции, такие как добавление, удаление, поиск и сортировка.

Виды структур данных

  1. Стек (Stack):

    • Стек — это структура данных, которая работает по принципу LIFO (Last In, First Out), то есть последний добавленный элемент извлекается первым.
    • Операции:
      • push: добавление элемента на вершину стека.
      • pop: удаление и возвращение элемента с вершины стека.
    • Применение:
      • Обратный порядок выполнения задач (например, в рекурсии).
      • Управление операциями (например, отмена действий).
  2. Очередь (Queue):

    • Очередь — это структура данных, которая работает по принципу FIFO (First In, First Out), то есть первый добавленный элемент извлекается первым.
    • Операции:
      • enqueue: добавление элемента в конец очереди.
      • dequeue: удаление и возвращение элемента из начала очереди.
      • Применение в JavaScript:
        • Управление задачами: например, планирование процессов в очереди выполнения (event loop).
        • Буферизация данных: например, в асинхронных операциях, таких как обработка запросов в сетевом программировании.
  3. Связанный список (Linked List):

    • Связанный список — это структура данных, состоящая из узлов, каждый из которых содержит данные и указатель на следующий узел.
    • Виды:
      • Односвязный список: каждый узел указывает на следующий узел.
      • Двусвязный список: каждый узел указывает на следующий и предыдущий узел.
    • Применение:
      • Динамическое управление памятью.
      • Реализация других структур данных (например, стека и очереди).
  4. Дерево (Tree):

    • Дерево — это иерархическая структура данных, состоящая из узлов, где каждый узел имеет потомков (детей).
    • Виды:
      • Бинарное дерево: каждый узел имеет не более двух детей.
      • Дерево поиска: бинарное дерево, где левый потомок меньше родительского узла, а правый — больше.
    • Применение:
      • Организация иерархических данных (например, файловая система).
      • Поиск и сортировка данных.
  5. Хеш-таблица (Hash Table):

    • Хеш-таблица — это структура данных, которая использует хеш-функцию для преобразования ключей в индексы массива, где хранятся значения.
    • Применение:
      • Быстрый доступ к данным по ключу.
      • Реализация ассоциативных массивов и словарей.
  6. Граф (Graph):

    • Граф — это структура данных, состоящая из узлов (вершин) и ребер, соединяющих узлы.
    • Виды:
      • Ориентированный граф: ребра имеют направление.
      • Неориентированный граф: ребра не имеют направления.
    • Применение:
      • Моделирование сетей (например, социальные сети, маршруты).
      • Поиск путей и соединений.

Краткое резюме

  • Стек: LIFO, операции push и pop.
  • Очередь: FIFO, операции enqueue и dequeue.
  • Связанный список: узлы с указателями, односвязный и двусвязный.
  • Дерево: иерархическая структура, бинарное дерево и дерево поиска.
  • Хеш-таблица: хеш-функция, быстрый доступ по ключу.
  • Граф: узлы и ребра, ориентированный и неориентированный.
Что такое Promise и для чего используется в JS?

Promise в JavaScript

Promise — это объект, представляющий результат асинхронной операции. Он может находиться в одном из трех состояний:

  1. Ожидание (Pending): начальное состояние, операция еще не завершена.
  2. Исполнено (Fulfilled): операция успешно завершена.
  3. Отклонено (Rejected): операция завершилась с ошибкой.

Promise позволяет обрабатывать асинхронные операции более элегантно по сравнению с использованием коллбеков, избегая "адской пирамиды вызовов" (callback hell).

Использование Promise

Promise используется для обработки асинхронных операций, таких как выполнение HTTP-запросов, таймеры и чтение файлов. Он позволяет выполнять последовательные и параллельные асинхронные задачи, а также обрабатывать возможные ошибки.

Создание и использование Promise

  1. Создание Promise:

    const myPromise = new Promise((resolve, reject) => {
        // Асинхронная операция
        let success = true;
        if (success) {
            resolve('Успех');
        } else {
            reject('Ошибка');
        }
    });
  2. Обработка результатов:

    myPromise
        .then(result => {
            console.log(result); // 'Успех'
        })
        .catch(error => {
            console.log(error); // 'Ошибка'
        });
  3. Цепочки Promise:

    myPromise
        .then(result => {
            console.log(result);
            return anotherPromise;
        })
        .then(anotherResult => {
            console.log(anotherResult);
        })
        .catch(error => {
            console.log(error);
        });

Методы Promise

  • Promise.all: принимает массив промисов и возвращает новый промис, который выполнится, когда все промисы из массива будут выполнены.

    Promise.all([promise1, promise2])
        .then(results => {
            console.log(results); // массив результатов
        })
        .catch(error => {
            console.log(error);
        });
  • Promise.race: принимает массив промисов и возвращает новый промис, который выполнится, когда первый из промисов выполнится или будет отклонен.

    Promise.race([promise1, promise2])
        .then(result => {
            console.log(result); // результат первого выполненного промиса
        })
        .catch(error => {
            console.log(error);
        });
  • Promise.resolve: возвращает промис, который выполнится с переданным значением.

    Promise.resolve('Успех').then(result => {
        console.log(result); // 'Успех'
    });
  • Promise.reject: возвращает промис, который будет отклонен с переданным значением.

    Promise.reject('Ошибка').catch(error => {
        console.log(error); // 'Ошибка'
    });

Краткое резюме

  • Promise: объект, представляющий результат асинхронной операции.
  • Состояния: ожидание (pending), исполнено (fulfilled), отклонено (rejected).
  • Методы: then, catch, finally, Promise.all, Promise.race, Promise.resolve, Promise.reject.
Что такое call-stack, task-queue (приведите примеры работы)?

Call Stack

Call Stack (стек вызовов) — это структура данных, используемая JavaScript для отслеживания выполнения функций. Когда вызывается функция, она помещается в стек вызовов. Когда функция завершается, она удаляется из стека.

Task Queue

Task Queue (очередь задач) — это очередь, в которой задачи ожидают выполнения. Когда стек вызовов пуст, движок JavaScript берет первую задачу из очереди и выполняет её.

Event Loop

Event Loop (цикл событий) — это механизм, который позволяет JavaScript выполнять асинхронный код. Он постоянно проверяет стек вызовов и очередь задач. Если стек вызовов пуст, он берет задачу из очереди задач и помещает её в стек вызовов.

Краткое резюме

  • Call Stack (стек вызовов): структура данных для отслеживания выполнения функций. Функции добавляются в стек при вызове и удаляются по завершении.
  • Task Queue (очередь задач): очередь, в которой асинхронные задачи ожидают выполнения. Event Loop берет задачи из очереди, когда стек вызовов пуст.
  • Event Loop (цикл событий): механизм, позволяющий JavaScript выполнять асинхронный код, проверяя стек вызовов и очередь задач.

Пример работы Call Stack

function firstFunction() {
    console.log("Начало первой функции");
    secondFunction();
    console.log("Конец первой функции");
}

function secondFunction() {
    console.log("Начало второй функции");
    thirdFunction();
    console.log("Конец второй функции");
}

function thirdFunction() {
    console.log("Начало третьей функции");
    // Здесь нет вызова других функций
    console.log("Конец третьей функции");
}

firstFunction();

Начало первой функции
Начало второй функции
Начало третьей функции
Конец третьей функции
Конец второй функции
Конец первой функции


console.log("Начало");

setTimeout(() => {
    console.log("Выполнение setTimeout");
}, 0);

console.log("Конец")
Начало
Конец
Выполнение setTimeout
Что такое макро и микро задачи в JS?

Макро и Микро задачи в JavaScript

В JavaScript существует два типа задач, которые обрабатываются в цикле событий (Event Loop): макрозадачи и микрозадачи. Они имеют разные приоритеты и обрабатываются по-разному.

Макрозадачи (Macro Tasks)

Макрозадачи включают в себя все асинхронные операции, такие как:

  • setTimeout
  • setInterval
  • setImmediate
  • Ввод-вывод (I/O) операции
  • События пользовательского интерфейса (например, клики, прокрутка)

Когда стек вызовов (Call Stack) становится пустым, цикл событий берет следующую макрозадачу из очереди задач и выполняет её.

Микрозадачи (Micro Tasks)

Микрозадачи имеют более высокий приоритет и выполняются до макрозадач, если они есть. Микрозадачи включают в себя:

  • Промисы (Promise)
  • process.nextTick (в Node.js)
  • MutationObserver

После выполнения каждой макрозадачи, цикл событий проверяет, есть ли микрозадачи в очереди микрозадач. Если есть, они выполняются до начала следующей макрозадачи.

Краткое резюме

  • Макрозадачи (Macro Tasks): включают setTimeout, setInterval, ввод-вывод (I/O) операции и события пользовательского интерфейса. Обрабатываются в очереди макрозадач.
  • Микрозадачи (Micro Tasks): включают промисы и process.nextTick. Имеют более высокий приоритет и выполняются после каждой макрозадачи, если они есть.
Назовите основные принципы ООП?

Основные принципы ООП

Объектно-Ориентированное Программирование (ООП) — это парадигма программирования, основанная на концепции "объектов", которые могут содержать данные и методы для их обработки. Основные принципы ООП включают:

  1. Инкапсуляция:

    • Принцип, который объединяет данные и методы, работающие с этими данными, в единый объект. Доступ к данным объекта ограничивается, предоставляя доступ только через публичные методы.
    • Преимущества:
      • Скрытие реализации.
      • Улучшение модульности и уменьшение связности кода.
  2. Наследование:

    • Принцип, который позволяет создавать новый класс на основе существующего класса, наследуя его свойства и методы. Это позволяет использовать повторно существующий код и расширять его функциональность.
    • Преимущества:
      • Повторное использование кода.
      • Упрощение создания новых классов.
  3. Полиморфизм:

    • Принцип, который позволяет использовать объекты разных классов через единый интерфейс. Это достигается через перегрузку методов и переопределение методов в подклассах.
    • Преимущества:
      • Повышение гибкости и расширяемости кода.
      • Упрощение работы с объектами разных типов.
  4. Абстракция:

    • Принцип, который позволяет выделить общие характеристики объектов, скрывая детали реализации. Абстракция фокусируется на том, что объект делает, а не на том, как он это делает.
    • Преимущества:
      • Упрощение понимания и использования объектов.
      • Улучшение читаемости и поддерживаемости кода.

Краткое резюме

  • Инкапсуляция: объединение данных и методов в единый объект, ограничение доступа к данным.
  • Наследование: создание нового класса на основе существующего, наследование свойств и методов.
  • Полиморфизм: использование объектов разных классов через единый интерфейс, перегрузка и переопределение методов.
  • Абстракция: выделение общих характеристик объектов, скрытие деталей реализации.
Что такое класс и интерфейс?

Класс

Класс — это шаблон или "чертеж" для создания объектов. Он определяет свойства и методы, которые будут у объектов этого класса. Классы используются для инкапсуляции данных и функций, относящихся к одному типу объектов.

Основные аспекты класса:

  • Конструктор: специальный метод, который вызывается при создании экземпляра класса. Он инициализирует свойства объекта.
  • Методы: функции, определенные внутри класса, которые могут быть вызваны для объекта этого класса.
  • Наследование: классы могут наследовать свойства и методы от другого класса, что позволяет повторно использовать код.

Пример класса в JavaScript:

class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    greet() {
        console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
    }
}

const person1 = new Person('Alice', 30);
person1.greet(); // Hello, my name is Alice and I am 30 years old.

Интерфейс

Интерфейс — это описание набора методов и свойств, которые класс должен реализовать. Интерфейсы не содержат реализацию методов, только их сигнатуры. В JavaScript, хотя нет встроенного синтаксиса для интерфейсов, интерфейсы могут быть имитированы с помощью типов или соглашений об именах.

Основные аспекты интерфейса:

  • Определение контракта: Интерфейсы определяют контракт, который класс должен следовать, обеспечивая наличие определенных методов и свойств.
  • Многообразие реализации: Несколько классов могут реализовывать один и тот же интерфейс, что позволяет использовать разные реализации одной и той же логики.

Пример интерфейса в TypeScript:

interface IAnimal {
    name: string;
    makeSound(): void;
}

class Dog implements IAnimal {
    name: string;

    constructor(name: string) {
        this.name = name;
    }

    makeSound() {
        console.log('Woof!');
    }
}

const myDog = new Dog('Buddy');
myDog.makeSound(); // Woof!

Класс

  • Определение: Шаблон для создания объектов, который определяет свойства и методы.
  • Особенности: Поддерживает наследование, что позволяет объектам наследовать свойства и методы от других классов.

Интерфейс

  • Определение: Описание набора методов и свойств без их реализации, которые класс должен реализовать.
  • Особенности: Определяет контракт для классов, которые его реализуют, обеспечивая наличие определенных методов и свойств.
Что такое конструктор класса?

Конструктор класса

Конструктор класса — это специальный метод в объектно-ориентированном программировании, используемый для инициализации новых объектов. Ключевой особенностью конструктора является его автоматический вызов в момент создания объекта класса.

Основные функции конструктора:

  • Инициализация свойств: Конструктор класса обычно используется для установки начальных значений свойств объекта.
  • Выполнение кода: В конструкторе можно разместить любой код, который должен выполниться при создании объекта.

Пример конструктора в JavaScript:

class Car {
    constructor(make, model, year) {
        this.make = make;
        this.model = model;
        this.year = year;
    }

    displayInfo() {
        console.log(`Car: ${this.make} ${this.model} (${this.year})`);
    }
}

// Создание нового объекта класса Car
const myCar = new Car('Toyota', 'Corolla', 2021);
myCar.displayInfo(); // Вывод: Car: Toyota Corolla (2021)

Зачем нужен конструктор?

Конструкторы классов выполняют несколько важных функций, которые делают их неотъемлемой частью объектно-ориентированного программирования:

Удобство

  • Конструктор позволяет установить необходимые свойства при создании объекта, что делает код более читаемым и удобным для использования.

Гибкость

  • С помощью параметров конструктора можно легко создавать разные объекты одного класса с различными начальными данными. Это позволяет разработчикам легко адаптировать объекты под разные сценарии использования без необходимости повторного написания кода для инициализации свойств.

Фундаментальная роль

  • Конструкторы являются фундаментальной частью классов во многих языках программирования, включая JavaScript, Java, C#, и другие, поддерживающие объектно-ориентированную парадигму. Они обеспечивают структурированный и предсказуемый способ создания и инициализации новых объектов.

Конструкторы не только облегчают процесс написания и поддержки кода, но и улучшают его безопасность и стабильность, гарантируя, что объекты классов полностью инициализированы перед их использованием.

Расскажите про стек TCP/IP, а также более подробно про, что такое HTTP и какую роль он играет при разработке приложений?

Стек TCP/IP

Стек TCP/IP — это набор протоколов передачи данных, который лежит в основе интернета и большинства локальных сетей. Название "TCP/IP" происходит от двух важнейших протоколов в этом стеке: Протокола управления передачей (TCP) и Интернет-протокола (IP). Стек TCP/IP состоит из четырех слоев, каждый из которых выполняет уникальные функции:

  1. Слой приложений: Включает протоколы, которые обеспечивают конкретные функции для приложений, такие как HTTP для веб-браузеров, SMTP для почтовых систем и FTP для передачи файлов.
  2. Транспортный слой: Отвечает за доставку данных от источника к получателю. TCP обеспечивает надежную, упорядоченную и безошибочную передачу данных.
  3. Интернет-слой: Использует IP для маршрутизации пакетов данных через различные сети.
  4. Слой сетевого интерфейса: Включает методы и операции, используемые для передачи данных по физической сети.

HTTP и его роль в разработке приложений

HTTP (Протокол передачи гипертекста) — это основной протокол слоя приложений в интернете, используемый для передачи данных между веб-браузером и сервером. Основные аспекты HTTP:

  • Безсостоянийность: HTTP является "безсостояниевым" протоколом, что означает, что каждый запрос обрабатывается независимо, без сохранения информации между запросами.
  • Методы HTTP: HTTP включает методы, такие как GET для запроса данных от сервера, POST для отправки данных на сервер, а также PUT и DELETE.
  • HTTP-заголовки: Заголовки в запросах и ответах HTTP содержат важную информацию о браузере, сервере, запрашиваемых или отправляемых данных.

Роль HTTP в разработке приложений

HTTP является краеугольным камнем веб-разработки, позволяя приложениям взаимодействовать с серверами и сервисами. Он позволяет:

  • Обмен данными: Разработчики используют HTTP для запросов к серверу и получения ответов, что является основой веб-сервисов и API.
  • Разработка RESTful API: Стандарты HTTP используются для создания RESTful API, которые обеспечивают доступ к ресурсам через четко определенные HTTP-запросы.
  • Интеграция с веб-технологиями: HTTP легко интегрируется с другими веб-технологиями, такими как HTML, CSS и JavaScript, обеспечивая гладкое взаимодействие пользовательского интерфейса с серверной логикой.

HTTP и стек TCP/IP вместе создают мощную основу для современной веб-разработки, поддерживая все от простых веб-сайтов до сложных распределенных веб-приложений и сервисов.

Что такое REST API, как происходит взаимодействие (расскажите про основные коды ошибок, заголовки пакетов и способы их отправки)?

REST API

REST (Representational State Transfer) API — это архитектурный стиль интерфейсов программирования приложений, который использует стандарты HTTP для общения между клиентом и сервером. RESTful API обеспечивает простой и понятный способ взаимодействия с серверными ресурсами через универсальные HTTP методы, такие как GET, POST, PUT, DELETE и PATCH.

Как происходит взаимодействие

  1. HTTP Методы:

    • GET: Запрос на получение данных.
    • POST: Запрос на создание новой записи.
    • PUT: Запрос на обновление существующей записи.
    • DELETE: Запрос на удаление записи.
    • PATCH: Запрос на частичное обновление записи.
  2. HTTP Заголовки:

    • Content-Type: Определяет тип данных, отправляемых в запросе (например, application/json).
    • Authorization: Используется для аутентификации, например, с помощью токенов Bearer.
    • Accept: Определяет типы контента, которые клиент готов принять от сервера.

Основные коды ошибок HTTP

  • 200 OK: Успешный запрос.
  • 201 Created: Успешно создан новый ресурс.
  • 400 Bad Request: Неверный запрос из-за некорректных данных.
  • 401 Unauthorized: Запрос требует аутентификации.
  • 403 Forbidden: Доступ запрещён.
  • 404 Not Found: Ресурс не найден.
  • 500 Internal Server Error: Внутренняя ошибка сервера.

Способы отправки запросов

Запросы к REST API могут быть отправлены через различные инструменты и технологии:

  • Curl: Командная строка для отправки HTTP запросов.
  • Postman: Графический интерфейс для составления и тестирования API запросов.
  • Библиотеки HTTP клиентов: Например, Axios или Fetch API в JavaScript.

Пример использования REST API с Fetch API в JavaScript:

fetch('https://api.example.com/data', {
    method: 'GET',
    headers: {
        'Accept': 'application/json'
    }
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

REST API играет ключевую роль в современной веб-разработке, обеспечивая гибкий и эффективный способ взаимодействия с данными через интернет.

Основны TypeScript
Зачем нам нужны определения типов, где есть JavaScript с динамической типизацией?

TypeScript добавляет строгую типизацию в JavaScript, что помогает обнаруживать и предотвращать многие типы ошибок во время компиляции, до того как скрипт будет выполнен. Типизация обеспечивает лучшую документированность и поддержку инструментов, улучшая поддержку больших кодовых баз и упрощая рефакторинг и чтение кода. Это особенно важно в больших и сложных проектах, где динамическая типизация JavaScript может привести к ошибкам, которые трудно отследить и исправить.

Что такое пользовательский тип данных?

Пользовательские типы данных в TypeScript

В TypeScript, пользовательские типы данных дают разработчикам возможность создавать более сложные и специфичные типы, используя комбинации существующих примитивных и объектных типов. Это может быть класс, интерфейс, перечисление (enum), алиасы типа (type aliases), или даже сложные структуры, использующие объединения (unions), пересечения (intersections) и дженерики (generics).

Примеры пользовательских типов данных:

  1. Классы: Классы в TypeScript не только определяют шаблон для создания объектов, но и действуют как типы для экземпляров этих объектов.

    class User {
        constructor(public name: string, public age: number) {}
    }
    
    let user: User = new User("John Doe", 30);
2. ## Интерфейсы

Интерфейсы в TypeScript определяют структуру для объектов и действуют как контракт, который должен быть выполнен. Они не содержат реализацию деталей, а только описывают форму, которую должен иметь объект или класс.

#### Пример определения интерфейса:

```typescript
interface IPoint {
  x: number;
  y: number;
}

let point: IPoint = { x: 10, y: 20 };
  1. Перечисления (Enums):

Перечисления позволяют разработчикам определять наборы именованных констант.

enum Color {
    Red,
    Green,
    Blue
}

let c: Color = Color.Green;
  1. Алиасы типа (Type Aliases):

Алиасы типа позволяют создавать новые имена для любого типа данных, будь то примитив, объединение или сложный объект.

type Point = {
   x: number;
   y: number;
};

let point: Point = { x: 0, y: 0 };
  1. Объединения (Union Types) и Пересечения (Intersection Types):

Эти типы позволяют комбинировать существующие типы в новые структуры.

type Combined = IPoint & { z: number };
let point3D: Combined = { x: 10, y: 20, z: 30 };

Значение пользовательских типов данных

Пользовательские типы улучшают структуризацию кода, делают его более понятным и удобным для повторного использования и тестирования. Они обеспечивают сильную типизацию, что помогает избежать ошибок на ранней стадии разработки, улучшая поддержку и обслуживание кода в долгосрочной перспективе.

Что такое Union Type (тип объединения) и для чего используется?

Union Type в TypeScript позволяет переменной принимать значения нескольких разных типов, например, number | string. Это удобно для функций, которые могут обрабатывать аргументы различных типов, и добавляет гибкость в использовании типов, сохраняя при этом строгость проверки типов.

Поддерживает ли TypeScript перегрузку методов?

Перегрузка методов в TypeScript

TypeScript поддерживает перегрузку методов, что позволяет разработчикам определять несколько методов с одним и тем же именем, но различающимися по типам или количеству аргументов. Это улучшает читаемость кода и обеспечивает дополнительную гибкость при реализации функций, которые могут вести себя по-разному в зависимости от переданных аргументов.

Как это работает:

TypeScript реализует перегрузку методов путем определения нескольких сигнатур для одного и того же метода, а затем определения одной реализации. Важно отметить, что в TypeScript, в отличие от некоторых других языков, реальная реализация функции не может быть непосредственно перегружена — только её сигнатуры.

Пример перегрузки методов:

function greet(firstName: string, lastName: string): string;
function greet(firstName: string): string;
function greet(firstName: string, lastName?: string): string {
    if (lastName) {
        return `Hello, ${firstName} ${lastName}`;
    } else {
        return `Hello, ${firstName}`;
    }
}

console.log(greet("Jane", "Doe")); // Output: Hello, Jane Doe
console.log(greet("John"));        // Output: Hello, John

Этот пример демонстрирует перегрузку функции greet, которая может принимать один или два строковых параметра. Перегрузки определяют, какие комбинации параметров допустимы, в то время как реализация функции определяет, что делать с этими параметрами.

Возможна ли перегрузка конструктора в TypeScript?

Перегрузка конструкторов в TypeScript

В TypeScript, как и во многих других языках программирования, перегрузка конструкторов напрямую не поддерживается. Однако TypeScript предоставляет возможности, которые могут имитировать перегрузку конструкторов, используя необязательные параметры или параметры по умолчанию. Это позволяет создавать объекты с различными начальными состояниями, используя один конструктор.

Пример имитации перегрузки конструктора:

class Point {
    x: number;
    y: number;

    constructor();
    constructor(x: number, y: number);
    constructor(x?: number, y?: number) {
        this.x = x || 0;
        this.y = y || 0;
    }
}

const point1 = new Point();          // Создается точка с координатами (0, 0)
const point2 = new Point(5, 5);      // Создается точка с координатами (5, 5)

В этом примере конструктор Point может быть вызван без параметров или с двумя параметрами. Внутри конструктора используются значения по умолчанию, чтобы обеспечить гибкость при создании объектов.

Почему TypeScript не поддерживает прямую перегрузку конструкторов?

Перегрузка конструкторов, как в классическом объектно-ориентированном программировании (например, в Java или C#), требует возможности определения нескольких конструкторов с различным количеством параметров или типами параметров. TypeScript решает эту задачу через использование опциональных параметров и параметров по умолчанию, что делает код более гибким и упрощенным.

Что такое декоратор и какие виды декораторов вы знаете?

Декоратор — способ добавления метаданных к объявлению класса. Это специальный вид объявления, который может быть присоединен к объявлению класса, методу, методу доступа, свойству или параметру.

Декораторы используют форму @expression, где expression - функция, которая будет вызываться во время выполнения с информацией о декорированном объявлении.

И, чтобы написать собственный декоратор, нам нужно сделать его factory и определить тип:

  • ClassDecorator
  • PropertyDecorator
  • MethodDecorator
  • ParameterDecorator

Декоратор класса

Вызывается перед объявлением класса, применяется к конструктору класса и может использоваться для наблюдения, изменения или замены определения класса. Expression декоратора класса будет вызываться как функция во время выполнения, при этом конструктор декорированного класса является единственным аргументом. Если класс декоратора возвращает значение, он заменит объявление класса вернувшимся значением.

export function logClass(target: Function) {
  // Сохранение ссылки на оригинальный конструктор
  const original = target;

  // Функция генерирует экземпляры класса
  function construct(constructor, args) {
    const c: any = function () {
      return constructor.apply(this, args);
    };
    c.prototype = constructor.prototype;
    return new c();
  }

  // Определение поведения нового конструктора
  const f: any = function (...args) {
    console.log(`New: ${original["name"]} is created`);
    //New: Employee создан
    return construct(original, args);
  };

  // Копирование прототипа, чтобы оператор intanceof работал
  f.prototype = original.prototype;

  // Возвращает новый конструктор, переписывающий оригинальный
  return f;
}

@logClass
class Employee {}

let emp = new Employee();
console.log("emp instanceof Employee");
//emp instanceof Employee
console.log(emp instanceof Employee);
//true


Декоратор свойства

Объявляется непосредственно перед объявлением метода. Будет вызываться как функция во время выполнения со следующими двумя аргументами:

  • target - прототип текущего объекта, т.е. если Employee является объектом, Employee.prototype
  • propertyKey - название свойства
function logParameter(target: Object, propertyName: string) {
  // Значение свойства
  let _val = this[propertyName];

  // Геттер свойства
  const getter = () => {
    console.log(`Get: ${propertyName} => ${_val}`);
    return _val;
  };

  // Сеттер свойства
  const setter = (newVal) => {
    console.log(`Set: ${propertyName} => ${newVal}`);
    _val = newVal;
  };

  // Удаление свойства
  if (delete this[propertyName]) {
    // Создает новое свойство с геттером и сеттером
    Object.defineProperty(target, propertyName, {
      get: getter,
      set: setter,
      enumerable: true,
      configurable: true,
    });
  }
}

class Employee {
  @logParameter
  name: string;
}

const emp = new Employee();
emp.name = "Mohan Ram";
console.log(emp.name);

// Set: name => Mohan Ram
// Get: name => Mohan Ram
// Mohan Ram


Декоратор метода

Объявляется непосредственно перед объявлением метода. Будет вызываться как функция во время выполнения со следующими двумя аргументами:

  • target - прототип текущего объекта, т.е. если Employee является объектом, Employee.prototype
  • propertyName - название свойства
  • descriptor - дескриптор свойства метода т.е. - Object.getOwnPropertyDescriptor (Employee.prototype, propertyName)
 export function logMethod(
     target: Object,
     propertyName: string,
     propertyDescriptor: PropertyDescriptor): PropertyDescriptor {
     const method = propertyDescriptor.value;
 
     propertyDescriptor.value = function (...args: any[]) {
 
         // Конвертация списка аргументов greet в строку
         const params = args.map(a => JSON.stringify(a)).join();
 
         // Вызов greet() и получение вернувшегося значения
         const result = method.apply(this, args);
 
         // Конвертация результата в строку
         const r = JSON.stringify(result);
 
         // Отображение в консоли деталей вызова
         console.log(`Call: ${propertyName}(${params}) => ${r}`);
 
         // Возвращение результата вызова
         return result;
     }
     return propertyDescriptor;
 }
 
 class Employee {
 
     constructor(
         private firstName: string,
         private lastName: string
     ) {
     }
 
     @logMethod
     greet(message: string): string {
         return `${this.firstName} ${this.lastName} says: ${message}`;
     }
 
 }
 
 const emp = new Employee('Mohan Ram', 'Ratnakumar');
 emp.greet('hello');
 //Call: greet("hello") => "Mohan Ram Ratnakumar says: hello"


Декоратор параметра

Объявляется непосредственно перед объявлением метода. Будет вызываться как функция во время выполнения со следующими двумя аргументами:

  • target - прототип текущего объекта, т.е. если Employee является объектом, Employee.prototype
  • propertyKey - название свойства
  • index - индекс параметра в массиве аргументов
function logParameter(target: Object, propertyName: string, index: number) {
  // Генерация метаданных для соответствующего метода
  // для сохранения позиции декорированных параметров
  const metadataKey = `log_${propertyName}_parameters`;

  if (Array.isArray(target[metadataKey])) {
    target[metadataKey].push(index);
  } else {
    target[metadataKey] = [index];
  }
}

class Employee {
  greet(@logParameter message: string): void {
    console.log(`hello ${message}`);
  }
}
const emp = new Employee();
emp.greet("world");
Основные концепции
Что такое Angular?

Angular — это платформа для разработки мобильных и десктопных веб-приложений. Наши приложения теперь представляют из себя «толстый клиент», где управление отображением и часть логики перенесены на сторону браузера. Так сервер уделяет больше времени доставке данных, плюс пропадает необходимость в постоянной перерисовке. С Angular мы описываем структуру приложения декларативно, а с TypeScript начинаем допускать меньше ошибок, благодаря статической типизации. В Angular присутствует огромное количество возможностей из коробки. Это может быть одновременно и хорошо и плохо, в зависимости от того, что вам необходимо.


Какие плюсы можно выделить:

  • Поддержка Google, Microsoft
  • Инструменты разработчика (CLI)
  • Typescript из коробки
  • Реактивное программирование с RxJS
  • Единственный фреймворк с Dependency Injection из коробки
  • Шаблоны, основанные на расширении HTML
  • Кроссбраузерный Shadow DOM из коробки (либо его эмуляция)
  • Кроссбраузерная поддержка HTTP, WebSockets, Service Workers
  • Не нужно ничего дополнительно настраивать. Больше никаких оберток. jQuery плагины и D3 можно использовать на прямую
  • Более современный фреймворк, чем AngularJS (на уровне React, Vue)
  • Большое комьюнити

Минусы:

  • Выше порог вхождения из-за Observable (RxJS) и Dependency Injection
  • Чтобы все работало хорошо и быстро нужно тратить время на дополнительные оптимизации (он не супер быстрый, по умолчанию, но быстрее AngularJS во много раз)
  • Если вы планируете разрабатывать большое enterprise-приложение, то в этом случае, у вас нет архитектуры из коробки - нужно добавлять Mobx, Redux, MVVM, CQRS/CQS или другой state-менеджер, чтобы потом не сломать себе мозг
  • Angular-Universal имеет много подводных камней
  • Динамическое создание компонентов оказывается нетривиальной задачей
В чем разница между AngularJS и Angular?


AngularJS является фреймворком, который может помочь вам в разработке Single Page Application. Он появился в 2009 году и с годами выяснилось, что имел много проблем. Angular (Angular 2+) же в свою очередь направлен на тоже самое, но дает больше преимуществ по сравнению с AngularJS 1.x, включая лучшую производительность, ленивую загрузку, более простой API, более легкую отладку.

Что появилось в Angular:

  • Angular ориентирован на мобильные платформы и имеет лучшую производительность
  • Angular имеет встроенные сервисы для поддержки интернационализации
  • AngularJS проще настроить, чем Angular
  • AngularJS использует контроллеры и $scope
  • Angular имеет много способов определения локальных переменных
  • В Angular новый синтаксис структурных директив (camelCase)
  • Angular работает напрямую с свойствами и событиями DOM элементов
  • Одностороннее связывание данных через [property]
  • Двустороннее связывание данных через [(property)]
  • Новый механизм DI, роутинга, запуска приложения

Основные преимущества Angular:

  • Обратная совместимость Angular 2, 4, 5, ..
  • TypeScript с улучшенной проверкой типов
  • Встроенный компилятор с режимами JIT и AOT (+сокращение кода)
  • Встроенные анимации
Какой должна быть структура каталогов компонентов любого Angular приложения и почему?
in progress..
Что такое MVVM и в чем разница перед MVC?

MVVM - шаблон проектирования архитектуры приложения. Состоит из 3 ключевых блоков: Model, View, ViewModel.
Отличие от MVС заключаются в:

  • View реагирует на действия пользователя и передает их во View Model через Data Binding.
  • View Model, в отличие от контроллера в MVC, имеет особый механизм, автоматизирующий связь между View и связанными свойствами в ViewModel.

  • Привязка данных между View и ViewModel может быть односторонней или двусторонней (one-way, two-way data-binding).

    Angular Template синтаксис
    Что такое интерполяция в Angular?

    Разметка интерполяции с внедренными выражениями используется в Angular для присвоения данных текстовым нодам и значения аттрибутов. Например:

      <a href="img/{{username}}.jpg">Hello {{username}}!</a>

    Какие способы использования шаблонов в Angular вы знаете?

    Способы использования шаблонов в Angular

    Angular предлагает мощные и гибкие способы работы с шаблонами для создания динамичных веб-приложений. Шаблоны в Angular используют HTML-разметку с директивами Angular и биндингами, которые позволяют динамически управлять DOM (Document Object Model).

    1. Интерполяция:

    • Описание: Вставка данных в шаблон через двойные фигурные скобки {{ }}.
    • Пример: {{ title }} автоматически выводит переменную title из компонента в HTML.

    2. Свойства Binding:

    • Описание: Связывание свойств HTML-элементов с данными компонента.
    • Пример: [src]="userImage" связывает свойство src изображения с переменной userImage в компоненте.

    3. Событийный Binding:

    • Описание: Связывание обработчиков событий с методами компонента.
    • Пример: (click)="handleClick()" вызывает метод handleClick при клике на элемент.

    4. Директивы:

    • Структурные: Изменяют структуру DOM, добавляя или удаляя элементы.
      • Пример: *ngIf="isVisible" и *ngFor="let item of items".
    • Атрибутные: Изменяют внешний вид или поведение элемента, к которому применяются.
      • Пример: [ngClass]="{ active: isActive }" добавляет класс active, если isActive истина.

    5. Двустороннее связывание:

    • Описание: Используется для форм и ввода данных, позволяет связать элементы формы непосредственно с атрибутами модели.
    • Пример: [(ngModel)]="username" автоматически связывает значение инпута с переменной username в компоненте.

    6. Локальные переменные в шаблонах:

    • Описание: Локальные переменные можно определить в шаблонах для управления элементами DOM.
    • Пример: #myInput в <input #myInput> позволяет ссылаться на этот элемент в шаблоне.

    7. Встраивание шаблонов (ng-template):

    • Описание: Динамическое управление контентом с помощью шаблонов, которые вставляются только при выполнении определённых условий.
    • Пример: <ng-template [ngIf]="isLoading">Loading...</ng-template> отображает сообщение, если isLoading истина.

    Эти методы позволяют разработчикам Angular создавать динамичные, адаптивные и высокоинтерактивные веб-приложения, эффективно управляя структурой и поведением веб-страниц на клиентской стороне.

    В чем разница между структурной и атрибутной директивой, назовите встроенные директивы?

  • Структурные директивы влияют на DOM и могут добавлять/удалять элементы
    (ng-template, NgIf, NgFor, NgSwitch, etc)
  • Атрибутные директивы меняют внешний вид или поведение элементов, компонентов или других директив
    (NgStyle, NgClass, etc).
  • Для чего нужны директивы ng-template, ng-container, ng-content?

    1. ng-template

    <template> — это механизм для отложенного рендера клиентского контента, который не отображается во время загрузки, но может быть инициализирован при помощи JavaScript.

    Template можно представить себе как фрагмент контента, сохранённый для последующего использования в документе. Хотя парсер и обрабатывает содержимое элемента template во время загрузки страницы, он делает это только чтобы убедиться в валидности содержимого; само содержимое при этом не отображается.

    <ng-template> - является имплементацией стандартного элемента template, данный элемент появился с четвертой версии Angular, это было сделано с точки зрения совместимости со встраиваемыми на страницу template элементами, которые могли попасть в шаблон ваших компонентов по тем или иным причинам.

    Пример:

    <div class="lessons-list" *ngIf="lessons else loading">...</div>
    
    <ng-template #loading>
      <div>Loading...</div>
    </ng-template>

    2. ng-container

    <ng-container> - это логический контейнер, который может использоваться для группировки узлов, но не отображается в дереве DOM как узел (node).

    На самом деле структурные директивы (*ngIf, *ngFor, …) являются синтаксическим сахаром для наших шаблонов. В реальности, данные шаблоны трансформируются в такие конструкции:

    <ng-template [ngIf]="lessons" [ngIfElse]="loading">
       <div class="lessons-list">
         ...
       </div>
    </div>
    
    <ng-template #loading>
        <div>Loading...</div>
    </ng-template>

    Но что делать, если я хочу применить несколько структурных директив? (спойлер: к сожалению, так нельзя сделать)

    <div class="lesson" *ngIf="lessons" *ngFor="let lesson of lessons">
      <div class="lesson-detail">{{lesson | json}}</div>
    </div>
    Uncaught Error: Template parse errors:
    Can't have multiple template bindings on one element. Use only one attribute
    named 'template' or prefixed with *
    

    Но можно сделать так:

    <div *ngIf="lessons">
      <div class="lesson" *ngFor="let lesson of lessons">
        <div class="lesson-detail">{{lesson | json}}</div>
      </div>
    </div>

    Однако, чтобы избежать необходимости создавать дополнительный div, мы можем вместо этого использовать директиву ng-container:

    <ng-container *ngIf="lessons">
      <div class="lesson" *ngFor="let lesson of lessons">
        <div class="lesson-detail">{{lesson | json}}</div>
      </div>
    </ng-container>

    Как мы видим, директива ng-container предоставляет нам элемент, в котором мы можем использовать структурную директиву, без необходимости создавать дополнительный элемент.

    Еще пара примечательных примеров, если все же вы хотите использовать ng-template вместо ng-container, по определенным правилам вы не сможете использовать полную конструкцию структурных директив.

    Вы можете писать либо так:

    <div class="mainWrap">
      <ng-container *ngIf="true">
        <h2>Title</h2>
        <div>Content</div>
      </ng-container>
    </div>

    Либо так:

    <div class="mainWrap">
      <ng-template [ngIf]="true">
        <h2>Title</h2>
        <div>Content</div>
      </ng-template>
    </div>

    На выходе, при рендеринге будет одно и тоже:

    <div class="mainWrap">
      <h2>Title</h2>
      <div>Content</div>
    </div>

    3. ng-content

    <ng-content> - позволяет внедрять родительским компонентам html-код в дочерние компоненты.

    Здесь на самом деле, немного сложнее уже чем с ng-template, ng-container. Так как ng-content решает задачу проецирования контента в ваши веб-компоненты. Веб-компоненты состоят из нескольких отдельных технологий. Вы можете думать о Веб-компонентах как о переиспользуемых виджетах пользовательского интерфейса, которые создаются с помощью открытых веб-технологий. Они являются частью браузера и поэтому не нуждаются во внешних библиотеках, таких как jQuery или Dojo. Существующий Веб-компонент может быть использован без написания кода, просто путем импорта выражения на HTML-страницу. Веб-компоненты используют новые или разрабатываемые стандартные возможности браузера.

    Давайте представим ситуацию от обратного, нам нужно параметризовать наш компонент. Мы хотим сделать так, чтобы на вход в компонент мы могли передать какие-либо статичные данные. Это можно сделать несколькими способами.

    comment.component.ts:

    @Component({
      selector: "comment",
      template: `
        <h1>Комментарий:</h1>
        <p>{{ data }}</p>
      `,
    })
    export class CommentComponent {
      @Input() data: string = null;
    }

    app.component.html

    <div *ngFor="let message of comments">
      <comment [data]="message"></comment>
    </div>

    Но можно поступить и другим путем.
    comment.component.ts:

    @Component({
      selector: "comment",
      template: `
        <h1>Комментарий:</h1>
        <ng-content></ng-content>
      `,
    })
    export class CommentComponent {}

    app.component.html

    <div *ngFor="let message of comments">
      <comment>
        <p>{{message}}</p>
      </comment>
    </div>

    Конечно, эти примеры плохо демонстрируют подводные камни, свои плюсы и минусы. Но второй способ демонстрирует подход при работе, когда мы оперируем независимыми абстракциями и можем проецировать контент внутрь наших компонентов (подход веб-компонентов).

    Angular development environments
    Что такое директива и как создать собственную?

    Директивы бывают трех видов: компонент, структурные и атрибутные (см. выше).

    Создание атрибутных директив:

    @Directive({ 
       selector: '[appHighlight]' 
    })
    export class HighlightDirective {  }

    Декоратор определяет селектор атрибута [appHighlight], [] - указывают что это селектор атрибута. Angular найдет каждый элемент в шаблоне с этим атрибутом и применит к ним логику директивы.

    @Directive({
      selector: "[appHighlight]",
    })
    export class HighlightDirective {
      constructor(el: ElementRef) {
        el.nativeElement.style.backgroundColor = "yellow";
      }
    }


    Необходимо указать в конструкторе ElementRef, чтобы через его свойство nativeElement иметь прямой доступ к DOM элементу, который должен быть изменен.
    Теперь, используя @HostListener, можно добавить обработчики событий, взаимодействующие с декоратором.

    @Component()
    class EtcComponent {
      @HostListener("mouseenter")
      public onMouseEnter(): void {
        this.highlight("yellow");
      }
    
      @HostListener("mouseleave")
      public onMouseLeave(): void {
        this.highlight(null);
      }
    
      private highlight(color: string): void {
        this.el.nativeElement.style.backgroundColor = color;
      }
    }

    Структурные директивы создаются так:

    Напишем UnlessDirective, которая будет противоположна NgIf.
    Необходимо использовать @Directive, и импортировать Input, TemplateRef, и ViewContainerRef. Они вам понадобятся при воздании любой структурной директивы.

    import { Directive, Input, TemplateRef, ViewContainerRef } from "@angular/core";
    
    @Directive({ selector: "[appUnless]" })
    export class UnlessDirective {}

    В конструкторе мы получаем доступ к view container и содержимое .

      constructor(
        private templateRef: TemplateRef<any>,
        private viewContainer: ViewContainerRef) { }
    

    Наша директива предполагает работу с true/false. Для этого нужно свойство appUnless, добавленное через @Input.

    @Directive({ selector: "[appUnless]" })
    export class UnlessDirective {
      @Input() public set appUnless(condition: boolean) {
        if (!condition && !this.hasView) {
          this.viewContainer.createEmbeddedView(this.templateRef);
          this.hasView = true;
        } else if (condition && this.hasView) {
          this.viewContainer.clear();
          this.hasView = false;
        }
      }
    }
    Что такое директива, компонент, модуль, сервис, пайп в Angular и для чего они нужны?

    • Директива - см. выше.
    • Компонент контролирует участок экрана, т.н. view.
    • Сервис это класс с узкой, четко определенной целью. Это может быть значение, функция, запрос, etc. Главное в них то, что они повторно используются, отделяя чистую функциональность компонента.
    • Пайп преобразует отображение значений в шаблоне, к примеру отображение дат в разных локалях или изменяют в отображении регистр строк.
    Расскажите об основных параметрах @NgModule, @Component, @Directive, @Injectable, @Pipe

    Декораторы динамически подключают дополнительное поведение к объекту. Они помечают класс и предоставляют конфигурационные метаданные.

    @NgModule может содержать следующие параметры:

  • providers - список инжектируемых объектов, которые добавляются в этот модуль
  • declarations - компоненты, директивы и пайпы, принадлежащие этому модулю
  • imports - модули, которые экспортируются декларируемыми и доступны в шаблоне этого модуля
  • exports - компоненты, директивы и пайпы, которые объявлены декларируемыми, и могут быть им пользованы в шаблоне любого компонента, которые принадлежит NgModule импортирующему их.
  • entryComponent - компилируемые компоненты при определении NgModule, для динамической загрузки в view.
  • bootstrap - компоненты, которые загружаются при загрузке этого модуля, автоматически добавляются в entryComponent.
  • schemas - набор схем, объявляющих разрешенные элементы в MgModules
  • id - имя или путь, уникальный идентификатор этого NgModule в getModuleFactory. Если не заполнять - не будет там зарегистрирован.
  • jit - если true, то этот модуль будет пропущен компилятором AOT и всегда будет компилироваться JIT.
  • @Component может содержать следующие параметры:

  • changeDetection - стратегия обнаружения изменений, используемая для этого компонента
  • viewProviders - инжектируемые объекты, которые видны DOM children этого компонента.
  • moduleId - id модуля, к которому относится компонент.
  • templateUrl - относительный путь или абсолютный URL к шаблону компонента.
  • template - инлайн шаблон для этого компонента.
  • styleUrls - один и более путь до файла, содержащего CSS, абсолютный или относительный.
  • styles - инлайн CSS, используемые в этом компоненте.
  • animations - один и более вызовов анимации trigger(), содержащих state() и transition().
  • encapsulation - правила инкапсуляции для шаблона и CSS.
  • interpolation - переопределение базовых знаков интерполяции.
  • entryComponents - компоненты, которые должны быть скомпилированы вместе с этим компонентом. Для каждого упомянутого здесь компонента создается ComponentFactory и сохраняется в ComponentFactoryResolver.
  • preserveWhitespaces - при значении true удаляются потенциально лишние пробелы из скомпилированного шаблона.
  • @Directive может содержать следующие параметры:

  • selector - CSS-селектор, который идентифицирует эту директиву в шаблоне и запускает создание этой директивы.
  • inputs - свойство для определения значение @Input() параметра. Значение из inputs можно сразу использовать в шаблоне, без объявления переменной в классе. Пример объявления: inputs: ['name', 'id: id-from-parent']. Значение в inputs массиве может состоять из:
    • directiveProperty - наименование свойства @Input, которое будет использоваться в дочернем компоненте для вывода в шаблоне и использования в самом классе.
    • bindingProperty - наименование свойства, из которого будет производится чтение и запись в directiveProperty. Не обязательное. При отсутсвии параметра значение будет браться из directiveProperty
    Пример использования:
    @Component({
      selector: "child-component",
      template: `Name {{ name }} Id {{ id }}`,
      inputs: ["name", "id: parentId"],
    })
    export class ChildComponent {}
    
    @Component({
      selector: "parent-component",
      template: `
        <child-component
          [name]="parentName"
          [parentId]="parentIdValue"
        ></child-component>
      `,
    })
    export class Parent {
      public parentIdValue = "123";
      public parentName = "Name";
    }
  • outputs - свойство для определения @Output. В отличии от inputs, объявление свойства в классе обязательно. Пример:
    @Component({
      selector: "child-dir",
      outputs: ["bankNameChange"],
      template: `<input (input)="bankNameChange.emit($event.target.value)" />`,
    })
    class ChildDir {
      bankNameChange: EventEmitter<string> = new EventEmitter<string>();
    }
    
    @Component({
      selector: "main",
      template: `
        {{ bankName }}
        <child-dir (bankNameChange)="onBankNameChange($event)"></child-dir>
      `,
    })
    class MainComponent {
      bankName: string;
    
      onBankNameChange(bankName: string) {
        this.bankName = bankName;
      }
    }
  • providers - настраивает инжектор этой директивы или компонента с помощью токена.
  • exportAs - определяет имя, которое можно использовать в шаблоне для присвоения этой директивы переменной.
  • queries - настраивает запросы, которые могут быть инжектированы в директиву.
  • host - составляет свойства класса со сбайнженными элементами для свойств, атрибутов и ивентов.
  • jit - если true, то этот модуль будет пропущен компилятором AOT и всегда будет компилироваться JIT.
  • @Injectable может содержать следующие параметры:

  • providedIn - определяет, где будет заинжектировано, либо, если объявлено "root" распространится на все приложение.
  • @Pipe может содержать следующие параметры:

  • name - имя пайпа, которое будет использовано в шаблоне.
  • pure - если true, то пайп считается "чистым", и метод transform() вызовется только при изменении его входных агрументов. По умолчанию стоит true.
  • Что такое динамические компоненты и как их можно использовать в Angular?

    Динамические компоненты - компоненты, которые добавляются на страницу во время выполнения приложения (runtime). Динамические компоненты можно использовать в тех случаях, когда компонент можно отобразить не сразу при загрузке страницы. Например: диалоговые окна, нотификации, контент в табах.

    Для того, чтобы использовать динамические компоненты, необходимо убедиться, что:

    1. добавлен элемент ("якорь") - ng-container/ng-template - на нужной странице/в шаблоне, куда будет помещен динамический компонент. Именно в этот элемент будет загружаться динамический компонент.
    2. в классе компонента определено свойство для хранения ng-container/ng-template. Например:
      @Component({
        template: `<div>
          <ng-container #dynamicContent></ng-container>
        </div> `,
      })
      export class AppComponent {
        @ViewChild("dynamicContent", { read: ViewContainerRef })
        public dynamicContainer: ViewContainerRef;
      }
    3. при загрузке страницы ng-container/ng-template определен и загружен. Проверить загрузку и определение "якоря" можно в хуке ngAfterViewInit()

    В динамический компонент можно внедрить зависимости. Зависимости могут понадобится для общения основного и динамического компонентов. Перед внедрением зависимости нужно создать injector. Создание injector похоже на определение параметра providers в @NgModule. Пример создания Injector:

    // класс, который будет использоваться в constructor
    export abstract class IDynamicComponentProps {
      public abstract onClickDynamicComponent(): void;
      public abstract items: Array<string>;
    }
    
    // Использование зависимости в динамическом компоненте
    @Component({
      template: `
        <span *ngFor='let item of dynamicItems'>{{item}}</span>
        <button (click)='onClick()'></button>
      `
    })
    export class DynamicComponent {
      public dynamicItems: Array<string> =
        this.dynamicComponentProps.items;
      constructor(
        private dynamicComponentProps: IDynamicComponentProps,
      ) {}
    
      public onClick(): void {
        this.dynamicComponentProps.onClickDynamicComponent();
      }
    }
    
    // Создание инжектора в сервисе или родительском компоненте
    @Component({
      ...
    })
    export class ParentComponent {
      public onClickHandler: EventEmitter<number> = new EventEmitter();
      public parentItems: Array<string> = ['str1', 'str2'];
      constructor(
        private injector: Injector,
      ) {}
    
      public createInjector() {
        const injector: Injector = Injector.create(
            [
              {
                provide: IDynamicComponentProps,
                useValue:{
                  onClickDynamicComponent: () => { this.onClickHandler.emit(0) },
                  items: this.parentItems
                }
              }
            ],
            this.injector
          );
      }
    }

    Последовательность действий для отображения динамического компонента

    1. Добавить в шаблон "якорь" для компонента, объявить переменную для работы с этим элементом
    2. Очистить содержимое динамического компонента (при необходимости)
    3. Создать ComponentFactory с помощью resolveComponentFactory()
    4. Вызвать метод из созданного ComponentFactory для создания компонента на странице.

    Примечание: Для динамического компонента не обязательно создавать Injector. Обязательным параметром для метода createComponent является только ComponentFactory.

    Ниже указана последовательность действий, реализованная кодом. В примере используется Основной компонент (MainComponent), динамический компонент (DynamicCompoent) и сервис для рендера (MainComponentService)

    //основной компонент
    @Component({
      selector: "main-component",
      template: `<h1>Dynamic Component Example</h1>
        <ng-container #dynamicComponent></ng-container> `,
    })
    export class MainComponent {
      @ViewChild("dynamicComponent", { read: ViewContainerRef })
      public dynamicContainer: ViewContainerRef;
    
      public parentItems: Array<string> = ["str1", "str2"];
      constructor(private mainComponentService: MainComponentService) {
        this.mainComponentService.onClickHandler().subscribe((x) => console.log(x));
        // console - 0 form dynamic component
      }
    
      public ngAfterViewInit(): void {
        this.mainComponentService.render(this.dynamicContainer, this.parentItems);
      }
    }
    
    // класс для DI
    export abstract class IDynamicComponentProps {
      public abstract onClickDynamicComponent(): void;
      public abstract items: Array<string>;
    }
    
    // сервис для рендера
    @Injectable()
    export class MainComponentService {
      public onClickHandler: EventEmitter<number> = new EventEmitter();
      constructor(
        private componentFactoryResolver: ComponentFactoryResolver,
        private injector: Injector
      ) {}
    
      public render(container: ViewContainerRef, parentItems: Array<string>): void {
        if (!isUndefined(container)) {
          container.clear();
        }
    
        const injector: Injector = Injector.create(
          [
            {
              provide: IDynamicComponentProps,
              useValue: {
                onClickDynamicComponent: () => {
                  this.onClickHandler.emit(0);
                },
                items: parentItems,
              },
            },
          ],
          this.injector
        );
    
        const factory = this.componentFactoryResolver.resolveComponentFactory(
          DynamicCompoent
        );
    
        container.createComponent(factory, 0, injector);
      }
    }
    
    //динамический компонент
    @Component({
      template: `
        <span *ngFor="let item of dynamicItems">{{ item }}</span>
        <button (click)="onClick()"></button>
      `,
    })
    export class DynamicComponent {
      public dynamicItems: Array<string> = this.dynamicComponentProps.items;
      constructor(private dynamicComponentProps: IDynamicComponentProps) {}
    
      public onClick(): void {
        this.dynamicComponentProps.onClickDynamicComponent();
      }
    }
    Как применить анимацию к компонентам?

    Анимации в Angular построены на основе функциональности CSS. При работе с анимациями нужно иметь ввиду, что применять анимацию можно только к тем свойствам, которые можно анимировать.

    Перед началом создания анимаций нужно:

    • Подключить модуль BrowserAnimationsModule в основной модуль приложения (root)
    • Подключить функции для анимации в нужном компоненте:

    import {
      trigger,
      state,
      style,
      animate,
      transition,
      // ...
    } from "@angular/animations";
  • Добавить свойство animations в декоратор компонента @Component():

  • @Component({
      selector: 'app-root',
      templateUrl: 'app.component.html',
      styleUrls: ['app.component.css'],
      animations: [
        // animation triggers go here
      ]
    })

    Анимация состоит из:

    1. триггера - событие, по которому возникает анимация. Для инициализации триггера используется функция trigger(). В параметрах функции нужно указать событие, которое будет указано в компоненте и к которому будет привязана анимация. Так же, указывается массив из функций state() и transition()
    2. состояний в конце перехода - стили, которые будут применятся к элементу в конце анимации. Для объявления состояний используется функция state(). В функции нужно указать название состояния, указать стили состояния с помощью функции style(). Если анимация отключена ([@.disabled]='true'), то стили конечных состояний нужно прописать.
    3. промежуточных состояний - стилей, которые применяются к элементу между окончательными состояниями. С помощью промежуточных состояний можно анимировать переходы. Для этого используется функция transition(). В функции нужно прописать выражение, в котором указано направление между состояниями и функции для определения стилей между состояниями, анимации.

    Для объявления триггера, нужно прописать функцию trigger() в метаданных компонента, в свойстве animations. Первым параметром нужно указать событие, которое будет привязано в шаблоне к элементу. Вторым параметром нужно указать состояние state() и анимации в transition(). Например:

    @Component({
      selector: "example",
      animations: [
        trigger("toggle", [
          state(
            "open",
            style({
              height: "200px",
              opacity: 1,
              backgroundColor: "yellow",
            })
          ),
          state(
            "closed",
            style({
              height: "100px",
              opacity: 0.5,
              backgroundColor: "green",
            })
          ),
          transition("open => closed", [animate("1s")]),
          transition("closed => open", [animate("0.5s")]),
          //...
        ]),
      ],
      template: `<div [@toggle]="isOpen ? 'open' : 'closed'"></div>`,
    })
    export class ExampleComponent {}

    Дополнительные состояния переходов

    При работе с переходами можно указывать не только состояния, указанные в функции state(). Анимации в Angular поддерживают следующие состояния:

    • * - любое состояние. Полезно для определения переходов, которые применяются независимо от начального или конечного состояния HTML-элемента. Можно использовать конструкцию * => *, для того, чтобы определить переходы тем состояниям, у которых не назначена анимация. Эту конструкцию можно добавить после того, как будут перечислены конкретные переходы состояний.
    • void - состояние, когда элемент появляется в DOM или удаляется из него. Например, при ngIf. Void входит в состояние *.
    animations: [
      trigger("openClose", [
        state(
          "open",
          style({
            height: "200px",
            opacity: 1,
            backgroundColor: "yellow",
          })
        ),
        state(
          "closed",
          style({
            height: "100px",
            opacity: 0.5,
            backgroundColor: "green",
          })
        ),
        transition("open => closed", [animate("1s")]),
        transition("closed => open", [animate("0.5s")]),
        transition("* => closed", [animate("1s")]),
        transition("* => open", [animate("0.5s")]),
        transition("* => open", [animate("1s", style({ opacity: "*" }))]),
        transition("* => *", [animate("1s")]),
      ]),
    ];

    Два вышеперечисленных состояния можно использовать вместе - void => * и * => void. У этих конструкций есть алиасы - :enter (void => *) и :leave (* => void). Например:

    const animation = trigger("eventTrigger", [
      transition("void => *", [
        style({ opacity: 0 }),
        animate("5s", style({ opacity: 1 })),
      ]),
      // or
      transition(":enter", [
        style({ opacity: 0 }),
        animate("5s", style({ opacity: 1 })),
      ]),
    
      //-------------//
    
      transition("* => void", [animate("5s", style({ opacity: 0 }))]),
      //or
      transition(":leave", [animate("5s", style({ opacity: 0 }))]),
    ]);

    Для работы с переходами можно использовать числовые и булевы значения. При работе с числовыми значениями, можно использовать алиасы :increment и :decrement. С булевыми значениями можно просто прописать true/false. Например:

    @Component({
      selector: "toggle",
      animations: [
        trigger("isOpen", [
          state("true", style({ height: "*" })),
          state("false", style({ height: "0px" })),
          transition("true <=> false", [animate("1s")]),
        ]),
      ],
      template: `<div [@isOpen]="isOpen ? true : false"></div>`,
    })
    export class ToggleComponent {
      public isOpen: boolean = true;
    }
    
    @Component({
      template: `<ul class="heroes" [@filterAnimation]="heroTotal"></ul>`,
      animations: [
        trigger("filterAnimation", [
          transition(":enter, * => 0, * => -1", []),
          transition(":increment", [
            query(
              ":enter",
              [
                style({ opacity: 0, width: "0px" }),
                stagger(50, [
                  animate("300ms ease-out", style({ opacity: 1, width: "*" })),
                ]),
              ],
              { optional: true }
            ),
          ]),
          transition(":decrement", [
            query(":leave", [
              stagger(50, [
                animate("300ms ease-out", style({ opacity: 0, width: "0px" })),
              ]),
            ]),
          ]),
        ]),
      ],
    })
    export class HeroListPageComponent implements OnInit {
      public heroTotal: number = -1;
    }

    Примечание: хорошей практикой является перенос анимаций в отдельные файлы *.animation.ts. Эта практика уменьшит размер файла компонента, обеспечит декомпозицию, даст возможность переиспользования анимаций.

    Гайд ангуляра по переиспользованию анимаций

    Отключение анимации

    Анимацию можно принудительно отключить как в отдельном компоненте, так и во всем приложении.

    Для отключения анимации в компоненте нужно указать [@.disabled]='isDisabled' в нужной ноде компонента. Например:

    <div [@.disabled]="isDisabled"></div>

    Для отключения анимации во всем приложении, нужно указать @HostBinding('@.disabled') в корневом компоненте. Например:

    @Component({
      selector: "app-root",
      templateUrl: "app.component.html",
      styleUrls: ["app.component.css"],
      animations: [
        // animation  go here
      ],
    })
    export class AppComponent {
      @HostBinding("@.disabled")
      public animationsDisabled = true;
    }

    Дополнительная функциональность для анимаций

    Можно указывать конкретные значения стилей в определенный промежуток времени с помощью keyframes()

    Есть возможность запускать анимации параллельно, указав их в функции group(). Запускать последовательно с помощью функции sequence().

    Анимацию можно применять к конкретному селектору, который можно указать в параметрах фукнции query(). Так же. можно управлять анимациями дочерних элементов с помощью animateChild() (только для анимаций, описанных с помощью Angular)

    Angular render lifecycle and core environments
    Объясните механизм загрузки (bootstrap) Angular-приложения в браузере?

    Запуск Angular приложения начинается с файла main.ts. Этот файл содержит в себе примерно следующее:

    import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
    import { AppModule } from "./app/app.module";
    
    const platform = platformBrowserDynamic();
    
    platform.bootstrapModule(AppModule);

    platformBrowserDynamic запускает AppModule. После этого, начинает работать логика в AppModule.

    В AppModule обычно задается компонент, который будет использоваться для отображения при загрузке. Компонент находится в параметре bootstrap

    @NgModule({
      imports: [BrowserModule, FormsModule],
      declarations: [AppComponent],
      bootstrap: [AppComponent],
    })
    export class AppModule {}
    Как происходит взаимодействие компонентов в Angular (опишите components view)?

    Взаимодействие компонентов может быть:

    • между родительским и дочерним компонентом - селектор одного компонента объявлен в шаблоне другого
    • между компонентами одного уровня - селекторы компонентов не вложенные

    Способы взаимодействия

    1. @Input()/@Output() декораторы свойств - используются между дочерним и родительским компонентами. В @Input() можно получить значение из родителя. Через @Output() отправить данные из дочернего в родительский компонент.

      В шаблоне родительского компонента ставится селектор дочернего. В селекторе дочернего компонента прописываются атрибуты, через которые будут передаваться данные в переменные @Input()/@Output(). Для обозначения @Input свойства в селекторе нужно прописать . Для обозначения @Output свойства в селекторе нужно прописать .

      В классе родительского компонента нужно обозначить public свойства/методы, которые будут прописаны в атрибутах дочернего селектора.

      В классе дочернего компонента нужно прописать public свойства с декораторами @Input()/@Output(). Названия свойств должны совпадать с именами в атрибутах дочернего селектора. В @Input() можно передать значения как обычных типов данных (string, number, Array и тп), так и потоки (Subject, Observable). В @Output обычно используется EventEmitter. Через него можно отправить значения в функцию родительского компонента, которая прописана в атрибуте селектора.

      Пример

      @Component({
        selector: "parent",
        template: `
          <div>
            <child [count]="value" (increment)="onInstement($event)"></child>
          </div>
        `,
      })
      export class ParentComponent {
        public value: number = 0;
      
        public onIncrement(value: number): void {
          // actions with child's value
        }
      }
      
      @Component({
        selector: "child",
        template: `
          <div>
            <button type="button" (click)="onClickIncrement()">+1</button>
          </div>
        `,
        changeDetection: ChangeDetectionStrategy.OnPush, //см пункт "Какие существуют стратегии обнаружения изменений?"
      })
      export class ChildComponent {
        @Input() public count: number;
      
        @Output() public increment: EventEmitter<number> = new EventEmitter();
      
        public onClickIncrement(): void {
          const result = this.count++;
          this.increment.emit(result);
        }
      }
    2. @ViewChild() директива - получение доступа к свойствам дочернего компонента. В родительском шаблоне нужно указать селектор дочернего. Так же, в родительском компоненте нужно обозначить public свойство с директивой @ViewChild().

      По умолчанию, доступ к свойствам @ViewChild() можно получить в хуке ngAfterViewInit(). Так же, нужно учитывать свойство static при использовании @ViewChild(). static параметр указывает, когда можно получить доступ к ViewChild() - до или после change detection. Это может понадобится, когда @ViewChild используется в циклах (*ngFor) или доступен только по условию (*ngIf). Если static = false, то доступ можно получить до change detection в хуке ngAfterViewInit().

      Примеры

      @Component({
        selector: "parent",
        template: `<child #childRef *ngIf="isShowChild"></child>`,
      })
      export class ParentComponent {
        @ViewChild("childRef", { static: false }) public viewChild: ChildComponent;
      
        public isShowChild: boolean = false;
      
        public ngAfterViewInit(): void {
          console.log(this.viewChild.title);
        }
      }
      
      @Component({
        selector: "parent",
        template: `<child #childRef></child>`,
      })
      export class ParentComponent {
        @ViewChild("childRef", { static: false }) public viewChild: ChildComponent;
      
        public ngAfterViewInit(): void {
          console.log(this.viewChild.title);
        }
      }
    3. Через сервис - передача данных между компонентами через единый сервис. Этим способом можно взаимодействовать с компонентами одного уровня. Так же, можно избавиться от иерархии зависимостей и не использовать всплывающие события (Output)

      Необходимо создать общий сервис, который объявляется в параметре providers в общем модуле соединяемых компонентов. В сервисе можно создать public свойства и методы для передачи данных. Можно использовать Observable и Subjects для передачи данных. Пример:

      @Injectable()
      export class CountService {
        private count$: BehaviorSubject<number> = new BehaviorSubject(0);
      
        public get value$(): Observable<number> {
          return this.count$.asObservable();
        }
      
        public get value(): number {
          return this.count$.getValue();
        }
      
        public setState(value: number): void {
          this.count$.next(value);
        }
      
        public reset(): void {
          this.count$.next(0);
        }
      }
      
      @Component({
        selector: "counter",
        template: `
           value = {{ counter.value$ | async }}  <br/>
          <button type='button' (click)='counter.setState(counter.value + 1)>+1</button>
          <button type='button' (click)='counter.setState(counter.value - 1)>-1</button>
          <button type='reset' (click)='counter.reset()>reset</button>
        `,
      })
      export class CounterComponent {
        constructor(private counter: CountService) {}
      }
    Каков жизненный цикл у компонентов?

    После создания компонента или директивы через вызов конструктора, Angular вызывает методы жизненного цикла в следующей последовательности в строго определенные моменты:

  • ngOnChanges() - вызывается когда Angular переприсваивает привязанные данные к input properties. Метод получает объект SimpleChanges, со старыми и новыми значениями. Вызывается перед NgOnInit и каждый раз, когда изменяется одно или несколько связанных свойств.
  • ngOnInit() - инициализирует директиву/компонент после того, как Angular впервые отобразит связанные свойства и устанавливает входящие параметры.
  • ngDoCheck() - при обнаружении изменений, которые Angular не может самостоятельно обнаружить, реагирует на них.
  • ngAfterContentInit() - вызывается после того, как Angular спроецирует внешний контент в отображение компонента или отображение с директивой. Вызывается единожды, после первого ngDoCheck().
  • ngAfterContentChecked() - реагирует на проверку Angular-ом проецируемого содержимого. Вызывается после ngAfterContentInit() и каждый последующий ngDoCheck().
  • ngAfterViewInit() - вызывается после инициализации отображения компонента и дочерних/директив. Вызывается единожды, после первого ngAfterContentChecked().
  • ngAfterViewChecked() - реагирует на проверку отображения компонента и дочерних/директив. Вызывается после ngAfterViewInit() и каждый последующий ngAfterContentChecked().
  • ngOnDestroy() - после уничтожения директивы/компонента выполняется очистка. Отписывает Observables и отключает обработчики событий, чтобы избежать утечек памяти.
  • Что такое Shadow DOM и как с ним работать в Angular?

    Shadow DOM и его использование в Angular

    Shadow DOM — это часть спецификации Web Components, которая позволяет инкапсулировать DOM и стили, обеспечивая изоляцию компонентов. Shadow DOM помогает избежать конфликтов стилей и упрощает создание переиспользуемых компонентов с уникальной структурой и поведением.

    Что такое Shadow DOM?

    • Инкапсуляция: Shadow DOM создает собственное дерево DOM для компонента, отделенное от основного документа. Это изолирует стили и структуру компонента, предотвращая конфликты с внешними стилями.
    • Shadow Root: Корень Shadow DOM, который является точкой входа для инкапсулированного DOM.

    Применение Shadow DOM в Angular

    В Angular вы можете использовать Shadow DOM для инкапсуляции стилей и структуры компонента. Angular предоставляет настройку encapsulation в декораторе @Component.

    Что такое Data Binding и какие проблемы связанные с ним вы знаете?
    Angular поддерживает одностороннюю и двустороннюю Data Binding. Это механизм координации частей шаблона с частями компонента.
    Добавление специальной разметки сообщает Angular как соединять обе стороны. Следующая диаграмма показывает четыре формы привязки данных.
    Односторонние:
  • От компонента к DOM с привязкой значения: {{hero.name}}
  • От компонента к DOM с привязкой свойства и присвоением значения: [hero]="selectedHero"
  • От DOM к компоненту с привязкой на ивент: (click)="selectHero(hero)"

  • Двусторонняя в основном используется в template-driven forms, сочетает в себе параметр и ивент. Вот пример, использующий привязку с директивой ngModel.

      <input [(ngModel)]="hero.name">


    Здесь значение попадает в input из компонента, как при привязке значения, но при изменении юзером значения новое передается в компонент и переопределяется.

    Как вы используете одностороннюю и двухстороннюю привязку данных?

    Привязка данных в Angular

    Angular предоставляет мощные механизмы для привязки данных, которые позволяют синхронизировать данные между моделью и представлением. Существует два основных типа привязки данных: односторонняя и двухсторонняя привязка.

    Односторонняя привязка данных

    Односторонняя привязка данных означает, что данные передаются только в одном направлении: из компонента в шаблон или из шаблона в компонент. В Angular существует несколько способов реализации односторонней привязки данных:

    1. Интерполяция:

      • Используется для отображения данных из компонента в шаблоне.
      • Пример:
        <h1>{{ title }}</h1>
    2. Привязка свойств (Property Binding):

      • Связывает свойство элемента DOM с выражением из компонента.
      • Пример:
        <img [src]="imageUrl" />
    3. Привязка событий (Event Binding):

      • Связывает событие элемента DOM с методом компонента.
      • Пример:
        <button (click)="onClick()">Click me</button>

    Двусторонняя привязка данных

    Двусторонняя привязка данных позволяет синхронизировать данные между компонентом и шаблоном в обоих направлениях. Это особенно полезно для форм и ввода данных пользователем. В Angular двухсторонняя привязка данных достигается с помощью директивы ngModel.

    1. Двусторонняя привязка с использованием ngModel:
      • Обеспечивает синхронизацию данных между полем ввода и свойством компонента.
      • Пример:
        <input [(ngModel)]="name" />
        <p>Your name is: {{ name }}</p>

    Пример использования в компоненте:

    import { Component } from '@angular/core';
    
    @Component({
      selector: 'app-data-binding-example',
      template: `
        <h1>{{ title }}</h1> <!-- Интерполяция -->
        <img [src]="imageUrl" /> <!-- Привязка свойств -->
        <button (click)="onClick()">Click me</button> <!-- Привязка событий -->
        <input [(ngModel)]="name" /> <!-- Двусторонняя привязка данных -->
        <p>Your name is: {{ name }}</p>
      `,
      styles: []
    })
    export class DataBindingExampleComponent {
      title = 'Data Binding in Angular';
      imageUrl = 'https://example.com/image.jpg';
      name = '';
    
      onClick() {
        this.title = 'Button Clicked!';
      }
    }
    В чем преимущества и недостатки Regular DOM (Angular) перед Virtual DOM (React)?
    in progress..
    Что такое ngZone?

    NgZone - это сервис, который является обёрткой над zone.js, для выполнения кода внутри или вне зоны Angular. Этот сервис создаёт зону с именем angular для автоматического запуска обнаружения изменений, когда выполняются следующие условия:

  • Когда выполняется синхронная или асинхронная функция
  • Когда нет запланированной микро-задачи в очереди

  • Наиболее распространённое применение NgZone — это оптимизация производительности посредством выполнения асинхронной логики вне зоны Angular (метод runOutsideAngular), тем самым не вызывая обнаружение изменений или обработку ошибок. Или наоборот, данный сервис может использоваться для выполнения логики внутри зоны (метод run), что в конечном итоге приведёт к тому, что Angular снова вызовет обнаружение изменений и при необходимости перерисует представление.

    Как обновлять представление, если ваша модель данных обновляется вне 'зоны'?
    1. Используя метод ApplicationRef.prototype.tick, который запустит change detection на всем дереве компонентов.
    import { Component, ApplicationRef, NgZone } from "@angular/core";
    
    @Component({
      selector: "app-root",
      template: ` <h1>Hello, {{ name }}!</h1> `,
    })
    export class AppComponent {
      public name: string = null;
    
      constructor(private app: ApplicationRef, private zone: NgZone) {
        this.zone.runOutsideAngular(() => {
          setTimeout(() => {
            this.name = window.prompt("What is your name?", "Jake");
            this.app.tick();
          }, 5000);
        });
      }
    }
    1. Используя метод NgZone.prototype.run, который также запустит change detection на всем дереве.
    import { Component, NgZone } from "@angular/core";
    import { SomeService } from "./some.service";
    
    @Component({
      selector: "app-root",
      template: ` <h1>Hello, {{ name }}!</h1> `,
      providers: [SomeService],
    })
    export class AppComponent {
      public name: string = null;
    
      constructor(private zone: NgZone, private service: SomeService) {
        this.zone.runOutsideAngular(() => {
          this.service.getName().then((name: string) => {
            this.zone.run(() => (this.name = name));
          });
        });
      }
    }

    Метод run под капотом сам вызывает tick, а параметром принимает функцию, которую нужно выполнить перед tick. То есть:

    this.zone.run(() => (this.name = name));
    
    // идентично
    
    this.name = name;
    this.app.tick();
    1. Используя метод ChangeDetectorRef.prototype.detectChanges, который запустит change detection на текущем компоненте и дочерних.
    import { Component, NgZone, ChangeDetectorRef } from "@angular/core";
    
    @Component({
      selector: "app-root",
      template: ` <h1>Hello, {{ name }}!</h1> `,
    })
    export class AppComponent {
      public name: string = null;
    
      constructor(private zone: NgZone, private ref: ChangeDetectorRef) {
        this.zone.runOutsideAngular(() => {
          this.name = window.prompt("What is your name?", "Jake");
          this.ref.detectChanges();
        });
      }
    }
    Что такое EventEmitter и как подписываться на события?

    Используется в директивах и компонентах для подписки на пользовательские ивенты синхронно или асинхронно, и регистрации обработчиков для этих ивентов.
    Что такое Change Detection, как работает Change Detection Mechanism?

    1. Change Detection

    Change Detection - процесс синхронизации модели с представлением. В Angular поток информации однонаправленный, даже при использовании ngModel для реализации двустороннего связывания, которая является синтаксическим сахаром поверх однонаправленного потока.

    2. Change Detection Mechanism

    Change Detection Mechanism - продвигается только вперед и никогда не оглядывается назад, начиная с корневого (рут) компонента до последнего. В этом и есть смысл одностороннего потока данных. Архитектура Angular приложения очень проста — дерево компонентов. Каждый компонент указывает на дочерний, но дочерний не указывает на родительский. Односторонний поток устраняет необходимость $digest цикла.


    Какие существуют стратегии обнаружения изменений?

    Всего есть две стратегии - Default и OnPush. Если все компоненты используют первую стратегию, то Zone проверяет все дерево независимо от того, где произошло изменение. Чтобы сообщить Angular, что мы будем соблюдать условия повышения производительности нужно использовать стратегию обнаружения изменений OnPush. Это сообщит Angular, что наш компонент зависит только от входных данных и любой объект, который передается ему должен считаться immutable. Это все построено на принципе автомата Мили, где текущее состояние зависит только от входных значений.


    Сколько Change Detector(ов) может быть во всем приложении?
    У каждого компонента есть свой Change Detector, все Change Detector(ы) наследуются от AbstractChangeDetector.
    Основное отличие constructor от ngOnInit?

    Конструктор сам по себе является фичей самого класса, а не Angular. Основная разница в том, что Angular запустит ngOnInit, после того, как закончит настройку компонента, то есть, это сигнал, благодаря которому свойства @Input() и другие байндинги, и декорируемые свойства доступны в ngOnInit, но не определены внутри конструктора, по дизайну.


    RxJS
    Для чего нужен RxJS и какую проблему он решает?

    Назначение RxJS

    RxJS (Reactive Extensions for JavaScript) — это библиотека для реактивного программирования, использующая паттерн наблюдателя для работы с асинхронными событиями и потоками данных. Она предоставляет мощные абстракции в виде observables (наблюдаемые объекты), которые помогают управлять потоками данных и событий с использованием широкого набора операторов.

    Проблемы, которые решает RxJS

    1. Управление асинхронными операциями: RxJS упрощает работу с асинхронными процессами, такими как запросы к серверу, пользовательский ввод, файловый ввод/вывод и другие. Она предоставляет чистый и удобный способ объединения, создания, преобразования, фильтрации и обработки асинхронных данных.

    2. Устранение "Callback Hell": Вместо вложенных или последовательных обратных вызовов RxJS использует функциональный стиль обработки данных, что делает код чище и понятнее.

    3. Обработка сложных сценариев временных интервалов: RxJS облегчает обработку задач, где необходимо учитывать временные интервалы, такие как тайм-ауты, дебаунсинг или тайминг для опросов (polling).

    4. Объединение нескольких источников данных: Библиотека позволяет эффективно интегрировать и манипулировать данными из различных источников, будь то события DOM, WebSocket сообщения или обычные AJAX запросы.

    5. Управление состоянием: RxJS может быть использован для создания и управления состоянием приложения, облегчая разработку функциональных и реактивных интерфейсов.

    Пример использования RxJS

    import { fromEvent } from 'rxjs';
    import { map, filter } from 'rxjs/operators';
    
    // Создаем observable, который слушает клики по документу
    const clicks = fromEvent(document, 'click');
    const positions = clicks.pipe(
        filter(e => e.target.tagName === 'DIV'),  // Пропускаем только клики по div
        map(e => ({ x: e.clientX, y: e.clientY })) // Преобразуем событие в объект с координатами
    );
    
    // Подписываемся на обработку событий
    positions.subscribe(pos => console.log(`Clicked at: x=${pos.x}, y=${pos.y}`));

    Этот пример демонстрирует создание потока событий кликов, фильтрацию по типу элемента и последующее преобразование данных события. RxJS позволяет легко расширять и модифицировать поток данных с помощью различных операторов.

    Что такое Observable?

    Observable— это наблюдатель, который подписывается и реагирует на все события до момента отписки.
    В чём разница между Observable и Promise?

    Promise обрабатывает одно значение по завершению асинхронной операции, вне зависимости от ее исхода, и не поддерживают отмену операции.

    Observable же является потоком, и позволяет передавать как ноль, так и несколько событий, когда callback вызывается для каждого события.

    В чём разница между Observable и BehaviorSubject/Subject (Higher Order Observables)?

    Subjects - специальные Observable. Представьте, что есть спикер с микрофоном, который выступает в комнате, полной людей. Это и есть Subjects, их сообщение передается сразу нескольким получателям. Обычные же Observables можно сравнить с разговором 1 на 1.

    • Subject - является multicast, то есть может передавать значение сразу нескольким подписчикам.
    • BehaviorSubject - требует начальное значение и передает текущее значение новым подпискам.
    В чем разница между Subject, BehaviorSubject, ReplaySubject, AsyncSubject?

    • Subject - не хранит свои предыдущие состояния, зритель получает информацию только тогда, когда Subject сгенерирует новое событие, используя метод .next().
    • BehaviorSubject - при подписке поведенческий Subject уведомляет своего зрителя о последнем произошедшем в нём событии или, если в Subject-е не происходило событий, создаёт для зрителя событие с изначальной информацией, которая передаётся при создании Subject-а.
    • ReplaySubject - при подписке повторяющийся Subject уведомляет своего нового зрителя о всех произошедшем в нём событиях с момента создания. Для оптимизации при создании повторяющегося Subject-а можно передать число последних событий, которые будут повторяться для каждого нового зрителя. Стоит отметить, что создание ReplaySubject-а c числом повторяющихся событий равное 1 эквивалетно созданию BehaviorSubject-а.
    • AsyncSubject - асинхронный Subject уведомляет своих зрителей только о последнем произошедшем событии и только когда Subject успешно завершается. Если AsyncSubject завершится ошибкой, его зрители будут уведомлены только об ошибке.
    В чём разница между операторами switchMap, mergeMap, concatMap?

    • switchMap - отменит подписку на Observable, возвращенный ее аргументом project, как только он снова вызовет ее в новом элементе.
    • mergeMap - в отличие от switchMap позволяет реализовать одновременно несколько внутренних подписок.
    • concatMap - последовательно обрабатывает каждое событие, в отличие от mergeMap.
    Как бы вы кешировали наблюдаемые данные из потоков (stream)?
    in progress..
    Angular data flow
    Что такое Dependency Injection?

    Это важный паттерн шаблон проектирования приложений. В Angular внедрение зависимостей реализовано из-под капота.

    Зависимости - это сервисы или объекты, которые нужны классу для выполнения своих функций. DI -позволяет запрашивать зависимости от внешних источников.
    Что такое Singleton Service и с какой целью его используют в Angular?

    Это сервисы, объявленные в приложении и имеющие один экземпляр на все приложение. Его можно объявить двумя способами:

  • Объявить его @Injectable(root)
  • Включить его в AppModule в providers, либо в единственный модуль импортируемый в AppModule.
  • Как можно определить свой обработчик ErrorHandler, Logging, Cache в Angular?

    Настраиваемые обработчики в Angular

    Angular предоставляет гибкие возможности для создания настраиваемых обработчиков ошибок, логирования и кэширования. Эти механизмы позволяют улучшить управление состоянием приложения, отладку и производительность.

    1. Настраиваемый ErrorHandler

    Чтобы создать собственный обработчик ошибок в Angular, вы можете реализовать класс, который соответствует интерфейсу ErrorHandler. Это позволит перехватывать и обрабатывать исключения на уровне всего приложения.

    import { ErrorHandler, Injectable } from '@angular/core';
    
    @Injectable()
    export class AppErrorHandler implements ErrorHandler {
        handleError(error: any) {
            // Ваша логика обработки ошибок
            console.error('An error occurred:', error);
        }
    }
    
    // И включение его в модуль:
    @NgModule({
        providers: [{ provide: ErrorHandler, useClass: AppErrorHandler }]
    })
    export class AppModule {}
    1. Логирование

    Для создания настраиваемой системы логирования можно определить сервис, который будет отвечать за обработку логов. Этот сервис может быть интегрирован с удаленными серверами логирования, системами аналитики или просто выводить информацию в консоль.

    import { Injectable } from '@angular/core';
    
    @Injectable({
        providedIn: 'root'
    })
    export class LoggingService {
        log(message: string): void {
            console.log(message);
            // Дополнительно отправить на сервер или в другую систему
        }
    
        error(message: string): void {
            console.error(message);
            // Дополнительные действия при ошибке
        }
    }
    1. Кэширование

    Кэширование можно реализовать с помощью сервиса, который будет хранить данные в памяти или использовать браузерные API, такие как localStorage или sessionStorage для сохранения данных на стороне клиента.

    import { Injectable } from '@angular/core';
    
    @Injectable({
        providedIn: 'root'
    })
    export class CacheService {
        private cache = new Map<string, any>();
    
        set(key: string, data: any): void {
            this.cache.set(key, data);
        }
    
        get(key: string): any {
            return this.cache.get(key);
        }
    }

    Эти настраиваемые обработчики и сервисы позволяют управлять поведением приложения более гибко, адаптируя его к специфическим требованиям проекта.

    Что такое управление состоянием приложения?
    Управление состоянием в Angular — это процесс контроля над данными, которые необходимы для работы приложения, включая пользовательский интерфейс, данные пользователя, сеансы, настройки и многое другое. В Angular управление состоянием может быть реализовано разными способами, в зависимости от сложности приложения и требований к его масштабируемости.

    Почему это важно?

    1. Компонентное разделение: Angular построен на компонентной архитектуре, которая естественным образом ведет к необходимости управления состоянием, чтобы синхронизировать данные между компонентами и обеспечить их актуальность.

    2. Обеспечение реактивности: Использование реактивных форм и библиотеки RxJS позволяет Angular эффективно реагировать на изменения данных в реальном времени, что критично для динамичных приложений.

    3. Масштабируемость и поддержка: Управление состоянием позволяет обеспечить организованность и поддерживаемость кода, что особенно важно в крупных и сложных приложениях.

    В чем отличие между NGRX, NGXS, Akita и какую проблему они решают?
    NgRx, NGXS и Akita — это библиотеки управления состоянием в Angular, которые предлагают разные подходы к реализации Flux или Redux-подобных архитектур. Они помогают решать проблемы управления состоянием в больших приложениях, где требуется обеспечить согласованность данных между компонентами, оптимизировать производительность и упростить тестирование.

    NgRx

    NgRx — это библиотека управления состоянием для Angular, вдохновлённая Redux. Она применяет принципы реактивного программирования с помощью RxJS, предоставляя строгую архитектуру для управления состоянием через действия, редьюсеры и эффекты.

    Основные особенности:

    • Шаблоны и церемонии: NgRx требует строгого соблюдения шаблонов проектирования, что может увеличить сложность кода.
    • Редьюсеры: Определяют, как изменяется состояние в ответ на действия.
    • Эффекты: Обрабатывают асинхронные операции и побочные эффекты, такие как запросы данных, сохраняя чистоту редьюсеров.
    • Селекторы: Оптимизируют производительность и помогают извлекать данные из хранилища.

    Пример использования NgRx:

    import { createAction, props, createReducer, on } from '@ngrx/store';
    
    // Действие
    export const login = createAction('[Login Page] Login', props<{ username: string; password: string; }>());
    
    // Редьюсер
    export const userReducer = createReducer(
      { username: '', password: '' },
      on(login, (state, { username, password }) => ({ username, password }))
    );
    
    // В компоненте:
    store.dispatch(login({ username: 'user', password: 'pass' }));

    NGXS

    NGXS — это ещё одна библиотека управления состоянием для Angular, которая предлагает более декларативный подход к управлению состоянием с меньшим количеством шаблонного кода по сравнению с NgRx.

    Особенности:

    • Более простой синтаксис: Использует декораторы для определения действий и состояний, что упрощает понимание и использование.
    • Встроенная поддержка асинхронности: Легче работать с асинхронными операциями.

    Akita

    Akita предлагает более простой и менее церемонный подход к управлению состоянием, используя объектно-ориентированные принципы.

    Особенности:

    • Объектно-ориентированный подход: Акцент на простоте и поддержке объектно-ориентированных принципов.
    • Минимум шаблонного кода: Проще в освоении и использовании, особенно для новых разработчиков.
    Angular with Backend integrations
    Какими способами можно взаимодействовать с API бэкенда, что требуется для проксирования запросов?

    Взаимодействие с API

    В экосистеме ангуляр существует пакет для общения с сервером (@angular/common/http), которого достаточно для повседневной разработки. Его интерфейс основан на rxjs потоках, поэтому его легко использовать для работы с потоками данных в приложении.

    Кроме этого, как и в ванильной версии javascript, можно использовать XMLHttpRequest, fetch API, axios(или многие другие библиотеки), но их использование вместо встроенного клиента, считается неоправданным и всячески возбраняется.

    Существуют и другие способы взаимодействия с сервером(см. Веб-сокеты), но для них не существует каноничных встроенных библиотек, поэтому используются сторонние библиотеки или собственные реалиации. Хорошей практикой здесь будет привести интерфейс построенный на промисах и обратных вызовах(callback) к интерфейсу основанному на rxjs потоках, для естественного использования в экосистеме Angular.


    Proxy

    Чтобы тестировать взаимодействие приложения с сервером, который должен находиться на том же домене, используется функциональность в Angular CLI для этого нужно создать файл с конфигурацией прокси, где будут указаны:

    • Контекст для проксирования
    • Ссылка на работающий экземпляр API
    • secure: false для работы в тестовой среде без сертификатов

    Также большинство серверов не настроены для работы с разными доменами(CORS), поэтому для корректной работы на API сервере, необходимо явно указать с какого домена(ов) можно принимать запросы.

    Что такое HTTP Interceptors?

    Interceptor (перехватчик) - просто причудливое слово для функции, которая получает запросы / ответы до того, как они будут обработаны / отправлены на сервер. Нужно использовать перехватчики, если имеет смысл предварительно обрабатывать многие типы запросов одним способом. Например нужно для всех запросов устанавливать хедер авторизации `Bearer`:

    token.interceptor.ts

    import { Injectable } from "@angular/core";
    import {
      HttpInterceptor,
      HttpRequest,
      HttpHandler,
      HttpEvent,
    } from "@angular/common/http";
    import { Observable } from "rxjs/Observable";
    
    @Injectable()
    export class TokenInterceptor implements HttpInterceptor {
      public intercept(
        req: HttpRequest<any>,
        next: HttpHandler
      ): Observable<HttpEvent<any>> {
        const token = localStorage.getItem("token") as string;
    
        if (token) {
          req = req.clone({
            setHeaders: {
              Authorization: `Bearer ${token}`,
            },
          });
        }
    
        return next.handle(req);
      }
    }

    И регистрируем перехватчик как синглтон в провайдерах модуля:

    app.module.ts

    import { NgModule } from "@angular/core";
    import { BrowserModule } from "@angular/platform-browser";
    import { HTTP_INTERCEPTORS } from "@angular/common/http";
    import { AppComponent } from "./app.component";
    import { TokenInterceptor } from "./token.interceptor";
    
    @NgModule({
      imports: [BrowserModule],
      declarations: [AppComponent],
      bootstrap: [AppComponent],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: TokenInterceptor,
          multi: true, // <--- может быть зарегистрирован массив перехватчиков
        },
      ],
    })
    export class AppModule {}

    Как использовать Json Web Tokens для аутентификации при разработке на Angular?
    in progress..
    Как обрабатываются атаки XSS и CSRF в Angular?
    in progress..
    Angular routing
    Что такое роутинг и как его создать в Angular?

    Роутинг позволяет реализовать навигацию от одного view приложения к другому при работе пользователя с приложением.
    Это реализовано через взаимодействие с адресной строкой, Angular Router интерпретирует ее как инструкцию по переходу между view. Возможна передача параметров вспомогательному компоненту для конкретизирования предоставляемого контента. Навигация может осуществлять по ссылкам на странице, кнопкам или другим элементам, как кнопки "вперед-назад" в браузере.
    Для создания роутинга первым делом необходимо импортировать "RouterModule" и "Routes" в AppModule.
    Затем необходимо реализовать конфигурацию по приложению, определить path и относящие к ним компоненты, и в метод RouterModule.forRoot() передать конфигурацию.
    Наконец необходимо добавить routerLink в шаблон.
    Каков жизненный цикл у Angular Router?

    Источник информации

    1. NavigationStart - начало навигации. Возникает во время нажатия на директиву router link, вызове функций navigate и navigateByUrl
    2. RoutesRecognized - cопоставление URL-адресов и редиректы. Роутер сопоставляет URL-адрес навигации из первого события с одним из свойств path в конфигурации, применяя любые редиректы по-пути.
    3. GuardsCheckStart, GuardsCheckEnd - функции, которые использует роутер для определения может ли он выполнить навигацию. Пример:
      // router configuration
      const config = {
        path: "users",
        /* ... */
        canActivate: [CanActivateGuard],
      };
      
      class Guard {
        // router guard implementation
        canActivate(
          route: ActivatedRouteSnapshot,
          state: RouterStateSnapshot
        ): boolean {
          return this.auth.isAuthorized(route.queryParams.login);
        }
      }

      Если вызов isAuthorized(route.queryParams.login) возвращает true, guard завершится успехом. В противном случае guard упадет, роутер сгенерирует событие NavigationCancel и отменит всю дальнейшую навигацию.

      Другие guard включают canLoad (должен ли модуль быть лениво загружен или нет). canActivateChild и canDeactivate (которые полезны для предотвращения ухода пользователя со страницы, например, при заполнении формы).

    4. ResolveStart, ResolveEnd - функции, которые мы можем использовать для подгрузки данных во время навигации. Например:
      // router configuration
      const config = {
        path: "users",
        /* ... */
        resolve: { users: UserResolver },
      };
      
      // router resolver implementation
      export class UserResolver implements Resolve<Observable<any>> {
        constructor(private userService: MockUserDataService) {}
        resolve(): Observable<any> {
          return this.userService.getUsers();
        }
      }

      Результат, то есть данные, будет положен в data объект сервиса ActivatedRoute с ключом users. Данная информация может быть прочитаны с помощью подписки на data observable.

      export class UsersComponent implements OnInit {
        public users = [];
        constructor(private route: ActivatedRoute) {}
        ngOnInit() {
          this.route.data
            .pipe(first())
            .subscribe((data) => (this.users = data.users));
        }
      }
    5. ActivationStart, ActivationEnd, ChildActivationStart, ChildActivationEnd - события, во время которых активируются компоненты и отображаются их с помощью .Роутер может извлечь необходимую информацию о компоненте из дерева ActivatedRouteSnapshots. Он был построен в предыдущие шаги навигационного цикла.
    6. Updating the URL - последний шаг в навигационном цикле — это обновление URL-адреса в address bar
    Что такое ленивая загрузка (Lazy-loading) и для чего она используется?
    in progress..
    В чем разница между Routing и Navigation?
    in progress..
    Как загрузить данные до того как активируется роут?
    in progress..
    Angular Forms (also big ui enterprise)
    Что такое FormGroup и FormControl и для чего они используются?

  • FormControl - отслеживает значение и статус валидации отдельного элемента формы.
  • FormGroup - отслеживает состояние и статус валидации группы FormControl

  • Они используются для создания и работы с формами.
    Что такое реактивные формы в Angular?
    Реактивные формы обеспечивают управляемый моделями подход к обработке входных данных форм, значения которых могут меняться со временем. Они строятся вокруг наблюдаемых потоков, где входные данные и значения форм предоставляются в виде потоков входных значений, к которым можно получить синхронный доступ.
    Как применять валидацию для простых и сложных форм?
    В реактивных формах валидация реализуется в компоненте. Есть два типа валидаторов: синхронные и асинхронные.
    Можно использовать встроенные валидаторы, либо создать свои. Валидаторы добавляются
    Build environments
    В чем разница между Angular CLI и Webpack Development Environment?
    in progress..
    Что такое JIT и AOT, в чем их отличия и каковы сферы применения?

    Angular приложение можно скомпилировать с помощью команд ng serve и ng build. При этом, можно работать с разными видами компиляции:

    • JIT - (Just-In-Time compilation) - компиляция "на лету", динамическая компиляция. В Angular используется по умолчанию.
    • AOT - (Ahead-Of-Time compilation) - компиляции перед исполнением.

    Основные различия:

    ПараметрыJITAOT
    Когда компилируется в момент запуска приложения в браузере в момент сборки приложения
    Рекомендуется использовать для локальной разработки создания продуктовой сборки
    Как включить Не нужно выставлять дополнительных флагов Нужно добавить флаг --aot или --prod
    Скорость Скорость компиляции быстрее, загрузка в браузере дольше Скорость компиляции дольше, загрузка в браузере быстрее
    Размер бандла Бандл имеет большой размер из-за включенного в бандл компилятора. Бандл имеет небольшой размер, тк содержит полностью скомпилированный и оптимизированный код.
    Выявление ошибок Ошибки отобразятся во время запуска приложения в браузере Выявление ошибок во время компиляции
    Работа с файлами Может компилировать только измененные файлы отдельно Компилирует сразу все файлы приложения
    Test development
    Что такое Unit-тестирование, интеграционное, e2e-тестирование (End-to-End) и как оно применяется в Angular?

    Различные типы тестирования в Angular

    В разработке приложений Angular часто используются различные типы тестирования для обеспечения качества и функциональности. Это включает в себя unit-тестирование, интеграционное тестирование и e2e-тестирование (End-to-End).

    1. Unit-тестирование

    Unit-тестирование заключается в проверке отдельных компонентов, сервисов или других частей приложения в изоляции от остальной части системы. Цель состоит в том, чтобы убедиться, что каждый отдельный модуль функционирует правильно.

    • Применение в Angular: Angular предоставляет встроенную поддержку для unit-тестирования с помощью Jasmine и Karma. Разработчики могут создавать спецификации (specs), которые описывают ожидаемое поведение компонентов, и использовать TestBed для настройки тестового окружения.

      import { ComponentFixture, TestBed } from '@angular/core/testing';
      import { MyComponent } from './my.component';
      
      describe('MyComponent', () => {
        let component: MyComponent;
        let fixture: ComponentFixture<MyComponent>;
      
        beforeEach(() => {
          TestBed.configureTestingModule({
            declarations: [MyComponent]
          });
          fixture = TestBed.createComponent(MyComponent);
          component = fixture.componentInstance;
          fixture.detectChanges();
        });
      
        it('should create', () => {
          expect(component).toBeTruthy();
        });
      });

      2. Интеграционное тестирование

    Интеграционное тестирование проверяет, как различные модули или сервисы работают вместе. В контексте Angular, это может включать тестирование взаимодействия компонентов с шаблонами или сервисами.

    Применение в Angular:

    Используя TestBed, можно настроить модули, которые включают компоненты, директивы и сервисы для проверки их взаимодействия.

    3. e2e-тестирование (End-to-End)

    e2e-тестирование оценивает приложение в целом, имитируя действия реального пользователя. Тесты e2e проверяют приложение от начала до конца, что помогает обнаружить проблемы в потоках пользователя и основных функциях.

    Применение в Angular:

    Angular рекомендует использовать Protractor для e2e-тестирования, который обеспечивает интеграцию с приложениями Angular для имитации пользовательских взаимодействий.

    import { browser, by, element } from 'protractor';
    
    describe('My App', () => {
        it('should have a title', () => {
            browser.get('/');
            expect(browser.getTitle()).toEqual('My Angular App');
        });
    
        it('should add one and two', () => {
            browser.get('/');
            element(by.css('#num1')).sendKeys('1');
            element(by.css('#num2')).sendKeys('2');
            element(by.css('#addButton')).click();
            expect(element(by.css('#result')).getText()).toBe('3');
        });
    });

    Каждый из этих подходов к тестированию играет важную роль в жизненном цикле разработки приложений на Angular, помогая убедиться, что приложение работает как ожидается, а изменения в коде не вносят регрессий.

    Что такое Karma, Jasmine (зачем их используют совместно при разработке на Angular)?
    in progress..
    В чем разница между Jest и Karma?
    in progress..
    В чем разница между Protractor и Cypress?
    in progress..
    Как протестировать входные параметры и всплывающие события компонентов?
    in progress..
    Code convention
    Требования к написанию кода на TypeScript

    На самом деле требования бывают разные и зависят от команды к команде. Самые эффективные для себя считаю использование модификаторов доступа и принудительного указания типов данных для всех переменных, методов и членов класса, которые вы используете в коде. Желательно все необходимые правила конвенции кода настраивать в ESLint.

    // my.ts
    export interface My {}
    
    // my-impl.ts
    export class MyImp implements My {
      public field: string;
    
      public myMethod(): void {
        // ...
      }
    
      private myProtectedMethod(): Date {
        return new Date();
      }
    
      private myPrivateMethod(): MyClassImpl {
        // ...
    
        return this;
      }
    }
    Зачем нужен ESLint (TSLint) и Prettier?
    in progress..

    About

    Вопросы на собеседовании по Angular

    Resources

    Stars

    Watchers

    Forks

    Releases

    No releases published

    Packages

    No packages published