Skip to content

uirouter/core v7 #887

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
git config --global user.name uirouter_github_actions
- name: Install Dependencies
run: yarn install --pure-lockfile
- name: Check Peer Dependencies
run: npx check-peer-dependencies
#- name: Check Peer Dependencies
# run: npx check-peer-dependencies
- name: Run yarn ${{ matrix.yarncmd }}
run: yarn ${{ matrix.yarncmd }}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"peerDependencies": {
"@angular/common": "^10.0.0 || ^11.0.0",
"@angular/core": "^10.0.0 || ^11.0.0",
"@uirouter/core": "^6.0.7",
"@uirouter/core": "^7.0.0-beta.3",
"@uirouter/rx": "^0.6.0"
},
"devDependencies": {
Expand All @@ -58,7 +58,7 @@
"@angular/platform-browser-dynamic": "^10.0.0",
"@types/jest": "^26.0.4",
"@types/jquery": "^3.3.33",
"@uirouter/core": "^6.0.7",
"@uirouter/core": "v7beta",
"@uirouter/publish-scripts": "2.5.5",
"@uirouter/rx": "^0.6.0",
"bufferutil": "4.0.2",
Expand Down
14 changes: 8 additions & 6 deletions src/directives/uiSref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import {
OnChanges,
SimpleChanges,
HostListener,
OnInit,
} from '@angular/core';
import { UIView, ParentUIViewInject } from './uiView';
import { UIView } from './uiView';
import { ReplaySubject, Subscription } from 'rxjs';

/**
Expand Down Expand Up @@ -79,7 +80,7 @@ export class AnchorUISref {
selector: '[uiSref]',
exportAs: 'uiSref',
})
export class UISref implements OnChanges {
export class UISref implements OnChanges, OnInit {
/**
* `@Input('uiSref')` The name of the state to link to
*
Expand Down Expand Up @@ -117,16 +118,16 @@ export class UISref implements OnChanges {
/** @internal */ private _statesSub: Subscription;
/** @internal */ private _router: UIRouter;
/** @internal */ private _anchorUISref: AnchorUISref;
/** @internal */ private _parent: ParentUIViewInject;
/** @internal */ private _parentUiViewId: string;

constructor(
_router: UIRouter,
@Optional() _anchorUISref: AnchorUISref,
@Inject(UIView.PARENT_INJECT) parent: ParentUIViewInject
@Inject(UIView.PARENT_UIVIEW_ID_TOKEN) parent: string
) {
this._router = _router;
this._anchorUISref = _anchorUISref;
this._parent = parent;
this._parentUiViewId = parent;

this._statesSub = _router.globals.states$.subscribe(() => this.update());
}
Expand Down Expand Up @@ -180,8 +181,9 @@ export class UISref implements OnChanges {
}

getOptions() {
const parent = this._router.viewService._pluginapi._registeredUIView(this._parentUiViewId);
const defaultOpts: TransitionOptions = {
relative: this._parent && this._parent.context && this._parent.context.name,
relative: parent?.contentState?.name,
inherit: true,
source: 'sref',
};
Expand Down
123 changes: 63 additions & 60 deletions src/directives/uiView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,16 @@ import {
Input,
OnDestroy,
OnInit,
ReflectiveInjector,
ViewChild,
ViewContainerRef,
} from '@angular/core';

import {
ActiveUIView,
filter,
inArray,
isFunction,
NATIVE_INJECTOR_TOKEN,
Param,
parse,
PathNode,
ResolveContext,
StateDeclaration,
Expand All @@ -29,21 +26,11 @@ import {
TransitionHookFn,
UIRouter,
unnestR,
ViewConfig,
ViewContext,
} from '@uirouter/core';
import { UIViewPortalRenderCommand } from '@uirouter/core/lib/view/interface';
import { Ng2ViewConfig } from '../statebuilders/views';
import { MergeInjector } from '../mergeInjector';

/** @hidden */
let id = 0;

/** @internal These are provide()d as the string UIView.PARENT_INJECT */
export interface ParentUIViewInject {
context: ViewContext;
fqn: string;
}

/** @internal */
interface InputMapping {
token: string;
Expand Down Expand Up @@ -113,14 +100,24 @@ const ng2ComponentInputs = (factory: ComponentFactory<any>): InputMapping[] => {
exportAs: 'uiView',
template: `
<ng-template #componentTarget></ng-template>
<ng-content *ngIf="!_componentRef"></ng-content>
<ng-content *ngIf="!_componentRef && !_renderInterop"></ng-content>
<div #interopDiv *ngIf="_renderInterop"></div>
`,
})
export class UIView implements OnInit, OnDestroy {
static PARENT_INJECT = 'UIView.PARENT_INJECT';
/** This injection token is used to inject the parent UIView ID */
static PARENT_UIVIEW_ID_TOKEN = 'UIView.PARENT_INJECT';

@ViewChild('componentTarget', { read: ViewContainerRef, static: true })
_componentTarget: ViewContainerRef;

@ViewChild('interopDiv', { read: ViewContainerRef })
set interopDiv(ref: ViewContainerRef) {
if (this._renderCommand.portalContentType === 'RENDER_INTEROP_DIV') {
this._renderCommand.giveDiv(ref.element.nativeElement);
}
}

@Input('name') name: string;

@Input('ui-view')
Expand All @@ -136,40 +133,41 @@ export class UIView implements OnInit, OnDestroy {
private _deregisterUiCanExitHook: Function;
/** Deregisters the master uiOnParamsChanged transition hook */
private _deregisterUiOnParamsChangedHook: Function;

/** Data about the this UIView */
private _uiViewData: ActiveUIView = <any>{};
private _parent: ParentUIViewInject;
private _id: string;
private _parentUIViewId: string;
protected _renderCommand: UIViewPortalRenderCommand;
/** @internal */
_renderInterop = false;

constructor(
public router: UIRouter,
@Inject(UIView.PARENT_INJECT) parent,
@Inject(UIView.PARENT_UIVIEW_ID_TOKEN) parentUIViewId: string,
public viewContainerRef: ViewContainerRef
) {
this._parent = parent;
this._parentUIViewId = parentUIViewId;
}

private _getViewConfig(): Ng2ViewConfig {
if (this._renderCommand?.portalContentType === 'RENDER_ROUTED_VIEW') {
return this._renderCommand.uiViewPortalRegistration.viewConfig as Ng2ViewConfig;
}
}

/**
* @returns the UI-Router `state` that is filling this uiView, or `undefined`.
*/
public get state(): StateDeclaration {
return parse('_uiViewData.config.viewDecl.$context.self')(this);
return this._renderCommand.portalContentType === 'RENDER_ROUTED_VIEW'
? this._renderCommand.uiViewPortalRegistration.contentState
: undefined;
}

ngOnInit() {
const router = this.router;
const parentFqn = this._parent.fqn;
const name = this.name || '$default';

this._uiViewData = {
$type: 'ng2',
id: id++,
name: name,
fqn: parentFqn ? parentFqn + '.' + name : name,
creationContext: this._parent.context,
configUpdated: this._viewConfigUpdated.bind(this),
config: undefined,
};

this._deregisterUiCanExitHook = router.transitionService.onBefore({}, (trans) => {
return this._invokeUiCanExitHook(trans);
});
Expand All @@ -178,7 +176,8 @@ export class UIView implements OnInit, OnDestroy {
this._invokeUiOnParamsChangedHook(trans)
);

this._deregisterUIView = router.viewService.registerUIView(this._uiViewData);
const renderContentIntoUIViewPortal = this._renderContentIntoUIViewPortal.bind(this);
router.viewService._pluginapi._registerView('ng2', this._parentUIViewId, name, renderContentIntoUIViewPortal);
}

/**
Expand Down Expand Up @@ -212,14 +211,14 @@ export class UIView implements OnInit, OnDestroy {
const uiOnParamsChanged: TransitionHookFn = instance && instance.uiOnParamsChanged;

if (isFunction(uiOnParamsChanged)) {
const viewState: StateDeclaration = this.state;
const resolveContext: ResolveContext = new ResolveContext(this._uiViewData.config.path);
const resolveContext: ResolveContext = new ResolveContext(this._getViewConfig()?.path ?? []);
const viewCreationTrans = resolveContext.getResolvable('$transition$').data;

// Exit early if the $transition$ is the same as the view was created within.
// Exit early if the $transition$ will exit the state the view is for.
if ($transition$ === viewCreationTrans || $transition$.exiting().indexOf(viewState as StateDeclaration) !== -1)
if ($transition$ === viewCreationTrans || $transition$.exiting().indexOf(this.state) !== -1) {
return;
}

const toParams: { [paramName: string]: any } = $transition$.params('to');
const fromParams: { [paramName: string]: any } = $transition$.params('from');
Expand Down Expand Up @@ -249,40 +248,46 @@ export class UIView implements OnInit, OnDestroy {
}

ngOnDestroy() {
if (this._deregisterUIView) this._deregisterUIView();
if (this._id) this.router.viewService._pluginapi._deregisterView(this._id);
if (this._deregisterUiCanExitHook) this._deregisterUiCanExitHook();
if (this._deregisterUiOnParamsChangedHook) this._deregisterUiOnParamsChangedHook();
this._deregisterUIView = this._deregisterUiCanExitHook = this._deregisterUiOnParamsChangedHook = null;
this._disposeLast();
}

private _saveUiViewId(uiViewId: string) {
if (typeof this._id === 'string' && this._id !== uiViewId) {
throw new Error(
`Received a render command for wrong UIView. Render command id: ${uiViewId}, UIView id: ${this._id}`
);
}

this._id = uiViewId;
}

/**
* The view service is informing us of an updated ViewConfig
* The view service is informing us to change what we are rendering in the portal
* (usually because a transition activated some state and its views)
*/
_viewConfigUpdated(config: ViewConfig) {
// The config may be undefined if there is nothing currently targeting this UIView.
// Dispose the current component, if there is one
if (!config) return this._disposeLast();

// Only care about Ng2 configs
if (!(config instanceof Ng2ViewConfig)) return;

// The "new" viewconfig is already applied, so exit early
if (this._uiViewData.config === config) return;

// This is a new ViewConfig. Dispose the previous component
_renderContentIntoUIViewPortal(renderCommand: UIViewPortalRenderCommand): void {
this._renderCommand = renderCommand;
this._saveUiViewId(renderCommand.uiViewPortalRegistration.id); // Dispose the previous component
this._disposeLast();
trace.traceUIViewConfigUpdated(this._uiViewData, config && config.viewDecl.$context);

this._applyUpdatedConfig(config);
// UIView template will handle RENDER_INTEROP_DIV and RENDER_DEFAULT_CONTENT
this._renderInterop = renderCommand.portalContentType === 'RENDER_INTEROP_DIV';

if (renderCommand.portalContentType === 'RENDER_ROUTED_VIEW') {
const registeredportal = this.router.viewService._pluginapi._registeredUIView(this._id);
trace.traceUIViewConfigUpdated(registeredportal as any, this.state.$$state()); // TODO: move to core
this._renderRoutedConfigComponent(renderCommand.uiViewPortalRegistration.viewConfig);
}

// Initiate change detection for the newly created component
this._componentRef.changeDetectorRef.markForCheck();
this._componentRef?.changeDetectorRef.markForCheck();
}

private _applyUpdatedConfig(config: Ng2ViewConfig) {
this._uiViewData.config = config;
private _renderRoutedConfigComponent(config: Ng2ViewConfig) {
// Create the Injector for the routed component
const context = new ResolveContext(config.path);
const componentInjector = this._getComponentInjector(context);
Expand Down Expand Up @@ -317,15 +322,13 @@ export class UIView implements OnInit, OnDestroy {
.filter((r) => r.resolved);

const newProviders = resolvables.map((r) => ({ provide: r.token, useValue: context.injector().get(r.token) }));

const parentInject = { context: this._uiViewData.config.viewDecl.$context, fqn: this._uiViewData.fqn };
newProviders.push({ provide: UIView.PARENT_INJECT, useValue: parentInject });
newProviders.push({ provide: UIView.PARENT_UIVIEW_ID_TOKEN, useValue: this._id });

const parentComponentInjector = this.viewContainerRef.injector;
const moduleInjector = context.getResolvable(NATIVE_INJECTOR_TOKEN).data;
const mergedParentInjector = new MergeInjector(moduleInjector, parentComponentInjector);

return ReflectiveInjector.resolveAndCreate(newProviders, mergedParentInjector);
return Injector.create({ providers: newProviders, parent: mergedParentInjector });
}

/**
Expand All @@ -335,7 +338,7 @@ export class UIView implements OnInit, OnDestroy {
* to the resolve data.
*/
private _applyInputBindings(factory: ComponentFactory<any>, component: any, context: ResolveContext, componentClass) {
const bindings = this._uiViewData.config.viewDecl['bindings'] || {};
const bindings = this._getViewConfig()?.viewDecl?.bindings ?? {};
const explicitBoundProps = Object.keys(bindings);

// Returns the actual component property for a renamed an input renamed using `@Input('foo') _foo`.
Expand Down
8 changes: 2 additions & 6 deletions src/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ import {
Resolvable,
NATIVE_INJECTOR_TOKEN,
} from '@uirouter/core';
import { UIView, ParentUIViewInject } from './directives/uiView';
import { UIView } from './directives/uiView';
import { UIROUTER_MODULE_TOKEN, UIROUTER_ROOT_MODULE } from './injectionTokens';
import { ng2ViewsBuilder, Ng2ViewConfig } from './statebuilders/views';
import { Ng2ViewDeclaration } from './interface';
Expand Down Expand Up @@ -183,17 +183,13 @@ export function appInitializer(router: UIRouter) {
};
}

export function parentUIViewInjectFactory(r: StateRegistry) {
return { fqn: null, context: r.root() } as ParentUIViewInject;
}

export const _UIROUTER_INSTANCE_PROVIDERS: Provider[] = [
{
provide: UIRouter,
useFactory: uiRouterFactory,
deps: [LocationStrategy, UIROUTER_ROOT_MODULE, UIROUTER_MODULE_TOKEN, Injector],
},
{ provide: UIView.PARENT_INJECT, useFactory: parentUIViewInjectFactory, deps: [StateRegistry] },
{ provide: UIView.PARENT_UIVIEW_ID_TOKEN, useValue: null, deps: [StateRegistry] },
{ provide: APP_INITIALIZER, useFactory: appInitializer, deps: [UIRouter], multi: true },
];

Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -763,10 +763,10 @@
dependencies:
"@types/yargs-parser" "*"

"@uirouter/core@^6.0.7":
version "6.0.7"
resolved "https://registry.yarnpkg.com/@uirouter/core/-/core-6.0.7.tgz#a66743dceb13a56d8908474ff891d63e5d5a2b01"
integrity sha512-KUTJxL+6q0PiBnFx4/Z+Hsyg0pSGiaW5yZQeJmUxknecjpTbnXkLU8H2EqRn9N2B+qDRa7Jg8RcgeNDPY72O1w==
"@uirouter/core@v7beta":
version "7.0.0-beta.3"
resolved "https://registry.yarnpkg.com/@uirouter/core/-/core-7.0.0-beta.3.tgz#a4df98267d5aa4f512b545cc1535f20192270f8e"
integrity sha512-T4UT0V2MgadE+Z/ZaB8bc/QhZdvw0foQWWb/BrVCxoKDZJMghxAk7/7PI3KGRTBvJkk+Py0n2INafwGlPH4M+w==

"@uirouter/publish-scripts@2.5.5":
version "2.5.5"
Expand Down