diff --git a/nativescript-angular/animations.ts b/nativescript-angular/animations.ts new file mode 100644 index 000000000..30b32f3d6 --- /dev/null +++ b/nativescript-angular/animations.ts @@ -0,0 +1,52 @@ +import { NgModule, Injectable, NgZone, Provider, RendererFactory2 } from "@angular/core"; + +import { + AnimationDriver, + ɵAnimationEngine as AnimationEngine, + ɵAnimationStyleNormalizer as AnimationStyleNormalizer, + ɵWebAnimationsStyleNormalizer as WebAnimationsStyleNormalizer +} from "@angular/animations/browser"; + +import { ɵAnimationRendererFactory as AnimationRendererFactory } from "@angular/platform-browser/animations"; + +import { NativeScriptAnimationEngine } from "./animations/animation-engine"; +import { NativeScriptAnimationDriver } from "./animations/animation-driver"; +import { NativeScriptModule } from "./nativescript.module"; +import { NativeScriptRendererFactory } from "./renderer"; + +@Injectable() +export class InjectableAnimationEngine extends NativeScriptAnimationEngine { + constructor(driver: AnimationDriver, normalizer: AnimationStyleNormalizer) { + super(driver, normalizer); + } +} + +export function instantiateSupportedAnimationDriver() { + return new NativeScriptAnimationDriver(); +} + +export function instantiateRendererFactory( + renderer: NativeScriptRendererFactory, engine: AnimationEngine, zone: NgZone) { + return new AnimationRendererFactory(renderer, engine, zone); +} + +export function instanciateDefaultStyleNormalizer() { + return new WebAnimationsStyleNormalizer(); +} + +export const NATIVESCRIPT_ANIMATIONS_PROVIDERS: Provider[] = [ + {provide: AnimationDriver, useFactory: instantiateSupportedAnimationDriver}, + {provide: AnimationStyleNormalizer, useFactory: instanciateDefaultStyleNormalizer}, + {provide: AnimationEngine, useClass: InjectableAnimationEngine}, { + provide: RendererFactory2, + useFactory: instantiateRendererFactory, + deps: [NativeScriptRendererFactory, AnimationEngine, NgZone] + } +]; + +@NgModule({ + imports: [NativeScriptModule], + providers: NATIVESCRIPT_ANIMATIONS_PROVIDERS, +}) +export class NativeScriptAnimationsModule { +} diff --git a/nativescript-angular/animations/animation-driver.ts b/nativescript-angular/animations/animation-driver.ts new file mode 100644 index 000000000..d1dd2b032 --- /dev/null +++ b/nativescript-angular/animations/animation-driver.ts @@ -0,0 +1,32 @@ +import { AnimationPlayer } from "@angular/animations"; +import { NgView } from "../element-registry"; + +import { NativeScriptAnimationPlayer } from "./animation-player"; +import { Keyframe } from "./utils"; + +export abstract class AnimationDriver { + abstract animate( + element: any, + keyframes: Keyframe[], + duration: number, + delay: number, + easing: string + ): AnimationPlayer; +} + +export class NativeScriptAnimationDriver implements AnimationDriver { + computeStyle(element: NgView, prop: string): string { + return element.style[`css-${prop}`]; + } + + animate( + element: NgView, + keyframes: Keyframe[], + duration: number, + delay: number, + easing: string + ): AnimationPlayer { + return new NativeScriptAnimationPlayer( + element, keyframes, duration, delay, easing); + } +} diff --git a/nativescript-angular/animations/animation-engine.ts b/nativescript-angular/animations/animation-engine.ts new file mode 100644 index 000000000..f9fab7b44 --- /dev/null +++ b/nativescript-angular/animations/animation-engine.ts @@ -0,0 +1,143 @@ +import { ɵDomAnimationEngine as DomAnimationEngine } from "@angular/animations/browser"; +import { AnimationEvent, AnimationPlayer } from "@angular/animations"; + +import { NgView } from "../element-registry"; +import { + copyArray, + cssClasses, + deleteFromArrayMap, + eraseStylesOverride, + getOrSetAsInMap, + makeAnimationEvent, + optimizeGroupPlayer, + setStyles, +} from "./dom-utils"; + +const MARKED_FOR_ANIMATION = "ng-animate"; + +interface QueuedAnimationTransitionTuple { + element: NgView; + player: AnimationPlayer; + triggerName: string; + event: AnimationEvent; +} + +// we are extending Angular's animation engine and +// overriding a few methods that work on the DOM +export class NativeScriptAnimationEngine extends DomAnimationEngine { + // this method is almost completely copied from + // the original animation engine, just replaced + // a few method invocations with overriden ones + animateTransition(element: NgView, instruction: any): AnimationPlayer { + const triggerName = instruction.triggerName; + + let previousPlayers: AnimationPlayer[]; + if (instruction.isRemovalTransition) { + previousPlayers = this._onRemovalTransitionOverride(element); + } else { + previousPlayers = []; + const existingTransitions = this._getTransitionAnimation(element); + const existingPlayer = existingTransitions ? existingTransitions[triggerName] : null; + if (existingPlayer) { + previousPlayers.push(existingPlayer); + } + } + + // it's important to do this step before destroying the players + // so that the onDone callback below won"t fire before this + eraseStylesOverride(element, instruction.fromStyles); + + // we first run this so that the previous animation player + // data can be passed into the successive animation players + let totalTime = 0; + const players = instruction.timelines.map(timelineInstruction => { + totalTime = Math.max(totalTime, timelineInstruction.totalTime); + return (this)._buildPlayer(element, timelineInstruction, previousPlayers); + }); + + previousPlayers.forEach(previousPlayer => previousPlayer.destroy()); + const player = optimizeGroupPlayer(players); + player.onDone(() => { + player.destroy(); + const elmTransitionMap = this._getTransitionAnimation(element); + if (elmTransitionMap) { + delete elmTransitionMap[triggerName]; + if (Object.keys(elmTransitionMap).length === 0) { + (this)._activeTransitionAnimations.delete(element); + } + } + deleteFromArrayMap((this)._activeElementAnimations, element, player); + setStyles(element, instruction.toStyles); + }); + + const elmTransitionMap = getOrSetAsInMap((this)._activeTransitionAnimations, element, {}); + elmTransitionMap[triggerName] = player; + + this._queuePlayerOverride( + element, triggerName, player, + makeAnimationEvent( + element, triggerName, instruction.fromState, instruction.toState, + null, // this will be filled in during event creation + totalTime)); + + return player; + } + + // overriden to use eachChild method of View + // instead of DOM querySelectorAll + private _onRemovalTransitionOverride(element: NgView): AnimationPlayer[] { + // when a parent animation is set to trigger a removal we want to + // find all of the children that are currently animating and clear + // them out by destroying each of them. + let elms = []; + element.eachChild(child => { + if (cssClasses(child).get(MARKED_FOR_ANIMATION)) { + elms.push(child); + } + + return true; + }); + + for (let i = 0; i < elms.length; i++) { + const elm = elms[i]; + const activePlayers = this._getElementAnimation(elm); + if (activePlayers) { + activePlayers.forEach(player => player.destroy()); + } + + const activeTransitions = this._getTransitionAnimation(elm); + if (activeTransitions) { + Object.keys(activeTransitions).forEach(triggerName => { + const player = activeTransitions[triggerName]; + if (player) { + player.destroy(); + } + }); + } + } + + // we make a copy of the array because the actual source array is modified + // each time a player is finished/destroyed (the forEach loop would fail otherwise) + return copyArray(this._getElementAnimation(element)); + } + + // overriden to use cssClasses method to access native element's styles + // instead of DOM element's classList + private _queuePlayerOverride( + element: NgView, triggerName: string, player: AnimationPlayer, event: AnimationEvent) { + const tuple = { element, player, triggerName, event }; + (this)._queuedTransitionAnimations.push(tuple); + player.init(); + + cssClasses(element).set(MARKED_FOR_ANIMATION, true); + player.onDone(() => cssClasses(element).set(MARKED_FOR_ANIMATION, false)); + } + + private _getElementAnimation(element: NgView) { + return (this)._activeElementAnimations.get(element); + } + + private _getTransitionAnimation(element: NgView) { + return (this)._activeTransitionAnimations.get(element); + } +} diff --git a/nativescript-angular/animations/animation-player.ts b/nativescript-angular/animations/animation-player.ts new file mode 100644 index 000000000..fcc60d8f1 --- /dev/null +++ b/nativescript-angular/animations/animation-player.ts @@ -0,0 +1,108 @@ +import { AnimationPlayer } from "@angular/animations"; +import { + KeyframeAnimation, + KeyframeAnimationInfo, +} from "tns-core-modules/ui/animation/keyframe-animation"; + +import { NgView } from "../element-registry"; +import { Keyframe, getAnimationCurve, parseAnimationKeyframe } from "./utils"; + +export class NativeScriptAnimationPlayer implements AnimationPlayer { + public parentPlayer: AnimationPlayer = null; + + private _startSubscriptions: Function[] = []; + private _doneSubscriptions: Function[] = []; + private _finished = false; + private _started = false; + private animation: KeyframeAnimation; + + constructor( + private target: NgView, + keyframes: Keyframe[], + duration: number, + delay: number, + easing: string + ) { + this.initKeyframeAnimation(keyframes, duration, delay, easing); + } + + init(): void { + } + + hasStarted(): boolean { + return this._started; + } + + onStart(fn: Function): void { this._startSubscriptions.push(fn); } + onDone(fn: Function): void { this._doneSubscriptions.push(fn); } + onDestroy(fn: Function): void { this._doneSubscriptions.push(fn); } + + play(): void { + if (!this.animation) { + return; + } + + if (!this._started) { + this._started = true; + this._startSubscriptions.forEach(fn => fn()); + this._startSubscriptions = []; + } + + this.animation.play(this.target) + .then(() => this.onFinish()) + .catch((_e) => { }); + } + + pause(): void { + throw new Error("AnimationPlayer.pause method is not supported!"); + } + + finish(): void { + throw new Error("AnimationPlayer.finish method is not supported!"); + } + + reset(): void { + if (this.animation && this.animation.isPlaying) { + this.animation.cancel(); + } + } + + restart(): void { + this.reset(); + this.play(); + } + + destroy(): void { + this.reset(); + this.onFinish(); + } + + setPosition(_p: any): void { + throw new Error("AnimationPlayer.setPosition method is not supported!"); + } + + getPosition(): number { + return 0; + } + + private initKeyframeAnimation(keyframes: Keyframe[], duration: number, delay: number, easing: string) { + let info = new KeyframeAnimationInfo(); + info.isForwards = true; + info.iterations = 1; + info.duration = duration === 0 ? 0.01 : duration; + info.delay = delay; + info.curve = getAnimationCurve(easing); + info.keyframes = keyframes.map(parseAnimationKeyframe); + + this.animation = KeyframeAnimation.keyframeAnimationFromInfo(info); + } + + private onFinish() { + if (!this._finished) { + this._finished = true; + this._started = false; + this._doneSubscriptions.forEach(fn => fn()); + this._doneSubscriptions = []; + } + } +} diff --git a/nativescript-angular/animations/dom-utils.ts b/nativescript-angular/animations/dom-utils.ts new file mode 100644 index 000000000..00584f70e --- /dev/null +++ b/nativescript-angular/animations/dom-utils.ts @@ -0,0 +1,77 @@ +import { + AnimationEvent, + AnimationPlayer, + NoopAnimationPlayer, + ɵAnimationGroupPlayer, + ɵStyleData, +} from "@angular/animations"; +import { unsetValue } from "tns-core-modules/ui/core/view"; + +import { NgView } from "../element-registry"; + +// overriden to use the default 'unsetValue' +// instead of empty string '' +export function eraseStylesOverride(element: NgView, styles: ɵStyleData) { + if (element["style"]) { + Object.keys(styles).forEach(prop => { + element.style[prop] = unsetValue; + }); + } +} + +export function cssClasses(element: NgView) { + if (!element.ngCssClasses) { + element.ngCssClasses = new Map(); + } + return element.ngCssClasses; +} + +// The following functions are from +// the original DomAnimationEngine +export function getOrSetAsInMap(map: Map, key: any, defaultValue: any) { + let value = map.get(key); + if (!value) { + map.set(key, value = defaultValue); + } + return value; +} + +export function deleteFromArrayMap(map: Map, key: any, value: any) { + let arr = map.get(key); + if (arr) { + const index = arr.indexOf(value); + if (index >= 0) { + arr.splice(index, 1); + if (arr.length === 0) { + map.delete(key); + } + } + } +} + +export function optimizeGroupPlayer(players: AnimationPlayer[]): AnimationPlayer { + switch (players.length) { + case 0: + return new NoopAnimationPlayer(); + case 1: + return players[0]; + default: + return new ɵAnimationGroupPlayer(players); + } +} + +export function copyArray(source: any[]): any[] { + return source ? source.splice(0) : []; +} + +export function makeAnimationEvent( + element: NgView, triggerName: string, fromState: string, toState: string, phaseName: string, + totalTime: number): AnimationEvent { + return {element, triggerName, fromState, toState, phaseName, totalTime}; +} + +export function setStyles(element: NgView, styles: ɵStyleData) { + if (element["style"]) { + Object.keys(styles).forEach(prop => element.style[prop] = styles[prop]); + } +} diff --git a/nativescript-angular/animations/utils.ts b/nativescript-angular/animations/utils.ts new file mode 100644 index 000000000..05833e3a3 --- /dev/null +++ b/nativescript-angular/animations/utils.ts @@ -0,0 +1,139 @@ +import { + KeyframeDeclaration, + KeyframeInfo, +} from "tns-core-modules/ui/animation/keyframe-animation"; +import { CssAnimationProperty } from "tns-core-modules/ui/core/properties"; +import { AnimationCurve } from "tns-core-modules/ui/enums"; + +export interface Keyframe { + [key: string]: string | number; +} + +interface Transformation { + property: string; + value: number | { x: number, y: number }; +} + +const TRANSFORM_MATCHER = new RegExp(/(.+)\((.+)\)/); +const TRANSFORM_SPLITTER = new RegExp(/[\s,]+/); + +const STYLE_TRANSFORMATION_MAP = Object.freeze({ + "scale": value => ({ property: "scale", value }), + "scale3d": value => ({ property: "scale", value }), + "scaleX": value => ({ property: "scale", value: { x: value, y: 1 } }), + "scaleY": value => ({ property: "scale", value: { x: 1, y: value } }), + + "translate": value => ({ property: "translate", value }), + "translate3d": value => ({ property: "translate", value }), + "translateX": value => ({ property: "translate", value: { x: value, y: 0 } }), + "translateY": value => ({ property: "translate", value: { x: 0, y: value } }), + + "rotate": value => ({ property: "rotate", value }), + + "none": _value => [ + { property: "scale", value: { x: 1, y: 1 } }, + { property: "translate", value: { x: 0, y: 0 } }, + { property: "rotate", value: 0 }, + ], +}); + +const STYLE_CURVE_MAP = Object.freeze({ + "ease": AnimationCurve.ease, + "linear": AnimationCurve.linear, + "ease-in": AnimationCurve.easeIn, + "ease-out": AnimationCurve.easeOut, + "ease-in-out": AnimationCurve.easeInOut, + "spring": AnimationCurve.spring, +}); + +export function getAnimationCurve(value: string): any { + if (!value) { + return AnimationCurve.ease; + } + + const curve = STYLE_CURVE_MAP[value]; + if (curve) { + return curve; + } + + const [, property = "", pointsString = ""] = TRANSFORM_MATCHER.exec(value) || []; + const coords = pointsString.split(TRANSFORM_SPLITTER).map(stringToBezieCoords); + + if (property !== "cubic-bezier" || coords.length !== 4) { + throw new Error(`Invalid value for animation: ${value}`); + } else { + return (AnimationCurve).cubicBezier(...coords); + } +} + +export function parseAnimationKeyframe(styles: Keyframe) { + let keyframeInfo = {}; + keyframeInfo.duration = styles.offset; + keyframeInfo.declarations = Object.keys(styles).reduce((declarations, prop) => { + let value = styles[prop]; + + const property = CssAnimationProperty._getByCssName(prop); + if (property) { + if (typeof value === "string" && property._valueConverter) { + value = property._valueConverter(value); + } + declarations.push({ property: property.name, value }); + } else if (typeof value === "string" && prop === "transform") { + declarations.push(...parseTransformation(value)); + } + + return declarations; + }, new Array()); + + return keyframeInfo; +} + +function stringToBezieCoords(value: string): number { + let result = parseFloat(value); + if (result < 0) { + return 0; + } else if (result > 1) { + return 1; + } + + return result; +} + +function parseTransformation(styleString: string): KeyframeDeclaration[] { + return parseStyle(styleString) + .reduce((transformations, style) => { + const transform = STYLE_TRANSFORMATION_MAP[style.property](style.value); + + if (Array.isArray(transform)) { + transformations.push(...transform); + } else if (typeof transform !== "undefined") { + transformations.push(transform); + } + + return transformations; + }, new Array()); +} + +function parseStyle(text: string): Transformation[] { + return text.split(TRANSFORM_SPLITTER).map(stringToTransformation).filter(t => !!t); +} + +function stringToTransformation(text: string): Transformation { + const [, property = "", stringValue = ""] = TRANSFORM_MATCHER.exec(text) || []; + if (!property) { + return; + } + + const [x, y] = stringValue.split(",").map(parseFloat); + if (x && y) { + return { property, value: {x, y} }; + } else { + let value: number = x; + + if (stringValue.slice(-3) === "rad") { + value *= 180.0 / Math.PI; + } + + return { property, value }; + } +} diff --git a/nativescript-angular/dom-adapter.ts b/nativescript-angular/dom-adapter.ts index e558c7832..119e41d91 100644 --- a/nativescript-angular/dom-adapter.ts +++ b/nativescript-angular/dom-adapter.ts @@ -49,6 +49,7 @@ export class NativeScriptDomAdapter implements ɵDomAdapter { getProperty(_el: Element, _name: string): any { throw new Error("Not implemented!") } invoke(_el: Element, _methodName: string, _args: any[]): any { throw new Error("Not implemented!") } + contains(_nodeA: any, _nodeB: any): any /** TODO #9100 */ { throw new Error("Not implemented!") } parse(_templateHtml: string): any /** TODO #9100 */ { throw new Error("Not implemented!") } query(_selector: string): any { throw new Error("Not implemented!") } querySelector(_el: any /** TODO #9100 */, _selector: string): HTMLElement { throw new Error("Not implemented!") } diff --git a/nativescript-angular/package.json b/nativescript-angular/package.json index 5c9133f34..60de0b199 100644 --- a/nativescript-angular/package.json +++ b/nativescript-angular/package.json @@ -25,15 +25,14 @@ }, "dependencies": { "nativescript-intl": "~0.0.8", - "@angular/animations": "4.0.0-rc.3", - "@angular/core": "4.0.0-rc.3", - "@angular/common": "4.0.0-rc.3", - "@angular/compiler": "4.0.0-rc.3", - "@angular/http": "4.0.0-rc.3", - "@angular/platform-browser": "4.0.0-rc.3", - "@angular/platform-browser-dynamic": "4.0.0-rc.3", - "@angular/forms": "4.0.0-rc.3", - "@angular/router": "4.0.0-rc.3", + "@angular/core": "4.0.0-rc.5", + "@angular/common": "4.0.0-rc.5", + "@angular/compiler": "4.0.0-rc.5", + "@angular/http": "4.0.0-rc.5", + "@angular/platform-browser": "4.0.0-rc.5", + "@angular/platform-browser-dynamic": "4.0.0-rc.5", + "@angular/forms": "4.0.0-rc.5", + "@angular/router": "4.0.0-rc.5", "rxjs": "^5.0.1", "reflect-metadata": "~0.1.8", "punycode": "1.3.2", @@ -41,12 +40,13 @@ "url": "0.10.3" }, "devDependencies": { - "@angular/compiler-cli": "4.0.0-rc.3", + "@angular/animations": "4.0.0-rc.5", + "@angular/compiler-cli": "4.0.0-rc.5", "codelyzer": "~2.0.0", "tns-core-modules": "internal-preview", "tslint": "~4.4.0", "typescript": "~2.2.1", - "zone.js": "^0.7.2" + "zone.js": "^0.8.2" }, "nativescript": {} } diff --git a/nativescript-angular/postinstall.js b/nativescript-angular/postinstall.js index c0eb367da..28b8ea45f 100644 --- a/nativescript-angular/postinstall.js +++ b/nativescript-angular/postinstall.js @@ -1,9 +1,21 @@ var fs = require("fs"); var os = require("os"); +var path = require("path"); var hookHelper = require("./hooks/hook-helper"); var projectDir = hookHelper.findProjectDir(); if (projectDir) { + var bundlePath = path.join(projectDir, "node_modules/@angular/animations/browser/package.json"); + + try { + var content = require(bundlePath); + content.main = "../bundles/animations-browser.umd.js"; + console.log(content) + fs.writeFileSync(bundlePath, JSON.stringify(content), "utf8"); + } catch(e) { + console.error(e.message); + } + var hooksDir = hookHelper.getHooksDir(), beforeLivesyncHookDir = hookHelper.getBeforeLivesyncHookDir(), content = 'module.exports = require("nativescript-angular/hooks/before-livesync");'; diff --git a/nativescript-angular/renderer.ts b/nativescript-angular/renderer.ts index 7352f96a6..71a83cb4c 100644 --- a/nativescript-angular/renderer.ts +++ b/nativescript-angular/renderer.ts @@ -1,7 +1,7 @@ import { Inject, Injectable, Optional, NgZone, Renderer2, RendererFactory2, RendererType2, - ViewEncapsulation, + RendererStyleFlags2, ViewEncapsulation, } from "@angular/core"; import { APP_ROOT_VIEW, DEVICE } from "./platform-providers"; import { isBlank } from "./lang-facade"; @@ -173,12 +173,12 @@ export class NativeScriptRenderer extends Renderer2 { this.viewUtil.removeClass(view, name); } - setStyle(view: NgView, styleName: string, value: any, _hasVendorPrefix?: boolean, _hasImportant?: boolean): void { + setStyle(view: NgView, styleName: string, value: any, _flags?: RendererStyleFlags2): void { traceLog(`NativeScriptRenderer.setStyle: ${styleName} = ${value}`); this.viewUtil.setStyle(view, styleName, value); } - removeStyle(view: NgView, styleName: string, _hasVendorPrefix?: boolean): void { + removeStyle(view: NgView, styleName: string, _flags?: RendererStyleFlags2): void { traceLog("NativeScriptRenderer.removeStyle: ${styleName}"); this.viewUtil.removeStyle(view, styleName); } diff --git a/nativescript-angular/router/page-router-outlet.ts b/nativescript-angular/router/page-router-outlet.ts index b4b9a2797..1208dd214 100644 --- a/nativescript-angular/router/page-router-outlet.ts +++ b/nativescript-angular/router/page-router-outlet.ts @@ -1,6 +1,6 @@ import { Attribute, ComponentFactory, ComponentRef, Directive, - ReflectiveInjector, ResolvedReflectiveProvider, ViewContainerRef, + ViewContainerRef, Inject, ComponentFactoryResolver, Injector } from "@angular/core"; import { isPresent } from "../lang-facade"; @@ -68,7 +68,9 @@ export class PageRouterOutlet { // tslint:disable-line:directive-class-suffix public outletMap: RouterOutletMap; - get locationInjector(): Injector { return this.containerRef.injector; } + /** @deprecated from Angular since v4 */ + get locationInjector(): Injector { return this.location.injector; } + /** @deprecated from Angular since v4 */ get locationFactoryResolver(): ComponentFactoryResolver { return this.resolver; } get isActivated(): boolean { @@ -92,7 +94,7 @@ export class PageRouterOutlet { // tslint:disable-line:directive-class-suffix constructor( parentOutletMap: RouterOutletMap, - private containerRef: ViewContainerRef, + private location: ViewContainerRef, @Attribute("name") name: string, private locationStrategy: NSLocationStrategy, private componentFactoryResolver: ComponentFactoryResolver, @@ -145,37 +147,35 @@ export class PageRouterOutlet { // tslint:disable-line:directive-class-suffix * Called by the Router to instantiate a new component during the commit phase of a navigation. * This method in turn is responsible for calling the `routerOnActivate` hook of its child. */ - activate( - activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver, injector: Injector, - providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap): void { + activateWith( + activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null, + outletMap: RouterOutletMap): void { this.outletMap = outletMap; this.currentActivatedRoute = activatedRoute; + resolver = resolver || this.resolver; + if (this.locationStrategy._isPageNavigatingBack()) { this.activateOnGoBack(activatedRoute, outletMap); } else { - this.activateOnGoForward(activatedRoute, providers, outletMap, resolver, injector); + this.activateOnGoForward(activatedRoute, outletMap, resolver); } } private activateOnGoForward( activatedRoute: ActivatedRoute, - providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap, - loadedResolver: ComponentFactoryResolver, - injector: Injector): void { + loadedResolver: ComponentFactoryResolver): void { const factory = this.getComponentFactory(activatedRoute, loadedResolver); const pageRoute = new PageRoute(activatedRoute); - providers = [...providers, ...ReflectiveInjector.resolve( - [{ provide: PageRoute, useValue: pageRoute }])]; + const inj = new OutletInjector(activatedRoute, outletMap, this.location.injector); if (this.isInitialPage) { log("PageRouterOutlet.activate() initial page - just load component"); this.isInitialPage = false; - const inj = ReflectiveInjector.fromResolvedProviders(providers, injector); - this.currentActivatedComp = this.containerRef.createComponent( - factory, this.containerRef.length, inj, []); + this.currentActivatedComp = this.location.createComponent( + factory, this.location.length, inj, []); this.currentActivatedComp.changeDetectorRef.detectChanges(); @@ -189,13 +189,8 @@ export class PageRouterOutlet { // tslint:disable-line:directive-class-suffix isNavigation: true, componentType: factory.componentType }); - const pageResolvedProvider = ReflectiveInjector.resolve([ - { provide: Page, useValue: page } - ]); - const childInjector = ReflectiveInjector.fromResolvedProviders( - [...providers, ...pageResolvedProvider], injector); - const loaderRef = this.containerRef.createComponent( - this.detachedLoaderFactory, this.containerRef.length, childInjector, []); + const loaderRef = this.location.createComponent( + this.detachedLoaderFactory, this.location.length, inj, []); loaderRef.changeDetectorRef.detectChanges(); this.currentActivatedComp = loaderRef.instance.loadWithFactory(factory); @@ -275,6 +270,23 @@ export class PageRouterOutlet { // tslint:disable-line:directive-class-suffix } } +class OutletInjector implements Injector { + constructor( + private route: ActivatedRoute, private map: RouterOutletMap, private parent: Injector) { } + + get(token: any, notFoundValue?: any): any { + if (token === ActivatedRoute) { + return this.route; + } + + if (token === RouterOutletMap) { + return this.map; + } + + return this.parent.get(token, notFoundValue); + } +} + function log(msg: string) { routerLog(msg); } diff --git a/ng-sample/app/app.ts b/ng-sample/app/app.ts index 2dc904021..738ca625b 100644 --- a/ng-sample/app/app.ts +++ b/ng-sample/app/app.ts @@ -7,6 +7,8 @@ // "nativescript-angular/application" import should be first in order to load some required settings (like globals and reflect-metadata) import { NativeScriptModule, platformNativeScriptDynamic } from "nativescript-angular/platform"; +import { NativeScriptAnimationsModule } from "nativescript-angular/animations"; + import { onAfterLivesync, onBeforeLivesync } from "nativescript-angular/platform-common"; import { NgModule } from "@angular/core"; import { Router } from "@angular/router"; @@ -69,7 +71,10 @@ import { AnimationStatesTest } from "./examples/animation/animation-states-test" class ExampleModule { } function makeExampleModule(componentType) { - let imports: any[] = [ExampleModule]; + let imports: any[] = [ + NativeScriptAnimationsModule, + ExampleModule, + ]; if (componentType.routes) { imports.push(NativeScriptRouterModule.forRoot(componentType.routes)) } @@ -82,20 +87,22 @@ function makeExampleModule(componentType) { entries = componentType.entries; } entries.push(componentType); + let providers = []; if (componentType.providers) { - providers = componentType.providers; + providers = [componentType.providers]; } + @NgModule({ bootstrap: [componentType], - imports: imports, + imports, entryComponents: entries, declarations: [ ...entries, ...exports, ], - providers: providers, - exports: exports, + providers, + exports, }) class ExampleModuleForComponent { } @@ -111,7 +118,7 @@ const customPageFactoryProvider = { } }; -platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RendererTest)); +// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RendererTest)); // platformNativeScriptDynamic(undefined, [customPageFactoryProvider]).bootstrapModule(makeExampleModule(RendererTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(TabViewTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(Benchmark)); @@ -134,7 +141,7 @@ platformNativeScriptDynamic().bootstrapModule(makeExampleModule(RendererTest)); // animations // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationStatesTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationNgClassTest)); -// platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationKeyframesTest)); +platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationKeyframesTest)); // platformNativeScriptDynamic().bootstrapModule(makeExampleModule(AnimationEnterLeaveTest)); //Livesync test diff --git a/ng-sample/app/examples/animation/animation-keyframes-test.ts b/ng-sample/app/examples/animation/animation-keyframes-test.ts index f4c5a48ca..b9da4a5f0 100644 --- a/ng-sample/app/examples/animation/animation-keyframes-test.ts +++ b/ng-sample/app/examples/animation/animation-keyframes-test.ts @@ -12,7 +12,7 @@ import {Component, trigger, style, animate, state, transition, keyframes } from state('inactive', style({ transform: 'translateX(0)', opacity: 0.2 })), transition('inactive => active', [ animate(300, keyframes([ - style({opacity: 0.2, transform: 'translateX(-100)', offset: 0}), + style({opacity: 0.2, transform: 'translateX(-100),translateY(100)', offset: 0}), style({opacity: 1, transform: 'translateX(15)', offset: 0.3}), style({opacity: 1, transform: 'translateX(0)', offset: 1.0}) ])) diff --git a/ng-sample/package.json b/ng-sample/package.json index ceff974d9..0c0385fc3 100644 --- a/ng-sample/package.json +++ b/ng-sample/package.json @@ -23,20 +23,21 @@ }, "homepage": "https://github.com/NativeScript/template-hello-world", "dependencies": { - "@angular/common": "4.0.0-rc.3", - "@angular/compiler": "4.0.0-rc.3", - "@angular/core": "4.0.0-rc.3", - "@angular/forms": "4.0.0-rc.3", - "@angular/http": "4.0.0-rc.3", - "@angular/platform-browser": "4.0.0-rc.3", - "@angular/platform-browser-dynamic": "4.0.0-rc.3", - "@angular/router": "4.0.0-rc.3", + "@angular/animations": "4.0.0-rc.5", + "@angular/common": "4.0.0-rc.5", + "@angular/compiler": "4.0.0-rc.5", + "@angular/core": "4.0.0-rc.5", + "@angular/forms": "4.0.0-rc.5", + "@angular/http": "4.0.0-rc.5", + "@angular/platform-browser": "4.0.0-rc.5", + "@angular/platform-browser-dynamic": "4.0.0-rc.5", + "@angular/router": "4.0.0-rc.5", "nativescript-angular": "file:../nativescript-angular", "rxjs": "~5.0.1", - "tns-core-modules": "internal-preview" + "tns-core-modules": "internal-preview", + "zone.js": "~0.8.2" }, "devDependencies": { - "zone.js": "~0.7.2", "babel-traverse": "6.9.0", "babel-types": "6.10.0", "babylon": "6.8.1", diff --git a/tests/package.json b/tests/package.json index f65d641aa..8258c4c40 100644 --- a/tests/package.json +++ b/tests/package.json @@ -26,16 +26,17 @@ ], "homepage": "http://nativescript.org", "dependencies": { - "@angular/common": "4.0.0-rc.3", - "@angular/compiler": "4.0.0-rc.3", - "@angular/core": "4.0.0-rc.3", - "@angular/http": "4.0.0-rc.3", - "@angular/platform-browser": "4.0.0-rc.3", - "@angular/platform-browser-dynamic": "4.0.0-rc.3", - "@angular/router": "4.0.0-rc.3", + "@angular/common": "4.0.0-rc.5", + "@angular/compiler": "4.0.0-rc.5", + "@angular/core": "4.0.0-rc.5", + "@angular/http": "4.0.0-rc.5", + "@angular/platform-browser": "4.0.0-rc.5", + "@angular/platform-browser-dynamic": "4.0.0-rc.5", + "@angular/router": "4.0.0-rc.5", "nativescript-angular": "internal-preview", "nativescript-unit-test-runner": "^0.3.4", "rxjs": "~5.0.1", + "zone.js": "^0.8.2", "tns-core-modules": "internal-preview" }, "devDependencies": { @@ -58,8 +59,7 @@ "socket.io": "1.4.8", "socket.io-client": "1.4.8", "typescript": "~2.2.0", - "wd": "0.4.0", - "zone.js": "^0.7.2" + "wd": "0.4.0" }, "scripts": { "updateTests": "grunt updateTests",