Skip to content

Commit

Permalink
feat(ui-router-ng2): Initial angular2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherthielen committed Feb 19, 2016
1 parent 002e8d1 commit 217de70
Show file tree
Hide file tree
Showing 16 changed files with 1,008 additions and 27 deletions.
40 changes: 24 additions & 16 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,8 @@ module.exports = function (grunt) {
},
clean: [ '<%= builddir %>' ],
ts: {
es5: {
src: files.src,
outDir: '<%= builddir %>/es5',
options: { module: 'commonjs'}
},
es6: {
src: files.src,
outDir: '<%= builddir %>/es6',
options: { target: "es6"}
}
ng1: { tsconfig: 'tsconfig.json' },
ng2: { tsconfig: 'tsconfig-ng2.json' }
},
uglify: {
options: {
Expand All @@ -39,7 +31,7 @@ module.exports = function (grunt) {
},
build: {
files: {
'<%= builddir %>/ui-router.min.js': ['<banner:meta.banner>', '<%= builddir %>/ui-router.js'],
'<%= builddir %>/ui-router-ng2.min.js': ['<banner:meta.banner>', '<%= builddir %>/ui-router-ng2.js'],
'<%= builddir %>/<%= pkg.name %>.min.js': ['<banner:meta.banner>', '<%= builddir %>/<%= pkg.name %>.js'],
'<%= builddir %>/ng1/stateEvents.min.js': ['<banner:meta.banner>', '<%= builddir %>/ng1/stateEvents.js']
}
Expand Down Expand Up @@ -67,18 +59,34 @@ module.exports = function (grunt) {
}
]
},
core: {
entry: files.justjsCommonJsEntrypoint,
ng2: {
entry: files.ng2CommonJsEntrypoint,
output: {
path: '<%= builddir %>',
filename: 'ui-router-justjs.js',
filename: 'ui-router-ng2.js',
library: 'uiRouter',
libraryTarget: 'umd'
},
module: {
loaders: []
}
},
externals: [{
'angular2/core': {root: ['ng', 'core'], commonjs: 'angular2/core', commonjs2: 'angular2/core', amd: 'angular2/core'},
'angular2/common': {root: ['ng', 'common'], commonjs: 'angular2/common', commonjs2: 'angular2/common', amd: 'angular2/common'}
}]
}
//,core: {
// entry: files.justjsCommonJsEntrypoint,
// output: {
// path: '<%= builddir %>',
// filename: 'ui-router-justjs.js',
// library: 'uiRouter',
// libraryTarget: 'umd'
// },
// module: {
// loaders: []
// }
//}
},
release: {
files: ['<%= pkg.name %>.js', '<%= pkg.name %>.min.js'],
Expand Down Expand Up @@ -164,7 +172,7 @@ module.exports = function (grunt) {

grunt.registerTask('integrate', ['clean', 'build', 'karma:ng12', 'karma:ng13', 'karma:ng14', 'karma:ng15']);
grunt.registerTask('default', ['build', 'karma:unit', 'docs']);
grunt.registerTask('build', 'Perform a normal build', ['clean', 'ts', 'webpack', 'bundles', 'uglify']);
grunt.registerTask('build', 'Perform a normal build', ['clean', 'ts', 'bundles', 'uglify']);
grunt.registerTask('dist-docs', 'Perform a clean build and generate documentation', ['build', 'ngdocs']);
grunt.registerTask('release', 'Tag and perform a release', ['prepare-release', 'build', 'perform-release']);
grunt.registerTask('dev', 'Run dev server and watch for changes', ['build', 'connect:server', 'karma:background', 'watch']);
Expand Down
6 changes: 4 additions & 2 deletions files.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
routerFiles = {
ng1CommonJsEntrypoint: ['./build/es5/ng1.js'],
ng2CommonJsEntrypoint: ['./build/es5/ng2.js'],
justjsCommonJsEntrypoint: ['./build/es5/justjs.js'],
// es6Entrypoint: ['./build/es6/ng1.js'],

src: [
'src/ui-router.ts', // Main UI-Router module (re-exports all other core modules)
'src/ng1.ts', // UI-Router angular1 module (re-exports ui-router and ng1 modules)
'src/justjs.ts', // UI-Router plain ol js module (re-exports ui-router)
'src/ng1/stateEvents.ts' // There might be a better approach to compiling this file
//'src/ui-router.ts', // Main UI-Router module (re-exports all other core modules)
//'src/ng2.ts', // UI-Router angular2 module (re-exports ui-router and ng2 modules)
//'src/justjs.ts', // UI-Router plain ol js module (re-exports ui-router)
],

// Test helpers
Expand Down
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@
"angular": "^1.2"
},
"devDependencies": {
"angular2": "^2.0.0-beta.1",
"babel-core": "^5.8.14",
"es6-module-loader": "^0.17.3",
"es6-promise": "^3.0.2",
"es6-shim": "^0.33.13",
"faithful-exec": "~0.1.0",
"grunt": "~0.4.1",
"grunt-contrib-clean": "~0.5.0",
Expand All @@ -63,7 +66,7 @@
"grunt-karma": "~0.11.2",
"grunt-ngdocs": "~0.1.7",
"grunt-shell": "^1.1.2",
"grunt-ts": "^4.2.0",
"grunt-ts": "^5.2.0",
"grunt-webpack": "^1.0.10",
"jasmine-core": "~2.3.4",
"jsdoc": "git://github.com/jsdoc3/jsdoc.git#v3.2.2",
Expand All @@ -75,13 +78,16 @@
"karma-systemjs": "^0.7.2",
"load-grunt-tasks": "~0.4.0",
"phantomjs-polyfill": "0.0.1",
"reflect-metadata": "^0.1.2",
"rxjs": "^5.0.0-beta.0",
"shelljs": "~0.2.6",
"systemjs": "^0.18.4",
"tslint": "=2.5.0",
"typedoc": "git://github.com/christopherthielen/typedoc.git#v0.3-uirouter",
"typescript": "=1.7.3",
"webpack": "1.x",
"webpack-dev-server": "1.x"
"webpack-dev-server": "1.x",
"zone.js": "^0.5.10"
},
"main": "release/angular-ui-router.js"
}
10 changes: 8 additions & 2 deletions src/justjs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/// <reference path="../node_modules/typescript/lib/lib.es6.d.ts"/>

export * from "./ui-router";
import {services} from "./common/coreservices";
import {isDefined, isFunction, isArray, isObject, isInjectable} from "./common/predicates";
Expand Down Expand Up @@ -87,3 +85,11 @@ loc.onChange = (cb) => {
window.addEventListener("hashchange", cb, false);
};

let locCfg = <any> services.locationConfig;

locCfg.port = () => location.port;
locCfg.protocol = () => location.protocol;
locCfg.host = () => location.host;
locCfg.baseHref = () => "";
locCfg.html5Mode = () => false;
locCfg.hashPrefix = () => "";
12 changes: 12 additions & 0 deletions src/ng2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference path='../typings/es6-shim/es6-shim.d.ts' />
/**
* Main entry point for angular 2.x build
*/
/** for typedoc */

export * from "./ui-router";
import "./justjs";
export * from "./ng2/uiView";
export * from "./ng2/uiSref";
export * from "./ng2/uiSrefActive";

53 changes: 53 additions & 0 deletions src/ng2/uiSref.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {UIRouter} from "../router";
import {Directive} from "angular2/core";
import {Optional} from "angular2/core";
import {Input} from "angular2/core";
import {ElementRef} from "angular2/core";
import {Renderer} from "angular2/core";

@Directive({ selector: 'a[uiSref]' })
export class AnchorUiSref {
constructor( public _el: ElementRef, public _renderer: Renderer) { }
update(href) {
this._renderer.setElementProperty(this._el, 'href', href);
}
}

@Directive({
selector: '[uiSref]',
inputs: ['uiSref', 'uiParams', 'uiOptions'],
host: { '(click)': 'go()' }
})
export class UiSref {
state: string;
params: any;
options: any;

constructor(
private _router: UIRouter,
@Optional() private _anchorUiSref: AnchorUiSref
) { }

set "ui-sref"(val) { this.state = val; this.update(); }
set "uiSref"(val) { this.state = val; this.update(); }
set "uiParams"(val) { this.params = val; this.update(); }
set "uiOptions"(val) { this.options = val; this.update(); }

ngOnInit() {
this.update();
}

update() {
if (this._anchorUiSref) {
this._anchorUiSref.update(this._router.stateService.href(this.state, this.params));
}
// TODO: process ui-sref-active
}

go() {
this._router.stateService.go(this.state, this.params, this.options);
return false;
}
}


51 changes: 51 additions & 0 deletions src/ng2/uiSrefActive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {UIRouter} from "../router";
import {Directive} from "angular2/core";
import {UiSref} from "./uiSref";

@Directive({
selector: '[uiSrefClass]',
inputs: ['uiSrefClass']
})
export class UiSrefClass {
// current statuses of the bound uiSref directive
active = false;
exact = false;
entering = false;
exiting = false;
inactive = true;

patterns: any;
classes: string;
sref: UiSref;

//constructor($transitions: TransitionService, public router: UIRouter) {
constructor(public router: UIRouter) {
this.ngOnDestroy = <any> router.transitionService.onSuccess({}, this._update.bind(this));
}

ngOnDestroy() {}

/**
* e.g.
* {
* active: 'active && !exiting',
* loading: 'entering',
* active: matches('admin.*')
* }
*/
set uiSrefClass(val) {
console.log(val); // [uiSrefClass]="{active: isActive}" logs as "{active: undefined}"
this.patterns = val;
}

public provideUiSref(sref: UiSref) {
this.sref = sref;
this._update();
}

private _update() {
if (!this.sref) return;
// update classes
}
}

121 changes: 121 additions & 0 deletions src/ng2/uiView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import {Component, ElementRef, DynamicComponentLoader} from 'angular2/core';
import {Injector} from "angular2/core";
import {provide} from "angular2/core";
import {Input} from "angular2/core";
import {ComponentRef} from "angular2/core";
import {Type} from "angular2/core";

import {UIRouter} from "../router";
import {trace} from "../common/trace";
import {ViewConfig} from "../view/view";
import {Inject} from "angular2/core";
import {ViewContext} from "../view/interface";

let id = 0;

const getProviders = (injector) => {
let providers = [], parentInj = injector.parent;
for (let i = 0; i < parentInj._proto.numberOfProviders; i++) {
providers.push(parentInj._proto.getProviderAtIndex(i));
}
return providers;
};

@Component({
selector: 'ui-view, [ui-view]',
styles: [`
.done-true {
text-decoration: line-through;
color: grey;
}`
],
template: `
<div style="padding: 1em; border: 1px solid lightgrey;">
<div #content style="color: lightgrey; font-size: smaller;">
<div>ui-view #{{uiViewData.id}} created by '{{ parentContext.name || "(root)" }}' state</div>
<div>name: (absolute) '{{uiViewData.fqn}}' (contextual) '{{uiViewData.name}}@{{parentContext.name}}' </div>
<div>currently filled by: '{{(uiViewData.config && uiViewData.config.context) || 'empty...'}}'
</div>
</div>
`
})
export class UiView {
@Input() name: string;
@Input() set 'ui-view'(val) { this.name = val; }

componentRef: ComponentRef;
deregister: Function;
uiViewData: any = {};

static INJECT = {
fqn: "UiView.parentFQN",
context: "UiView.parentContext"
};

constructor(
public router: UIRouter,
@Inject(UiView.INJECT.context) public parentContext: ViewContext,
@Inject(UiView.INJECT.fqn) public parentFqn: string,
public dcl: DynamicComponentLoader,
public elementRef: ElementRef,
public injector: Injector
) { }

ngOnInit() {
let parentFqn = this.parentFqn;
let name = this.name || '$default';
console.log(`parentFqn: ${parentFqn}`);

this.uiViewData = {
id: id++,
name: name,
fqn: parentFqn ? parentFqn + "." + name : name,
creationContext: this.parentContext,
configUpdated: this.viewConfigUpdated.bind(this),
config: undefined
};

this.deregister = this.router.viewService.registerUiView(this.uiViewData);
}

disposeLast() {
if (this.componentRef) this.componentRef.dispose();
}

ngOnDestroy() {
this.deregister();
this.disposeLast();
}

viewConfigUpdated(config: ViewConfig) {
let {uiViewData, injector, dcl, elementRef} = this;

// The "new" viewconfig is already applied, so exit early
if (uiViewData.config === config) return;
// This is a new viewconfig. Destroy the old component
this.disposeLast();
trace.traceUiViewConfigUpdated(uiViewData, config && config.context);
uiViewData.config = config;
// The config may be undefined if there is nothing state currently targeting this UiView.
if (!config) return;

// Do some magic
let rc = config.node.resolveContext;
let resolvables = rc.getResolvables();
let rawProviders = Object.keys(resolvables).map(key => provide(key, { useValue: resolvables[key].data }));
rawProviders.push(provide(UiView.INJECT.context, { useValue: config.context }));
rawProviders.push(provide(UiView.INJECT.fqn, { useValue: uiViewData.fqn }));
let providers = Injector.resolve(rawProviders);

let exclusions = [UiView.INJECT.context, UiView.INJECT.fqn];
providers = getProviders(injector).filter(x => exclusions.indexOf(x.key.displayName) === -1).concat(providers);

// The 'controller' should be a Component class
// TODO: pull from 'component' declaration, do not require template.
let component = <Type> config.viewDeclarationObj.controller;
dcl.loadIntoLocation(component, elementRef, "content", providers).then(ref => this.componentRef = ref);
}
}

Loading

0 comments on commit 217de70

Please sign in to comment.