Skip to content

Commit 217de70

Browse files
feat(ui-router-ng2): Initial angular2 support
1 parent 002e8d1 commit 217de70

16 files changed

+1008
-27
lines changed

Gruntfile.js

+24-16
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,8 @@ module.exports = function (grunt) {
2121
},
2222
clean: [ '<%= builddir %>' ],
2323
ts: {
24-
es5: {
25-
src: files.src,
26-
outDir: '<%= builddir %>/es5',
27-
options: { module: 'commonjs'}
28-
},
29-
es6: {
30-
src: files.src,
31-
outDir: '<%= builddir %>/es6',
32-
options: { target: "es6"}
33-
}
24+
ng1: { tsconfig: 'tsconfig.json' },
25+
ng2: { tsconfig: 'tsconfig-ng2.json' }
3426
},
3527
uglify: {
3628
options: {
@@ -39,7 +31,7 @@ module.exports = function (grunt) {
3931
},
4032
build: {
4133
files: {
42-
'<%= builddir %>/ui-router.min.js': ['<banner:meta.banner>', '<%= builddir %>/ui-router.js'],
34+
'<%= builddir %>/ui-router-ng2.min.js': ['<banner:meta.banner>', '<%= builddir %>/ui-router-ng2.js'],
4335
'<%= builddir %>/<%= pkg.name %>.min.js': ['<banner:meta.banner>', '<%= builddir %>/<%= pkg.name %>.js'],
4436
'<%= builddir %>/ng1/stateEvents.min.js': ['<banner:meta.banner>', '<%= builddir %>/ng1/stateEvents.js']
4537
}
@@ -67,18 +59,34 @@ module.exports = function (grunt) {
6759
}
6860
]
6961
},
70-
core: {
71-
entry: files.justjsCommonJsEntrypoint,
62+
ng2: {
63+
entry: files.ng2CommonJsEntrypoint,
7264
output: {
7365
path: '<%= builddir %>',
74-
filename: 'ui-router-justjs.js',
66+
filename: 'ui-router-ng2.js',
7567
library: 'uiRouter',
7668
libraryTarget: 'umd'
7769
},
7870
module: {
7971
loaders: []
80-
}
72+
},
73+
externals: [{
74+
'angular2/core': {root: ['ng', 'core'], commonjs: 'angular2/core', commonjs2: 'angular2/core', amd: 'angular2/core'},
75+
'angular2/common': {root: ['ng', 'common'], commonjs: 'angular2/common', commonjs2: 'angular2/common', amd: 'angular2/common'}
76+
}]
8177
}
78+
//,core: {
79+
// entry: files.justjsCommonJsEntrypoint,
80+
// output: {
81+
// path: '<%= builddir %>',
82+
// filename: 'ui-router-justjs.js',
83+
// library: 'uiRouter',
84+
// libraryTarget: 'umd'
85+
// },
86+
// module: {
87+
// loaders: []
88+
// }
89+
//}
8290
},
8391
release: {
8492
files: ['<%= pkg.name %>.js', '<%= pkg.name %>.min.js'],
@@ -164,7 +172,7 @@ module.exports = function (grunt) {
164172

165173
grunt.registerTask('integrate', ['clean', 'build', 'karma:ng12', 'karma:ng13', 'karma:ng14', 'karma:ng15']);
166174
grunt.registerTask('default', ['build', 'karma:unit', 'docs']);
167-
grunt.registerTask('build', 'Perform a normal build', ['clean', 'ts', 'webpack', 'bundles', 'uglify']);
175+
grunt.registerTask('build', 'Perform a normal build', ['clean', 'ts', 'bundles', 'uglify']);
168176
grunt.registerTask('dist-docs', 'Perform a clean build and generate documentation', ['build', 'ngdocs']);
169177
grunt.registerTask('release', 'Tag and perform a release', ['prepare-release', 'build', 'perform-release']);
170178
grunt.registerTask('dev', 'Run dev server and watch for changes', ['build', 'connect:server', 'karma:background', 'watch']);

files.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
routerFiles = {
22
ng1CommonJsEntrypoint: ['./build/es5/ng1.js'],
3+
ng2CommonJsEntrypoint: ['./build/es5/ng2.js'],
34
justjsCommonJsEntrypoint: ['./build/es5/justjs.js'],
45
// es6Entrypoint: ['./build/es6/ng1.js'],
56

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

1315
// Test helpers

package.json

+8-2
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@
5151
"angular": "^1.2"
5252
},
5353
"devDependencies": {
54+
"angular2": "^2.0.0-beta.1",
5455
"babel-core": "^5.8.14",
5556
"es6-module-loader": "^0.17.3",
57+
"es6-promise": "^3.0.2",
58+
"es6-shim": "^0.33.13",
5659
"faithful-exec": "~0.1.0",
5760
"grunt": "~0.4.1",
5861
"grunt-contrib-clean": "~0.5.0",
@@ -63,7 +66,7 @@
6366
"grunt-karma": "~0.11.2",
6467
"grunt-ngdocs": "~0.1.7",
6568
"grunt-shell": "^1.1.2",
66-
"grunt-ts": "^4.2.0",
69+
"grunt-ts": "^5.2.0",
6770
"grunt-webpack": "^1.0.10",
6871
"jasmine-core": "~2.3.4",
6972
"jsdoc": "git://github.com/jsdoc3/jsdoc.git#v3.2.2",
@@ -75,13 +78,16 @@
7578
"karma-systemjs": "^0.7.2",
7679
"load-grunt-tasks": "~0.4.0",
7780
"phantomjs-polyfill": "0.0.1",
81+
"reflect-metadata": "^0.1.2",
82+
"rxjs": "^5.0.0-beta.0",
7883
"shelljs": "~0.2.6",
7984
"systemjs": "^0.18.4",
8085
"tslint": "=2.5.0",
8186
"typedoc": "git://github.com/christopherthielen/typedoc.git#v0.3-uirouter",
8287
"typescript": "=1.7.3",
8388
"webpack": "1.x",
84-
"webpack-dev-server": "1.x"
89+
"webpack-dev-server": "1.x",
90+
"zone.js": "^0.5.10"
8591
},
8692
"main": "release/angular-ui-router.js"
8793
}

src/justjs.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/// <reference path="../node_modules/typescript/lib/lib.es6.d.ts"/>
2-
31
export * from "./ui-router";
42
import {services} from "./common/coreservices";
53
import {isDefined, isFunction, isArray, isObject, isInjectable} from "./common/predicates";
@@ -87,3 +85,11 @@ loc.onChange = (cb) => {
8785
window.addEventListener("hashchange", cb, false);
8886
};
8987

88+
let locCfg = <any> services.locationConfig;
89+
90+
locCfg.port = () => location.port;
91+
locCfg.protocol = () => location.protocol;
92+
locCfg.host = () => location.host;
93+
locCfg.baseHref = () => "";
94+
locCfg.html5Mode = () => false;
95+
locCfg.hashPrefix = () => "";

src/ng2.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path='../typings/es6-shim/es6-shim.d.ts' />
2+
/**
3+
* Main entry point for angular 2.x build
4+
*/
5+
/** for typedoc */
6+
7+
export * from "./ui-router";
8+
import "./justjs";
9+
export * from "./ng2/uiView";
10+
export * from "./ng2/uiSref";
11+
export * from "./ng2/uiSrefActive";
12+

src/ng2/uiSref.ts

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import {UIRouter} from "../router";
2+
import {Directive} from "angular2/core";
3+
import {Optional} from "angular2/core";
4+
import {Input} from "angular2/core";
5+
import {ElementRef} from "angular2/core";
6+
import {Renderer} from "angular2/core";
7+
8+
@Directive({ selector: 'a[uiSref]' })
9+
export class AnchorUiSref {
10+
constructor( public _el: ElementRef, public _renderer: Renderer) { }
11+
update(href) {
12+
this._renderer.setElementProperty(this._el, 'href', href);
13+
}
14+
}
15+
16+
@Directive({
17+
selector: '[uiSref]',
18+
inputs: ['uiSref', 'uiParams', 'uiOptions'],
19+
host: { '(click)': 'go()' }
20+
})
21+
export class UiSref {
22+
state: string;
23+
params: any;
24+
options: any;
25+
26+
constructor(
27+
private _router: UIRouter,
28+
@Optional() private _anchorUiSref: AnchorUiSref
29+
) { }
30+
31+
set "ui-sref"(val) { this.state = val; this.update(); }
32+
set "uiSref"(val) { this.state = val; this.update(); }
33+
set "uiParams"(val) { this.params = val; this.update(); }
34+
set "uiOptions"(val) { this.options = val; this.update(); }
35+
36+
ngOnInit() {
37+
this.update();
38+
}
39+
40+
update() {
41+
if (this._anchorUiSref) {
42+
this._anchorUiSref.update(this._router.stateService.href(this.state, this.params));
43+
}
44+
// TODO: process ui-sref-active
45+
}
46+
47+
go() {
48+
this._router.stateService.go(this.state, this.params, this.options);
49+
return false;
50+
}
51+
}
52+
53+

src/ng2/uiSrefActive.ts

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import {UIRouter} from "../router";
2+
import {Directive} from "angular2/core";
3+
import {UiSref} from "./uiSref";
4+
5+
@Directive({
6+
selector: '[uiSrefClass]',
7+
inputs: ['uiSrefClass']
8+
})
9+
export class UiSrefClass {
10+
// current statuses of the bound uiSref directive
11+
active = false;
12+
exact = false;
13+
entering = false;
14+
exiting = false;
15+
inactive = true;
16+
17+
patterns: any;
18+
classes: string;
19+
sref: UiSref;
20+
21+
//constructor($transitions: TransitionService, public router: UIRouter) {
22+
constructor(public router: UIRouter) {
23+
this.ngOnDestroy = <any> router.transitionService.onSuccess({}, this._update.bind(this));
24+
}
25+
26+
ngOnDestroy() {}
27+
28+
/**
29+
* e.g.
30+
* {
31+
* active: 'active && !exiting',
32+
* loading: 'entering',
33+
* active: matches('admin.*')
34+
* }
35+
*/
36+
set uiSrefClass(val) {
37+
console.log(val); // [uiSrefClass]="{active: isActive}" logs as "{active: undefined}"
38+
this.patterns = val;
39+
}
40+
41+
public provideUiSref(sref: UiSref) {
42+
this.sref = sref;
43+
this._update();
44+
}
45+
46+
private _update() {
47+
if (!this.sref) return;
48+
// update classes
49+
}
50+
}
51+

src/ng2/uiView.ts

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import {Component, ElementRef, DynamicComponentLoader} from 'angular2/core';
2+
import {Injector} from "angular2/core";
3+
import {provide} from "angular2/core";
4+
import {Input} from "angular2/core";
5+
import {ComponentRef} from "angular2/core";
6+
import {Type} from "angular2/core";
7+
8+
import {UIRouter} from "../router";
9+
import {trace} from "../common/trace";
10+
import {ViewConfig} from "../view/view";
11+
import {Inject} from "angular2/core";
12+
import {ViewContext} from "../view/interface";
13+
14+
let id = 0;
15+
16+
const getProviders = (injector) => {
17+
let providers = [], parentInj = injector.parent;
18+
for (let i = 0; i < parentInj._proto.numberOfProviders; i++) {
19+
providers.push(parentInj._proto.getProviderAtIndex(i));
20+
}
21+
return providers;
22+
};
23+
24+
@Component({
25+
selector: 'ui-view, [ui-view]',
26+
styles: [`
27+
.done-true {
28+
text-decoration: line-through;
29+
color: grey;
30+
}`
31+
],
32+
template: `
33+
<div style="padding: 1em; border: 1px solid lightgrey;">
34+
35+
<div #content style="color: lightgrey; font-size: smaller;">
36+
<div>ui-view #{{uiViewData.id}} created by '{{ parentContext.name || "(root)" }}' state</div>
37+
<div>name: (absolute) '{{uiViewData.fqn}}' (contextual) '{{uiViewData.name}}@{{parentContext.name}}' </div>
38+
<div>currently filled by: '{{(uiViewData.config && uiViewData.config.context) || 'empty...'}}'
39+
</div>
40+
41+
</div>
42+
`
43+
})
44+
export class UiView {
45+
@Input() name: string;
46+
@Input() set 'ui-view'(val) { this.name = val; }
47+
48+
componentRef: ComponentRef;
49+
deregister: Function;
50+
uiViewData: any = {};
51+
52+
static INJECT = {
53+
fqn: "UiView.parentFQN",
54+
context: "UiView.parentContext"
55+
};
56+
57+
constructor(
58+
public router: UIRouter,
59+
@Inject(UiView.INJECT.context) public parentContext: ViewContext,
60+
@Inject(UiView.INJECT.fqn) public parentFqn: string,
61+
public dcl: DynamicComponentLoader,
62+
public elementRef: ElementRef,
63+
public injector: Injector
64+
) { }
65+
66+
ngOnInit() {
67+
let parentFqn = this.parentFqn;
68+
let name = this.name || '$default';
69+
console.log(`parentFqn: ${parentFqn}`);
70+
71+
this.uiViewData = {
72+
id: id++,
73+
name: name,
74+
fqn: parentFqn ? parentFqn + "." + name : name,
75+
creationContext: this.parentContext,
76+
configUpdated: this.viewConfigUpdated.bind(this),
77+
config: undefined
78+
};
79+
80+
this.deregister = this.router.viewService.registerUiView(this.uiViewData);
81+
}
82+
83+
disposeLast() {
84+
if (this.componentRef) this.componentRef.dispose();
85+
}
86+
87+
ngOnDestroy() {
88+
this.deregister();
89+
this.disposeLast();
90+
}
91+
92+
viewConfigUpdated(config: ViewConfig) {
93+
let {uiViewData, injector, dcl, elementRef} = this;
94+
95+
// The "new" viewconfig is already applied, so exit early
96+
if (uiViewData.config === config) return;
97+
// This is a new viewconfig. Destroy the old component
98+
this.disposeLast();
99+
trace.traceUiViewConfigUpdated(uiViewData, config && config.context);
100+
uiViewData.config = config;
101+
// The config may be undefined if there is nothing state currently targeting this UiView.
102+
if (!config) return;
103+
104+
// Do some magic
105+
let rc = config.node.resolveContext;
106+
let resolvables = rc.getResolvables();
107+
let rawProviders = Object.keys(resolvables).map(key => provide(key, { useValue: resolvables[key].data }));
108+
rawProviders.push(provide(UiView.INJECT.context, { useValue: config.context }));
109+
rawProviders.push(provide(UiView.INJECT.fqn, { useValue: uiViewData.fqn }));
110+
let providers = Injector.resolve(rawProviders);
111+
112+
let exclusions = [UiView.INJECT.context, UiView.INJECT.fqn];
113+
providers = getProviders(injector).filter(x => exclusions.indexOf(x.key.displayName) === -1).concat(providers);
114+
115+
// The 'controller' should be a Component class
116+
// TODO: pull from 'component' declaration, do not require template.
117+
let component = <Type> config.viewDeclarationObj.controller;
118+
dcl.loadIntoLocation(component, elementRef, "content", providers).then(ref => this.componentRef = ref);
119+
}
120+
}
121+

0 commit comments

Comments
 (0)