Обзор
Я вижу странное поведение в том, что свойства, которые добавляются к объекту посредством назначения деструктурирования (или Object.assign
), присутствуют при передаче в forRoot
, но не присутствуют при внедрении в службу. Кроме того, обновления, сделанные после инициализации, присутствуют при передаче в forRoot
, но не присутствуют при внедрении в службу. Это происходит только при сборке с AOT.
Я создал минимальный проект, который воспроизводит проблему:
https://github.com/bygrace1986/wat
Версии пакета
- угловой: 5.2.0
- angular-cli: 1.6.4 (мое приложение), 1.7.4 (мой тест)
- машинопись: 2.4.2
Настройка
Создайте модуль со статическим forRoot
методом, который будет принимать объект, который он затем предоставит через InjectionToken
и внедрит в службу.
TestModule
import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { TestDependency, TEST_DEPENDENCY, TestService } from './test.service';
@NgModule({
imports: [
CommonModule
],
declarations: []
})
export class TestModule {
static forRoot(dependency?: TestDependency): ModuleWithProviders {
return {
ngModule: TestModule,
providers: [
TestService,
{ provide: TEST_DEPENDENCY, useValue: dependency }
]
}
}
}
TestService
import { Injectable, InjectionToken, Inject } from '@angular/core';
export interface TestDependency {
property: string;
}
export const TEST_DEPENDENCY = new InjectionToken<TestDependency>('TEST_DEPENDENCY');
@Injectable()
export class TestService {
constructor(@Inject(TEST_DEPENDENCY) dependency: TestDependency) {
console.log('[TestService]', dependency);
}
}
Сценарий немутантного объекта
Этот сценарий показывает, что передача немутатного объекта в forRoot
правильно внедряется в службу, которая зависит от него.
Чтобы установить ссылку на TestService
в app.component.html
(или где-нибудь, где он будет введен). Передайте зависимость методу forRoot
в TestModule
.
AppModule
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { TestModule } from './test/test.module';
import { TestDependency } from './test/test.service';
const dependency = {
property: 'value'
} as TestDependency;
console.log('[AppModule]', dependency)
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
TestModule.forRoot(dependency)
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Выполнить ng serve --aot
.
выход
[AppModule] {property: "value"}
[TestService] {property: "value"}
Сценарий мутированного объекта
Этот сценарий иллюстрирует, что изменения, внесенные в объект посредством назначения деструктуризации объекта во время инициализации, или изменения, сделанные после инициализации, игнорируются, когда предоставленный объект внедряется в сервис, который зависит от него.
Для настройки создайте новый объект с дополнительными свойствами и используйте деструктуризацию объекта, чтобы присвоить свойства старого объекта новому. Затем обновите свойство в dependency
и передайте его методу forRoot
в TestModule
.
AppModule
const dependency = {
property: 'value'
} as TestDependency;
const dependencyCopy = { id: 1, name: 'first', ...dependency };
dependencyCopy.name = 'last';
console.log('[AppModule]', dependencyCopy);
...
TestModule.forRoot(dependencyCopy)
Выполнить ng serve --aot
.
выход
[AppModule] {id: 1, name: "last", property: "value"}
[TestService] {id: 1, name: "first"}
Неожиданный результат
Любое свойство, добавленное с помощью назначения деструктурирования объекта, было удалено, и любое обновление, выполненное после инициализации, возвращается к тому, когда объект передается в forRoot
и вводится в TestService
. На самом деле, это не тот же объект (я отлаживал и проверял ===
). Это как если бы исходный объект, созданный до назначения или мутации, использовался ... каким-то образом.
Мутированный объект, предоставленный в сценарии AppModule
Этот сценарий показывает, что мутации объекта не возвращаются, если они предоставлены на уровне AppModule
, а не через forRoot
.
Для настройки ничего не передавайте forRoot
. Вместо этого используйте токен инъекции, чтобы предоставить объект в списке AppModule
поставщиков.
AppModule
imports: [
BrowserModule,
TestModule.forRoot()
],
providers: [
{ provide: TEST_DEPENDENCY, useValue: dependencyCopy }
],
Выполнить ng serve --aot
.
выход
[AppModule] {id: 1, name: "last", property: "value"}
[TestService] {id: 1, name: "last", property: "value"}
Вопрос
Почему изменения, внесенные в объект, предоставленный с помощью forRoot
, возвращаются, когда объект внедряется в зависимый класс?
Обновление
- Понял, что обновления после инициализации также отменены.
- Понял, что это может быть воспроизведено только с флагом
--aot
, а не с более широким флагом --prod
.
- Обновлено до последней стабильной версии cli (1.7.4), но проблема все еще есть.
- Открыта ошибка в проекте
angular-cli
: https://github.com/angular/angular-cli/issues/10610
- Из чтения сгенерированного кода я думаю, что проблема заключается в перезаписи метаданных фазы 2. Когда только ссылка на переменную в
forRoot
, я получаю это: i0.ɵmpd(256, i6.TEST_DEPENDENCY, { id: 1, name: "first" }, [])]);
. Когда на него ссылаются в списке AppModule
провайдеров, я получаю это: i0.ɵmpd(256, i6.TEST_DEPENDENCY, i1.ɵ0, [])]);
, а затем это в модуле приложения var ɵ0 = dependencyCopy; exports.ɵ0 = ɵ0;
.