')($rootScope);
192 | $rootScope.$digest();
193 | });
194 | return elt;
195 | }
196 | });
197 |
--------------------------------------------------------------------------------
/src/ngOutlet.js:
--------------------------------------------------------------------------------
1 | function createOutlet($q, $animate) {
2 | function Outlet(router, scope, element, controller, $transclude) {
3 | this.router = router;
4 | this.scope = scope;
5 | this.element = element;
6 | this.controller = controller;
7 | this.$transclude = $transclude;
8 | }
9 | Outlet.prototype.cleanupLastView = function () {
10 | var _this = this;
11 | if (this.previousLeaveAnimation) {
12 | $animate.cancel(this.previousLeaveAnimation);
13 | this.previousLeaveAnimation = null;
14 | }
15 | if (this.currentScope) {
16 | this.currentScope.$destroy();
17 | this.currentScope = null;
18 | }
19 | if (this.currentElement) {
20 | this.previousLeaveAnimation = $animate.leave(this.currentElement);
21 | this.previousLeaveAnimation.then(function () { _this.previousLeaveAnimation = null; });
22 | this.currentElement = null;
23 | }
24 | };
25 | Outlet.prototype.reuse = function (instruction) {
26 | var next = $q.when(true);
27 | var previousInstruction = this.currentInstruction;
28 | this.currentInstruction = instruction;
29 | if (this.currentController && this.currentController.$routerOnReuse) {
30 | next = $q.when(
31 | this.currentController.$routerOnReuse(this.currentInstruction, previousInstruction));
32 | }
33 | return next;
34 | };
35 | Outlet.prototype.routerCanReuse = function (nextInstruction) {
36 | var result;
37 | if (!this.currentInstruction ||
38 | this.currentInstruction.componentType !== nextInstruction.componentType) {
39 | result = false;
40 | }
41 | else if (this.currentController && this.currentController.$routerCanReuse) {
42 | result = this.currentController.$routerCanReuse(nextInstruction, this.currentInstruction);
43 | }
44 | else {
45 | result = nextInstruction === this.currentInstruction ||
46 | angular.equals(nextInstruction.params, this.currentInstruction.params);
47 | }
48 | return $q.when(result);
49 | };
50 | Outlet.prototype.routerCanDeactivate = function (instruction) {
51 | if (this.currentController && this.currentController.$routerCanDeactivate) {
52 | return $q.when(
53 | this.currentController.$routerCanDeactivate(instruction, this.currentInstruction));
54 | }
55 | return $q.when(true);
56 | };
57 | Outlet.prototype.deactivate = function (instruction) {
58 | if (this.currentController && this.currentController.$routerOnDeactivate) {
59 | return $q.when(
60 | this.currentController.$routerOnDeactivate(instruction, this.currentInstruction));
61 | }
62 | return $q.when();
63 | };
64 | Outlet.prototype.activate = function (instruction) {
65 | var _this = this;
66 | this.previousInstruction = this.currentInstruction;
67 | this.currentInstruction = instruction;
68 | var componentName = this.controller.$$componentName = instruction.componentType;
69 | if (typeof componentName !== 'string') {
70 | throw new Error('Component is not a string for ' + instruction.urlPath);
71 | }
72 | this.controller.$$template = '<' + dashCase(componentName) + ' $router="::$$router">' +
73 | dashCase(componentName) + '>';
74 | this.controller.$$router = this.router.childRouter(instruction.componentType);
75 | this.controller.$$outlet = this;
76 | var newScope = this.scope.$new();
77 | newScope.$$router = this.controller.$$router;
78 | this.deferredActivation = $q.defer();
79 | var clone = this.$transclude(newScope, function (clone) {
80 | $animate.enter(clone, null, _this.currentElement || _this.element);
81 | _this.cleanupLastView();
82 | });
83 | this.currentElement = clone;
84 | this.currentScope = newScope;
85 | return this.deferredActivation.promise;
86 | };
87 | return Outlet;
88 | }
89 |
90 | /**
91 | * @name ngOutlet
92 | *
93 | * @description
94 | * An ngOutlet is where resolved content goes.
95 | *
96 | * ## Use
97 | *
98 | * ```html
99 | *
100 | * ```
101 | *
102 | * The value for the `ngOutlet` attribute is optional.
103 | */
104 | exports.ngOutletDirective = function($animate, $q, $rootRouter) {
105 | var Outlet = createOutlet($q, $animate);
106 | var rootRouter = $rootRouter;
107 |
108 | return {
109 | restrict: 'AE',
110 | transclude: 'element',
111 | terminal: true,
112 | priority: 400,
113 | require: ['?^^ngOutlet', 'ngOutlet'],
114 | link: function outletLink(scope, element, attrs, ctrls, $transclude) {
115 | var parentCtrl = ctrls[0], myCtrl = ctrls[1],
116 | router = (parentCtrl && parentCtrl.$$router) || rootRouter;
117 | myCtrl.$$currentComponent = null;
118 | router.registerPrimaryOutlet(new Outlet(router, scope, element, myCtrl, $transclude));
119 | },
120 | controller: function() {},
121 | controllerAs: '$$ngOutlet'
122 | };
123 | };
124 |
125 | /**
126 | * This directive is responsible for compiling the contents of ng-outlet
127 | */
128 | exports.ngOutletFillContentDirective = function($compile) {
129 | return {
130 | restrict: 'EA',
131 | priority: -400,
132 | require: 'ngOutlet',
133 | link: function(scope, element, attrs, ctrl) {
134 | var template = ctrl.$$template;
135 | element.html(template);
136 | $compile(element.contents())(scope);
137 | }
138 | };
139 | };
140 |
141 |
142 |
143 | exports.routerTriggerDirective = function($q) {
144 | return {
145 | require: '^ngOutlet',
146 | priority: -1000,
147 | link: function(scope, element, attr, ngOutletCtrl) {
148 | var promise = $q.when();
149 | var outlet = ngOutletCtrl.$$outlet;
150 | var currentComponent = outlet.currentController =
151 | element.controller(ngOutletCtrl.$$componentName);
152 | if (currentComponent.$routerOnActivate) {
153 | promise = $q.when(currentComponent.$routerOnActivate(outlet.currentInstruction,
154 | outlet.previousInstruction));
155 | }
156 | promise.then(outlet.deferredActivation.resolve, outlet.deferredActivation.reject);
157 | }
158 | };
159 | };
160 |
161 | function dashCase(str) {
162 | return str.replace(/[A-Z]/g, function(match) { return '-' + match.toLowerCase(); });
163 | }
--------------------------------------------------------------------------------
/examples/outdated/phone-kitten/phones/phones.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "age": 0,
4 | "id": "motorola-xoom-with-wi-fi",
5 | "imageUrl": "img/phones/motorola-xoom-with-wi-fi.0.jpg",
6 | "name": "Motorola XOOM\u2122 with Wi-Fi",
7 | "snippet": "The Next, Next Generation\r\n\r\nExperience the future with Motorola XOOM with Wi-Fi, the world's first tablet powered by Android 3.0 (Honeycomb)."
8 | },
9 | {
10 | "age": 1,
11 | "id": "motorola-xoom",
12 | "imageUrl": "img/phones/motorola-xoom.0.jpg",
13 | "name": "MOTOROLA XOOM\u2122",
14 | "snippet": "The Next, Next Generation\n\nExperience the future with MOTOROLA XOOM, the world's first tablet powered by Android 3.0 (Honeycomb)."
15 | },
16 | {
17 | "age": 2,
18 | "carrier": "AT&T",
19 | "id": "motorola-atrix-4g",
20 | "imageUrl": "img/phones/motorola-atrix-4g.0.jpg",
21 | "name": "MOTOROLA ATRIX\u2122 4G",
22 | "snippet": "MOTOROLA ATRIX 4G the world's most powerful smartphone."
23 | },
24 | {
25 | "age": 3,
26 | "id": "dell-streak-7",
27 | "imageUrl": "img/phones/dell-streak-7.0.jpg",
28 | "name": "Dell Streak 7",
29 | "snippet": "Introducing Dell\u2122 Streak 7. Share photos, videos and movies together. It\u2019s small enough to carry around, big enough to gather around."
30 | },
31 | {
32 | "age": 4,
33 | "carrier": "Cellular South",
34 | "id": "samsung-gem",
35 | "imageUrl": "img/phones/samsung-gem.0.jpg",
36 | "name": "Samsung Gem\u2122",
37 | "snippet": "The Samsung Gem\u2122 brings you everything that you would expect and more from a touch display smart phone \u2013 more apps, more features and a more affordable price."
38 | },
39 | {
40 | "age": 5,
41 | "carrier": "Dell",
42 | "id": "dell-venue",
43 | "imageUrl": "img/phones/dell-venue.0.jpg",
44 | "name": "Dell Venue",
45 | "snippet": "The Dell Venue; Your Personal Express Lane to Everything"
46 | },
47 | {
48 | "age": 6,
49 | "carrier": "Best Buy",
50 | "id": "nexus-s",
51 | "imageUrl": "img/phones/nexus-s.0.jpg",
52 | "name": "Nexus S",
53 | "snippet": "Fast just got faster with Nexus S. A pure Google experience, Nexus S is the first phone to run Gingerbread (Android 2.3), the fastest version of Android yet."
54 | },
55 | {
56 | "age": 7,
57 | "carrier": "Cellular South",
58 | "id": "lg-axis",
59 | "imageUrl": "img/phones/lg-axis.0.jpg",
60 | "name": "LG Axis",
61 | "snippet": "Android Powered, Google Maps Navigation, 5 Customizable Home Screens"
62 | },
63 | {
64 | "age": 8,
65 | "id": "samsung-galaxy-tab",
66 | "imageUrl": "img/phones/samsung-galaxy-tab.0.jpg",
67 | "name": "Samsung Galaxy Tab\u2122",
68 | "snippet": "Feel Free to Tab\u2122. The Samsung Galaxy Tab\u2122 brings you an ultra-mobile entertainment experience through its 7\u201d display, high-power processor and Adobe\u00ae Flash\u00ae Player compatibility."
69 | },
70 | {
71 | "age": 9,
72 | "carrier": "Cellular South",
73 | "id": "samsung-showcase-a-galaxy-s-phone",
74 | "imageUrl": "img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg",
75 | "name": "Samsung Showcase\u2122 a Galaxy S\u2122 phone",
76 | "snippet": "The Samsung Showcase\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance, even outdoors"
77 | },
78 | {
79 | "age": 10,
80 | "carrier": "Verizon",
81 | "id": "droid-2-global-by-motorola",
82 | "imageUrl": "img/phones/droid-2-global-by-motorola.0.jpg",
83 | "name": "DROID\u2122 2 Global by Motorola",
84 | "snippet": "The first smartphone with a 1.2 GHz processor and global capabilities."
85 | },
86 | {
87 | "age": 11,
88 | "carrier": "Verizon",
89 | "id": "droid-pro-by-motorola",
90 | "imageUrl": "img/phones/droid-pro-by-motorola.0.jpg",
91 | "name": "DROID\u2122 Pro by Motorola",
92 | "snippet": "The next generation of DOES."
93 | },
94 | {
95 | "age": 12,
96 | "carrier": "AT&T",
97 | "id": "motorola-bravo-with-motoblur",
98 | "imageUrl": "img/phones/motorola-bravo-with-motoblur.0.jpg",
99 | "name": "MOTOROLA BRAVO\u2122 with MOTOBLUR\u2122",
100 | "snippet": "An experience to cheer about."
101 | },
102 | {
103 | "age": 13,
104 | "carrier": "T-Mobile",
105 | "id": "motorola-defy-with-motoblur",
106 | "imageUrl": "img/phones/motorola-defy-with-motoblur.0.jpg",
107 | "name": "Motorola DEFY\u2122 with MOTOBLUR\u2122",
108 | "snippet": "Are you ready for everything life throws your way?"
109 | },
110 | {
111 | "age": 14,
112 | "carrier": "T-Mobile",
113 | "id": "t-mobile-mytouch-4g",
114 | "imageUrl": "img/phones/t-mobile-mytouch-4g.0.jpg",
115 | "name": "T-Mobile myTouch 4G",
116 | "snippet": "The T-Mobile myTouch 4G is a premium smartphone designed to deliver blazing fast 4G speeds so that you can video chat from practically anywhere, with or without Wi-Fi."
117 | },
118 | {
119 | "age": 15,
120 | "carrier": "US Cellular",
121 | "id": "samsung-mesmerize-a-galaxy-s-phone",
122 | "imageUrl": "img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg",
123 | "name": "Samsung Mesmerize\u2122 a Galaxy S\u2122 phone",
124 | "snippet": "The Samsung Mesmerize\u2122 delivers a cinema quality experience like you\u2019ve never seen before. Its innovative 4\u201d touch display technology provides rich picture brilliance,even outdoors"
125 | },
126 | {
127 | "age": 16,
128 | "carrier": "Sprint",
129 | "id": "sanyo-zio",
130 | "imageUrl": "img/phones/sanyo-zio.0.jpg",
131 | "name": "SANYO ZIO",
132 | "snippet": "The Sanyo Zio by Kyocera is an Android smartphone with a combination of ultra-sleek styling, strong performance and unprecedented value."
133 | },
134 | {
135 | "age": 17,
136 | "id": "samsung-transform",
137 | "imageUrl": "img/phones/samsung-transform.0.jpg",
138 | "name": "Samsung Transform\u2122",
139 | "snippet": "The Samsung Transform\u2122 brings you a fun way to customize your Android powered touch screen phone to just the way you like it through your favorite themed \u201cSprint ID Service Pack\u201d."
140 | },
141 | {
142 | "age": 18,
143 | "id": "t-mobile-g2",
144 | "imageUrl": "img/phones/t-mobile-g2.0.jpg",
145 | "name": "T-Mobile G2",
146 | "snippet": "The T-Mobile G2 with Google is the first smartphone built for 4G speeds on T-Mobile's new network. Get the information you need, faster than you ever thought possible."
147 | },
148 | {
149 | "age": 19,
150 | "id": "motorola-charm-with-motoblur",
151 | "imageUrl": "img/phones/motorola-charm-with-motoblur.0.jpg",
152 | "name": "Motorola CHARM\u2122 with MOTOBLUR\u2122",
153 | "snippet": "Motorola CHARM fits easily in your pocket or palm. Includes MOTOBLUR service."
154 | }
155 | ]
156 |
--------------------------------------------------------------------------------
/test/ng_link_spec.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | describe('ngLink', function () {
4 |
5 | describe('html5Mode enabled', function () {
6 | runHrefTestsAndExpectPrefix('/', true);
7 | });
8 |
9 | describe('html5Mode disabled', function () {
10 | runHrefTestsAndExpectPrefix('', false, '');
11 | });
12 |
13 | describe('html5Mode disabled, with hash prefix', function () {
14 | runHrefTestsAndExpectPrefix('', false, '!');
15 | });
16 |
17 | describe('html5Mode enabled', function () {
18 | runHrefTestsAndExpectPrefix('/moo', true);
19 | });
20 |
21 | describe('html5Mode disabled', function () {
22 | runHrefTestsAndExpectPrefix('/moo', false, '');
23 | });
24 |
25 | describe('html5Mode disabled, with hash prefix', function () {
26 | runHrefTestsAndExpectPrefix('/moo', false, '!');
27 | });
28 |
29 | function runHrefTestsAndExpectPrefix(baseHref, html5Mode, hashPrefix) {
30 | var prefix = html5Mode ? '.' : '#' + hashPrefix;
31 |
32 | it('should allow linking from the parent to the child', function () {
33 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
34 | configureRouter([
35 | { path: '/a', component: 'oneCmp' },
36 | { path: '/b', component: 'twoCmp', name: 'Two' }
37 | ]);
38 |
39 | var elt = compile('
link | outer {
}');
40 | navigateTo('/a');
41 | expect(elt.find('a').attr('href')).toBe(prefix + '/b');
42 | });
43 |
44 | it('should allow linking from the child and the parent', function () {
45 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
46 | configureRouter([
47 | { path: '/a', component: 'oneCmp' },
48 | { path: '/b', component: 'twoCmp', name: 'Two' }
49 | ]);
50 |
51 | var elt = compile('outer {
}');
52 | navigateTo('/b');
53 | expect(elt.find('a').attr('href')).toBe(prefix + '/b');
54 | });
55 |
56 |
57 | it('should allow params in routerLink directive', function () {
58 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
59 | registerComponent('twoLinkCmp', '
', function () {this.number = 'two'});
60 | configureRouter([
61 | { path: '/a', component: 'twoLinkCmp' },
62 | { path: '/b/:param', component: 'twoCmp', name: 'Two' }
63 | ]);
64 |
65 | var elt = compile('
');
66 | navigateTo('/a');
67 | expect(elt.find('a').attr('href')).toBe(prefix + '/b/lol');
68 | });
69 |
70 |
71 | it('should update the href of links with bound params', function () {
72 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
73 | registerComponent('twoLinkCmp', '
', function () {this.number = 43});
74 | configureRouter([
75 | { path: '/a', component: 'twoLinkCmp' },
76 | { path: '/b/:param', component: 'twoCmp', name: 'Two' }
77 | ]);
78 |
79 | var elt = compile('
');
80 | navigateTo('/a');
81 | expect(elt.find('a').text()).toBe('43');
82 | expect(elt.find('a').attr('href')).toBe(prefix + '/b/43');
83 | });
84 |
85 |
86 | it('should navigate on left-mouse click when a link url matches a route', function () {
87 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
88 | configureRouter([
89 | { path: '/', component: 'oneCmp' },
90 | { path: '/two', component: 'twoCmp', name: 'Two'}
91 | ]);
92 |
93 | var elt = compile('
link |
');
94 | expect(elt.text()).toBe('link | one');
95 | expect(elt.find('a').attr('href')).toBe(prefix + '/two');
96 |
97 | elt.find('a')[0].click();
98 | inject(function($rootScope) { $rootScope.$digest(); });
99 | expect(elt.text()).toBe('link | two');
100 | });
101 |
102 |
103 | it('should not navigate on non-left mouse click when a link url matches a route', function() {
104 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
105 | configureRouter([
106 | { path: '/', component: 'oneCmp' },
107 | { path: '/two', component: 'twoCmp', name: 'Two'}
108 | ]);
109 |
110 | var elt = compile('
link |
');
111 | expect(elt.text()).toBe('link | one');
112 | elt.find('a').triggerHandler({ type: 'click', which: 3 });
113 | inject(function($rootScope) { $rootScope.$digest(); });
114 | expect(elt.text()).toBe('link | one');
115 | });
116 |
117 |
118 | // See https://github.com/angular/router/issues/206
119 | it('should not navigate a link without an href', function () {
120 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
121 | configureRouter([
122 | { path: '/', component: 'oneCmp' },
123 | { path: '/two', component: 'twoCmp', name: 'Two'}
124 | ]);
125 | expect(function () {
126 | var elt = compile('
link');
127 | expect(elt.text()).toBe('link');
128 | elt.find('a')[0].click();
129 | inject(function($rootScope) { $rootScope.$digest(); });
130 | }).not.toThrow();
131 | });
132 |
133 | it('should add an ng-link-active class on the current link', function() {
134 | setup({baseHref: baseHref, html5Mode: html5Mode, hashPrefix: hashPrefix});
135 | configureRouter([
136 | { path: '/', component: 'oneCmp', name: 'One' }
137 | ]);
138 |
139 | var elt = compile('
one |
');
140 | navigateTo('/');
141 | expect(elt.find('a').attr('class')).toBe('ng-link-active');
142 | });
143 | }
144 |
145 | function registerComponent(name, template, controller) {
146 | module(function($compileProvider) {
147 | $compileProvider.component(name, {
148 | template: template,
149 | controller: controller
150 | });
151 | });
152 | }
153 |
154 | function setup(config) {
155 | module(function($provide) {
156 | $provide.decorator('$browser', function($delegate) {
157 | $delegate.baseHref = function() { return config.baseHref; };
158 | return $delegate;
159 | });
160 | });
161 | module('ngComponentRouter');
162 | module(function($locationProvider) {
163 | $locationProvider.html5Mode(config.html5Mode);
164 | $locationProvider.hashPrefix(config.hashPrefix);
165 | });
166 | registerComponent('userCmp', '
hello {{$ctrl.$routeParams.name}}
', function () {});
167 | registerComponent('oneCmp', '
{{$ctrl.number}}
', function () {this.number = 'one'});
168 | registerComponent('twoCmp', '
', function () {this.number = 'two'});
169 | }
170 |
171 | function configureRouter(routeConfig) {
172 | inject(function($rootRouter) {
173 | $rootRouter.config(routeConfig);
174 | });
175 | }
176 |
177 | function compile(template) {
178 | var elt;
179 | inject(function($compile, $rootScope) {
180 | elt = $compile('
' + template + '
')($rootScope);
181 | $rootScope.$digest();
182 | });
183 | return elt;
184 | }
185 |
186 | function navigateTo(url) {
187 | inject(function($rootRouter, $rootScope) {
188 | $rootRouter.navigateByUrl(url);
189 | $rootScope.$digest();
190 | });
191 | }
192 | });
193 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing to Angular 1 Component Router
2 |
3 | We'd love for you to contribute to our source code and to make the Angular 1 Component Router even
4 | better than it is today!
5 |
6 | Remember that this is only for the Angular 1 Component Router. If you have issues with the Angular 2
7 | version of the Component Router then see the [Angular 2 repository][angular2]
8 |
9 | Here are the guidelines we'd like you to follow:
10 |
11 | - [Code of Conduct](#coc)
12 | - [Issue, Question or Problem?](#question)
13 | - [Feature Requests](#feature)
14 | - [Submission Guidelines](#submit)
15 | - [Coding Rules](#rules)
16 | - [Commit Message Guidelines](#commit)
17 | - [Signing the CLA](#cla)
18 |
19 | ##
Code of Conduct
20 | Help us keep Angular open and inclusive. Please read and follow our [Code of Conduct][coc].
21 |
22 | ##
Got an Issue, Question or Problem?
23 |
24 | If you find a bug in the source code or a mistake in the documentation, you can help us by
25 | submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request
26 | with a fix.
27 |
28 | **Please see the Submission Guidelines below**.
29 |
30 | ##
Want a Feature?
31 | You can request a new feature by submitting an issue to our [GitHub Repository][github]. If you
32 | would like to implement a new feature then consider what kind of change it is:
33 |
34 |
35 | ##
Submission Guidelines
36 |
37 | ### Submitting an Issue
38 | Before you submit your issue search the [archive][issues], maybe your question was already answered.
39 |
40 | If your issue appears to be a bug, and hasn't been reported, open a new issue.
41 | Help us to maximize the effort we can spend fixing issues and adding new
42 | features, by not reporting duplicate issues. Providing the following information will increase the
43 | chances of your issue being dealt with quickly:
44 |
45 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps
46 | * **Motivation for or Use Case** - explain why this is a bug for you
47 | * **Angular Version(s)** - is it a regression?
48 | * **Browsers and Operating System** - is this a problem with all browsers or only IE8?
49 | * **Reproduce the Error** - provide a live example (using [Plunker][plunker] or
50 | [JSFiddle][jsfiddle]) or an unambiguous set of steps.
51 | * **Related Issues** - has a similar issue been reported before?
52 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be
53 | causing the problem (line of code or commit)
54 |
55 | Here is a great example of a well defined issue: https://github.com/angular/angular.js/issues/5069
56 |
57 | **If you get help, help others. Good karma rulez!**
58 |
59 | ### Submitting a Pull Request
60 | Before you submit your pull request consider the following guidelines:
61 |
62 | * Search [GitHub][pulls] for an open or closed Pull Request
63 | that relates to your submission. You don't want to duplicate effort.
64 | * Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull
65 | requests. We cannot accept code without this.
66 | * Make your changes in a new git branch:
67 |
68 | ```shell
69 | git checkout -b my-fix-branch master
70 | ```
71 |
72 | * Create your patch, **including appropriate test cases**.
73 | * Follow our [Coding Rules](#rules).
74 | * Run the full test suite and ensure that all tests pass.
75 | * Commit your changes using a descriptive commit message that follows our
76 | [commit message conventions](#commit-message-format) and passes our commit message presubmit hook
77 | `validate-commit-msg.js`. Adherence to the [commit message conventions](#commit-message-format)
78 | is required because release notes are automatically generated from these messages.
79 |
80 | ```shell
81 | git commit -a
82 | ```
83 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
84 |
85 | * Push your branch to GitHub:
86 |
87 | ```shell
88 | git push origin my-fix-branch
89 | ```
90 |
91 | * In GitHub, send a pull request to the `master` branch.
92 | * If we suggest changes then:
93 | * Make the required updates.
94 | * Re-run the Angular test suite to ensure tests are still passing.
95 | * Commit your changes to your branch (e.g. `my-fix-branch`).
96 | * Push the changes to your GitHub repository (this will update your Pull Request).
97 |
98 | If the PR gets too outdated we may ask you to rebase and force push to update the PR:
99 |
100 | ```shell
101 | git rebase master -i
102 | git push origin my-fix-branch -f
103 | ```
104 |
105 | *WARNING. Squashing or reverting commits and forced push thereafter may remove GitHub comments
106 | on code that were previously made by you and others in your commits.*
107 |
108 | That's it! Thank you for your contribution!
109 |
110 | #### After your pull request is merged
111 |
112 | After your pull request is merged, you can safely delete your branch and pull the changes
113 | from the main (upstream) repository:
114 |
115 | * Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows:
116 |
117 | ```shell
118 | git push origin --delete my-fix-branch
119 | ```
120 |
121 | * Check out the master branch:
122 |
123 | ```shell
124 | git checkout master -f
125 | ```
126 |
127 | * Delete the local branch:
128 |
129 | ```shell
130 | git branch -D my-fix-branch
131 | ```
132 |
133 | * Update your master with the latest upstream version:
134 |
135 | ```shell
136 | git pull --ff upstream master
137 | ```
138 |
139 | ##
Coding Rules
140 | To ensure consistency throughout the source code, keep these rules in mind as you are working:
141 |
142 | * All features or bug fixes **must be tested** by one or more specs.
143 | * All public API methods **must be documented** with a jsdoc style comment block.
144 |
145 | ##
Git Commit Guidelines
146 |
147 | We have very precise rules over how our git commit messages can be formatted. This leads to **more
148 | readable messages** that are easy to follow when looking through the **project history**. But also,
149 | we use the git commit messages to **generate the AngularJS change log**.
150 |
151 | The commit message formatting can be added using a typical git workflow or through the use of a CLI wizard
152 | ([Commitizen](https://github.com/commitizen/cz-cli)). To use the wizard, run `npm run commit` in your terminal
153 | after staging your changes in git.
154 |
155 | ### Commit Message Format
156 | Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
157 | format that includes a **type**, a **scope** and a **subject**:
158 |
159 | ```
160 |
():
161 |
162 |
163 |
164 |