为什么每次更改路线时我的 angular2 组件都会重新实例化?

Why do my angular2 components get re-instantiated each time I change the route?

我正在尝试制作 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$;
    }
}

所以为了调试FirstSecond组件的实例化,我在constructors/ngOnInit中添加了一个console.log:

我注意到每次我通过点击链接更改路线时,我都会得到:

constructor first
ngOnInit first

constructor second
ngOnInit second
...

谁能告诉我这是否是预期的行为?如果是这样,我怎样才能让 Angular2 只实例化我的组件一次?

请注意,我明确要求 FirstSecond 组件通过在根级组件中添加 providers 数组来实例化。

P.S。这是此项目的 github 存储库:https://github.com/balteo/demo-angular2-rxjs/tree/WITH-ROUTER

编辑

我仍在努力寻找解决此问题的方法。我的路由器或组件出现问题。我已经把app推送到githubhere希望有人能提供建议

>=2.3.0-rc.0

可以实施自定义 RouteReuseStrategy 来控制何时销毁、重新创建或重用路由组件。

>=2.0.0

CanReuse 在新路由器中不再存在。如果在同一路线上仅路线参数发生变化,则不会重新创建组件。

如果更改路由并导航回同一组件,则会重新创建该组件。

<=RC.x

Note that I have explicitly required that the First and Second components be instantiated at the root-level component by adding a providers array there.

添加组件到 providers: [] 基本上没有意义,尤其是在这个问题上。

你可以实施CanReuse

通过添加

routerCanReuse(next: ComponentInstruction, prev: ComponentInstruction) { return true; }

到您的组件,但对于重复使用同一实例的哪种导航来说是非常有限的(如果您保持在同一路线上并且只更改参数)。

如果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 元素的生命周期)。这些东西应该存在于其他地方(例如在服务中)并通过注入在组件之间共享(或作为输入传递)。

"It is absolutely expected behavior that the components will be constructed and destructed as your route changes"

关键是组件不应在两个 link 之间来回导航时一次又一次地重建(它们彼此不同)。如果您有复杂的图形并说明您正在对一个 link 进行修改,而当您返回继续工作时该对象被销毁并重新创建,该怎么办?

框架不应该规定对象是否应该被销毁并一次又一次地重新创建。它应该提供两个选项,并且在默认选项中,它不应该销毁和重新创建,因为这是直观的行为。这是大多数 UI 框架多年来使用的行为。