diff --git a/e2e/modal-navigation-ng/app/home/home.component.ts b/e2e/modal-navigation-ng/app/home/home.component.ts index 72e8f889e..c40be5634 100644 --- a/e2e/modal-navigation-ng/app/home/home.component.ts +++ b/e2e/modal-navigation-ng/app/home/home.component.ts @@ -10,6 +10,7 @@ import { ModalViewComponent } from "../modal-shared/modal-view.component"; import { confirm } from "tns-core-modules/ui/dialogs"; import { AppModule } from "../app.module"; +import { PageService } from "nativescript-angular"; @Component({ moduleId: module.id, @@ -21,7 +22,10 @@ export class HomeComponent { private modal: ModalDialogService, private vcRef: ViewContainerRef, private viewContainerRefService: ViewContainerRefService, - private routerExtension: RouterExtensions) { } + private pageService: PageService, + private routerExtension: RouterExtensions) { + this.pageService.inPage$.subscribe((inPage) => console.log("HomeComponent - inPage", inPage)); + } onNavigateSecond() { this.routerExtension.navigate(["second"]); diff --git a/nativescript-angular/nativescript.module.ts b/nativescript-angular/nativescript.module.ts index 8a4da1ef6..a4a05d68a 100644 --- a/nativescript-angular/nativescript.module.ts +++ b/nativescript-angular/nativescript.module.ts @@ -27,7 +27,7 @@ import { NativeScriptCommonModule } from "./common"; import { NativeScriptRendererFactory } from "./renderer"; import { DetachedLoader } from "./common/detached-loader"; import { throwIfAlreadyLoaded } from "./common/utils"; -import { FrameService } from "./platform-providers"; +import { FrameService, PageService } from "./platform-providers"; export function errorHandlerFactory() { return new ErrorHandler(); @@ -41,6 +41,7 @@ export { DetachedLoader }; ], providers: [ FrameService, + PageService, NativeScriptRendererFactory, SystemJsNgModuleLoader, { provide: APP_ROOT, useValue: true }, diff --git a/nativescript-angular/platform-providers.ts b/nativescript-angular/platform-providers.ts index ebe16e8a8..69b26af05 100644 --- a/nativescript-angular/platform-providers.ts +++ b/nativescript-angular/platform-providers.ts @@ -1,9 +1,11 @@ -import { InjectionToken, Injectable } from "@angular/core"; +import { InjectionToken, Injectable, OnDestroy } from "@angular/core"; -import { Frame } from "tns-core-modules/ui/frame"; +import { Frame, NavigatedData } from "tns-core-modules/ui/frame"; import { View } from "tns-core-modules/ui/core/view"; import { Page } from "tns-core-modules/ui/page"; import { device, Device } from "tns-core-modules/platform"; +import { BehaviorSubject, Subject, Observable } from "rxjs"; +import { distinctUntilChanged } from "rxjs/operators"; export const APP_ROOT_VIEW = new InjectionToken("App Root View"); export const DEVICE = new InjectionToken("platform device"); @@ -71,3 +73,45 @@ export class FrameService { return topmostFrame; } } + +@Injectable() +export class PageService implements OnDestroy { + private _inPage$ = new BehaviorSubject(false); + private _pageEvents$ = new Subject(); + + get inPage(): boolean { return this._inPage$.value; } + get inPage$(): Observable { return this._inPage$.pipe(distinctUntilChanged()); } + get pageEvents$(): Observable { return this._pageEvents$.asObservable(); } + constructor(public page: Page) { + if (this.page) { + this.page.on("navigatedFrom", this.pageEvent, this); + this.page.on("navigatedTo", this.pageEvent, this); + this.page.on("navigatingFrom", this.pageEvent, this); + this.page.on("navigatingTo", this.pageEvent, this); + } + } + + ngOnDestroy() { + if (this.page) { + this.page.off("navigatedFrom", this.pageEvent, this); + this.page.off("navigatedTo", this.pageEvent, this); + this.page.off("navigatingFrom", this.pageEvent, this); + this.page.off("navigatingTo", this.pageEvent, this); + } + this._inPage$.complete(); + this._pageEvents$.complete(); + } + + private pageEvent(evt: NavigatedData) { + this._pageEvents$.next(evt); + switch (evt.eventName) { + case "navigatedTo": + this._inPage$.next(true); + break; + case "navigatedFrom": + this._inPage$.next(false); + break; + default: + } + } +} diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index 34ba4446f..05c941c31 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -3,7 +3,7 @@ import { ComponentFactory, ComponentFactoryResolver, ComponentRef, Directive, Inject, InjectionToken, Injector, OnDestroy, EventEmitter, Output, - Type, ViewContainerRef, ElementRef + Type, ViewContainerRef, ElementRef, InjectFlags } from "@angular/core"; import { ActivatedRoute, @@ -19,7 +19,7 @@ import { profile } from "tns-core-modules/profiling"; import { BehaviorSubject } from "rxjs"; -import { DEVICE, PAGE_FACTORY, PageFactory } from "../platform-providers"; +import { DEVICE, PAGE_FACTORY, PageFactory, PageService } from "../platform-providers"; import { routerLog as log, routerError as error, isLogEnabled } from "../trace"; import { DetachedLoader } from "../common/detached-loader"; import { ViewUtil } from "../view-util"; @@ -48,23 +48,28 @@ export function destroyComponentRef(componentRef: ComponentRef) { } } -class ChildInjector implements Injector { - constructor( - private providers: ProviderMap, - private parent: Injector - ) { } - - get(token: Type | InjectionToken, notFoundValue?: T): T { - let localValue = this.providers.get(token); - if (localValue) { - return localValue; +class DestructibleInjector implements Injector { + private refs = new Set(); + constructor(private destructableProviders: ProviderSet, private parent: Injector) { + } + get(token: Type | InjectionToken, notFoundValue?: T, flags?: InjectFlags): T { + const ref = this.parent.get(token, notFoundValue, flags); + if (this.destructableProviders.has(token)) { + this.refs.add(ref); } - - return this.parent.get(token, notFoundValue); + return ref; + } + destroy() { + this.refs.forEach((ref) => { + if (ref.ngOnDestroy instanceof Function) { + ref.ngOnDestroy(); + } + }); + this.refs.clear(); } } -type ProviderMap = Map | InjectionToken, any>; +type ProviderSet = Set | InjectionToken>; /** * There are cases where multiple activatedRoute nodes should be associated/handled by the same PageRouterOutlet. @@ -335,16 +340,24 @@ export class PageRouterOutlet implements OnDestroy { // tslint:disable-line:dire componentType: factory.componentType, }); - const providers = new Map(); - providers.set(Page, page); - providers.set(Frame, this.frame); - providers.set(PageRoute, new PageRoute(activatedRoute)); - providers.set(ActivatedRoute, activatedRoute); - providers.set(ChildrenOutletContexts, this.parentContexts.getOrCreateContext(this.name).children); + const destructables = new Set([PageService]); + const injector = Injector.create({ + providers: [ + { provide: PageService, useClass: PageService, deps: [Page] }, + { provide: Page, useValue: page }, + { provide: Frame, useValue: this.frame }, + { provide: PageRoute, useValue: new PageRoute(activatedRoute) }, + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: ChildrenOutletContexts, + useValue: this.parentContexts.getOrCreateContext(this.name).children } + ], + parent: this.location.injector + }); - const childInjector = new ChildInjector(providers, this.location.injector); + const childInjector = new DestructibleInjector(destructables, injector); const loaderRef = this.location.createComponent( this.detachedLoaderFactory, this.location.length, childInjector, []); + loaderRef.onDestroy(() => childInjector.destroy()); this.changeDetector.markForCheck(); this.activated = loaderRef.instance.loadWithFactory(factory);