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


我正在尝试组建一个演示应用程序 角度2 + Rx.JS 5 /下一个。

我注意到每次切换路由时都会重新实例化我的组件。

以下是根应用程序的代码:

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只实例化我的组件一次?

请注意,我已经明确要求了 First 和 Second 通过添加a,在根级组件上实例化组件 providers 阵列那里。

附:这是该项目的github存储库: https://github.com/balteo/demo-angular2-rxjs/tree/WITH-ROUTER

编辑

我仍在努力找到解决这个问题的方法。我的路由器或组件出了问题。我已将应用程序推送到github 这里 希望有人可以提供建议。


5921
2018-04-26 10:11


起源

你有没有得到解决方案? - pd farhad


答案:


> = 2.3.0-rc.0

一个习俗 RouteReuseStrategy 可以实现控制何时销毁和重新创建或重用路由组件。

> = 2.0.0

CanReuse 在新路由器中不再存在。当只有路由参数在停留在同一路径上时发生更改时,不会重新创建该组件。

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

<= RC.x

请注意,我明确要求通过在那里添加providers数组,在根级别组件中实例化First和Second组件。

添加组件到 providers: [] 对于这个问题,尤其是毫无意义。

你可以实现 CanReuse

通过增加

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

对于你的组件,但是对于重用相同实例的哪种导航(如果你保持相同的路由并且只改变参数),这是非常有限的。

如果 CanReuse 不修复您的问题然后将数据移动到服务,其中保留实例并将组件的视图绑定到此服务的数据,以使组件显示当前数据。


6
2018-04-26 10:16



非常感谢Günter,我正在尝试实施第二个解决方案(将数据移动到服务中)。当我设法让它工作时,我会接受答案。 - balteo
我打开了另一篇与原帖相关的帖子。看到 stackoverflow.com/questions/36866215 - balteo
我已将数据放入服务中,但由于我使用EventSource并且每次路由更改时组件都会重新实例化,因此仍无法正常工作...请参阅我的编辑。 - balteo
你能更新吗? Plunker 证明你的问题 - Günter Zöchbauer
我更新了plunker。这里是: plnkr.co/edit/y3MBjvitr6HnR2ymTRe8 - balteo


绝对预期的行为是在路线改变时构建和破坏组件。

请记住,组件与模板的DOM元素密切相关。随着这些dom元素被移除或改变,组件也必须被破坏并且构造新组件。

这有一些例外,但不是因为它与你的用例有关(例如 CanReuse 在另一个答案中提到的方法,但这意味着完全不同的东西。

什么 CanReuse 是的,是从一条路线出发(让我们称之为 route1)到另一条路线(route2),两条路线都使用相同的组件 MyComponent,如果你告诉它允许重新使用该组件,它基本上是这样说的:

“由于我正在使用的路径也使用与我当前所使用的路径完全相同的组件类型,因此可以在新路由上重用我当前的组件实例”(可能因为它是无状态的)。

您没有确切地说明您要实现的目标,或者为什么仅对您的两个组件实例化一次非常重要,但通常,组件并非用于存储长期应用程序状态(或者更准确地说,超过与组件关联的dom元素的生命周期。这些东西应该存在于其他地方(例如服务中),并通过注入(或作为输入传入)在组件之间共享。


2
2018-04-30 22:21





“绝对预期的行为是,随着路线的变化,组件将被构建和破坏”

关键是组件不应该一次又一次地重建,因为一个链路在两个链路之间来回导航(彼此不同)。如果您在一个链接上修改了复杂的图形和状态,并且在您返回以恢复工作时该对象被销毁并重新创建,该怎么办?

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


1
2018-04-04 17:06