【问题标题】:Why do my angular2 components get re-instantiated each time I change the route?为什么每次更改路线时我的 angular2 组件都会重新实例化?
【发布时间】:2016-04-26 10:11:41
【问题描述】:

我正在尝试制作 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 数组来实例化。

附:这里是这个项目的 github 仓库:https://github.com/balteo/demo-angular2-rxjs/tree/WITH-ROUTER

编辑

我仍在努力寻找解决此问题的方法。我的路由器或我的组件有问题。我已经将应用推送到githubhere希望有人可以提供建议。

【问题讨论】:

  • 你找到解决方案了吗?

标签: angular angular2-routing


【解决方案1】:

>= 2.3.0-rc.0

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

>= 2.0.0

CanReuse 在新路由器中不再存在。当保持在同一路由上时仅更改路由参数时,不会重新创建组件。

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

请注意,我已明确要求通过在根级组件中添加提供程序数组来实例化 First 和 Second 组件。

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

你可以实现CanReuse

通过添加

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

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

如果CanReuse 不能解决您的问题,则将数据移动到服务中,在该服务中保留实例并将组件的视图绑定到该服务的数据,以使组件显示当前数据。

【讨论】:

  • 非常感谢 Günter,我正在尝试实施第二种解决方案(将数据移动到服务中)。当我设法让它工作时,我会接受答案。
  • 我打开了另一个与原帖相关的帖子。见stackoverflow.com/questions/36866215
  • 我已将数据放入服务中,但由于我使用 EventSource 并且组件在每次路由更改时重新实例化,它仍然无法正常工作...查看我的编辑.
  • 您能否更新Plunker 来证明您的问题
  • 我已经更新了插件。这是:plnkr.co/edit/y3MBjvitr6HnR2ymTRe8
【解决方案2】:

当您的路线更改时,组件将被构造和销毁是绝对预期的行为。

请记住,组件与模板的 DOM 元素密切相关。随着这些 dom 元素被删除或更改,组件也必须被销毁并构建一个新组件。

这有一些例外,但与您的用例无关(例如另一个答案中提到的CanReuse 方法,但这是完全不同的。

CanReuse 的作用是从一条路线(我们称之为route1)到另一条路线(route2),并且两条路线使用相同的组件MyComponent,如果你告诉它它是允许的要重用组件,基本上是在说:

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

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

【讨论】:

    【解决方案3】:

    “当你的路由改变时,组件将被构造和销毁是绝对预期的行为”

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

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

    【讨论】:

      猜你喜欢
      • 2017-12-27
      • 1970-01-01
      • 1970-01-01
      • 2019-07-02
      • 2021-06-23
      • 2018-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多