Compare commits

...

30 Commits
8.2.2 ... 8.2.3

Author SHA1 Message Date
31e8a52722 release: cut the v8.2.3 release 2019-08-21 13:28:20 -07:00
e3c9f9d794 ci: update material-unit-tests commit [patch] (#32244)
Updates the SHA that will be tested against in the `material-unit-tests` job
to the latest commit in the components repository. SHA 18b9ef3f5529f0fa8f034944681486447af7b879
is needed in order to make the newly introduced material-ci test blocklist effective.

Patch version of #32243.

PR Close #32244
2019-08-21 11:49:43 -07:00
e7c4e94c9a docs: update collaborators page (#32229)
PR Close #32229
2019-08-21 08:26:44 -07:00
26dc826821 docs: change to global approvers (#31940)
PR Close #31940
2019-08-20 16:47:16 -07:00
287247ef05 ci: update material commit we use to run integration tests (#32224)
PR Close #32224
2019-08-20 14:37:38 -07:00
af170d2ae9 ci: exclude the upstream g3 branch from building on CI (#32202)
We don't need to build this branch as it's informative for the purposes of figuring out
the diff between the master and what's synced into google3.

PR Close #32202
2019-08-20 09:55:56 -07:00
a01ed0d7b4 refactor(core): remove disabled injectable-pipe migration (#32184) (#32206)
Initially the plan was to have a migration that adds `@Injectable()` to
all pipes in a CLI project so that the pipes can be injected in Ivy
similarly to how it worked in view engine.

Due to the planned refactorings which ensure that `@Directive`, `@Component`
and `@Pipe` also have a factory definition, this migration is no longer
needed for Ivy. Additionally since it is already disabled (due to
572b54967c) and we have a more generic
migration (known as `missing-injectable)` that could do the same as
`injectable-pipe`, we remove the migration from the code-base.

PR Close #32184

PR Close #32206
2019-08-20 09:48:10 -07:00
a7b94783b5 fix(bazel): pin @microsoft/api-extractor (#32187)
The API of `@microsoft/api-extractor` changed in a minor version which is causes an error when using dts flattening downstream.

API wil be updated on master https://github.com/angular/angular/pull/32185

PR Close #32187
2019-08-19 15:42:45 -07:00
789dd6a0da docs(service-worker): mention that dataGroups only cache non-mutating requests (#32142)
Fixes #28988

PR Close #32142
2019-08-19 10:11:28 -07:00
e5b18e810c build(docs-infra): upgrade cli command docs sources to e0055d293 (#32175)
Updating [angular#8.2.x](https://github.com/angular/angular/tree/8.2.x) from [cli-builds#8.3.x](https://github.com/angular/cli-builds/tree/8.3.x).

##
Relevant changes in [commit range](43fc440c0...e0055d293):

**Added**
- help/deploy.json

**Modified**
- help/generate.json
- help/new.json
- help/serve.json

##

PR Close #32175
2019-08-19 10:09:09 -07:00
b8f86ad7d5 ci: move bazel saucelabs execution to script to be used across all Angular repos (#32141)
PR Close #32141
2019-08-16 09:57:23 -07:00
6f2e8afaed docs: fix typo of Typescript to TypeScript (#32153)
PR Close #32153
2019-08-15 12:44:42 -07:00
699c705a8d docs: clarify hierarchical injectors (#28700)
PR Close #28700
2019-08-15 12:43:51 -07:00
adc869ff88 build: add ngcc as a valid commit message scope (#32144)
PR Close #32144
2019-08-15 10:33:37 -07:00
46f2dcc470 build(docs-infra): upgrade cli command docs sources to 43fc440c0 (#32148)
Updating [angular#8.2.x](https://github.com/angular/angular/tree/8.2.x) from [cli-builds#8.2.x](https://github.com/angular/cli-builds/tree/8.2.x).

##
Relevant changes in [commit range](de49294bf...43fc440c0):

**Modified**
- help/serve.json

##

PR Close #32148
2019-08-15 09:54:24 -07:00
2d9c4c1d82 docs(router): fix router description (#32136)
PR Close #32136
2019-08-14 14:09:02 -07:00
0757702d63 docs: add guide to reproduce material-unit-test failures locally (#32138)
Adds a new guide that can be used to reproduce failures
reported in the `material-unit-tests` job locally.

The document should live in the framework repository as
the package building scripts are local to the framework
repository.

PR Close #32138
2019-08-14 12:02:13 -07:00
174777443d ci: remove material-unit-tests failure blocklist (#32138)
Initially when the `material-unit-tests` job got wired up,
Ivy was not really backwards-compatible and a few bugs caused
test failures when running the Angular Material test suites w/ Ivy.

These bugs got fixed progressively and eventually the test
blocklist became empty. At this point we don't want to regress
in the future and the blocklist should never have new items.

Additionally since we switched the unit-tests job to run against
Angular Material `master` with Bazel, the blocklist is no
longer respected. Therefore we can safely remove the blocklist.

PR Close #32138
2019-08-14 12:02:13 -07:00
baf2d9ddbc fix(docs-infra): fix broken toc ul styles (#32124)
Fixes #32027

PR Close #32124
2019-08-14 12:01:18 -07:00
da444984a8 docs: add info about reviewing PRs from code owners (#32108)
PR Close #32108
2019-08-14 11:59:14 -07:00
84a3daf609 docs: doc browser support for service workers (#32046)
PR Close #32046
2019-08-14 11:58:21 -07:00
c88cdea9ac build(core): add missing tsconfig-build.json dependency (#31943)
For some reason (on OS/X) this transitive dependency is not being passed
through to the final TS builds that rely on this rule, so the build fails
with a missing file error:

```
The specified path does not exist:
'/.../sandbox/darwin-sandbox/451/execroot/angular/packages/tsconfig-build.json'.
```

PR Close #31943
2019-08-14 11:56:14 -07:00
7b3bd219f7 build: ensure schematics are built with typescript strict flag (#31967)
Follow-up to #30993 where we build all Angular packages with
the TypeScript `--strict` flag. The flag improves overall code
health and also helps us catch issues easier.

PR Close #31967
2019-08-13 11:39:01 -07:00
dc76c14e31 docs: edit location doc (#32042)
PR Close #32042
2019-08-13 11:37:14 -07:00
0ffc1d0d21 docs: update marketing resources with Angular UI Toolkit (#31969)
PR Close #31969
2019-08-13 11:36:37 -07:00
3d1b82be67 docs: correct description of animation example (#32009)
PR Close #32009
2019-08-13 11:16:32 -07:00
dec7e5286f ci: use local strategy for AngularTemplateCompile and TypescriptCompile on CI (#32112)
PR Close #32112
2019-08-13 09:57:51 -07:00
2f812f31d5 docs: update events page and fix ordering (#32106)
PR Close #32106
2019-08-13 09:57:28 -07:00
18bac15ddd ci: add ivy commits to generated CHANGELOG (#32114)
Historically, we've cleaned Ivy commits out of the CHANGELOG because
Ivy was not available except as a preview. Given that Ivy will soon
be the default in 9.0.0, it no longer makes sense to remove the Ivy
commits from the log. This changes the gulp changelog task so that
Ivy commits are included by default.

PR Close #32114
2019-08-12 16:03:37 -07:00
a1fe52b41c docs: format currency api (#32107)
PR Close #32107
2019-08-12 15:16:15 -07:00
100 changed files with 2164 additions and 1045 deletions

View File

@ -36,22 +36,6 @@ build --incompatible_strict_action_env
run --incompatible_strict_action_env
test --incompatible_strict_action_env
###############################
# Saucelabs support #
# Turn on these settings with #
# --config=saucelabs #
###############################
# Expose SauceLabs environment to actions
# These environment variables are needed by
# web_test_karma to run on Saucelabs
test:saucelabs --action_env=SAUCE_USERNAME
test:saucelabs --action_env=SAUCE_ACCESS_KEY
test:saucelabs --action_env=SAUCE_READY_FILE
test:saucelabs --action_env=SAUCE_PID_FILE
test:saucelabs --action_env=SAUCE_TUNNEL_IDENTIFIER
test:saucelabs --define=KARMA_WEB_TEST_MODE=SL_REQUIRED
###############################
# Release support #
# Turn on these settings with #

View File

@ -37,5 +37,5 @@ build --verbose_failures=true
# > Example job: https://circleci.com/gh/angular/angular/385517
# We expect that TypeScript compilations will parallelize wider than the number of local cores anyway
# so we should saturate remote workers with TS compilations
build --strategy=TypeScriptCompile=standalone
build --strategy=AngularTemplateCompile=standalone
build --strategy=AngularTemplateCompile=local
build --strategy=TypeScriptCompile=local

View File

@ -143,7 +143,7 @@ var_14: &notify_dev_infra_on_fail
# Cache key for the Material unit tests job. **Note** when updating the SHA in the cache keys,
# also update the SHA for the "MATERIAL_REPO_COMMIT" environment variable.
var_15: &material_unit_tests_cache_key v4-angular-material-701302dc482d7e4b77990b24e3b5ab330bbf1aa5
var_15: &material_unit_tests_cache_key v4-angular-material-18b9ef3f5529f0fa8f034944681486447af7b879
var_16: &material_unit_tests_cache_key_short v4-angular-material
version: 2
@ -256,23 +256,19 @@ jobs:
- *init_environment
- *setup_circleci_bazel_config
- run:
name: Preparing environment for running tests on Saucelabs.
command: setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev)
- run:
name: Starting Saucelabs tunnel
command: ./scripts/saucelabs/start-tunnel.sh
background: true
# Waits for the Saucelabs tunnel to be ready. This ensures that we don't run tests
# too early without Saucelabs not being ready.
- run: ./scripts/saucelabs/wait-for-tunnel.sh
# All web tests are contained within a single //:test_web_all target for Saucelabs
# as running each set of tests as a separate target will attempt to acquire too
# many browsers on Saucelabs (7 per target currently) and some tests will always
# fail to acquire browsers. For example:
# 14 02 2019 19:52:33.170:WARN [launcher]: chrome beta on SauceLabs have not captured in 180000 ms, killing.
# //packages/forms/test:web_test_sauce TIMEOUT in 315.0s
- run: yarn bazel test --config=saucelabs //:test_web_all
- run: ./scripts/saucelabs/stop-tunnel.sh
name: Run Bazel tests in saucelabs
# All web tests are contained within a single //:test_web_all target for Saucelabs
# as running each set of tests as a separate target will attempt to acquire too
# many browsers on Saucelabs (7 per target currently) and some tests will always
# fail to acquire browsers. For example:
# 14 02 2019 19:52:33.170:WARN [launcher]: chrome beta on SauceLabs have not captured in 180000 ms, killing.
# //packages/forms/test:web_test_sauce TIMEOUT in 315.0s
command: |
./scripts/saucelabs/run-bazel-via-tunnel.sh \
--tunnel-id angular-${CIRCLE_BUILD_NUM}-${CIRCLE_NODE_INDEX} \
--username $SAUCE_USERNAME \
--key $(echo $SAUCE_ACCESS_KEY | rev) \
yarn bazel test //:test_web_all
- *notify_dev_infra_on_fail
test_aio:
@ -673,7 +669,10 @@ workflows:
version: 2
default_workflow:
jobs:
- setup
- setup:
filters:
branches:
ignore: g3
- lint:
requires:
- setup
@ -784,9 +783,3 @@ workflows:
branches:
only:
- master
# TODO:
# - don't build the g3 branch
# - verify that we are bootstrapping with the right yarn version coming from the docker image
# - check local chrome version pulled from docker image
# - remove /tools/ngcontainer

View File

@ -61,6 +61,7 @@ else
setPublicVar SAUCE_USERNAME "angular-ci";
setSecretVar SAUCE_ACCESS_KEY "9b988f434ff8-fbca-8aa4-4ae3-35442987";
fi
# TODO(josephperrott): Remove environment variables once all saucelabs tests are via bazel method.
setPublicVar SAUCE_LOG_FILE /tmp/angular/sauce-connect.log
setPublicVar SAUCE_READY_FILE /tmp/angular/sauce-connect-ready-file.lock
setPublicVar SAUCE_PID_FILE /tmp/angular/sauce-connect-pid-file.lock
@ -79,7 +80,7 @@ setPublicVar MATERIAL_REPO_TMP_DIR "/tmp/material2"
setPublicVar MATERIAL_REPO_URL "https://github.com/angular/material2.git"
setPublicVar MATERIAL_REPO_BRANCH "master"
# **NOTE**: When updating the commit SHA, also update the cache key in the CircleCI "config.yml".
setPublicVar MATERIAL_REPO_COMMIT "701302dc482d7e4b77990b24e3b5ab330bbf1aa5"
setPublicVar MATERIAL_REPO_COMMIT "18b9ef3f5529f0fa8f034944681486447af7b879"
# Source `$BASH_ENV` to make the variables available immediately.
source $BASH_ENV;

9
.github/CODEOWNERS vendored
View File

@ -101,7 +101,6 @@
#
# - brandonroberts
# - gkalpak
# - jenniferfell
# - petebacondarwin
@ -911,14 +910,6 @@ testing/** @angular/fw-test
# ================================================
# Material CI
# ================================================
/tools/material-ci/** @angular/fw-core @angular/framework-global-approvers
# ================================================
# Public API
# ================================================

View File

@ -1,3 +1,13 @@
<a name="8.2.3"></a>
## [8.2.3](https://github.com/angular/angular/compare/8.2.2...8.2.3) (2019-08-21)
### Bug Fixes
* **bazel:** pin `[@microsoft](https://github.com/microsoft)/api-extractor` ([#32187](https://github.com/angular/angular/issues/32187)) ([a7b9478](https://github.com/angular/angular/commit/a7b9478))
<a name="8.2.2"></a>
## [8.2.2](https://github.com/angular/angular/compare/8.2.1...8.2.2) (2019-08-12)

View File

@ -233,6 +233,7 @@ There are currently a few exceptions to the "use package name" rule:
* **docs-infra**: used for docs-app (angular.io) related changes within the /aio directory of the
repo
* **ivy**: used for changes to the [Ivy renderer](https://github.com/angular/angular/issues/21706).
* **ngcc**: used for changes to the [Angular Compatibility Compiler](./packages/compiler-cli/ngcc/README.md)
* none/empty string: useful for `style`, `test` and `refactor` changes that are done across all
packages (e.g. `style: add missing semicolons`) and for docs changes that are not related to a
specific package (e.g. `docs: fix typo in tutorial`).

View File

@ -6,7 +6,7 @@
# Angular
Angular is a development platform for building mobile and desktop web applications using Typescript/JavaScript and other languages.
Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages.
## Quickstart
@ -14,7 +14,7 @@ Angular is a development platform for building mobile and desktop web applicatio
## Changelog
[Learn about the latest improvements][changelog].
[Learn about the latest improvements][changelog].
## Want to help?

View File

@ -0,0 +1,24 @@
import { browser, element, by } from 'protractor';
import { logging } from 'selenium-webdriver';
describe('Providers and ViewProviders', function () {
beforeEach(() => {
browser.get('');
});
it('shows basic flower emoji', function() {
expect(element.all(by.css('p')).get(0).getText()).toContain('🌺');
});
it('shows whale emoji', function() {
expect(element.all(by.css('p')).get(1).getText()).toContain('🐳');
});
it('shows sunflower from FlowerService', function() {
expect(element.all(by.css('p')).get(8).getText()).toContain('🌻');
});
});

View File

@ -0,0 +1,10 @@
// #docregion animal-service
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AnimalService {
emoji = '🐳';
}
// #enddocregion animal-service

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { FlowerService } from './flower.service';
import { AnimalService } from './animal.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
// #docregion injection
export class AppComponent {
constructor(public flower: FlowerService) {}
}
// #enddocregion injection

View File

@ -0,0 +1,15 @@
<h2>From AppComponent:</h2>
<!-- #docregion binding-flower -->
<p>Emoji from FlowerService: {{flower.emoji}}</p>
<!-- #enddocregion binding-flower -->
<!-- #docregion binding-animal -->
<p>Emoji from AnimalService: {{animal.emoji}}</p>
<!-- #enddocregion binding-animal -->
<hr />
<h2>From ChildComponent:</h2>
<!-- #docregion content-projection -->
<app-child><app-inspector></app-inspector></app-child>
<!-- #enddocregion content-projection -->

View File

@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { FlowerService } from './flower.service';
import { AnimalService } from './animal.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
// #docregion inject-animal-service
export class AppComponent {
constructor(public flower: FlowerService, public animal: AnimalService) {}
}
// #enddocregion inject-animal-service

View File

@ -0,0 +1,17 @@
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { ChildComponent } from './child/child.component';
import { InspectorComponent } from './inspector/inspector.component';
// #docregion appmodule
@NgModule({
imports: [ BrowserModule, FormsModule ],
declarations: [ AppComponent, ChildComponent, InspectorComponent ],
bootstrap: [ AppComponent ],
providers: []
})
export class AppModule { }
// #enddocregion appmodule

View File

@ -0,0 +1,19 @@
import { Component, OnInit, Host, SkipSelf, Optional } from '@angular/core';
import { FlowerService } from '../flower.service';
// #docregion flowerservice
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
// use the providers array to provide a service
providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }]
})
export class ChildComponent {
// inject the service
constructor( public flower: FlowerService) { }
}
// #enddocregion flowerservice

View File

@ -0,0 +1,4 @@
.container {
border: 1px solid darkblue;
padding: 1rem;
}

View File

@ -0,0 +1,24 @@
<!-- #docplaster -->
<!-- #docregion child-component -->
<!-- #docregion flower-binding -->
<p>Emoji from FlowerService: {{flower.emoji}}</p>
<!-- #enddocregion flower-binding -->
<!-- #docregion animal-binding -->
<p>Emoji from AnimalService: {{animal.emoji}}</p>
<!-- #enddocregion animal-binding -->
<div class="container">
<h3>Content projection</h3>
<!-- #enddocregion child-component -->
<p>The following is coming from content. It doesn't get to see the puppy because the puppy is declared inside the view only.</p>
<!-- #docregion child-component -->
<ng-content></ng-content>
</div>
<h3>Inside the view</h3>
<!-- #enddocregion child-component -->
<p>The following is inside the view so it does see the puppy.</p>
<!-- #docregion child-component -->
<app-inspector></app-inspector>
<!-- #enddocregion child-component -->

View File

@ -0,0 +1,44 @@
// #docplaster
import { Component, OnInit, Host, SkipSelf, Optional } from '@angular/core';
import { FlowerService } from '../flower.service';
import { AnimalService } from '../animal.service';
// #docregion provide-animal-service
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
styleUrls: ['./child.component.css'],
// provide services
providers: [{ provide: FlowerService, useValue: { emoji: '🌻' } }],
viewProviders: [{ provide: AnimalService, useValue: { emoji: '🐶' } }]
})
export class ChildComponent {
// inject service
constructor( public flower: FlowerService, public animal: AnimalService) { }
// #enddocregion provide-animal-service
// viewProviders ensures that only the view gets to see this.
// With the AnimalService in the viewProviders, the
// InspectorComponent doesn't get to see it because the
// inspector is in the content.
// constructor( public flower: FlowerService, @Optional() @Host() public animal: AnimalService) { }
// Comment out the above constructor and alternately
// uncomment the two following constructors to see the
// effects of @Host() and @Host() + @SkipSelf().
// constructor(
// @Host() public animal : AnimalService,
// @Host() @Optional() public flower : FlowerService) { }
// constructor(
// @SkipSelf() @Host() public animal : AnimalService,
// @SkipSelf() @Host() @Optional() public flower : FlowerService) { }
// #docregion provide-animal-service
}
// #enddocregion provide-animal-service

View File

@ -0,0 +1,11 @@
import { Injectable } from '@angular/core';
// #docregion flowerservice
@Injectable({
providedIn: 'root'
})
export class FlowerService {
emoji = '🌺';
}
// #enddocregion flowerservice

View File

@ -0,0 +1,4 @@
<!-- #docregion binding -->
<p>Emoji from FlowerService: {{flower.emoji}}</p>
<p>Emoji from AnimalService: {{animal.emoji}}</p>
<!-- #enddocregion binding -->

View File

@ -0,0 +1,14 @@
import { Component } from '@angular/core';
import { FlowerService } from '../flower.service';
import { AnimalService } from '../animal.service';
@Component({
selector: 'app-inspector',
templateUrl: './inspector.component.html',
styleUrls: ['./inspector.component.css']
})
// #docregion injection
export class InspectorComponent {
constructor(public flower: FlowerService, public animal: AnimalService) { }
}
// #enddocregion injection

View File

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>providers vs. viewProviders</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,10 @@
{
"description": "Inputs and Outputs",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["Inputs and Outputs"]
}

View File

@ -0,0 +1,21 @@
import { browser, element, by } from 'protractor';
describe('Resolution-modifiers-example', function () {
beforeAll(function () {
browser.get('');
});
it('shows basic flower emoji', function() {
expect(element.all(by.css('p')).get(0).getText()).toContain('🌸');
});
it('shows basic leaf emoji', function() {
expect(element.all(by.css('p')).get(1).getText()).toContain('🌿');
});
it('shows yellow flower in host child', function() {
expect(element.all(by.css('p')).get(9).getText()).toContain('🌼');
});
});

View File

@ -0,0 +1,14 @@
<h1>DI resolution modifiers</h1>
<p>Basic flower service: {{flower.emoji}}</p>
<p>Basic leaf service: {{leaf.emoji}}</p>
<app-optional></app-optional>
<app-self></app-self>
<app-self-no-data></app-self-no-data>
<app-skipself></app-skipself>
<app-host-parent></app-host-parent>

View File

@ -0,0 +1,13 @@
import { Component } from '@angular/core';
import { LeafService } from './leaf.service';
import { FlowerService } from './flower.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: [ './app.component.css' ]
})
export class AppComponent {
name = 'Angular';
constructor(public flower: FlowerService, public leaf: LeafService) {}
}

View File

@ -0,0 +1,33 @@
;
import { OptionalComponent } from './optional/optional.component';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { SelfNoDataComponent } from './self-no-data/self-no-data.component';
import { HostComponent } from './host/host.component';
import { SelfComponent } from './self/self.component';
import { SkipselfComponent } from './skipself/skipself.component';
import { HostParentComponent } from './host-parent/host-parent.component';
import { HostChildComponent } from './host-child/host-child.component';
@NgModule({
imports: [
BrowserModule,
FormsModule
],
declarations: [
AppComponent,
OptionalComponent,
SelfComponent,
SelfNoDataComponent,
HostComponent,
SkipselfComponent,
HostParentComponent,
HostChildComponent
],
bootstrap: [AppComponent],
providers: []
})
export class AppModule { }

View File

@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root' // provide this service in the root ModuleInjector
})
export class FlowerService {
emoji = '🌸';
}

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,4 @@
<div class="section">
<h2>Child of @Host() Component</h2>
<p>Flower emoji: {{flower.emoji}}</p>
</div>

View File

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
import { FlowerService } from '../flower.service';
@Component({
selector: 'app-host-child',
templateUrl: './host-child.component.html',
styleUrls: ['./host-child.component.css']
})
export class HostChildComponent {
constructor(public flower: FlowerService) { }
}

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,5 @@
<div class="section">
<h2>Parent of @Host() Component</h2>
<p>Flower emoji: {{flower.emoji}}</p>
<app-host></app-host>
</div>

View File

@ -0,0 +1,16 @@
import { Component } from '@angular/core';
import { FlowerService } from '../flower.service';
@Component({
selector: 'app-host-parent',
templateUrl: './host-parent.component.html',
styleUrls: ['./host-parent.component.css'],
providers: [{ provide: FlowerService, useValue: { emoji: '🌺' } }]
})
export class HostParentComponent {
constructor(public flower: FlowerService) { }
}

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,6 @@
<div class="section">
<h2>@Host() Component</h2>
<p>Flower emoji: {{flower.emoji}}</p>
<p><i>(@Host() stops it here)</i></p>
<app-host-child></app-host-child>
</div>

View File

@ -0,0 +1,21 @@
import { Component, Host, Optional } from '@angular/core';
import { FlowerService } from '../flower.service';
// #docregion host-component
@Component({
selector: 'app-host',
templateUrl: './host.component.html',
styleUrls: ['./host.component.css'],
// provide the service
providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }]
})
export class HostComponent {
// use @Host() in the constructor when injecting the service
constructor(@Host() @Optional() public flower: FlowerService) { }
}
// #enddocregion host-component
// if you take out @Host() and the providers array, flower will be red hibiscus

View File

@ -0,0 +1,11 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
// #docregion leafservice
export class LeafService {
emoji = '🌿';
}
// #enddocregion leafservice

View File

@ -0,0 +1,7 @@
import { Injectable } from '@angular/core';
@Injectable()
export class OptionalService {
}
// This service isn't provided anywhere.

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,4 @@
<div class="section">
<h2>@Optional() Component</h2>
<p>This component still works even though the OptionalService (notice @Optional() in the consturctor isn't provided or configured anywhere. Angular goes through tree and visibilty rules, and if it doesn't find the requested service, returns null.</p>
</div>

View File

@ -0,0 +1,21 @@
import { Component, Optional } from '@angular/core';
import { OptionalService } from '../optional.service';
@Component({
selector: 'app-optional',
templateUrl: './optional.component.html',
styleUrls: ['./optional.component.css']
})
// #docregion optional-component
export class OptionalComponent {
constructor(@Optional() public optional: OptionalService) {}
}
// #enddocregion optional-component
// The OptionalService isn't provided here, in the @Injectable()
// providers array, or in the NgModule. If you remove @Optional()
// from the constructor, you'll get an error.

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,4 @@
<div class="section">
<h2>@Self() Component (without a provider)</h2>
<p>Leaf emoji: {{leaf?.emoji}}</p>
</div>

View File

@ -0,0 +1,18 @@
import { Component, Self, Optional } from '@angular/core';
import { LeafService } from '../leaf.service';
// #docregion self-no-data-component
@Component({
selector: 'app-self-no-data',
templateUrl: './self-no-data.component.html',
styleUrls: ['./self-no-data.component.css']
})
export class SelfNoDataComponent {
constructor(@Self() @Optional() public leaf: LeafService) { }
}
// #enddocregion self-no-data-component
// The app doesn't break because the value being available at self is optional.
// If you remove @Optional(), the app breaks.

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,4 @@
<div class="section">
<h2>@Self() Component</h2>
<p>Flower emoji: {{flower?.emoji}}</p>
</div>

View File

@ -0,0 +1,19 @@
import { Component, Self } from '@angular/core';
import { FlowerService } from '../flower.service';
// #docregion self-component
@Component({
selector: 'app-self',
templateUrl: './self.component.html',
styleUrls: ['./self.component.css'],
providers: [{ provide: FlowerService, useValue: { emoji: '🌼' } }]
})
export class SelfComponent {
constructor(@Self() public flower: FlowerService) {}
}
// #enddocregion self-component
// This component provides the FlowerService so the injector
// doesn't have to look further up the injector tree

View File

@ -0,0 +1,5 @@
.section {
border: 2px solid #369;
padding: 1rem;
margin: 1rem 0;
}

View File

@ -0,0 +1,4 @@
<div class="section">
<h2>@SkipSelf() Component</h2>
<p>Leaf emoji: {{leaf.emoji}}</p>
</div>

View File

@ -0,0 +1,18 @@
import { Component, SkipSelf } from '@angular/core';
import { LeafService } from '../leaf.service';
// #docregion skipself-component
@Component({
selector: 'app-skipself',
templateUrl: './skipself.component.html',
styleUrls: ['./skipself.component.css'],
// Angular would ignore this LeafService instance
providers: [{ provide: LeafService, useValue: { emoji: '🍁' } }]
})
export class SkipselfComponent {
// Use @SkipSelf() in the constructor
constructor(@SkipSelf() public leaf: LeafService) { }
}
// #enddocregion skipself-component
// @SkipSelf(): Specifies that the dependency resolution should start from the parent injector, not here.

View File

@ -0,0 +1,14 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>DI Resolution Modifiers Example</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>

View File

@ -0,0 +1,11 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule);

View File

@ -0,0 +1,10 @@
{
"description": "NgModules",
"files": [
"!**/*.d.ts",
"!**/*.js",
"!**/*.[1,2].*"
],
"file": "src/app/app.component.ts",
"tags": ["NgModules"]
}

View File

@ -30,7 +30,7 @@ The Filter/Stagger tab in the live example shows a list of heroes with an introd
The following example demonstrates how to use `query()` and `stagger()` functions on the entry of an animated element.
* Use `query()` to look for any element entering or leaving the page. The query specifies elements meeting certain CSS class criteria.
* Use `query()` to look for an element entering the page that meets certain criteria.
* For each of these elements, use `style()` to set the same initial style for the element. Make it invisible and use `transform` to move it out of position so that it can slide into place.

View File

@ -134,7 +134,7 @@ The `@NgModule()` and `@Component()` decorators have the `providers` metadata op
Components are directives, and the `providers` option is inherited from `@Directive()`. You can also configure providers for directives and pipes at the same level as the component.
Learn more about [where to configure providers](guide/hierarchical-dependency-injection#where-to-register).
Learn more about [where to configure providers](guide/hierarchical-dependency-injection).
</div>

File diff suppressed because it is too large Load Diff

View File

@ -137,8 +137,9 @@ export interface DataGroup {
Similar to `assetGroups`, every data group has a `name` which uniquely identifies it.
### `urls`
A list of URL patterns. URLs that match these patterns will be cached according to this data group's policy.<br>
_(Negative glob patterns are not supported and `?` will be matched literally; i.e. it will not match any character other than `?`.)_
A list of URL patterns. URLs that match these patterns are cached according to this data group's policy. Only non-mutating requests (GET and HEAD) are cached.
* Negative glob patterns are not supported.
* `?` is matched literally; that is, it matches *only* the character `?`.
### `version`
Occasionally APIs change formats in a way that is not backward-compatible. A new version of the app may not be compatible with the old API format and thus may not be compatible with existing cached resources from that API.

View File

@ -31,11 +31,34 @@ Installing the Angular service worker is as simple as including an `NgModule`. I
## Prerequisites
Your application must run in a web browser that supports service workers. Currently, service workers are supported in the latest versions of Chrome, Firefox, Edge, Safari, Opera, UC Browser (Android version) and Samsung Internet. Browsers like IE and Opera Mini do not provide the support. To learn more about other browsers that are service worker ready, see the [Can I Use](https://caniuse.com/#feat=serviceworkers) page and [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API).
To make use of all the features of Angular service worker, use the latest versions of Angular and the Angular CLI.
In addition, in order for service workers to be registered, the app must be accessed over HTTPS, not HTTP. Browsers will ignore service workers on pages that are served over an insecure connection. The reason is that service workers are quite powerful, so extra care needs to be taken to ensure the service worker script has not been tampered with.
In order for service workers to be registered, the app must be accessed over HTTPS, not HTTP.
Browsers ignore service workers on pages that are served over an insecure connection.
The reason is that service workers are quite powerful, so extra care needs to be taken to ensure the service worker script has not been tampered with.
There is one exception to this rule: to make local development easier, browsers do _not_ require a secure connection when accessing an app on `localhost`.
### Browser support
To benefit from the Angular service worker, your app must run in a web browser that supports service workers in general.
Currently, service workers are supported in the latest versions of Chrome, Firefox, Edge, Safari, Opera, UC Browser (Android version) and Samsung Internet.
Browsers like IE and Opera Mini do not support service workers.
If the user is accessing your app via a browser that does not support service workers, the service worker is not registered and related behavior such as offline cache management and push notifications does not happen.
More specifically:
* The browser does not download the service worker script and `ngsw.json` manifest file.
* Active attempts to interact with the service worker, such as calling `SwUpdate.checkForUpdate()`, return rejected promises.
* The observable events of related services, such as `SwUpdate.available`, are not triggered.
It is highly recommended that you ensure that your app works even without service worker support in the browser.
Although an unsupported browser ignores service worker caching, it will still report errors if the app attempts to interact with the service worker.
For example, calling `SwUpdate.checkForUpdate()` will return rejected promises.
To avoid such an error, you can check whether the Angular service worker is enabled using `SwUpdate.isEnabled()`.
To learn more about other browsers that are service worker ready, see the [Can I Use](https://caniuse.com/#feat=serviceworkers) page and [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API).
There is one exception to this rule: To make local development easier, browsers do _not_ require a secure connection when accessing an app on `localhost`.
## Related resources
@ -46,7 +69,6 @@ For more information about browser support, see the [browser support](https://de
The remainder of this Angular documentation specifically addresses the Angular implementation of service workers.
## More on Angular service workers
## Next steps
You may also be interested in the following:
* [Getting Started with service workers](guide/service-worker-getting-started).
To begin using Angular service workers, see [Getting Started with service workers](guide/service-worker-getting-started).

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 600 445" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><g id="NullInjector"><rect x="11.864" y="16.952" width="575.424" height="114.429" style="fill:#fff;stroke:#0159d3;stroke-width:3.81px;"/><text x="208.488px" y="67.96px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:30.514px;">NullInjector()</text><g transform="matrix(0.847458,0,0,0.847619,-81.3559,-80.5238)"><text x="286.27px" y="212px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:24px;">always throws an error unless</text><text x="334.768px" y="236.785px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:24px;">you use @Optional()</text></g></g><path d="M286.822,144.941l-7.161,0l11.017,-11.017l11.017,11.017l-7.161,0l0,38.992l-7.712,0l0,-38.992Z" style="fill:#0159d3;stroke:#0159d3;stroke-width:3.81px;"/><path d="M286.822,297.512l-7.161,0l11.017,-11.017l11.017,11.017l-7.161,0l0,38.993l-7.712,0l0,-38.993Z" style="fill:#0159d3;stroke:#0159d3;stroke-width:3.81px;"/><g id="Moduleinjector"><rect x="11.864" y="168.913" width="575.424" height="114.429" style="fill:#fff;stroke:#0159d3;stroke-width:3.81px;"/><text x="215.313px" y="211.956px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:25.429px;">ModuleInjector</text><g transform="matrix(0.847458,0,0,0.847619,-24.5763,-79.6762)"><text x="215.592px" y="385px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:24px;">(configured by PlatformModule)</text><text x="74.879px" y="409.785px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:24px;">has special things like DomSanitizer =&gt; platformBrowser()</text></g></g><g id="root"><rect x="10.599" y="320.428" width="577.966" height="113.581" style="fill:#fff;stroke:#0159d3;stroke-width:3.81px;"/><g transform="matrix(0.847458,0,0,0.847619,-59.3163,262.743)"><text x="280.144px" y="115px" style="font-family:'Courier';font-size:30px;">root</text><text x="370.158px" y="115px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:30px;">ModuleInjector</text></g><g transform="matrix(0.847458,0,0,0.847619,15.5593,-82.219)"><text x="165.889px" y="559px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:24px;">(configured by <tspan x="324.209px 338.014px 351.361px " y="559px 559px 559px ">You</tspan>rAppModule)</text><text x="5.57px" y="583.785px" style="font-family:'ArialMT', 'Arial', sans-serif;font-size:24px;">has things for your app  =&gt; bootstrapModule(Y<tspan x="498.332px 511.68px " y="583.785px 583.785px ">ou</tspan>rAppModule)</text></g></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -276,8 +276,7 @@
"twitter": "deborahkurata",
"website": "http://blogs.msmvps.com/deborahk/",
"bio": "Deborah is a software developer, author, and Google Developer Expert. She is author of several Pluralsight courses including: 'Angular 2: Getting Started' and Angular Routing",
"groups": ["Collaborators", "GDE"],
"mentor": "kara"
"groups": ["GDE"]
},
"alyssa": {
"name": "Alyssa Nicoll",

View File

@ -13,36 +13,18 @@
</tr>
</thead>
<tbody>
<!-- ng-conf 2019-->
<tr>
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
<td>Salt Lake City, Utah</td>
<td>May 1-3, 2019</td>
</tr>
<!-- ngVikings 2019-->
<tr>
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
<td>Copenhagen, Denmark</td>
<td>May 26 (workshops), 27-28 (conference), 2019</td>
</tr>
<!-- ngJapan-->
<tr>
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
<td>Tokyo, Japan</td>
<td>July 13, 2019</td>
</tr>
<!-- AngularConnect 2019-->
<tr>
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>
<td>London, UK</td>
<td>September 19-20, 2019</td>
</tr>
<!-- NG-DE 2019-->
<tr>
<th><a href="https://ng-de.org/" title="NG-DE">NG-DE</a></th>
<td>Berlin, Germany</td>
<td>August 29th workshops, 30-31 conference, 2019</td>
</tr>
<!-- AngularConnect 2019-->
<tr>
<th><a href="https://www.angularconnect.com/?utm_source=angular.io&utm_medium=referral" title="AngularConnect">AngularConnect</a></th>
<td>London, UK</td>
<td>September 19-20, 2019</td>
</tr>
<!-- ReactiveConf 2019 -->
<tr>
<th><a href="https://reactiveconf.com/" title="ReactiveConf">ReactiveConf</a></th>
@ -62,6 +44,24 @@
</tr>
</thead>
<tbody>
<!-- ngJapan-->
<tr>
<th><a href="https://ngjapan.org" title="ng-japan">ng-japan</a></th>
<td>Tokyo, Japan</td>
<td>July 13, 2019</td>
</tr>
<!-- ngVikings 2019-->
<tr>
<th><a href="https://ngvikings.org/" title="ngVikings">ngVikings</a></th>
<td>Copenhagen, Denmark</td>
<td>May 26 (workshops), 27-28 (conference), 2019</td>
</tr>
<!-- ng-conf 2019-->
<tr>
<th><a href="https://ng-conf.org/" title="ng-conf">ng-conf</a></th>
<td>Salt Lake City, Utah</td>
<td>May 1-3, 2019</td>
</tr>
<!-- ng-India 2019-->
<tr>
<th><a href="https://www.ng-ind.com/" title="ng-India">ng-India</a></th>

View File

@ -259,6 +259,12 @@
"UI Components": {
"order": 4,
"resources": {
"AngularUIToolkit": {
"desc": "Angular UI Toolkit: 115 professionally maintained UI components ranging from a robust grid to charts and more. Try for free & build Angular apps faster.",
"rev": true,
"title": "Angular UI Toolkit",
"url": "https://www.angular-ui-tools.com"
},
"IgniteUIforAngular": {
"desc": "Ignite UI for Angular is a dependency-free Angular toolkit for building modern web apps.",
"rev": true,

View File

@ -23,7 +23,7 @@
"build-with-ivy": "yarn ~~build",
"prebuild-with-ivy-ci": "yarn setup-local --no-build-packages && node scripts/switch-to-ivy",
"build-with-ivy-ci": "yarn ~~build --progress=false",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js de49294bf",
"extract-cli-command-docs": "node tools/transforms/cli-docs-package/extract-cli-commands.js e0055d293",
"lint": "yarn check-env && yarn docs-lint && ng lint && yarn example-lint && yarn tools-lint",
"test": "yarn check-env && ng test",
"pree2e": "yarn check-env && yarn update-webdriver",

View File

@ -50,140 +50,140 @@
background: $lightgray;
}
}
}
&.toc-heading {
mat-icon.rotating-icon {
height: 18px;
width: 18px;
position: relative;
left: -4px;
top: 5px;
}
&:hover:not(.embedded) {
color: $accentblue;
}
&.toc-heading {
mat-icon.rotating-icon {
height: 18px;
width: 18px;
position: relative;
left: -4px;
top: 5px;
}
&.toc-more-items {
color: $mediumgray;
top: 10px;
&:hover:not(.embedded) {
color: $accentblue;
}
}
&.toc-more-items {
color: $mediumgray;
top: 10px;
position: relative;
&:hover {
color: $accentblue;
}
&::after {
content: 'expand_less';
}
&.collapsed::after {
content: 'more_horiz';
}
}
.mat-icon {
&.collapsed {
@include rotate(0deg);
}
&:not(.collapsed) {
@include rotate(90deg);
}
}
ul.toc-list {
list-style-type: none;
margin: 0;
padding: 0 8px 0 0;
@media (max-width: 800px) {
width: auto;
}
li {
box-sizing: border-box;
@include font-size(12);
@include line-height(16);
padding: 3px 0 3px 12px;
position: relative;
transition: all 0.3s ease-in-out;
&.h1:after {
content: '';
display: block;
height: 1px;
width: 40%;
margin: 7px 0 4px 0;
background: $lightgray;
clear: both;
}
&.h3 {
padding-left: 24px;
}
a {
color: lighten($darkgray, 10);
overflow: visible;
@include font-size(12);
display: table-cell;
}
&:hover {
color: $accentblue;
}
&::after {
content: 'expand_less';
}
&.collapsed::after {
content: 'more_horiz';
}
}
.mat-icon {
&.collapsed {
@include rotate(0deg);
}
&:not(.collapsed) {
@include rotate(90deg);
}
}
ul.toc-list {
list-style-type: none;
margin: 0;
padding: 0 8px 0 0;
@media (max-width: 800px) {
width: auto;
}
li {
box-sizing: border-box;
@include font-size(12);
@include line-height(16);
padding: 3px 0 3px 12px;
position: relative;
transition: all 0.3s ease-in-out;
&.h1:after {
content: '';
display: block;
height: 1px;
width: 40%;
margin: 7px 0 4px 0;
background: $lightgray;
clear: both;
* {
color: $accentblue;
}
}
&.h3 {
padding-left: 24px;
}
&.active {
* {
color: $blue;
font-weight: 500;
a {
color: lighten($darkgray, 10);
overflow: visible;
@include font-size(12);
display: table-cell;
}
&:hover {
* {
color: $accentblue;
}
}
&.active {
* {
color: $blue;
font-weight: 500;
&:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $blue;
position: absolute;
width: 6px;
height: 6px;
}
&:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $blue;
position: absolute;
width: 6px;
height: 6px;
}
}
}
}
&:not(.embedded) li {
&:before {
border-left: 1px solid $lightgray;
bottom: 0;
content: '';
left: 0;
position: absolute;
top: 0;
}
&:not(.embedded) li {
&:before {
border-left: 1px solid $lightgray;
bottom: 0;
content: '';
left: 0;
position: absolute;
top: 0;
}
&:first-child:before {
top: 13px;
}
&:first-child:before {
top: 13px;
}
&:last-child:before {
bottom: calc(100% - 14px);
}
&:last-child:before {
bottom: calc(100% - 14px);
}
&:not(.active):hover a:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $lightgray;
position: absolute;
width: 6px;
height: 6px;
}
&:not(.active):hover a:before {
content: '';
border-radius: 50%;
left: -3px;
top: 12px;
background: $lightgray;
position: absolute;
width: 6px;
height: 6px;
}
}
}

View File

@ -0,0 +1,40 @@
# Debugging the Material unit tests job
Currently all changes to Ivy are validated against the test suite of the
`angular/components` repository. In order to debug the `material-unit-tests` CI
job, the following steps can be used:
1\) Build the Ivy package output by running `./scripts/build-ivy-npm-packages.sh` in
the `angular/angular` repo.
2\) Clone the `angular/components` repository if not done yet ([quick link to repo](https://github.com/angular/components)).
3\) Set up the package output in the `components` repository by running the following
command in the `angular/angular` repo:
```bash
node ./scripts/ci/update-deps-to-dist-packages.js {COMPONENTS_REPO}/package.json ./dist/packages-dist-ivy-aot
```
4\) Switch into the `components` repository and run the tests by using the
following command:
```bash
yarn test --deleted_packages=//src/dev-app --define=compile=aot
```
### Running tests for individual entry-points
The `yarn test` script from the `components` repository runs all tests in the project.
This is sometimes not desired because it involves building and testing of all packages
and entry-points. Running tests for an individual entry-point is possible by explicitly
selecting a given test target.
Here is an example of commands that run individual test targets. Note that it is
**important** to specify the `--define=compile=aot` flag in order to run tests with Ivy.
```bash
yarn bazel test --define=compile=aot src/material/slider:unit_tests
yarn bazel test --define=compile=aot src/cdk/a11y:unit_tests
yarn bazel test --define=compile=aot src/material/toolbar:unit_tests
```

View File

@ -12,7 +12,6 @@ The owner of the component is then responsible for the secondary / component-lev
The caretaker should be able to determine which component the issue belongs to.
The components have a clear piece of source code associated with it within the `/packages/` folder of this repo.
* `comp: docs-infra` - the angular.io application
* `comp: animations`
* `comp: bazel` - @angular/bazel rules
* `comp: benchpress`
@ -21,6 +20,8 @@ The components have a clear piece of source code associated with it within the `
* `comp: core & compiler` - because core, compiler, compiler-cli and
browser-platforms are very intertwined, we will be treating them as one
* `comp: ivy` - a subset of core representing the new Ivy renderer.
* `comp: ngcc` - a subset of ivy representing the [Angular Compatibility Compiler](../packages/compiler-cli/ngcc/README.md)
* `comp: docs-infra` - the angular.io application and docs-related tooling
* `comp: elements`
* `comp: forms`
* `comp: http`
@ -170,7 +171,9 @@ If a PR is missing the `PR target: *` label, or if the label is set to "TBD" whe
Before a PR can be merged it must be approved by the appropriate reviewer(s).
To ensure that the right people review each change, we configured [GitHub CODEOWNERS](https://help.github.com/articles/about-codeowners/) (via `.github/CODEOWNERS`) and require that each PR has at least one approval from the appropriate code owner.
To ensure that the right people review each change, we configured [GitHub CODEOWNERS](https://help.github.com/articles/about-codeowners/) (via `.github/CODEOWNERS`) and require that each PR has at least one approval from an appropriate code owner.
If the PR author is a code owner themselves, the approval can come from _any_ repo collaborator (person with write access). GitHub CODEOWNERs does not support this scenario, so the `merge-assistance` label must be added, mentioning that the PR author is a code owner. In any case, the reviewer should actually look through the code and provide feedback if necessary.
Note that approved state does not mean a PR is ready to be merged.
For example, a reviewer might approve the PR but request a minor tweak that doesn't need further review, e.g., a rebase or small uncontroversial change.

View File

@ -1,6 +1,6 @@
{
"name": "angular-srcs",
"version": "8.2.2",
"version": "8.2.3",
"private": true,
"description": "Angular - a web framework for modern web apps",
"homepage": "https://github.com/angular/angular",
@ -39,7 +39,7 @@
"@bazel/karma": "0.34.0",
"@bazel/protractor": "0.34.0",
"@bazel/typescript": "0.34.0",
"@microsoft/api-extractor": "^7.0.21",
"@microsoft/api-extractor": "7.0.21",
"@schematics/angular": "^8.0.0-beta.15",
"@types/angular": "^1.6.47",
"@types/base64-js": "1.2.5",

View File

@ -29,7 +29,7 @@
"@angular-devkit/architect": "^0.800.0-beta.15",
"@angular-devkit/core": "^8.0.0-beta.15",
"@angular-devkit/schematics": "^8.0.0-beta.15",
"@microsoft/api-extractor": "^7.0.21",
"@microsoft/api-extractor": "7.0.21",
"@schematics/angular": "^8.0.0-beta.15",
"@types/node": "6.0.84",
"semver": "^5.6.0",

View File

@ -134,7 +134,8 @@ function formatNumberToLocaleString(
* such as "$" or "Canadian Dollar". Used in output string, but does not affect the operation
* of the function.
* @param currencyCode The [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)
* currency code to use in the result string, such as `USD` for the US dollar and `EUR` for the euro.
* currency code, such as `USD` for the US dollar and `EUR` for the euro.
* Used to determine the number of digits in the decimal part.
* @param digitInfo Decimal representation options, specified by a string in the following format:
* `{minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}`. See `DecimalPipe` for more details.
*

View File

@ -25,12 +25,12 @@ export interface PopStateEvent {
*
* A service that applications can use to interact with a browser's URL.
*
* Depending on the `LocationStrategy` used, `Location` will either persist
* Depending on the `LocationStrategy` used, `Location` persists
* to the URL's path or the URL's hash segment.
*
* @usageNotes
*
* It's better to use the {@link Router#navigate} service to trigger route changes. Use
* It's better to use the `Router#navigate` service to trigger route changes. Use
* `Location` only if you need to interact with or create normalized URLs outside of
* routing.
*
@ -77,9 +77,9 @@ export class Location {
}
/**
* Returns the normalized URL path.
* Normalizes the URL path for this location.
*
* @param includeHash Whether path has an anchor fragment.
* @param includeHash True to include an anchor fragment in the path.
*
* @returns The normalized URL path.
*/
@ -90,17 +90,18 @@ export class Location {
}
/**
* Returns the current value of the history.state object.
* Reports the current state of the location history.
* @returns The current value of the `history.state` object.
*/
getState(): unknown { return this._platformLocation.getState(); }
/**
* Normalizes the given path and compares to the current normalized path.
*
* @param path The given URL path
* @param query Query parameters
* @param path The given URL path.
* @param query Query parameters.
*
* @returns `true` if the given URL path is equal to the current normalized path, `false`
* @returns True if the given URL path is equal to the current normalized path, false
* otherwise.
*/
isCurrentPathEqualTo(path: string, query: string = ''): boolean {
@ -108,23 +109,21 @@ export class Location {
}
/**
* Given a string representing a URL, returns the URL path after stripping the
* trailing slashes.
* Normalizes a URL path by stripping any trailing slashes.
*
* @param url String representing a URL.
*
* @returns Normalized URL string.
* @returns The normalized URL string.
*/
normalize(url: string): string {
return Location.stripTrailingSlash(_stripBaseHref(this._baseHref, _stripIndexHtml(url)));
}
/**
* Given a string representing a URL, returns the platform-specific external URL path.
* If the given URL doesn't begin with a leading slash (`'/'`), this method adds one
* before normalizing. This method also adds a hash if `HashLocationStrategy` is
* used, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*
* Normalizes an external URL path.
* If the given URL doesn't begin with a leading slash (`'/'`), adds one
* before normalizing. Adds a hash if `HashLocationStrategy` is
* in use, or the `APP_BASE_HREF` if the `PathLocationStrategy` is in use.
*
* @param url String representing a URL.
*
@ -139,12 +138,12 @@ export class Location {
// TODO: rename this method to pushState
/**
* Changes the browsers URL to a normalized version of the given URL, and pushes a
* Changes the browser's URL to a normalized version of a given URL, and pushes a
* new item onto the platform's history.
*
* @param path URL path to normalizze
* @param query Query parameters
* @param state Location history state
* @param path URL path to normalize.
* @param query Query parameters.
* @param state Location history state.
*
*/
go(path: string, query: string = '', state: any = null): void {
@ -157,9 +156,9 @@ export class Location {
* Changes the browser's URL to a normalized version of the given URL, and replaces
* the top item on the platform's history stack.
*
* @param path URL path to normalizze
* @param query Query parameters
* @param state Location history state
* @param path URL path to normalize.
* @param query Query parameters.
* @param state Location history state.
*/
replaceState(path: string, query: string = '', state: any = null): void {
this._platformStrategy.replaceState(state, '', path, query);
@ -178,8 +177,10 @@ export class Location {
back(): void { this._platformStrategy.back(); }
/**
* Register URL change listeners. This API can be used to catch updates performed by the Angular
* framework. These are not detectible through "popstate" or "hashchange" events.
* Registers a URL change listener. Use to catch updates performed by the Angular
* framework that are not detectible through "popstate" or "hashchange" events.
*
* @param fn The change handler function, which take a URL and a location history state.
*/
onUrlChange(fn: (url: string, state: unknown) => void) {
this._urlChangeListeners.push(fn);
@ -192,7 +193,7 @@ export class Location {
}
/**
* Subscribe to the platform's `popState` events.
* Subscribes to the platform's `popState` events.
*
* @param value Event that is triggered when the state history changes.
* @param exception The exception to throw.
@ -206,25 +207,24 @@ export class Location {
}
/**
* Given a string of url parameters, prepend with `?` if needed, otherwise return the
* parameters as is.
* Normalizes URL parameters by prepending with `?` if needed.
*
* @param params String of URL parameters
* @param params String of URL parameters.
*
* @returns URL parameters prepended with `?` or the parameters as is.
* @returns The normalized URL parameters string.
*/
public static normalizeQueryParams(params: string): string {
return params && params[0] !== '?' ? '?' + params : params;
}
/**
* Given 2 parts of a URL, join them with a slash if needed.
* Joins two parts of a URL with a slash if needed.
*
* @param start URL string
* @param end URL string
*
*
* @returns Given URL strings joined with a slash, if needed.
* @returns The joined URL string.
*/
public static joinWithSlash(start: string, end: string): string {
if (start.length == 0) {
@ -250,14 +250,13 @@ export class Location {
}
/**
* If URL has a trailing slash, remove it, otherwise return the URL as is. The
* method looks for the first occurrence of either `#`, `?`, or the end of the
* Removes a trailing slash from a URL string if needed.
* Looks for the first occurrence of either `#`, `?`, or the end of the
* line as `/` characters and removes the trailing slash if one exists.
*
* @param url URL string
* @param url URL string.
*
* @returns Returns a URL string after removing the trailing slash if one exists, otherwise
* returns the string as is.
* @returns The URL string, modified if needed.
*/
public static stripTrailingSlash(url: string): string {
const match = url.match(/#|\?|$/);

View File

@ -10,7 +10,6 @@ npm_package(
srcs = ["migrations.json"],
visibility = ["//packages/core:__pkg__"],
deps = [
"//packages/core/schematics/migrations/injectable-pipe",
"//packages/core/schematics/migrations/move-document",
"//packages/core/schematics/migrations/renderer-to-renderer2",
"//packages/core/schematics/migrations/static-queries",

View File

@ -6,7 +6,6 @@ ts_library(
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = ["//packages/core/schematics/test:__pkg__"],
deps = [
"//packages/core/schematics/migrations/injectable-pipe",
"//packages/core/schematics/migrations/missing-injectable",
"//packages/core/schematics/migrations/missing-injectable/google3",
"//packages/core/schematics/migrations/static-queries",

View File

@ -1,55 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Replacement, RuleFailure, Rules} from 'tslint';
import * as ts from 'typescript';
import {InjectablePipeVisitor} from '../injectable-pipe/angular/injectable_pipe_visitor';
import {INJECTABLE_DECORATOR_NAME, addImport, getNamedImports} from '../injectable-pipe/util';
/**
* TSLint rule that flags `@Pipe` classes that haven't been marked as `@Injectable`.
*/
export class Rule extends Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): RuleFailure[] {
const visitor = new InjectablePipeVisitor(program.getTypeChecker());
const printer = ts.createPrinter();
const failures: RuleFailure[] = [];
visitor.visitNode(sourceFile);
visitor.missingInjectablePipes.forEach(data => {
const {pipeDecorator, importDeclarationMissingImport} = data;
const fixes = [new Replacement(
pipeDecorator.getStart(), pipeDecorator.getWidth(),
`@${INJECTABLE_DECORATOR_NAME}()\n${pipeDecorator.getText()}`)];
if (importDeclarationMissingImport) {
const namedImports = getNamedImports(importDeclarationMissingImport);
// Add another fix that'll add the missing import.
if (namedImports) {
fixes.push(new Replacement(
namedImports.getStart(), namedImports.getWidth(),
printer.printNode(
ts.EmitHint.Unspecified, addImport(namedImports, INJECTABLE_DECORATOR_NAME),
sourceFile)));
}
}
// Add a failure on Pipe decorators that are missing the Injectable decorator.
failures.push(new RuleFailure(
sourceFile, pipeDecorator.getStart(), pipeDecorator.getWidth(),
'Classes with @Pipe should be decorated with @Injectable so that they can be injected.',
this.ruleName, fixes));
});
return failures;
}
}

View File

@ -1,18 +0,0 @@
load("//tools:defaults.bzl", "ts_library")
ts_library(
name = "injectable-pipe",
srcs = glob(["**/*.ts"]),
tsconfig = "//packages/core/schematics:tsconfig.json",
visibility = [
"//packages/core/schematics:__pkg__",
"//packages/core/schematics/migrations/google3:__pkg__",
"//packages/core/schematics/test:__pkg__",
],
deps = [
"//packages/core/schematics/utils",
"@npm//@angular-devkit/schematics",
"@npm//@types/node",
"@npm//typescript",
],
)

View File

@ -1,22 +0,0 @@
## Injectable annotation on pipes
In ViewEngine it was possible to inject a class that was annotated as a `Pipe`, however this no
longer works in Ivy if the class also doesn't have the `Injectable` decorator. This migration
adds `Injectable` automatically to all `Pipe` classes.
### Before
```ts
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
class MyPipe {}
```
### After
```ts
import { Pipe, Injectable } from '@angular/core';
@Injectable()
@Pipe({ name: 'myPipe' })
class MyPipe {}
```

View File

@ -1,67 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {getAngularDecorators} from '../../../utils/ng_decorators';
import {INJECTABLE_DECORATOR_NAME} from '../util';
/**
* Goes through all of the descendant nodes of a given node and lists out all of the pipes
* that don't have `@Injectable`, as well as their `@Pipe` decorator and the import declaration
* from which we'd need to import the `Injectable` decorator.
*/
export class InjectablePipeVisitor {
/**
* Keeps track of all the classes that have a `Pipe` decorator, but not `Injectable`, as well
* as a reference to the `Pipe` decorator itself and import declarations from which we'll have
* to import the `Injectable` decorator.
*/
missingInjectablePipes: {
classDeclaration: ts.ClassDeclaration,
importDeclarationMissingImport: ts.ImportDeclaration|null,
pipeDecorator: ts.Decorator
}[] = [];
constructor(private _typeChecker: ts.TypeChecker) {}
visitNode(node: ts.Node) {
switch (node.kind) {
case ts.SyntaxKind.ClassDeclaration:
this._visitClassDeclaration(node as ts.ClassDeclaration);
break;
}
ts.forEachChild(node, node => this.visitNode(node));
}
private _visitClassDeclaration(node: ts.ClassDeclaration) {
if (!node.decorators || !node.decorators.length) {
return;
}
const ngDecorators = getAngularDecorators(this._typeChecker, node.decorators);
const pipeDecorator = ngDecorators.find(decorator => decorator.name === 'Pipe');
const hasInjectableDecorator =
!ngDecorators.some(decorator => decorator.name === INJECTABLE_DECORATOR_NAME);
// Skip non-pipe classes and pipes that are already marked as injectable.
if (pipeDecorator && hasInjectableDecorator) {
const importNode = pipeDecorator.importNode;
const namedImports = importNode.importClause && importNode.importClause.namedBindings;
const needsImport = namedImports && ts.isNamedImports(namedImports) &&
!namedImports.elements.some(element => element.name.text === INJECTABLE_DECORATOR_NAME);
this.missingInjectablePipes.push({
classDeclaration: node,
importDeclarationMissingImport: needsImport ? importNode : null,
pipeDecorator: pipeDecorator.node
});
}
}
}

View File

@ -1,92 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Rule, SchematicsException, Tree} from '@angular-devkit/schematics';
import {dirname, relative} from 'path';
import * as ts from 'typescript';
import {getProjectTsConfigPaths} from '../../utils/project_tsconfig_paths';
import {parseTsconfigFile} from '../../utils/typescript/parse_tsconfig';
import {InjectablePipeVisitor} from './angular/injectable_pipe_visitor';
import {INJECTABLE_DECORATOR_NAME, addImport, getNamedImports} from './util';
/**
* Runs a migration over a TypeScript project that adds an `@Injectable`
* annotation to all classes that have `@Pipe`.
*/
export default function(): Rule {
return (tree: Tree) => {
const {buildPaths, testPaths} = getProjectTsConfigPaths(tree);
const basePath = process.cwd();
const allPaths = [...buildPaths, ...testPaths];
if (!allPaths.length) {
throw new SchematicsException(
'Could not find any tsconfig file. Cannot add Injectable annotation to pipes.');
}
for (const tsconfigPath of allPaths) {
runInjectablePipeMigration(tree, tsconfigPath, basePath);
}
};
}
function runInjectablePipeMigration(tree: Tree, tsconfigPath: string, basePath: string) {
const parsed = parseTsconfigFile(tsconfigPath, dirname(tsconfigPath));
const host = ts.createCompilerHost(parsed.options, true);
// We need to overwrite the host "readFile" method, as we want the TypeScript
// program to be based on the file contents in the virtual file tree. Otherwise
// if we run the migration for multiple tsconfig files which have intersecting
// source files, it can end up updating query definitions multiple times.
host.readFile = fileName => {
const buffer = tree.read(relative(basePath, fileName));
// Strip BOM as otherwise TSC methods (Ex: getWidth) will return an offset which
// which breaks the CLI UpdateRecorder.
// See: https://github.com/angular/angular/pull/30719
return buffer ? buffer.toString().replace(/^\uFEFF/, '') : undefined;
};
const program = ts.createProgram(parsed.fileNames, parsed.options, host);
const typeChecker = program.getTypeChecker();
const visitor = new InjectablePipeVisitor(typeChecker);
const sourceFiles = program.getSourceFiles().filter(
f => !f.isDeclarationFile && !program.isSourceFileFromExternalLibrary(f));
const printer = ts.createPrinter();
sourceFiles.forEach(sourceFile => visitor.visitNode(sourceFile));
visitor.missingInjectablePipes.forEach(data => {
const {classDeclaration, importDeclarationMissingImport} = data;
const sourceFile = classDeclaration.getSourceFile();
const update = tree.beginUpdate(relative(basePath, sourceFile.fileName));
// Note that we don't need to go through the AST to insert the decorator, because the change
// is pretty basic. Also this has a better chance of preserving the user's formatting.
update.insertLeft(classDeclaration.getStart(), `@${INJECTABLE_DECORATOR_NAME}()\n`);
// Add @Injectable to the imports if it isn't imported already. Note that this doesn't deal with
// the case where there aren't any imports for `@angular/core` at all. We don't need to handle
// it because the Pipe decorator won't be recognized if it hasn't been imported from Angular.
if (importDeclarationMissingImport) {
const namedImports = getNamedImports(importDeclarationMissingImport);
if (namedImports) {
update.remove(namedImports.getStart(), namedImports.getWidth());
update.insertRight(
namedImports.getStart(),
printer.printNode(
ts.EmitHint.Unspecified, addImport(namedImports, INJECTABLE_DECORATOR_NAME),
sourceFile));
}
}
tree.commitUpdate(update);
});
}

View File

@ -1,36 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
/** Name of the Injectable decorator. */
export const INJECTABLE_DECORATOR_NAME = 'Injectable';
/**
* Adds an import to a named import node, if the import does not exist already.
* @param node Node to which to add the import.
* @param importName Name of the import that should be added.
*/
export function addImport(node: ts.NamedImports, importName: string) {
const elements = node.elements;
const isAlreadyImported = elements.some(element => element.name.text === importName);
if (!isAlreadyImported) {
return ts.updateNamedImports(
node, [...elements, ts.createImportSpecifier(undefined, ts.createIdentifier(importName))]);
}
return node;
}
/** Gets the named imports node from an import declaration. */
export function getNamedImports(node: ts.ImportDeclaration): ts.NamedImports|null {
const importClause = node.importClause;
const namedImports = importClause && importClause.namedBindings;
return (namedImports && ts.isNamedImports(namedImports)) ? namedImports : null;
}

View File

@ -166,16 +166,18 @@ function filterQueryClassMemberNodes(
// (1) queries used in the "ngOnInit" lifecycle hook are static.
// (2) inputs with setters can access queries statically.
return classDecl.members
.filter(m => {
if (ts.isMethodDeclaration(m) && m.body && hasPropertyNameText(m.name) &&
STATIC_QUERY_LIFECYCLE_HOOKS[query.type].indexOf(m.name.text) !== -1) {
return true;
} else if (
knownInputNames && ts.isSetAccessor(m) && m.body && hasPropertyNameText(m.name) &&
knownInputNames.indexOf(m.name.text) !== -1) {
return true;
}
return false;
})
.map((member: ts.SetAccessorDeclaration | ts.MethodDeclaration) => member.body !);
.filter(
(m):
m is(ts.SetAccessorDeclaration | ts.MethodDeclaration) => {
if (ts.isMethodDeclaration(m) && m.body && hasPropertyNameText(m.name) &&
STATIC_QUERY_LIFECYCLE_HOOKS[query.type].indexOf(m.name.text) !== -1) {
return true;
} else if (
knownInputNames && ts.isSetAccessor(m) && m.body &&
hasPropertyNameText(m.name) && knownInputNames.indexOf(m.name.text) !== -1) {
return true;
}
return false;
})
.map(member => member.body !);
}

View File

@ -10,7 +10,6 @@ ts_library(
],
deps = [
"//packages/core/schematics/migrations/google3",
"//packages/core/schematics/migrations/injectable-pipe",
"//packages/core/schematics/migrations/missing-injectable",
"//packages/core/schematics/migrations/move-document",
"//packages/core/schematics/migrations/renderer-to-renderer2",

View File

@ -1,93 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {readFileSync, writeFileSync} from 'fs';
import {dirname, join} from 'path';
import * as shx from 'shelljs';
import {Configuration, Linter} from 'tslint';
describe('Google3 injectable pipe TSLint rule', () => {
const rulesDirectory = dirname(require.resolve('../../migrations/google3/injectablePipeRule'));
let tmpDir: string;
beforeEach(() => {
tmpDir = join(process.env['TEST_TMPDIR'] !, 'google3-test');
shx.mkdir('-p', tmpDir);
writeFile('tsconfig.json', JSON.stringify({compilerOptions: {module: 'es2015'}}));
});
afterEach(() => shx.rm('-r', tmpDir));
function runTSLint(fix = true) {
const program = Linter.createProgram(join(tmpDir, 'tsconfig.json'));
const linter = new Linter({fix, rulesDirectory: [rulesDirectory]}, program);
const config = Configuration.parseConfigFile(
{rules: {'injectable-pipe': true}, linterOptions: {typeCheck: true}});
program.getRootFileNames().forEach(fileName => {
linter.lint(fileName, program.getSourceFile(fileName) !.getFullText(), config);
});
return linter;
}
function writeFile(fileName: string, content: string) {
writeFileSync(join(tmpDir, fileName), content);
}
function getFile(fileName: string) { return readFileSync(join(tmpDir, fileName), 'utf8'); }
it('should report pipes that are not marked as Injectable', () => {
writeFile('index.ts', `
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
const linter = runTSLint(false);
const failures = linter.getResult().failures;
expect(failures.length).toBe(1);
expect(failures[0].getFailure()).toMatch(/@Pipe should be decorated with @Injectable/);
});
it('should add @Injectable to pipes that do not have it', () => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
runTSLint();
expect(getFile('/index.ts'))
.toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/);
});
it('should add an import for Injectable to the @angular/core import declaration', () => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe()
export class MyPipe {
}
`);
runTSLint();
const content = getFile('/index.ts');
expect(content).toContain('import { Pipe, Injectable } from \'@angular/core\'');
expect((content.match(/import/g) || []).length).toBe(1, 'Expected only one import statement');
});
});

View File

@ -1,143 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {getSystemPath, normalize, virtualFs} from '@angular-devkit/core';
import {TempScopedNodeJsSyncHost} from '@angular-devkit/core/node/testing';
import {HostTree} from '@angular-devkit/schematics';
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
import * as shx from 'shelljs';
describe('injectable pipe migration', () => {
let runner: SchematicTestRunner;
let host: TempScopedNodeJsSyncHost;
let tree: UnitTestTree;
let tmpDirPath: string;
let previousWorkingDir: string;
beforeEach(() => {
runner = new SchematicTestRunner('test', require.resolve('./test-migrations.json'));
host = new TempScopedNodeJsSyncHost();
tree = new UnitTestTree(new HostTree(host));
writeFile('/tsconfig.json', JSON.stringify({
compilerOptions: {
lib: ['es2015'],
}
}));
writeFile('/angular.json', JSON.stringify({
projects: {t: {architect: {build: {options: {tsConfig: './tsconfig.json'}}}}}
}));
previousWorkingDir = shx.pwd();
tmpDirPath = getSystemPath(host.root);
// Switch into the temporary directory path. This allows us to run
// the schematic against our custom unit test tree.
shx.cd(tmpDirPath);
});
afterEach(() => {
shx.cd(previousWorkingDir);
shx.rm('-r', tmpDirPath);
});
it('should add @Injectable to pipes that do not have it', async() => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/);
});
it('should add @Injectable to pipes that do not have it (BOM)', () => {
writeFile('/index.ts', `\uFEFF
import { Pipe } from '@angular/core';
@Pipe({ name: 'myPipe' })
export class MyPipe {
}
`);
runMigration();
expect(tree.readContent('/index.ts'))
.toMatch(/@Injectable\(\)\s+@Pipe\(\{ name: 'myPipe' \}\)\s+export class MyPipe/);
});
it('should add an import for Injectable to the @angular/core import declaration', async() => {
writeFile('/index.ts', `
import { Pipe } from '@angular/core';
@Pipe()
export class MyPipe {
}
`);
await runMigration();
const content = tree.readContent('/index.ts');
expect(content).toContain('import { Pipe, Injectable } from \'@angular/core\'');
expect((content.match(/import/g) || []).length).toBe(1, 'Expected only one import statement');
});
it('should not add an import for Injectable if it is imported already', async() => {
writeFile('/index.ts', `
import { Pipe, Injectable, NgModule } from '@angular/core';
@Pipe()
export class MyPipe {
}
`);
await runMigration();
expect(tree.readContent('/index.ts'))
.toContain('import { Pipe, Injectable, NgModule } from \'@angular/core\'');
});
it('should do nothing if the pipe is marked as injectable already', async() => {
const source = `
import { Injectable, Pipe } from '@angular/core';
@Injectable()
@Pipe()
export class MyPipe {
}
`;
writeFile('/index.ts', source);
await runMigration();
expect(tree.readContent('/index.ts')).toBe(source);
});
it('should not add @Injectable if @Pipe was not imported from @angular/core', async() => {
const source = `
import { Pipe } from '@not-angular/core';
@Pipe()
export class MyPipe {
}
`;
writeFile('/index.ts', source);
await runMigration();
expect(tree.readContent('/index.ts')).toBe(source);
});
function writeFile(filePath: string, contents: string) {
host.sync.write(normalize(filePath), virtualFs.stringToFileBuffer(contents));
}
function runMigration() {
runner.runSchematicAsync('migration-injectable-pipe', {}, tree).toPromise();
}
});

View File

@ -2,10 +2,6 @@
// Migrations which are not publicly enabled but still run as part of tests need to
// be part of a schematic collection in order to be able to run it.
"schematics": {
"migration-injectable-pipe": {
"description": "Migrates all Pipe classes so that they have an Injectable annotation",
"factory": "../migrations/injectable-pipe/index"
},
"migration-missing-injectable": {
"description": "Migrates all declared undecorated providers with the @Injectable decorator",
"factory": "../migrations/missing-injectable/index"

View File

@ -1,10 +1,8 @@
{
"compilerOptions": {
"noImplicitReturns": true,
"noImplicitAny": true,
"noFallthroughCasesInSwitch": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"strict": true,
"lib": ["es2015"],
"types": [],
"baseUrl": ".",

View File

@ -7,6 +7,7 @@ ts_config(
name = "tsconfig",
src = "tsconfig.json",
deps = [
"//packages:tsconfig-build.json",
"//packages:tsconfig-test",
],
)

View File

@ -279,7 +279,7 @@ function defaultRouterHook(snapshot: RouterStateSnapshot, runExtras: {
/**
* @description
*
* An NgModule that provides navigation and URL manipulation capabilities.
* A service that provides navigation and URL manipulation capabilities.
*
* @see `Route`.
* @see [Routing and Navigation Guide](guide/router).

View File

@ -21,10 +21,6 @@ cd ${MATERIAL_REPO_TMP_DIR}
# Note that it's not necessary to perform a yarn install, as Bazel performs its own yarn install.
node ${angular_dir}/scripts/ci/update-deps-to-dist-packages.js ${MATERIAL_REPO_TMP_DIR}/package.json ${angular_dir}/dist/packages-dist-ivy-aot/
# Append the test blocklist into angular/material2's karma-test-shim.js.
# This filters out known-failing tests because the goal is to prevent regressions.
cat ${angular_dir}/tools/material-ci/angular_material_test_blocklist.js >> ./test/karma-test-shim.js
# Create a symlink for the Bazel binary installed through NPM, as running through Yarn introduces OOM errors.
./scripts/circleci/setup_bazel_binary.sh

View File

@ -0,0 +1,204 @@
#!/usr/bin/env bash
set -u -e -o pipefail
# Prints out usage information for the script.
function printUsage {
echo -e "\e[1mrun-bazel-via-tunnel.sh\e[0m - Runs a bazel command using a saucelabs tunnel
\e[1mUsage:\e[0m $0 --tunnel-id=<tunnel_id> \\
--username=<saucelabs_username> --key=<saucelabs_key> <bazel command>
\e[1mExample:\e[0m ./run-bazel-via-tunnel.sh --tunnel-id=<tunnel_id> \\
--username=<saucelabs_username> --key=<saucelabs_key> \\
yarn bazel test //src:everything
Flags:
--username: The saucelabs username
--key: The saucelabs access key
--tunnel-id: An identifier for the saucelabs tunnel";
}
# Ensures a file is created, creating directories for the full path as needed.
function touch-safe {
for f in "$@"; do
[ -d $f:h ] || mkdir -p $f:h && command touch $f
done
}
# The root directory of the git project the script is running in.
readonly GIT_ROOT_DIR=$(git rev-parse --show-toplevel 2> /dev/null)
# Location for the saucelabs log file.
readonly SAUCE_LOG_FILE=/tmp/angular/sauce-connect.log
# Location for the saucelabs ready to connect lock file.
readonly SAUCE_READY_FILE=/tmp/angular/sauce-connect-ready-file.lock
# Location for the saucelabs ready to connection process id lock file.
readonly SAUCE_PID_FILE=/tmp/angular/sauce-connect-pid-file.lock
# Amount of seconds we wait for sauceconnect to establish a tunnel instance. In order to not
# acquire CircleCI instances for too long if sauceconnect failed, we need a connect timeout.
readonly SAUCE_READY_FILE_TIMEOUT=120
# Create saucelabs log file if it doesn't already exist.
touch-safe $SAUCE_LOG_FILE;
# Handle configuration of script from command line flags and arguments
OPTIONS=$(getopt -u -l tunnel-id:,username:,key:,help --options "" -- "$@")
# Exit if flag parsing fails.
if [ $? != 0 ] ; then echo "Failed to parse flags, exiting" && printUsage >&2 ; exit 1 ; fi
set -- $OPTIONS
while true; do
case "$1" in
--tunnel-id)
shift
SAUCE_TUNNEL_IDENTIFIER=$1
;;
--username)
shift
SAUCE_USERNAME=$1
;;
--key)
shift
SAUCE_ACCESS_KEY=$1
;;
--help)
printUsage
exit 2
;;
--)
shift
USER_COMMAND=$@
break
;;
*)
shift
;;
esac
done
# Check each required flag and parameter
if [[ -z ${SAUCE_TUNNEL_IDENTIFIER+x} ]]; then
echo "Missing required flag: --tunnel-id"
badCommandSyntax=1
fi
if [[ -z ${SAUCE_USERNAME+x} ]]; then
echo "Missing required flag: --username"
badCommandSyntax=1
fi
if [[ -z ${SAUCE_ACCESS_KEY+x} ]]; then
echo "Missing required flag: --key"
badCommandSyntax=1
fi
if [[ "${USER_COMMAND}" == "" ]]; then
echo "Missing required bazel command: Bazel command for running in saucelabs tunnel"
badCommandSyntax=1
elif [[ ! $USER_COMMAND =~ ^(yarn bazel) ]]; then
echo "The command provided must be a bazel command run via yarn, beginning with \"yarn bazel\""
badCommandSyntax=1
fi
# If any required flag or parameter were found to be missing or incorrect, exit the script.
if [[ ${badCommandSyntax+x} ]]; then
echo
printUsage
exit 1
fi
# Command arguments that will be passed to sauce-connect.
# By default we disable SSL bumping for all requests. This is because SSL bumping is
# not needed for our test setup and in order to perform the SSL bumping, Saucelabs
# intercepts all HTTP requests in the tunnel VM and modifies them. This can cause
# flakiness as it makes all requests dependent on the SSL bumping middleware.
# See: https://wiki.saucelabs.com/display/DOCS/Troubleshooting+Sauce+Connect#TroubleshootingSauceConnect-DisablingSSLBumping
sauceArgs="--no-ssl-bump-domains all"
sauceArgs="${sauceArgs} --logfile ${SAUCE_LOG_FILE}"
sauceArgs="${sauceArgs} --readyfile ${SAUCE_READY_FILE}"
sauceArgs="${sauceArgs} --pidfile ${SAUCE_PID_FILE}"
sauceArgs="${sauceArgs} --tunnel-identifier ${SAUCE_TUNNEL_IDENTIFIER}"
sauceArgs="${sauceArgs} -u ${SAUCE_USERNAME}"
#########################
# Open saucelabs tunnel #
#########################
${GIT_ROOT_DIR}/node_modules/sauce-connect/bin/sc -k $SAUCE_ACCESS_KEY ${sauceArgs} &
########################################
# Wait for saucelabs tunnel to connect #
########################################
counter=0
while [[ ! -f ${SAUCE_READY_FILE} ]]; do
counter=$((counter + 1))
# Counter needs to be multiplied by two because the while loop only sleeps a half second.
# This has been made in favor of better progress logging (printing dots every half second)
if [ $counter -gt $[${SAUCE_READY_FILE_TIMEOUT} * 2] ]; then
echo "Timed out after ${SAUCE_READY_FILE_TIMEOUT} seconds waiting for tunnel ready file."
echo "Printing logfile output:"
echo ""
cat ${SAUCE_LOG_FILE}
exit 5
fi
printf "."
sleep 0.5
done
#########################
# Execute Bazel command #
#########################
# Prevent immediate exit for Bazel test failures
set +e
(
cd $GIT_ROOT_DIR && \
# Run bazel command with saucelabs specific environment variables passed to the action
# The KARMA_WEB_TEST_MODE and SAUCE_TUNNEL_IDENTIFIER environment variables provide
# envirnment variables to be read in the karma configuration file to set correct
# configurations for karma saucelabs and browser configs.
# Usage of these envirnment variables can be seen in this repo in
# /karma-js.conf.js and /browser-providers.conf.js
eval "$USER_COMMAND --define=KARMA_WEB_TEST_MODE=SL_REQUIRED \
--action_env=SAUCE_USERNAME=$SAUCE_USERNAME \
--action_env=SAUCE_ACCESS_KEY=$SAUCE_ACCESS_KEY \
--action_env=SAUCE_READY_FILE=$SAUCE_READY_FILE \
--action_env=SAUCE_PID_FILE=$SAUCE_PID_FILE \
--action_env=SAUCE_TUNNEL_IDENTIFIER=$SAUCE_TUNNEL_IDENTIFIER"
)
BAZEL_EXIT_CODE=$?
echo "Exit code for bazel command was: $BAZEL_EXIT_CODE"
# Reenable immediate exit for failure exit code
set -e
##############################
# Close the saucelabs tunnel #
##############################
if [[ ! -f ${SAUCE_PID_FILE} ]]; then
echo "Could not find Saucelabs tunnel PID file. Cannot stop tunnel.."
exit 1
fi
echo "Shutting down Sauce Connect tunnel"
# The process id for the sauce-connect instance is stored inside of the pidfile.
tunnelProcessId=$(cat ${SAUCE_PID_FILE})
# Kill the process by using the PID that has been read from the pidfile. Note that
# we cannot use killall because CircleCI base container images don't have it installed.
kill ${tunnelProcessId}
while (ps -p ${tunnelProcessId} &> /dev/null); do
printf "."
sleep .5
done
echo ""
echo "Sauce Connect tunnel has been shut down"
exit $BAZEL_EXIT_CODE

View File

@ -11,7 +11,6 @@ module.exports = (gulp) => () => {
const ignoredScopes = [
'aio',
'docs-infra',
'ivy',
'zone.js',
];

View File

@ -1,20 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* Blocklist of unit tests from angular/material2 with ivy that are skipped when running on
* angular/angular. As bugs are resolved, items should be removed from this blocklist.
*
* The `notes` section should be used to keep track of specific issues associated with the failures.
*/
// clang-format off
// tslint:disable
window.testBlocklist = {};
// clang-format on

View File

@ -1,24 +0,0 @@
### Unit tests for Angular CDK/Material
The unit tests from angular/material2 run on CircleCI under the `material-unit-tests` job.
Known failing tests are skipped based on the blocklist in
`tools/material-ci/angular_material_test_blocklist.js`. Whenever the root cause of a known failure
is identified, the `notes` field for the corresponding tests should be updated. Whenever a failure
is resolved, the corresponding tests should be removed from the blocklist.
### Debugging
To debug a failure, you need to work against the angular/material2 repo:
1. Clone `angular/material2`
2. Checkout the `ivy-2019` branch
3. Run `yarn`
4. Run `scripts/ivy/install-angular.sh path/to/local/angular/repo`
5. Run `gulp test`
### Regenerating the blocklist
If a problem has been fixed, you can regenerate the blocklist by:
1. Clone `angular/material2`
2. Checkout the `ivy-2019` branch
3. Run `yarn`
4. Run `scripts/ivy/install-angular.sh path/to/local/angular/repo`
5. Run `gulp test`. Let it finish. It will take a few minutes.
6. Run `scripts/ivy/generate-blocklist.js path/to/local/angular/repo`
7. Copy the new blocklist from `dist/angular_material_test_blocklist.js`

View File

@ -27,6 +27,7 @@
"http",
"ivy",
"language-service",
"ngcc",
"platform-browser",
"platform-browser-dynamic",
"platform-server",

View File

@ -301,7 +301,7 @@
through2 "^2.0.0"
xdg-basedir "^3.0.0"
"@microsoft/api-extractor@^7.0.21":
"@microsoft/api-extractor@7.0.21":
version "7.0.21"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.0.21.tgz#d5ff9bba4ff7283503aac83ad489b16cad293fc7"
integrity sha512-7lFcHNykVz0tvgOz9juXqP+a1j0EmnJ9J080CBE/171IxL4fBrpslPhqN86dNuavuPragRpBLc8Okv/bV7FJPQ==