我正在尝试组建一个 Angular 2 Rx.JS 5 / Next的演示应用程序 .
我注意到每次切换路由时都会重新实例化我的组件 .
以下是根应用程序的代码:
import {bootstrap} from 'angular2/platform/browser';
import {HTTP_PROVIDERS} from 'angular2/http';
import {ROUTER_PROVIDERS} from 'angular2/router';
import {AppComponent} from './app.component.ts';
bootstrap(AppComponent, [HTTP_PROVIDERS, ROUTER_PROVIDERS]);
以下是根组件的代码:
import {Component} from 'angular2/core';
import {RouteConfig, ROUTER_DIRECTIVES} from 'angular2/router';
import {FirstComponent} from './app.first-component.ts';
import {SecondComponent} from './app.second-component.ts';
import {AppService} from "./app.services.ts";
@Component({
selector: 'my-app',
providers: [AppService, FirstComponent, SecondComponent],
directives: [FirstComponent, SecondComponent, ROUTER_DIRECTIVES],
template: `<h1>An Angular 2 App</h1>
<a [routerLink]="['First']">first-default</a>
<a [routerLink]="['Second']">second</a>
<router-outlet></router-outlet>`
})
@RouteConfig([
{path: '/', name: 'First', component: FirstComponent, useAsDefault: true},
{path: '/second', name: 'Second', component: SecondComponent}
])
export class AppComponent {
}
然后是第一个组件的代码(映射到 /
):
import {Component, OnInit, NgZone} from "angular2/core";
import {AppService} from "./app.services.ts";
import 'rxjs/Rx';
@Component({
selector: 'my-first',
template: `
<div>
<ul>
<li *ngFor="#s of someStrings">
a string: {{ s }}
</li>
</ul>
</div>`
})
export class FirstComponent implements OnInit {
zone:NgZone;
constructor(private appService:AppService) {
console.log('constructor', 'first');
this.zone = new NgZone({enableLongStackTrace: false});
}
someStrings:string[] = [];
ngOnInit() {
console.log('ngOnInit', 'first');
this.appService.refCounted.subscribe(
theStrings=> {
this.zone.run(() =>this.someStrings.push(...theStrings));
},
error=>console.log(error)
);
}
}
第二个组件(映射到 /second
):
import {Component, OnInit, NgZone} from "angular2/core";
import {AppService} from "./app.services.ts";
@Component({
selector: 'my-second',
template: `
<div>
<ul>
<li *ngFor="#s of someStrings">
a string: {{ s }}
</li>
</ul>
</div>`
})
export class SecondComponent implements OnInit {
zone:NgZone;
constructor(private appService:AppService) {
console.log('constructor', 'second');
this.zone = new NgZone({enableLongStackTrace: false});
}
someStrings:string[] = [];
ngOnInit() {
console.log('ngOnInit', 'second');
this.appService.refCounted.subscribe(
theStrings=> {
this.zone.run(() =>this.someStrings.push(...theStrings));
},
error=>console.log(error)
);
}
}
最后是应用程序服务(与此问题略有不同):
import {Injectable} from "angular2/core";
import {Observable} from "rxjs/Observable";
import {Subject} from "rxjs/Subject";
import 'rxjs/Rx';
@Injectable()
export class AppService {
constructor(){
console.log('constructor', 'appService');
}
someObservable$:Observable<string[]> = Observable.create(observer => {
const eventSource = new EventSource('/interval-sse-observable');
eventSource.onmessage = x => observer.next(JSON.parse(x.data));
eventSource.onerror = x => observer.error(console.log('EventSource failed'));
return () => {
eventSource.close();
};
});
subject$ = new Subject();
refCounted = this.someObservable$.multicast(this.subject$).refCount();
someMethod_() {
let someObservable$:Observable<string[]> = Observable.create(observer => {
const eventSource = new EventSource('/interval-sse-observable');
eventSource.onmessage = x => observer.next(JSON.parse(x.data));
eventSource.onerror = x => observer.error(console.log('EventSource failed'));
return () => {
eventSource.close();
};
});
return someObservable$;
}
}
因此,为了调试 First
和 Second
组件的实例化,我在构造函数/ ngOnInit中添加了一个console.log:
我注意到每次通过点击链接改变路线,我得到:
constructor first
ngOnInit first
constructor second
ngOnInit second
...
有人可以告知这是否是预期的行为?如果是这样,我怎么能让Angular2只实例化我的组件一次?
请注意,我明确要求通过在那里添加 providers
数组在根级别组件中实例化 First
和 Second
组件 .
附:这是该项目的github存储库:https://github.com/balteo/demo-angular2-rxjs/tree/WITH-ROUTER
edit :
我仍在努力找到解决这个问题的方法 . 我的路由器或组件出了问题 . 我已将应用程序推送到github here希望有人可以提供建议 .
3 回答
>= 2.3.0-rc.0
可以实现自定义
RouteReuseStrategy
来控制何时销毁和重新创建或重用路由组件 .https://angular.io/docs/ts/latest/api/router/index/RouteReuseStrategy-class.html
https://www.softwarearchitekt.at/post/2016/12/02/sticky-routes-in-angular-2-3-with-routereusestrategy.aspx
https://github.com/angular/angular/issues/7757
>= 2.0.0
CanReuse
在新路由器中不再存在 . 当只有路由参数在停留在同一路径上时发生更改时,不会重新创建该组件 .如果路径已更改并导航回同一组件,则会重新创建组件 .
<=RC.x
向
providers: []
添加组件几乎毫无意义,尤其是在此问题上 .你可以实现CanReuse
通过增加
对于您的组件,但是对于重用相同实例的哪种导航(如果您保持相同的路由并且仅更改参数),这是非常有限的 .
如果
CanReuse
无法解决您的问题,则将数据移动到保留实例的服务,并将组件的视图绑定到此服务的数据,以使组件显示当前数据 .绝对预期的行为是,随着路线的变化,组件将被构造和破坏 .
请记住,组件与模板的DOM元素密切相关 . 随着这些dom元素被移除或改变,组件也必须被破坏并且构造新组件 .
这有一些例外,但不是因为它与你的用例有关(例如在另一个答案中提到的
CanReuse
方法,但这意味着完全不同的东西 .CanReuse
的作用是从一条路线(让我们称之为route1
)到另一条路线(route2
),并且两条路线都使用相同的组件MyComponent
,如果你告诉它允许重新使用该组件,它基本上是在说:"Since the route I'm going too uses the exact same component type as the route I'm currently on, it's ok to reuse my current component instance on the new route"(也许是因为它是无国籍的) .
您没有确切地说明您要实现的目标,或者为什么仅对您的两个组件实例化一次非常重要,但通常情况下,组件并非用于存储长期应用程序状态(或者更准确地说,超过与组件关联的dom元素的生命周期 . 这些东西应该存在于其他地方(例如服务中),并通过注入(或作为输入传入)在组件之间共享 .
“绝对预期的行为是,随着路线的变化,组件将被构造和破坏”
关键是当组件在两个链接(彼此不同)之间来回导航时,不应一次又一次地重构组件 . 如果您在一个链接上修改了复杂的图形和状态,并且在您返回以恢复工作时该对象被销毁并重新创建,该怎么办?
框架不应该规定是否应该一次又一次地销毁和重新创建对象 . 它应该提供两个选项,并且在默认选项中,它不应该销毁和重新创建,因为这是直观的行为 . 这是大多数UI框架使用的行为这些年 .