Angular - Наблюдаемое изменение обнаружено в контроллере компонента, но не в его шаблоне
ВТОРОЙ РЕДАКТИРОВАТЬ:
Так! Я разобрался с проблемой, но чтобы понять проблему, вы должны сначала понять цель:
В настоящее время я работаю над Angular Chrome-Extension
который имеет постоянный фоновый скрипт (в отличие от всех примеров и учебных пособий, которые я нашел, которые объясняют только непостоянный фоновый скрипт или пример страницы событий).
Фоновый скрипт на самом деле является угловым компонентом, который содержит logic service
которые должны быть доступны в различных компонентах Chrome (всплывающее окно, параметры и т. д.)
- При внедрении логического сервиса с использованием наивного метода Angular DI все начинает запутываться, потому что с точки зрения chrome страница фонового сценария и страница всплывающего сценария имеют разные
window
объект, который приводит к наличию 2 экземпляров приложения, 1 в "фоновом окне" и другой в "всплывающем окне". - При использовании Chrome API
chrome.extension.getBackgroundPage()
чтобы получить сервис во всплывающем окне, я получаю правильный экземпляр, но теперь возникает другая проблема, изменения в фоновом сервисе не отражаются, потому что сервис находится вне контекста приложения Angular.
Итак, у меня есть новый вопрос:
Можно ли сказать Angular "добавить / посмотреть" службу Angular, для которой он не был создан, и добавить ее в контекст приложения?
РЕДАКТИРОВАНИЕ: я добавил 2 сервиса, кажется, у меня есть более глубокая проблема в моем проекте, потому что этот демонстрационный код работает, как и ожидалось... будет продолжать обновляться после разрешения.
Оригинальный вопрос
Я пытаюсь передать асинхронные данные из родительского компонента в дочерний компонент, и у меня возникают некоторые проблемы.
Служба содержит наблюдаемый и родитель подписывается на него + передавая его через async
труба к входу ребенка.
Проблема в том, что родитель выбирает изменения (только из контроллера), и ни один из интерфейсов не обновляется.
Я могу вызвать ChangeDetectorRef.detectChanges()
в функции подписки, но мне кажется, что это очень странно, потому что async
труба должна была понять эти изменения, но это не так.
Какие-либо предложения?
Рассмотрим следующий код (это работает как новый проект)
Родительский компонент:
@Component({
selector: 'parent',
template: '<div>
<button (click)="logic.doAction()">Get data</button>
<child [someInput]="logic.data$ | async"></child>
<div>data: {{logic.data$ | async | json}}</div>
</div>'
})
export class ParentComponent {
constructor(public logic: LogicService) {}
}
Дочерний компонент:
@Component({
selector: 'child',
template: '<span>{{someInput}}</child>'
})
export class ChildComponent {
@Input() someInput: string;
constructor() {}
}
Логический сервис:
@Injectable()
export class LogicService {
private readonly dataSubject: BehaviorSubject<Array<string>>;
private readonly _data$: Observable<Array<string>>;
constructor(private api: ApiService) {
this.dataSubject = new BehaviorSubject([]);
this._data$ = this.dataSubject.asObservable();
}
doAction(): void {
this.api.fetchFromAPI().then((data) => this.dataSubject.next(data));
}
get data$(): Observable<Array<string>> {
return this._data$;
}
}
API сервис:
@Injectable()
export class ApiService {
fetchFromAPI(): Promise<Array<string>> {
return new Promise((res, rej) => {
setInterval(() => {
res(['some', 'demo', 'data']);
}, 4000);
});
}
}
2 ответа
Итак, я создал директиву, которая принудительно обновляет представление при обновлении наблюдаемой.
@Directive({
selector: '[detectChanges]'
})
export class DetectChangesDirective implements OnInit, OnDestroy {
private subscription: Subscription;
@Input('detectChanges') observable$: Observable<any>;
constructor(private cd: ChangeDetectorRef) {
}
ngOnInit() {
this.subscription = this.observable$.subscribe((() => {
this.cd.detectChanges();
}));
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
Использовать его очень просто, просто примените его к представлению о проблемах с обновлением, например:
<div [detectChanges]="logic.data$">
<button (click)="logic.doAction()">Get data</button>
<child [someInput]="logic.dataValue"></child>
<div>data: {{logic.dataValue | json}}</div>
</div>
ВАЖНЫЙ:
- Обратите внимание, что я больше не использую
async
труба & вместо этого я передаю фактическое значение туда, где я его связываю. - Я передаю
Observable
в директиву, которая отслеживает, когда значение изменяется
ИМО, если сервисный метод возвращает наблюдаемое, его следует развернуть (подписать) в том месте, где нужны данные. Я бы сделал это следующим образом:
<child [someInput]="service.observable"></child>
А у ребенка:
export class ChildComponent {
@Input() someInput: Observable;
// subscribe to someInput in the child
}