├── .gitignore
├── .travis.yml
├── sample
├── app
│ ├── contacts
│ │ ├── contacts.detail.item.html
│ │ ├── contacts.detail.item.edit.html
│ │ ├── contacts.list.html
│ │ ├── contacts-service.js
│ │ ├── contacts.detail.html
│ │ ├── contacts.html
│ │ └── contacts.js
│ └── app.js
├── common
│ └── utils
│ │ └── utils-service.js
├── css
│ └── styles.css
├── assets
│ └── contacts.json
└── index.html
├── tsconfig.json
├── files.conf.js
├── bower.json
├── index.d.ts
├── karma.conf.js
├── tslint.json
├── src
└── angular-ui-router-title.ts
├── package.json
├── angular-ui-router-title.js
├── gulpfile.js
├── README.md
└── test
└── angular-ui-router-title.spec.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | src/**/*.js
2 | test/**/*.js
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "iojs"
4 | before_script:
5 | - export DISPLAY=:99.0
6 | - sh -e /etc/init.d/xvfb start
7 | - npm install
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.detail.item.html:
--------------------------------------------------------------------------------
1 |
2 | {{item.type}} Edit
3 | {{item.value}}
4 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.detail.item.edit.html:
--------------------------------------------------------------------------------
1 |
2 | {{item.type}} Done
3 |
4 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.list.html:
--------------------------------------------------------------------------------
1 | All Contacts
2 |
7 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "umd",
4 | "target": "es5",
5 | "moduleResolution": "node",
6 | "noImplicitAny": false,
7 | "sourceMap": false,
8 | "noEmitOnError": true,
9 | "lib": ["es5", "dom"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/files.conf.js:
--------------------------------------------------------------------------------
1 | files = {
2 | libs: [
3 | 'node_modules/lodash/index.js',
4 | 'node_modules/angular/angular.js',
5 | 'node_modules/angular-ui-router/release/angular-ui-router.js'
6 | ],
7 |
8 | src: [
9 | 'src/angular-ui-router-title.js'
10 | ],
11 |
12 | test: [
13 | 'node_modules/angular-mocks/angular-mocks.js',
14 | 'test/*.spec.js'
15 | ]
16 | };
17 |
18 | if (exports) {
19 | var _ = require('lodash');
20 | _.extend(exports, files);
21 | }
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-router-title",
3 | "version": "0.1.1",
4 | "homepage": "https://github.com/nonplus/angular-ui-router-title",
5 | "authors": [
6 | "Stepan Riha "
7 | ],
8 | "description": "AngularJS module for updating browser title/history based on the current ui-router state.",
9 | "main": "angular-ui-router-title.js",
10 | "license": "MIT",
11 | "ignore": [
12 | "**/.*",
13 | "node_modules",
14 | "bower_components",
15 | "test",
16 | "tests"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts-service.js:
--------------------------------------------------------------------------------
1 | angular.module('uiRouterSample.contacts.service', [
2 |
3 | ])
4 |
5 | // A RESTful factory for retrieving contacts from 'contacts.json'
6 | .factory('contacts', ['$http', 'utils', function ($http, utils) {
7 | var path = 'assets/contacts.json';
8 | var contacts = $http.get(path).then(function (resp) {
9 | return resp.data.contacts;
10 | });
11 |
12 | var factory = {};
13 | factory.all = function () {
14 | return contacts;
15 | };
16 | factory.get = function (id) {
17 | return contacts.then(function(){
18 | return utils.findById(contacts, id);
19 | })
20 | };
21 | return factory;
22 | }]);
23 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import angular = require("angular");
2 |
3 | declare module "angular" {
4 | interface IRootScopeService {
5 | $title?: string;
6 | $breadcrumbs: {
7 | title: string;
8 | state: string;
9 | stateParams: { ["key"]: any }
10 | }[];
11 | }
12 |
13 | namespace ui {
14 | interface ITitleService {
15 | title: () => string;
16 | breadCrumbs: () => {
17 | title: string;
18 | state: string;
19 | stateParams: { ["key"]: any }
20 | }[];
21 | }
22 |
23 | interface ITitleProvider {
24 | documentTitle(inlineAnnotatedFunction: (string | ((...args: any[]) => string))[]): void;
25 | documentTitle(func: ((...args: any[]) => string) & Function): void;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.detail.html:
--------------------------------------------------------------------------------
1 |
2 |
{{contact.name}}
3 |
12 |
13 |
15 | Click on a contact item to view and/or edit it.
16 |
17 |
18 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file
2 | module.exports = function (karma) {
3 |
4 | var files = require('./files.conf');
5 |
6 | karma.set({
7 | // base path, that will be used to resolve files and exclude
8 | basePath: '.',
9 |
10 | frameworks: ['jasmine'],
11 |
12 | // list of files / patterns to load in the browser
13 | files: [].concat(files.libs, files.src, files.test),
14 |
15 | // level of logging
16 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
17 | logLevel: karma.LOG_DEBUG,
18 |
19 | // Start these browsers, currently available:
20 | // - Chrome
21 | // - ChromeCanary
22 | // - Firefox
23 | // - Opera
24 | // - Safari
25 | // - PhantomJS
26 | browsers: [ 'Chrome' ]
27 | })
28 | };
--------------------------------------------------------------------------------
/sample/common/utils/utils-service.js:
--------------------------------------------------------------------------------
1 | angular.module('uiRouterSample.utils.service', [
2 |
3 | ])
4 |
5 | .factory('utils', function () {
6 | return {
7 | // Util for finding an object by its 'id' property among an array
8 | findById: function findById(a, id) {
9 | for (var i = 0; i < a.length; i++) {
10 | if (a[i].id == id) return a[i];
11 | }
12 | return null;
13 | },
14 |
15 | // Util for returning a random key from a collection that also isn't the current key
16 | newRandomKey: function newRandomKey(coll, key, currentKey){
17 | var randKey;
18 | do {
19 | randKey = coll[Math.floor(coll.length * Math.random())][key];
20 | } while (randKey == currentKey);
21 | return randKey;
22 | }
23 | };
24 | });
25 |
--------------------------------------------------------------------------------
/sample/css/styles.css:
--------------------------------------------------------------------------------
1 | .slide.ng-leave {
2 | position: relative;
3 | }
4 | .slide.ng-enter {
5 | position: absolute;
6 | }
7 | .slide.ng-enter, .slide.ng-leave {
8 | -webkit-transition: -webkit-transform 0.3s ease-in, opacity 0.3s ease-in;
9 | -moz-transition: -moz-transform 0.3s ease-in, opacity 0.3s ease-in;
10 | -o-transition: -o-transform 0.3s ease-in, opacity 0.3s ease-in;
11 | transition: transform 0.3s ease-in, opacity 0.3s ease-in;
12 | }
13 | .slide.ng-enter, .slide.ng-leave.ng-leave-active {
14 | -webkit-transform: scaleX(0.0001);
15 | -o-transform: scaleX(0.0001);
16 | transform: scaleX(0.0001);
17 | opacity: 0;
18 | }
19 | .slide, .slide.ng-enter.ng-enter-active {
20 | -webkit-transform: scaleX(1);
21 | -o-transform: scaleX(1);
22 | transform: scaleX(1);
23 | opacity: 1;
24 | }
25 |
--------------------------------------------------------------------------------
/sample/assets/contacts.json:
--------------------------------------------------------------------------------
1 | {
2 | "contacts":[
3 | {
4 | "id": 1,
5 | "name": "Alice",
6 | "items": [
7 | {
8 | "id": "a",
9 | "type": "phone number",
10 | "value": "555-1234-1234"
11 | },
12 | {
13 | "id": "b",
14 | "type": "email",
15 | "value": "alice@mailinator.com"
16 | }
17 | ]
18 | },
19 | {
20 | "id": 42,
21 | "name": "Bob",
22 | "items": [
23 | {
24 | "id": "a",
25 | "type": "blog",
26 | "value": "http://bob.blogger.com"
27 | },
28 | {
29 | "id": "b",
30 | "type": "fax",
31 | "value": "555-999-9999"
32 | }
33 | ]
34 | },
35 | {
36 | "id": 123,
37 | "name": "Eve",
38 | "items": [
39 | {
40 | "id": "a",
41 | "type": "full name",
42 | "value": "Eve Adamsdottir"
43 | }
44 | ]
45 | }
46 | ]
47 | }
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rules": {
3 | "class-name": true,
4 | "comment-format": [
5 | true,
6 | "check-space"
7 | ],
8 | "indent": [
9 | true,
10 | "tabs"
11 | ],
12 | "no-duplicate-variable": true,
13 | "no-eval": true,
14 | "no-internal-module": true,
15 | "no-trailing-whitespace": true,
16 | "no-unsafe-finally": true,
17 | "no-var-keyword": false,
18 | "one-line": [
19 | true,
20 | "check-open-brace",
21 | "check-whitespace"
22 | ],
23 | "quotemark": [
24 | false,
25 | "double"
26 | ],
27 | "semicolon": [
28 | true,
29 | "always"
30 | ],
31 | "triple-equals": [
32 | true,
33 | "allow-null-check"
34 | ],
35 | "typedef-whitespace": [
36 | true,
37 | {
38 | "call-signature": "nospace",
39 | "index-signature": "nospace",
40 | "parameter": "nospace",
41 | "property-declaration": "nospace",
42 | "variable-declaration": "nospace"
43 | }
44 | ],
45 | "variable-name": [
46 | true,
47 | "ban-keywords"
48 | ],
49 | "whitespace": [
50 | true,
51 | "check-branch",
52 | "check-decl",
53 | "check-operator",
54 | "check-separator",
55 | "check-type"
56 | ]
57 | }
58 | }
--------------------------------------------------------------------------------
/src/angular-ui-router-title.ts:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | let documentTitleCallback: (title: string) => string = undefined;
4 | let defaultDocumentTitle = document.title;
5 |
6 | angular.module("ui.router.title", ["ui.router"])
7 | .provider("$title", function $titleProvider() {
8 | return {
9 | documentTitle: (cb) => {
10 | documentTitleCallback = cb;
11 | },
12 | $get: ["$state", ($state: ng.ui.IStateService): ng.ui.ITitleService => {
13 | return {
14 | title: () => getTitleValue($state.$current.locals.globals["$title"]),
15 | breadCrumbs: () => {
16 | let $breadcrumbs = [];
17 | var state = $state.$current;
18 | while (state) {
19 | if (state["resolve"] && state["resolve"].$title) {
20 | $breadcrumbs.unshift({
21 | title: getTitleValue(state.locals.globals["$title"]) as string,
22 | state: state["self"].name,
23 | stateParams: state.locals.globals["$stateParams"]
24 | });
25 | }
26 | state = state["parent"];
27 | }
28 | return $breadcrumbs;
29 | }
30 | };
31 | }]
32 | };
33 | })
34 | .run(["$rootScope", "$timeout", "$title", "$injector", function(
35 | $rootScope: ng.IRootScopeService,
36 | $timeout: ng.ITimeoutService,
37 | $title: ng.ui.ITitleService,
38 | $injector
39 | ) {
40 |
41 | $rootScope.$on("$stateChangeSuccess", function() {
42 | var title = $title.title();
43 | $timeout(function() {
44 | $rootScope.$title = title;
45 | const documentTitle = documentTitleCallback ? $injector.invoke(documentTitleCallback) : title || defaultDocumentTitle;
46 | document.title = documentTitle;
47 | });
48 |
49 | $rootScope.$breadcrumbs = $title.breadCrumbs();
50 | });
51 |
52 | }]);
53 |
54 | function getTitleValue(title) {
55 | return angular.isFunction(title) ? title() : title;
56 | }
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-ui-router-title",
3 | "version": "0.1.1",
4 | "description": "AngularJS module for updating browser title/history based on the current ui-router state.",
5 | "main": "angular-ui-router-title.js",
6 | "types": "./index.d.ts",
7 | "directories": {
8 | "test": "test"
9 | },
10 | "scripts": {
11 | "ts-compile": "./node_modules/gulp/bin/gulp.js lint ts-compile",
12 | "karma-test": "./node_modules/karma/bin/karma start --browsers Firefox --single-run",
13 | "test": "npm run ts-compile && npm run karma-test"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/nonplus/angular-ui-router-title.git"
18 | },
19 | "author": "Stepan Riha ",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/nonplus/angular-ui-router-title/issues"
23 | },
24 | "homepage": "https://github.com/nonplus/angular-ui-router-title",
25 | "devDependencies": {
26 | "@types/angular-mocks": "^1.5.5",
27 | "@types/jasmine": "^2.5.36",
28 | "angular": "^1.3.15",
29 | "angular-mocks": "^1.3.15",
30 | "angular-ui-router": "^0.2.18",
31 | "gulp": "^3.8.11",
32 | "gulp-bump": "^0.3.0",
33 | "gulp-filter": "^2.0.2",
34 | "gulp-git": "^1.2.3",
35 | "gulp-karma": "0.0.4",
36 | "gulp-serve": "^0.3.1",
37 | "gulp-tag-version": "^1.2.1",
38 | "gulp-tslint": "^6.1.2",
39 | "gulp-typescript": "^3.1.2",
40 | "gulp-util": "^3.0.4",
41 | "gulp-wrap": "^0.11.0",
42 | "jasmine-core": "^2.3.2",
43 | "karma": "^0.12.31",
44 | "karma-chrome-launcher": "^0.1.8",
45 | "karma-firefox-launcher": "^1.0.0",
46 | "karma-jasmine": "^0.3.5",
47 | "lodash": "^3.8.0",
48 | "open": "0.0.5",
49 | "run-sequence": "^1.1.0",
50 | "tslint": "^3.15.1",
51 | "typescript": "^2.0.6"
52 | },
53 | "dependencies": {
54 | "@types/angular": "^1.5.17",
55 | "@types/angular-ui-router": "^1.1.34"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/angular-ui-router-title.js:
--------------------------------------------------------------------------------
1 | /**
2 | * AngularJS module for updating browser title/history based on the current ui-router state.
3 | *
4 | * @link https://github.com/nonplus/angular-ui-router-title
5 | *
6 | * @license angular-ui-router-title v0.1.1
7 | * (c) Copyright Stepan Riha
8 | * License MIT
9 | */
10 |
11 | (function(angular) {
12 |
13 | "use strict";
14 | var documentTitleCallback = undefined;
15 | var defaultDocumentTitle = document.title;
16 | angular.module("ui.router.title", ["ui.router"])
17 | .provider("$title", function $titleProvider() {
18 | return {
19 | documentTitle: function (cb) {
20 | documentTitleCallback = cb;
21 | },
22 | $get: ["$state", function ($state) {
23 | return {
24 | title: function () { return getTitleValue($state.$current.locals.globals["$title"]); },
25 | breadCrumbs: function () {
26 | var $breadcrumbs = [];
27 | var state = $state.$current;
28 | while (state) {
29 | if (state["resolve"] && state["resolve"].$title) {
30 | $breadcrumbs.unshift({
31 | title: getTitleValue(state.locals.globals["$title"]),
32 | state: state["self"].name,
33 | stateParams: state.locals.globals["$stateParams"]
34 | });
35 | }
36 | state = state["parent"];
37 | }
38 | return $breadcrumbs;
39 | }
40 | };
41 | }]
42 | };
43 | })
44 | .run(["$rootScope", "$timeout", "$title", "$injector", function ($rootScope, $timeout, $title, $injector) {
45 | $rootScope.$on("$stateChangeSuccess", function () {
46 | var title = $title.title();
47 | $timeout(function () {
48 | $rootScope.$title = title;
49 | var documentTitle = documentTitleCallback ? $injector.invoke(documentTitleCallback) : title || defaultDocumentTitle;
50 | document.title = documentTitle;
51 | });
52 | $rootScope.$breadcrumbs = $title.breadCrumbs();
53 | });
54 | }]);
55 | function getTitleValue(title) {
56 | return angular.isFunction(title) ? title() : title;
57 | }
58 |
59 |
60 | })(window.angular);
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | // dependencies
2 | var gulp = require('gulp');
3 | var git = require('gulp-git');
4 | var bump = require('gulp-bump');
5 | var filter = require('gulp-filter');
6 | var tag_version = require('gulp-tag-version');
7 | var tslint = require("gulp-tslint");
8 | var runSequence = require('run-sequence');
9 | var wrap = require("gulp-wrap");
10 | var gutil = require('gulp-util');
11 | var serve = require('gulp-serve');
12 | var karma = require('gulp-karma');
13 | var files = require('./files.conf');
14 | var testFiles = [].concat(files.libs, files.src, files.test);
15 |
16 | var port = 8082;
17 |
18 | gulp.task('bump-version', function () {
19 | return gulp.src(['./bower.json', './package.json'])
20 | .pipe(bump({type: "patch"}).on('error', gutil.log))
21 | .pipe(gulp.dest('./'));
22 | });
23 |
24 | gulp.task('commit-changes', ['test'], function () {
25 | return gulp.src('.')
26 | .pipe(git.commit('Bumped version number', {args: '-a'}));
27 | });
28 |
29 | gulp.task('tag-version', function() {
30 | return gulp.src('package.json')
31 | .pipe(tag_version());
32 | });
33 |
34 | gulp.task('push-changes', function (cb) {
35 | git.push('origin', 'master', cb);
36 | });
37 |
38 | gulp.task('release', ['ts-compile', 'test'], function (callback) {
39 | runSequence(
40 | 'bump-version',
41 | 'build',
42 | 'commit-changes',
43 | 'tag-version',
44 | function (error) {
45 | if (error) {
46 | console.log(error.message);
47 | } else {
48 | console.log('RELEASE FINISHED SUCCESSFULLY');
49 | }
50 | callback(error);
51 | });
52 | });
53 |
54 | gulp.task('tag-version', function() {
55 | return gulp.src('./package.json')
56 | .pipe(tag_version());
57 | });
58 |
59 | gulp.task('build', ['ts-compile'], function() {
60 | return gulp.src("src/angular-ui-router-title.js")
61 | .pipe(wrap({ src: './build.txt' }, { info: require('./package.json') }))
62 | .pipe(gulp.dest('.'));
63 | });
64 |
65 | gulp.task('ts-compile', function() {
66 | var ts = require('gulp-typescript');
67 | var tsProject = ts.createProject('tsconfig.json');
68 | return tsProject.src(['src/**/*.ts', 'test/**/*.ts'])
69 | .pipe(tsProject()).js
70 | .pipe(gulp.dest('.'));
71 | });
72 |
73 | gulp.task('serve', serve({
74 | root: __dirname,
75 | port: port,
76 | middleware: function(req, resp, next) {
77 | console.log(req.originalUrl);
78 | if(req.originalUrl == '/') {
79 | resp.statusCode = 302;
80 | resp.setHeader('Location', '/sample/');
81 | resp.setHeader('Content-Length', '0');
82 | resp.end();
83 | } else {
84 | next();
85 | }
86 | }
87 | }));
88 |
89 | gulp.task('demo', ['serve'], function() {
90 | require('open')('http://localhost:' + port);
91 | });
92 |
93 | gulp.task('test', function() {
94 | // Be sure to return the stream
95 | return gulp.src(testFiles)
96 | .pipe(karma({
97 | configFile: 'karma.conf.js',
98 | action: 'run'
99 | }))
100 | .on('error', function(err) {
101 | // Make sure failed tests cause gulp to exit non-zero
102 | throw err;
103 | });
104 | });
105 |
106 | gulp.task('watch', function() {
107 | gulp.src(testFiles)
108 | .pipe(karma({
109 | configFile: 'karma.conf.js',
110 | action: 'watch'
111 | }));
112 | });
113 |
114 | gulp.task('lint', function () {
115 | return gulp.src([
116 | "./src/**/*.ts",
117 | "./test/**/*.ts"
118 | ])
119 | .pipe(tslint({
120 | formatter: "verbose"
121 | }))
122 | .pipe(tslint.report());
123 | });
124 |
--------------------------------------------------------------------------------
/sample/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
23 |
24 |
25 |
28 |
29 |
30 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
46 |
ui-router
47 |
55 |
56 |
58 |
59 |
60 |
61 |
62 |
64 |
65 |
66 |
67 |
68 |
69 |
70 | $title = {{$title}}
71 | $breadcrumbs = [
72 | {{crumb}} ]
73 | $state = {{$state.current.name}}
74 | $stateParams = {{$stateParams}}
75 | $state full url = {{ $state.$current.url.source }}
76 |
78 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/sample/app/app.js:
--------------------------------------------------------------------------------
1 | // Make sure to include the `ui.router` module as a dependency
2 | angular.module('uiRouterSample', [
3 | 'uiRouterSample.contacts',
4 | 'uiRouterSample.contacts.service',
5 | 'uiRouterSample.utils.service',
6 | 'ui.router',
7 | 'ui.router.title',
8 | 'ngAnimate'
9 | ])
10 |
11 | .run(
12 | [ '$rootScope', '$state', '$stateParams',
13 | function ($rootScope, $state, $stateParams) {
14 |
15 | // It's very handy to add references to $state and $stateParams to the $rootScope
16 | // so that you can access them from any scope within your applications.For example,
17 | // will set the
18 | // to active whenever 'contacts.list' or one of its decendents is active.
19 | $rootScope.$state = $state;
20 | $rootScope.$stateParams = $stateParams;
21 | }
22 | ]
23 | )
24 |
25 | .config(
26 | [ '$stateProvider', '$urlRouterProvider', '$titleProvider',
27 | function ($stateProvider, $urlRouterProvider, $titleProvider) {
28 |
29 | $titleProvider.documentTitle(function($title) {
30 | return $title + ' - ui-router-title';
31 | });
32 |
33 | /////////////////////////////
34 | // Redirects and Otherwise //
35 | /////////////////////////////
36 |
37 | // Use $urlRouterProvider to configure any redirects (when) and invalid urls (otherwise).
38 | $urlRouterProvider
39 |
40 | // The `when` method says if the url is ever the 1st param, then redirect to the 2nd param
41 | // Here we are just setting up some convenience urls.
42 | .when('/c?id', '/contacts/:id')
43 | .when('/user/:id', '/contacts/:id')
44 |
45 | // If the url is ever invalid, e.g. '/asdf', then redirect to '/' aka the home state
46 | .otherwise('/');
47 |
48 |
49 | //////////////////////////
50 | // State Configurations //
51 | //////////////////////////
52 |
53 | // Use $stateProvider to configure your states.
54 | $stateProvider
55 |
56 | //////////
57 | // Home //
58 | //////////
59 |
60 | .state("home", {
61 |
62 | // Use a url of "/" to set a states as the "index".
63 | url: "/",
64 |
65 | resolve: {
66 | // Static $title
67 | $title: function() { return "Home"; }
68 | },
69 |
70 | // Example of an inline template string. By default, templates
71 | // will populate the ui-view within the parent state's template.
72 | // For top level states, like this one, the parent template is
73 | // the index.html file. So this template will be inserted into the
74 | // ui-view within index.html.
75 | template: 'Welcome to the UI-Router Demo
' +
76 | 'Use the menu above to navigate. ' +
77 | 'Pay attention to the $state and $stateParams values below.
' +
78 | 'Click these links—Alice or ' +
79 | 'Bob —to see a url redirect in action.
'
80 |
81 | })
82 |
83 | ///////////
84 | // About //
85 | ///////////
86 |
87 | .state('about', {
88 | url: '/about',
89 |
90 | resolve: {
91 | // Static $title
92 | $title: function() { return "About"; }
93 | },
94 |
95 | // Showing off how you could return a promise from templateProvider
96 | templateProvider: ['$timeout',
97 | function ( $timeout) {
98 | return $timeout(function () {
99 | return 'UI-Router Resources
';
106 | }, 100);
107 | }]
108 | })
109 | }
110 | ]
111 | );
112 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | angular-ui-router-title
2 | =========================
3 |
4 | [](https://travis-ci.org/nonplus/angular-ui-router-title)
5 |
6 | AngularJS module for updating browser title/history based on the current ui-router state.
7 |
8 |
9 | Motivation
10 | ----------
11 |
12 | Using ui-router states with `url` configurations enables browser history support and bookmarking of application state.
13 | It is important that the title in the browser history/bookmark represent the application state so that the user can tell
14 | where she's navigating to.
15 |
16 | The module provides a `$title` variable on the `$rootScope` that is populated based on the `$title` value resolved
17 | in `$state.$current` (or one of its parent states). If the current state doesn't resolve a `$title`,
18 | then `$rootScope.$title` will be `undefined`.
19 |
20 | The module also provides a `$breadcrumbs` array that is populated based on the `$title` of `$state.$current` and its parent states.
21 |
22 | The module sets the `document.title` to the value of the `$title` variable or, if configured, to the value returned by a `documentTitle(title)` callback.
23 | The browser sets bookmark and browser history text based on the `document.title`.
24 |
25 |
26 | Installing the Module
27 | ---------------------
28 | Installation can be done through bower:
29 | ``` shell
30 | bower install angular-ui-router-title
31 | ```
32 |
33 | In your page add:
34 | ```html
35 |
36 | ```
37 |
38 |
39 | Loading the Module
40 | ------------------
41 |
42 | This module declares itself as `ui.router.title`, so it can be declared as a dependency of your application as normal:
43 |
44 | ```javascript
45 | var app = angular.module('myApp', ['ng', 'ui.router.title']);
46 | ```
47 |
48 |
49 | Specifying the $title in the state definition
50 | ---------------------------------------------
51 |
52 | A state defines its title by declaring a `$title` value in its `resolve` block.
53 | It's a good idea for the `$title` to include information from the current state,
54 | so it may need to inject the `$stateParam` or another value that was resolved from them.
55 |
56 | ```javascript
57 | $stateProvider
58 | .state('home', {
59 | ...
60 | resolve: {
61 | // Constant title
62 | $title: function() { return 'Home'; }
63 | }
64 | })
65 | .state('about', {
66 | url: '/about',
67 | ...
68 | resolve: {
69 | // Constant title
70 | $title: function() { return 'About'; }
71 | }
72 | })
73 | .state('contacts', {
74 | url: '/contacts',
75 | ...
76 | resolve: {
77 | // List of contacts
78 | contacts: ['Contacts', function(Contacts) {
79 | // Use Contacts service to retrieve list
80 | return Contacts.query();
81 | }],
82 | // Dynamic title showing number of contacts
83 | $title: ['contacts', function(contacts) {
84 | return 'Contacts (' + contacts.length + ')';
85 | }]
86 | }
87 | })
88 | .state('contact', {
89 | url: '/contact/:contactId',
90 | ...
91 | resolve: {
92 | // Single contact
93 | contact: ['Contacts', '$stateParams', function(Contacts, $stateParams) {
94 | // Use Contacts service to retrieve a contact
95 | return Contacts.get({ id: $stateParams.contactId });
96 | }],
97 | // Dynamic title showing the name of contact
98 | $title: ['contact', function(contact) {
99 | return contact.name;
100 | }]
101 | }
102 | })
103 | .state('contact.edit', {
104 | url: '/edit',
105 | ...
106 | resolve: {
107 | // Dynamic title appending to parent state's title
108 | $title: ['$title', function($title) {
109 | return $title + " (edit)";
110 | }]
111 | }
112 | })
113 | ```
114 |
115 |
116 | Configuring a custom document.title
117 | -----------------------------------
118 |
119 | By default, the module will set the `document.title` to the value of `$rootScope.$title`. A common convention is to include
120 | the application name in the document.title. Customization of the `document.title` can be achieved via the `$titleProvier.documentTitle`
121 | callback specification.
122 |
123 | ```javascript
124 | angular.module('myApp', ['ng', 'ui.router.title'])
125 | .config(function($titleProvider) {
126 | $titleProvider.documentTitle(function($rootScope) {
127 | return $rootScope.$title ? $rootScope.$title + " - My Application" : "My Application";
128 | });
129 | });
130 | ```
131 |
132 |
133 | Using the $title in a header
134 | ----------------------------
135 |
136 | The `$title` property contains the resolve title and cen be used, for example, to set the contents of an `` tag.
137 |
138 | ```html
139 | My Application
140 | ```
141 |
142 |
143 | Using the $breadcrumbs
144 | ----------------------
145 |
146 | The `$breadcrumbs` array contains objects, one for each state that resolves a `$title` value. Each entry contains:
147 |
148 | * `title`: $title value of this state
149 | * `state`: name of the state
150 | * `stateParams`: $stateParams of the state.
151 |
152 | ```html
153 |
154 |
155 | {{crumb.title}}
156 | {{crumb.title}}
157 |
158 |
159 | ```
160 |
161 |
162 | Copyright & License
163 | -------------------
164 |
165 | Copyright 2015 Stepan Riha. All Rights Reserved.
166 |
167 | This may be redistributed under the MIT licence. For the full license terms, see the LICENSE file which
168 | should be alongside this readme.
169 |
--------------------------------------------------------------------------------
/sample/app/contacts/contacts.js:
--------------------------------------------------------------------------------
1 | angular.module('uiRouterSample.contacts', [
2 | 'ui.router'
3 | ])
4 |
5 | .config(
6 | [ '$stateProvider', '$urlRouterProvider',
7 | function ($stateProvider, $urlRouterProvider) {
8 | $stateProvider
9 | //////////////
10 | // Contacts //
11 | //////////////
12 | .state('contacts', {
13 |
14 | // With abstract set to true, that means this state can not be explicitly activated.
15 | // It can only be implicitly activated by activating one of its children.
16 | abstract: true,
17 |
18 | // This abstract state will prepend '/contacts' onto the urls of all its children.
19 | url: '/contacts',
20 |
21 | // Example of loading a template from a file. This is also a top level state,
22 | // so this template file will be loaded and then inserted into the ui-view
23 | // within index.html.
24 | templateUrl: 'app/contacts/contacts.html',
25 |
26 | // Use `resolve` to resolve any asynchronous controller dependencies
27 | // *before* the controller is instantiated. In this case, since contacts
28 | // returns a promise, the controller will wait until contacts.all() is
29 | // resolved before instantiation. Non-promise return values are considered
30 | // to be resolved immediately.
31 | resolve: {
32 | // Dynamic $title using resolved 'contacts'.
33 | $title: ["contacts", function(contacts) {
34 | return "Contacts (" + contacts.length + ")";
35 | }],
36 | contacts: ['contacts',
37 | function( contacts){
38 | return contacts.all();
39 | }]
40 | },
41 |
42 | // You can pair a controller to your template. There *must* be a template to pair with.
43 | controller: ['$scope', '$state', 'contacts', 'utils',
44 | function ( $scope, $state, contacts, utils) {
45 |
46 | // Add a 'contacts' field in this abstract parent's scope, so that all
47 | // child state views can access it in their scopes. Please note: scope
48 | // inheritance is not due to nesting of states, but rather choosing to
49 | // nest the templates of those states. It's normal scope inheritance.
50 | $scope.contacts = contacts;
51 |
52 | $scope.goToRandom = function () {
53 | var randId = utils.newRandomKey($scope.contacts, "id", $state.params.contactId);
54 |
55 | // $state.go() can be used as a high level convenience method
56 | // for activating a state programmatically.
57 | $state.go('contacts.detail', { contactId: randId });
58 | };
59 | }]
60 | })
61 |
62 | /////////////////////
63 | // Contacts > List //
64 | /////////////////////
65 |
66 | // Using a '.' within a state name declares a child within a parent.
67 | // So you have a new state 'list' within the parent 'contacts' state.
68 | .state('contacts.list', {
69 |
70 | // Using an empty url means that this child state will become active
71 | // when its parent's url is navigated to. Urls of child states are
72 | // automatically appended to the urls of their parent. So this state's
73 | // url is '/contacts' (because '/contacts' + '').
74 | url: '',
75 |
76 | // IMPORTANT: Now we have a state that is not a top level state. Its
77 | // template will be inserted into the ui-view within this state's
78 | // parent's template; so the ui-view within contacts.html. This is the
79 | // most important thing to remember about templates.
80 | templateUrl: 'app/contacts/contacts.list.html'
81 | })
82 |
83 | ///////////////////////
84 | // Contacts > Detail //
85 | ///////////////////////
86 |
87 | // You can have unlimited children within a state. Here is a second child
88 | // state within the 'contacts' parent state.
89 | .state('contacts.detail', {
90 |
91 | // Urls can have parameters. They can be specified like :param or {param}.
92 | // If {} is used, then you can also specify a regex pattern that the param
93 | // must match. The regex is written after a colon (:). Note: Don't use capture
94 | // groups in your regex patterns, because the whole regex is wrapped again
95 | // behind the scenes. Our pattern below will only match numbers with a length
96 | // between 1 and 4.
97 |
98 | // Since this state is also a child of 'contacts' its url is appended as well.
99 | // So its url will end up being '/contacts/{contactId:[0-9]{1,4}}'. When the
100 | // url becomes something like '/contacts/42' then this state becomes active
101 | // and the $stateParams object becomes { contactId: 42 }.
102 | url: '/{contactId:[0-9]{1,4}}',
103 |
104 | resolve: {
105 | // Dynamic $title using resolved 'contact' name.
106 | $title: ['contact', function(contact) {
107 | return contact.name;
108 | }],
109 | contact: ['utils', 'contacts', '$stateParams', function(utils, contacts, $stateParams) {
110 | return utils.findById(contacts, $stateParams.contactId);
111 | }]
112 | },
113 |
114 | // If there is more than a single ui-view in the parent template, or you would
115 | // like to target a ui-view from even higher up the state tree, you can use the
116 | // views object to configure multiple views. Each view can get its own template,
117 | // controller, and resolve data.
118 |
119 | // View names can be relative or absolute. Relative view names do not use an '@'
120 | // symbol. They always refer to views within this state's parent template.
121 | // Absolute view names use a '@' symbol to distinguish the view and the state.
122 | // So 'foo@bar' means the ui-view named 'foo' within the 'bar' state's template.
123 | views: {
124 |
125 | // So this one is targeting the unnamed view within the parent state's template.
126 | '': {
127 | templateUrl: 'app/contacts/contacts.detail.html',
128 | controller: ['$scope', 'contact',
129 | function ( $scope, contact) {
130 | $scope.contact = contact;
131 | }]
132 | },
133 |
134 | // This one is targeting the ui-view="hint" within the unnamed root, aka index.html.
135 | // This shows off how you could populate *any* view within *any* ancestor state.
136 | 'hint@': {
137 | template: 'This is contacts.detail populating the "hint" ui-view'
138 | },
139 |
140 | // This one is targeting the ui-view="menuTip" within the parent state's template.
141 | 'menuTip': {
142 | // templateProvider is the final method for supplying a template.
143 | // There is: template, templateUrl, and templateProvider.
144 | templateProvider: ['$stateParams',
145 | function ( $stateParams) {
146 | // This is just to demonstrate that $stateParams injection works for templateProvider.
147 | // $stateParams are the parameters for the new state we're transitioning to, even
148 | // though the global '$stateParams' has not been updated yet.
149 | return 'Contact ID: ' + $stateParams.contactId + ' ';
150 | }]
151 | }
152 | }
153 | })
154 |
155 | //////////////////////////////
156 | // Contacts > Detail > Item //
157 | //////////////////////////////
158 |
159 | .state('contacts.detail.item', {
160 |
161 | // So following what we've learned, this state's full url will end up being
162 | // '/contacts/{contactId}/item/:itemId'. We are using both types of parameters
163 | // in the same url, but they behave identically.
164 | url: '/item/:itemId',
165 |
166 | resolve: {
167 | // Dynamic $title using parent state's $title and resolved 'item' type.
168 | $title: ["$title", 'item', function($title, item) {
169 | return $title + " (" + item.type + ")";
170 | }],
171 | item: ['$stateParams', 'contact', 'utils',
172 | function ( $stateParams, contact, utils) {
173 | return utils.findById(contact.items, $stateParams.itemId);
174 | }]
175 | },
176 |
177 | views: {
178 |
179 | // This is targeting the unnamed ui-view within the parent state 'contact.detail'
180 | // We wouldn't have to do it this way if we didn't also want to set the 'hint' view below.
181 | // We could instead just set templateUrl and controller outside of the view obj.
182 | '': {
183 | templateUrl: 'app/contacts/contacts.detail.item.html',
184 | controller: ['$scope', '$stateParams', '$state', 'item',
185 | function ( $scope, $stateParams, $state, item) {
186 | $scope.item = item;
187 |
188 | $scope.edit = function () {
189 | // Here we show off go's ability to navigate to a relative state. Using '^' to go upwards
190 | // and '.' to go down, you can navigate to any relative state (ancestor or descendant).
191 | // Here we are going down to the child state 'edit' (full name of 'contacts.detail.item.edit')
192 | $state.go('.edit', $stateParams);
193 | };
194 | }]
195 | },
196 |
197 | // Here we see we are overriding the template that was set by 'contacts.detail'
198 | 'hint@': {
199 | template: ' This is contacts.detail.item overriding the "hint" ui-view'
200 | }
201 | }
202 | })
203 |
204 | /////////////////////////////////////
205 | // Contacts > Detail > Item > Edit //
206 | /////////////////////////////////////
207 |
208 | // Notice that this state has no 'url'. States do not require a url. You can use them
209 | // simply to organize your application into "places" where each "place" can configure
210 | // only what it needs. The only way to get to this state is via $state.go (or transitionTo)
211 | .state('contacts.detail.item.edit', {
212 | views: {
213 |
214 | // This is targeting the unnamed view within the 'contacts.detail' state
215 | // essentially swapping out the template that 'contacts.detail.item' had
216 | // inserted with this state's template.
217 | '@contacts.detail': {
218 | templateUrl: 'app/contacts/contacts.detail.item.edit.html',
219 | controller: ['$scope', '$stateParams', '$state', 'utils',
220 | function ( $scope, $stateParams, $state, utils) {
221 | $scope.item = utils.findById($scope.contact.items, $stateParams.itemId);
222 | $scope.done = function () {
223 | // Go back up. '^' means up one. '^.^' would be up twice, to the grandparent.
224 | $state.go('^', $stateParams);
225 | };
226 | }]
227 | }
228 | }
229 | });
230 | }
231 | ]
232 | );
233 |
--------------------------------------------------------------------------------
/test/angular-ui-router-title.spec.ts:
--------------------------------------------------------------------------------
1 | let mock = angular.mock;
2 |
3 | describe('on $stateChangeSuccess', function() {
4 |
5 | beforeEach(mock.module('ui.router.title'));
6 |
7 | describe("when no $title", function() {
8 |
9 | beforeEach(mock.module(function($stateProvider: ng.ui.IStateProvider) {
10 |
11 | $stateProvider
12 | .state('parent', {
13 | })
14 | .state('parent.child', {
15 | })
16 | ;
17 |
18 | }));
19 |
20 | it("should delete $rootScope.$title", mock.inject(function(
21 | $state: ng.ui.IStateService,
22 | $rootScope: ng.IRootScopeService,
23 | $timeout: ng.ITimeoutService
24 | ) {
25 | $rootScope.$title = 'originalTitle';
26 |
27 | $state.go('parent');
28 | $timeout.flush(); $rootScope.$digest();
29 | expect($rootScope.$title).toEqual(undefined);
30 |
31 | $rootScope.$title = 'originalTitle';
32 | $state.go('parent.child');
33 | $timeout.flush(); $rootScope.$digest();
34 | expect($rootScope.$title).toEqual(undefined);
35 | }));
36 |
37 | it("should set $rootScope.$breadCrumbs to []", mock.inject(function(
38 | $state: ng.ui.IStateService,
39 | $rootScope: ng.IRootScopeService,
40 | $timeout: ng.ITimeoutService
41 | ) {
42 | $rootScope.$breadcrumbs = [{ title: "title", state: "state", stateParams: null }];
43 |
44 | $state.go('parent');
45 | $timeout.flush(); $rootScope.$digest();
46 | expect($rootScope.$breadcrumbs).toEqual([]);
47 |
48 | $rootScope.$breadcrumbs = [{ title: "title", state: "state", stateParams: null }];
49 |
50 | $state.go('parent.child');
51 | $timeout.flush(); $rootScope.$digest();
52 | expect($rootScope.$breadcrumbs).toEqual([]);
53 | }));
54 |
55 | }); // when no $title
56 |
57 | describe("when $title is a function", function() {
58 |
59 | var parentTitle, grandChildTitle;
60 |
61 | beforeEach(mock.module(function($stateProvider: ng.ui.IStateProvider) {
62 |
63 | $stateProvider
64 | .state('parent', {
65 | params: {
66 | param1: null
67 | },
68 | resolve: {
69 | $title: function() {
70 | return function() {
71 | return parentTitle;
72 | };
73 | }
74 | }
75 | })
76 | .state('parent.child', {
77 | })
78 | .state('parent.child.grandchild', {
79 | params: {
80 | param2: null
81 | },
82 | resolve: {
83 | $title: function() {
84 | return function() {
85 | return grandChildTitle;
86 | };
87 | }
88 | }
89 | })
90 | ;
91 |
92 | }));
93 |
94 | it("should set $rootScope.$title", mock.inject(function(
95 | $state: ng.ui.IStateService,
96 | $rootScope: ng.IRootScopeService,
97 | $timeout: ng.ITimeoutService
98 | ) {
99 | $rootScope.$title = 'originalTitle';
100 |
101 | parentTitle = 'parent-title 1';
102 | $state.go('parent');
103 | $timeout.flush(); $rootScope.$digest();
104 | expect($rootScope.$title).toEqual('parent-title 1');
105 |
106 | parentTitle = 'parent-title 2';
107 | $state.go('parent.child');
108 | $timeout.flush(); $rootScope.$digest();
109 | $state.go('parent');
110 | $timeout.flush(); $rootScope.$digest();
111 | expect($rootScope.$title).toEqual('parent-title 2');
112 |
113 | parentTitle = 'parent-title 3';
114 | $state.go('parent.child');
115 | $timeout.flush(); $rootScope.$digest();
116 | expect($rootScope.$title).toEqual('parent-title 3');
117 |
118 | grandChildTitle = 'grandchild-title 1';
119 | $state.go('parent.child.grandchild', { param1: "param1" });
120 | $timeout.flush(); $rootScope.$digest();
121 | expect($rootScope.$title).toEqual('grandchild-title 1');
122 | }));
123 |
124 | it("should set $rootScope.$breadcrumbs", mock.inject(function(
125 | $state: ng.ui.IStateService,
126 | $rootScope: ng.IRootScopeService,
127 | $timeout: ng.ITimeoutService
128 | ) {
129 | $rootScope.$breadcrumbs = [{ title: "title", state: "state", stateParams: null }];
130 |
131 | parentTitle = 'parent-title 1';
132 | $state.go('parent');
133 | $timeout.flush(); $rootScope.$digest();
134 | expect($rootScope.$breadcrumbs).toEqual([{
135 | title: 'parent-title 1',
136 | state: 'parent',
137 | stateParams: { param1: null }
138 | }]);
139 |
140 | parentTitle = 'parent-title 2';
141 | $state.go('parent.child');
142 | $timeout.flush(); $rootScope.$digest();
143 | $state.go('parent');
144 | $timeout.flush(); $rootScope.$digest();
145 | expect($rootScope.$breadcrumbs).toEqual([{
146 | title: 'parent-title 2',
147 | state: 'parent',
148 | stateParams: { param1: null }
149 | }]);
150 |
151 | parentTitle = 'parent-title 3';
152 | $state.go('parent.child', { param1: "param1" });
153 | $timeout.flush(); $rootScope.$digest();
154 | expect($rootScope.$breadcrumbs).toEqual([{
155 | title: 'parent-title 3',
156 | state: 'parent',
157 | stateParams: { param1: "param1" }
158 | }]);
159 |
160 | grandChildTitle = 'grandchild-title 1';
161 | $state.go('parent.child.grandchild', { param1: "param1", param2: "param2" });
162 | $timeout.flush(); $rootScope.$digest();
163 | expect($rootScope.$breadcrumbs).toEqual([{
164 | title: 'parent-title 3',
165 | state: 'parent',
166 | stateParams: { param1: "param1" }
167 | }, {
168 | title: 'grandchild-title 1',
169 | state: 'parent.child.grandchild',
170 | stateParams: { param1: "param1", param2: "param2" }
171 | }]);
172 | }));
173 |
174 | }); // when $title is a function
175 |
176 | describe("when $title is a value", function() {
177 |
178 | beforeEach(mock.module(function($stateProvider: ng.ui.IStateProvider) {
179 |
180 | $stateProvider
181 | .state('parent', {
182 | params: {
183 | param1: null
184 | },
185 | resolve: {
186 | $title: () => 'parent-title'
187 | }
188 | })
189 | .state('parent.child', {
190 | })
191 | .state('parent.child.grandchild', {
192 | params: {
193 | param2: null
194 | },
195 | resolve: {
196 | $title: () => 'grandchild-title'
197 | }
198 | })
199 | ;
200 |
201 | }));
202 |
203 | it("should set $rootScope.$title", mock.inject(function(
204 | $state: ng.ui.IStateService,
205 | $rootScope: ng.IRootScopeService,
206 | $timeout: ng.ITimeoutService
207 | ) {
208 | $rootScope.$title = 'originalTitle';
209 |
210 | $state.go('parent');
211 | $timeout.flush(); $rootScope.$digest();
212 | expect($rootScope.$title).toEqual('parent-title');
213 |
214 | $state.go('parent.child');
215 | $timeout.flush(); $rootScope.$digest();
216 | expect($rootScope.$title).toEqual('parent-title');
217 |
218 | $state.go('parent.child.grandchild');
219 | $timeout.flush(); $rootScope.$digest();
220 | expect($rootScope.$title).toEqual('grandchild-title');
221 | }));
222 |
223 | it("should set $rootScope.$breadCrumbs", mock.inject(function(
224 | $state: ng.ui.IStateService,
225 | $rootScope: ng.IRootScopeService,
226 | $timeout: ng.ITimeoutService
227 | ) {
228 | $rootScope.$breadcrumbs = [{ title: "title", state: "state", stateParams: null }];
229 |
230 | $state.go('parent', { param1: "param1" });
231 | $timeout.flush(); $rootScope.$digest();
232 | expect($rootScope.$breadcrumbs).toEqual([{
233 | title: 'parent-title',
234 | state: 'parent',
235 | stateParams: { param1: "param1" }
236 | }]);
237 |
238 | $state.go('parent.child', { param1: "param1" });
239 | $timeout.flush(); $rootScope.$digest();
240 | expect($rootScope.$breadcrumbs).toEqual([{
241 | title: 'parent-title',
242 | state: 'parent',
243 | stateParams: { param1: "param1" }
244 | }]);
245 |
246 | $state.go('parent.child.grandchild', { param1: "param1", param2: "param2" });
247 | $timeout.flush(); $rootScope.$digest();
248 | expect($rootScope.$title).toEqual('grandchild-title');
249 | expect($rootScope.$breadcrumbs).toEqual([{
250 | title: 'parent-title',
251 | state: 'parent',
252 | stateParams: { param1: "param1" }
253 | }, {
254 | title: 'grandchild-title',
255 | state: 'parent.child.grandchild',
256 | stateParams: { param1: "param1", param2: "param2" }
257 | }]);
258 | }));
259 |
260 | }); // when $title is a value
261 |
262 | describe("when documentTitle configured", function() {
263 |
264 | function documentTitle(title) {
265 | return title + " Document Title";
266 | }
267 |
268 | beforeEach(mock.module(function($stateProvider: ng.ui.IStateProvider, $titleProvider: ng.ui.ITitleProvider) {
269 |
270 | $stateProvider
271 | .state('parent', {
272 | params: {
273 | param1: null
274 | },
275 | resolve: {
276 | $title: () => 'parent-title'
277 | }
278 | })
279 | .state('parent.child', {
280 | })
281 | .state('parent.child.grandchild', {
282 | params: {
283 | param2: null
284 | },
285 | resolve: {
286 | $title: () => 'grandchild-title'
287 | }
288 | })
289 | ;
290 |
291 | $titleProvider.documentTitle(function ($rootScope) {
292 | return documentTitle($rootScope.$title);
293 | });
294 |
295 | }));
296 |
297 | it("should set document.title and to documentTitle($title)", mock.inject(function(
298 | $state: ng.ui.IStateService,
299 | $rootScope: ng.IRootScopeService,
300 | $timeout: ng.ITimeoutService
301 | ) {
302 | $rootScope.$breadcrumbs = [{ title: "title", state: "state", stateParams: null }];
303 | for (let eltTitle of [].slice.call(document.getElementsByTagName("title"))) {
304 | eltTitle.parentElement.removeChild(eltTitle);
305 | }
306 | document.title = "Initial Title";
307 |
308 | $state.go('parent', { param1: "param1" });
309 | $timeout.flush(); $rootScope.$digest();
310 | assertTitle('parent-title');
311 |
312 | $state.go('parent.child', { param1: "param1" });
313 | $timeout.flush(); $rootScope.$digest();
314 | assertTitle('parent-title');
315 |
316 | $state.go('parent.child.grandchild', { param1: "param1", param2: "param2" });
317 | $timeout.flush(); $rootScope.$digest();
318 | assertTitle('grandchild-title');
319 | }));
320 |
321 | function assertTitle($title) {
322 | let expecte = documentTitle($title);
323 | expect(document.title).toEqual(expecte);
324 | expect((document.getElementsByTagName("title")[0] as HTMLTitleElement).text).toEqual(expecte);
325 | }
326 |
327 | }); // when documentTitle configured
328 |
329 | describe("when documentTitle not configured", function() {
330 |
331 | beforeEach(mock.module(function($stateProvider: ng.ui.IStateProvider, $titleProvider: ng.ui.ITitleProvider) {
332 |
333 | $stateProvider
334 | .state('parent', {
335 | params: {
336 | param1: null
337 | },
338 | resolve: {
339 | $title: () => 'parent-title'
340 | }
341 | })
342 | .state('parent.child', {
343 | })
344 | .state('parent.child.grandchild', {
345 | params: {
346 | param2: null
347 | },
348 | resolve: {
349 | $title: () => 'grandchild-title'
350 | }
351 | })
352 | ;
353 |
354 | $titleProvider.documentTitle(null);
355 |
356 | }));
357 |
358 | it("should set document.title and to $title", mock.inject(function(
359 | $state: ng.ui.IStateService,
360 | $rootScope: ng.IRootScopeService,
361 | $timeout: ng.ITimeoutService
362 | ) {
363 | $rootScope.$breadcrumbs = [{ title: "title", state: "state", stateParams: null }];
364 | for (let eltTitle of [].slice.call(document.getElementsByTagName("title"))) {
365 | eltTitle.parentElement.removeChild(eltTitle);
366 | }
367 | document.title = "Initial Title";
368 |
369 | $state.go('parent', { param1: "param1" });
370 | $timeout.flush(); $rootScope.$digest();
371 | assertTitle('parent-title');
372 |
373 | $state.go('parent.child', { param1: "param1" });
374 | $timeout.flush(); $rootScope.$digest();
375 | assertTitle('parent-title');
376 |
377 | $state.go('parent.child.grandchild', { param1: "param1", param2: "param2" });
378 | $timeout.flush(); $rootScope.$digest();
379 | assertTitle('grandchild-title');
380 | }));
381 |
382 | function assertTitle($title) {
383 | let expected = $title;
384 | expect(document.title).toEqual(expected);
385 | expect((document.getElementsByTagName("title")[0] as HTMLTitleElement).text).toEqual(expected);
386 | }
387 |
388 | }); // when documentTitle not configured
389 |
390 | }); // on $stateChangeSuccess
--------------------------------------------------------------------------------