在Angular中,有一种方法可以在模块热重新加载后保留应用程序状态吗?与VueJS中发生的情况类似:
到目前为止,我已经让HMR在几个教程之后工作了,但它所做的只是重新加载应用程序而不进行实际的页面刷新。快满了,是的。但仍然不是它可能的地方。
有没有人让它真正起作用?
PS:它与https://github.com/beeman/tutorial-angular-cli-hmr/issues/4
有关答案 0 :(得分:7)
我尝试了上面的鲈鱼方法,并且在使用它时遇到了一些困难。我确实发现它非常有用和信息。使用他的想法,我能够创建一个新的Angular 6应用程序,并通过HMR构建获得应用程序状态。我在Github上创建了一个项目,所以如果他们想要试验它,其他人可以把它拉下来,因为这是最好的学习方法。阅读代码注释并检查控制台日志,以了解事情发生的顺序以及它们的工作方式。
https://github.com/ermcgrat/NgStarter
克隆项目,执行npm安装,然后使用“ npm run start ”运行应用程序。尝试更改AppComponent中的代码,看看hmr的工作原理。
简而言之,我能够通过创建状态服务来实现状态持久性,并在我的 AppModule 和 hmrBootstrap 中利用它。首先,我开始使用Angular CLI团队指定的基本HMR功能:
https://github.com/angular/angular-cli/wiki/stories-configure-hmr
这将使HMR工作,但它不会持续状态。我扩展了 hmr.ts 文件,以便在处理(卸载)模块时保存我们的状态。评估新模块时,它将从HMR模块读取此保存状态并将其注入我们的新模块:
<强> hmr.ts 强>
export const hmrBootstrap = (module: any, bootstrap: () => Promise<NgModuleRef<any>>) => {
module.hot.accept();
bootstrap().then(mod => {
// Attach a dispose handler. When this module is replaced, we will first run this code before
// evaluating the new module. (eg. running main.ts)
module.hot.dispose(data => {
if (mod.instance.hmrOnDestroy) {
mod.instance.hmrOnDestroy(data);
}
const appRef: ApplicationRef = mod.injector.get(ApplicationRef);
const elements = appRef.components.map(c => c.location.nativeElement);
const makeVisible = createNewHosts(elements);
mod.destroy();
makeVisible();
});
// Does this module have an hmrOnInit method for us to run?
// And is there state data from previous unloaded module to initalize?
let prevData;
if (module.hot.data && module.hot.data.appState) {
prevData = module.hot.data.appState;
}
if (mod.instance.hmrOnInit && prevData) {
mod.instance.hmrOnInit(prevData);
}
});
};
这是我们的AppModule,实现上面使用的 hmrOnInit 和 hmrOnDestroy 方法。值得注意的是hmrOnInit如何通过状态服务恢复应用程序状态(如果存在)。
<强> app.module.ts 强>
export class AppModule {
constructor(private appRef: ApplicationRef, private stateService: AppStateService) { }
hmrOnInit(prevState: any) {
if (prevState) {
this.stateService.saveAppState(prevState);
// change detection.
this.appRef.tick();
}
}
hmrOnDestroy(data: any) {
// Here we will increment our hmrBuilds counter, and then save our state to
// data (module.hot.data), so that it will be available to the new module.
const hmrBuilds = this.stateService.getHmrBuilds() + 1;
this.stateService.saveHmrBuilds(hmrBuilds);
data.appState = this.stateService.getAppState();
}
}
最后是 AppStateService 。唯一特别棘手的是我们基本上以两种形式维护应用程序状态。其中一个是用于同步访问的普通旧香草对象(这是HMR重建所必需的,因为在评估新模块之前,无法保证模块配置中的异步功能完成)。第二个是我们的应用程序状态的可观察版本,因此各种组件可以轻松地观察状态的更改/更新。
<强> app.state.service.ts 强>
export class AppStateService {
// attach various component states to this object
// We maintain an object for synchronous use by the HMR, and an Observable for use by the application and its templates.
private appState: IAppState = { hmrBuilds: 0 };
private appStateSubject = new BehaviorSubject<IAppState>({ hmrBuilds: 0 });
public appState$: Observable<IAppState> = this.appStateSubject.asObservable();
constructor() { }
public getAppState() {
return this.appState;
}
public getHmrBuilds(): number {
return this.appState.hmrBuilds ? this.appState.hmrBuilds : 0;
}
public saveAppState(newState: IAppState) {
this.appState = newState;
this.appStateSubject.next(newState);
}
public saveHmrBuilds(buildNum: number) {
this.appState.hmrBuilds = buildNum;
}
}
最后,任何应用程序组件现在都可以观察到 appState $ 并在其组件代码或模板中使用它。
我还想指出,这种在HMR构建之间维持状态的方法实质上导致单一的事实来源。在我看来,像 ngrx 这样的州立图书馆可以完美地与这样的东西整合。
答案 1 :(得分:6)
好问题。经过很多头痛之后,我终于让webpack hmr工作并保持角度状态。 我的最终实现使用新的environment.hmr.ts作为在生产中或在测试开发服务器上禁用hmr的方法。我没有实现建议的.hmr文件,而是将该代码留在main.ts
中请考虑这一点:您在文件中进行更改并保存。 Webpack编译,magic happens,您的Angular应用程序即将拆除并与新更改交换。
Webpack在def interpolate(R_V, rawpoints, Elist, j):
pointslist = []
if R_V == 4.0:
for i in range(len(rawpoints)):
newlst = re.split('(?!\S)\s(?=\S)|(?!\S)\s+(?=\S)', rawpoints[i])
pointslist.append(newlst)
pointslist = pointslist[3:]
lambdalist = [float(item[0]) for item in pointslist]
k_abslist = [float(item[4]) for item in pointslist]
xvallist = [(c*h)/(lamb*1e-6) for lamb in lambdalist]
k_interp = scipy.interpolate.interp1d(xvallist, k_abslist)
return k_interp(Elist[j])
上调用我们的函数,后者又调用前一个应用程序的AppModule类中的module['hot']['dispose']
,让我们有机会保存我们的状态。
然后Webpack加载我们的新应用程序,它引导并在AppModule上调用OnDestroy
,并传递它是我们状态的OnInit
。
You can read more about what these 'hot' things mean
main.ts
import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } function bootstrap(AppModule) { return platformBrowserDynamic().bootstrapModule(AppModule) .then(MODULE_REF => { if (environment.hmr) { if (module['hot']) { module['hot']['accept'](); if (MODULE_REF.instance['OnInit']) { if (module['hot']['data']) { // Calls OnInit on app.module.ts MODULE_REF.instance['OnInit'](module['hot']['data']); } } if (MODULE_REF.instance['OnStatus']) { module['hot']['apply']((status) => { MODULE_REF.instance['OnStatus'](status); }); } if (MODULE_REF.instance['OnCheck']) { module['hot']['check']((err, outdatedModules) => { MODULE_REF.instance['OnCheck'](err, outdatedModules); }); } if (MODULE_REF.instance['OnDecline']) { module['hot']['decline']((dependencies) => { MODULE_REF.instance['OnDecline'](dependencies); }); } module['hot']['dispose'](store => { if (MODULE_REF.instance['OnDestroy']) { // Calls OnDestroy on app.module.ts MODULE_REF.instance['OnDestroy'](store); } MODULE_REF.destroy(); if (MODULE_REF.instance['AfterDestroy']) { MODULE_REF.instance['AfterDestroy'](store); } }); } } return MODULE_REF; }); } bootstrap(AppModule);
在这里,我们可以将之前应用的状态存储在作为商店参数传入的module['hot']['data']
中。同样的商店参数会传递到新应用的module['hot']['data']
中,从而使我们能够在新应用中维护任何状态对象。
app.module.ts
export class AppModule { constructor(private _state: StateService) { } OnInit(store) { if (store !== undefined) { this._state.SetState(store.State); } } OnDestroy(store) { store.State = this._state; } }
这是我的基本国家服务。您可能更喜欢在此处使用ngrx进行州管理,但我觉得这对我的项目来说太过分了。
state.service.ts
import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; @Injectable() export class StateService { public SideNavIsOpen: BehaviorSubject; constructor() { this.SideNavIsOpen = new BehaviorSubject(false); } public SetState(_state: StateService) { this.SideNavIsOpen = _state.SideNavIsOpen; } }