Основные различия между конструктором и ngOnInit в работе с компонентами Angular

Основные различия между конструктором и ngOnInit в работе с компонентами Angular

От автора: один из самых частых вопросов про Angular компоненты на stackoverflow – чем отличается конструктор от ngOnInit. У данного вопроса более 100к просмотров. Я дал там свой ответ и решил развить его в эту статью. Почти все ответы в треде и статьи в сети сосредоточены на различии в использовании. Я же дам более развернутое сравнение, касающееся процесса инициализации компонентов.

Основные различия между конструктором и ngOnInit в работе с компонентами Angular

Различия в JS/TS языке

Начнем с самого очевидного различия – различия языков. ngOnInit – это лишь метод класса, структурно не отличающийся от других методов класса. Команда Angular решила его так назвать, но у него могло быть совершенно другое название:

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

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

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

Переводится в:

Чтобы создать объект класса, эту функцию необходимо вызывать с оператором new:

Если не создавать конструктор в классе, он превратится в пустую функцию:

Переводится в пустую функцию:

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

Различия в процессе инициализации компонентов

Между двумя функциями есть огромное различие в плане инициализации компонентов. Процесс начальной загрузки Angular состоит из двух основных этапов:

Создание дерева компонентов

Запуск обнаружения изменений

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

Когда Angular создает дерево компонентов, инъектор корневого модуля уже настроен, т.е. вы можете вставлять любые глобальные зависимости. Когда Angular инициализирует класс дочернего компонента, инъектор родительского компонента уже настроен. Т.е. вы можете вставлять провайдеры, определенные по родительскому компоненту, включая сам родительский компонент. Конструктор компонента лишь метод, вызываемый в контексте инъектора. Если вам нужны зависимости, получить их можно только отсюда. Механизм коммуникации @input обрабатывается как часть следующей фазы обнаружения изменений, поэтому назначение входных данных не доступно в конструкторе.

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

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

Продемонстрируем эти фазы небольшим примером. Предположим, у вас следующий шаблон:

Angular начинает предварительную загрузку приложения. Как сказано выше, сначала он создает классы для всех компонентов. То есть вызывается конструктор MyAppComponent. При выполнении конструктора компонента Angular разрешает все зависимости, встроенные в конструктор MyAppComponent, и передает их в виде параметров. Также создается узел DOM, который будет хранить компонент my-app. Далее создается хост элемент для child-comp, и вызывается конструктор ChildComponent. На этом этапе Angular не связан с привязкой входных данных и хуками жизненного цикла. Т.е. после завершения процесса Angular имеет следующее дерево компонентов:

Только после этого Angular запускает обнаружение изменений и обновляет назначения для my-app и вызывает ngOnInit на объекте MyAppComponent. Далее происходит переход к обновлению назначений для child-comp и вызову ngOnInit на классе ChildComponent.

Более подробно о виде, о котором я говорил выше, можно прочитать в статье «вот почему вы не найдете компоненты в Angular».

Различия в использовании

Давайте разберем, в чем различие с точки зрения использования.

Конструктор

Конструктор класса в Angular почти всегда используется для вставки зависимостей. Angular вызывает constructor injection pattern, который подробно разбирался здесь. Более подробно о его архитектуре можно прочитать в статье «инъекция в конструкторе или инъекция в сеттере» от Miško Hevery.

Тем не менее, использование конструктора не ограничено DI. Например, директива router-outlet модуля @angular/router использует его для своей регистрации и определения своего положения (viewContainerRef) внутри экосистемы роутера. Я описывал этот подход в статье «вот как можно получить ViewContainerRef до обработки запроса @ViewChild».

Старайтесь писать как можно меньше логики в конструкторе.

NgOnInit

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

Старайтесь использовать ngOnInit для выполнения логики инициализации, даже если эта логика не зависит от DI, DOM и назначений входа.

Автор: Maxim Koretskyi

Источник: //blog.angularindepth.com

Редакция: Команда webformyself.

Метки:

Похожие статьи:

Комментарии Вконтакте: