diff --git a/.circleci/config.yml b/.circleci/config.yml index 74d5260504957..ee5883ac8c8d8 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,9 +85,28 @@ jobs: - "node_modules" - "~/bazel_repository_cache" + aio_monitoring: + <<: *job_defaults + steps: + - checkout: + <<: *post_checkout + - restore_cache: + key: *cache_key + - run: xvfb-run --auto-servernum ./aio/scripts/test-production.sh + workflows: version: 2 default_workflow: jobs: - lint - build + aio_monitoring: + jobs: + - aio_monitoring + triggers: + - schedule: + cron: "0 0 * * *" + filters: + branches: + only: + - master diff --git a/CHANGELOG.md b/CHANGELOG.md index 74804beddc010..0b1b6132370ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ + +## [5.2.10](https://github.com/angular/angular/compare/5.2.9...5.2.10) (2018-04-16) + + +### Bug Fixes + +* **animations:** avoid animation insertions during router back/refresh ([#21977](https://github.com/angular/angular/issues/21977)) ([641cc49](https://github.com/angular/angular/commit/641cc49)), closes [#19712](https://github.com/angular/angular/issues/19712) +* **common:** properly take className changes into account ([#21937](https://github.com/angular/angular/issues/21937)) ([54e9108](https://github.com/angular/angular/commit/54e9108)), closes [#21932](https://github.com/angular/angular/issues/21932) +* **compiler:** fix support for html-like text in translatable attributes ([#23053](https://github.com/angular/angular/issues/23053)) ([4f7c369](https://github.com/angular/angular/commit/4f7c369)) +* **compiler-cli:** emit correct css string escape sequences ([#22776](https://github.com/angular/angular/issues/22776)) ([db0afa9](https://github.com/angular/angular/commit/db0afa9)) +* **forms:** improve error message for invalid value accessors ([#22731](https://github.com/angular/angular/issues/22731)) ([dd61595](https://github.com/angular/angular/commit/dd61595)) +* **service-worker:** add badge to NOTIFICATION_OPTION_NAMES ([#23241](https://github.com/angular/angular/issues/23241)) ([7b23983](https://github.com/angular/angular/commit/7b23983)), closes [#23196](https://github.com/angular/angular/issues/23196) +* **service-worker:** do not enter degraded mode when offline ([#22883](https://github.com/angular/angular/issues/22883)) ([ae9c25f](https://github.com/angular/angular/commit/ae9c25f)), closes [#21636](https://github.com/angular/angular/issues/21636) +* **service-worker:** fix LruList bugs ([#22769](https://github.com/angular/angular/issues/22769)) ([65f8943](https://github.com/angular/angular/commit/65f8943)), closes [#22218](https://github.com/angular/angular/issues/22218) [#22768](https://github.com/angular/angular/issues/22768) +* **service-worker:** ignore invalid `only-if-cached` requests ([#22883](https://github.com/angular/angular/issues/22883)) ([0d4fe38](https://github.com/angular/angular/commit/0d4fe38)), closes [#22362](https://github.com/angular/angular/issues/22362) +* **upgrade:** correctly handle downgraded `OnPush` components ([#22209](https://github.com/angular/angular/issues/22209)) ([f43fba6](https://github.com/angular/angular/commit/f43fba6)), closes [#14286](https://github.com/angular/angular/issues/14286) +* **upgrade:** propagate return value of resumeBootstrap ([#22754](https://github.com/angular/angular/issues/22754)) ([ae76eec](https://github.com/angular/angular/commit/ae76eec)), closes [#22723](https://github.com/angular/angular/issues/22723) +* **upgrade:** two-way binding and listening for event ([#22772](https://github.com/angular/angular/issues/22772)) ([5391f96](https://github.com/angular/angular/commit/5391f96)), closes [#22734](https://github.com/angular/angular/issues/22734) + + + ## [5.2.9](https://github.com/angular/angular/compare/5.2.8...5.2.9) (2018-03-14) diff --git a/aio/.gitignore b/aio/.gitignore index 4ac6ca5434eb5..4790f4f2f739f 100644 --- a/aio/.gitignore +++ b/aio/.gitignore @@ -30,6 +30,7 @@ /connect.lock /coverage /libpeerconnection.log +debug.log npm-debug.log testem.log /typings @@ -45,4 +46,4 @@ protractor-results*.txt Thumbs.db # copied dependencies -src/assets/js/lunr* \ No newline at end of file +src/assets/js/lunr* diff --git a/aio/aio-builds-setup/dockerbuild/Dockerfile b/aio/aio-builds-setup/dockerbuild/Dockerfile index 7d082d1df1dd2..206af101ce1ae 100644 --- a/aio/aio-builds-setup/dockerbuild/Dockerfile +++ b/aio/aio-builds-setup/dockerbuild/Dockerfile @@ -19,8 +19,8 @@ ARG AIO_DOMAIN_NAME=ngbuilds.io ARG TEST_AIO_DOMAIN_NAME=$AIO_DOMAIN_NAME.localhost ARG AIO_GITHUB_ORGANIZATION=angular ARG TEST_AIO_GITHUB_ORGANIZATION=angular -ARG AIO_GITHUB_TEAM_SLUGS=angular-core,aio-contributors -ARG TEST_AIO_GITHUB_TEAM_SLUGS=angular-core,aio-contributors +ARG AIO_GITHUB_TEAM_SLUGS=team,aio-contributors +ARG TEST_AIO_GITHUB_TEAM_SLUGS=team,aio-contributors ARG AIO_NGINX_HOSTNAME=$AIO_DOMAIN_NAME ARG TEST_AIO_NGINX_HOSTNAME=$TEST_AIO_DOMAIN_NAME ARG AIO_NGINX_PORT_HTTP=80 diff --git a/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md b/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md index 7c3206886c18a..8b9ab9a7c7748 100644 --- a/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md +++ b/aio/aio-builds-setup/docs/vm-setup--set-up-secrets.md @@ -9,7 +9,7 @@ Necessary secrets: - Used for: - Retrieving open PRs without rate-limiting. - Retrieving PR author. - - Retrieving members of the `angular-core` team. + - Retrieving members of the trusted GitHub teams. - Posting comments with preview links on PRs. 2. `PREVIEW_DEPLOYMENT_TOKEN` diff --git a/aio/content/examples/architecture/src/app/backend.service.ts b/aio/content/examples/architecture/src/app/backend.service.ts index e3e21a6ff40ea..c05a8fcb8af2e 100644 --- a/aio/content/examples/architecture/src/app/backend.service.ts +++ b/aio/content/examples/architecture/src/app/backend.service.ts @@ -15,7 +15,7 @@ export class BackendService { getAll(type: Type): PromiseLike { if (type === Hero) { - // TODO get from the database + // TODO: get from the database return Promise.resolve(HEROES); } let err = new Error('Cannot get object of this type'); diff --git a/aio/content/examples/dependency-injection-in-action/src/app/hero-of-the-month.component.1.ts b/aio/content/examples/dependency-injection-in-action/src/app/hero-of-the-month.component.1.ts index beaa35c53a010..4eb3a14060834 100644 --- a/aio/content/examples/dependency-injection-in-action/src/app/hero-of-the-month.component.1.ts +++ b/aio/content/examples/dependency-injection-in-action/src/app/hero-of-the-month.component.1.ts @@ -8,7 +8,7 @@ import { MinimalLogger } from './minimal-logger.service'; @Component({ selector: 'app-hero-of-the-month', templateUrl: './hero-of-the-month.component.html', - // Todo: move this aliasing, `useExisting` provider to the AppModule + // TODO: move this aliasing, `useExisting` provider to the AppModule providers: [{ provide: MinimalLogger, useExisting: LoggerService }] }) export class HeroOfTheMonthComponent { diff --git a/aio/content/examples/dependency-injection-in-action/src/app/hero.service.ts b/aio/content/examples/dependency-injection-in-action/src/app/hero.service.ts index 2063c30d7a48b..6eb5ffa14b0ec 100644 --- a/aio/content/examples/dependency-injection-in-action/src/app/hero.service.ts +++ b/aio/content/examples/dependency-injection-in-action/src/app/hero.service.ts @@ -5,7 +5,7 @@ import { Hero } from './hero'; @Injectable() export class HeroService { - // TODO move to database + // TODO: move to database private heroes: Array = [ new Hero(1, 'RubberMan', 'Hero of many talents', '123-456-7899'), new Hero(2, 'Magma', 'Hero of all trades', '555-555-5555'), diff --git a/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts b/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts index f1e48d769599e..1a3eaf8695179 100644 --- a/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts +++ b/aio/content/examples/dependency-injection-in-action/src/app/parent-finder.component.ts @@ -151,7 +151,7 @@ export class BethComponent implements Parent { // #docregion alex-1 }) // #enddocregion alex-1 -// Todo: Add `... implements Parent` to class signature +// TODO: Add `... implements Parent` to class signature // #docregion alex-1 // #docregion alex-class-signature export class AlexComponent extends Base diff --git a/aio/content/examples/dependency-injection/src/app/user.service.ts b/aio/content/examples/dependency-injection/src/app/user.service.ts index 8fdda925db409..03e23b7687b43 100644 --- a/aio/content/examples/dependency-injection/src/app/user.service.ts +++ b/aio/content/examples/dependency-injection/src/app/user.service.ts @@ -7,7 +7,7 @@ export class User { public isAuthorized = false) { } } -// Todo: get the user; don't 'new' it. +// TODO: get the user; don't 'new' it. let alice = new User('Alice', true); let bob = new User('Bob', false); diff --git a/aio/content/examples/dynamic-form/src/app/question.service.ts b/aio/content/examples/dynamic-form/src/app/question.service.ts index bb452cf5e6699..a1d9499c6806a 100644 --- a/aio/content/examples/dynamic-form/src/app/question.service.ts +++ b/aio/content/examples/dynamic-form/src/app/question.service.ts @@ -8,8 +8,8 @@ import { TextboxQuestion } from './question-textbox'; @Injectable() export class QuestionService { - // Todo: get from a remote source of question metadata - // Todo: make asynchronous + // TODO: get from a remote source of question metadata + // TODO: make asynchronous getQuestions() { let questions: QuestionBase[] = [ diff --git a/aio/content/examples/http/src/app/http-interceptors/upload-interceptor.ts b/aio/content/examples/http/src/app/http-interceptors/upload-interceptor.ts index 1ad1891898f22..5ed63eb6a5ca4 100644 --- a/aio/content/examples/http/src/app/http-interceptors/upload-interceptor.ts +++ b/aio/content/examples/http/src/app/http-interceptors/upload-interceptor.ts @@ -15,7 +15,7 @@ export class UploadInterceptor implements HttpInterceptor { if (req.url.indexOf('/upload/file') === -1) { return next.handle(req); } - const delay = 300; // Todo: inject delay? + const delay = 300; // TODO: inject delay? return createUploadEvents(delay); } } diff --git a/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo-parent.component.ts b/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo-parent.component.ts index 3f2bd8585d178..7edb57bfc3081 100644 --- a/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo-parent.component.ts +++ b/aio/content/examples/lifecycle-hooks/src/app/peek-a-boo-parent.component.ts @@ -43,6 +43,7 @@ export class PeekABooParentComponent { this.heroName = 'Windstorm'; this.logger.clear(); // clear log on create } + this.hookLog = this.logger.logs; this.logger.tick(); } diff --git a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts b/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts index 409d42eb075c6..830dad7b91e47 100644 --- a/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts +++ b/aio/content/examples/reactive-forms/src/app/hero-list/hero-list.component.ts @@ -23,7 +23,7 @@ export class HeroListComponent implements OnInit { getHeroes() { this.isLoading = true; this.heroes = this.heroService.getHeroes() - // Todo: error handling + // TODO: error handling .finally(() => this.isLoading = false); this.selectedHero = undefined; } diff --git a/aio/content/examples/router/src/app/heroes/hero-list.component.1.ts b/aio/content/examples/router/src/app/heroes/hero-list.component.1.ts index 4ba93662e342e..5f7a595f5965b 100644 --- a/aio/content/examples/router/src/app/heroes/hero-list.component.1.ts +++ b/aio/content/examples/router/src/app/heroes/hero-list.component.1.ts @@ -1,6 +1,6 @@ // #docplaster // #docregion -// TODO SOMEDAY: Feature Componetized like HeroCenter +// TODO: Feature Componetized like HeroCenter import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { Observable } from 'rxjs/Observable'; diff --git a/aio/content/examples/router/src/app/heroes/hero-list.component.ts b/aio/content/examples/router/src/app/heroes/hero-list.component.ts index 6bb3edd87e696..bdc8c7bc73f33 100644 --- a/aio/content/examples/router/src/app/heroes/hero-list.component.ts +++ b/aio/content/examples/router/src/app/heroes/hero-list.component.ts @@ -1,6 +1,6 @@ // #docplaster // #docregion -// TODO SOMEDAY: Feature Componetized like CrisisCenter +// TODO: Feature Componetized like CrisisCenter // #docregion rxjs-imports import 'rxjs/add/operator/switchMap'; import { Observable } from 'rxjs/Observable'; diff --git a/aio/content/examples/rx-library/src/operators.2.ts b/aio/content/examples/rx-library/src/operators.2.ts index 4e38c8eb6df1f..a24c2d633855e 100644 --- a/aio/content/examples/rx-library/src/operators.2.ts +++ b/aio/content/examples/rx-library/src/operators.2.ts @@ -8,7 +8,7 @@ import { map } from 'rxjs/operators/map'; const squareOdd = Observable.of(1, 2, 3, 4, 5) .pipe( - filter(n => n % 2), + filter(n => n % 2 !== 0), map(n => n * n) ); diff --git a/aio/content/examples/testing/src/app/demo/demo.ts b/aio/content/examples/testing/src/app/demo/demo.ts index 32649feaa6cbd..abbfd58a85321 100644 --- a/aio/content/examples/testing/src/app/demo/demo.ts +++ b/aio/content/examples/testing/src/app/demo/demo.ts @@ -37,8 +37,8 @@ export class ValueService { // #docregion MasterService @Injectable() export class MasterService { - constructor(private masterService: ValueService) { } - getValue() { return this.masterService.getValue(); } + constructor(private valueService: ValueService) { } + getValue() { return this.valueService.getValue(); } } // #enddocregion MasterService diff --git a/aio/content/examples/testing/src/app/model/hero.service.spec.ts b/aio/content/examples/testing/src/app/model/hero.service.spec.ts index b228692154a33..22bd98246d1cd 100644 --- a/aio/content/examples/testing/src/app/model/hero.service.spec.ts +++ b/aio/content/examples/testing/src/app/model/hero.service.spec.ts @@ -15,7 +15,7 @@ describe ('HeroesService (with spies)', () => { let heroService: HeroService; beforeEach(() => { - // Todo: spy on other methods too + // TODO: spy on other methods too httpClientSpy = jasmine.createSpyObj('HttpClient', ['get']); heroService = new HeroService( httpClientSpy); }); diff --git a/aio/content/examples/toh-pt4/src/app/hero.service.ts b/aio/content/examples/toh-pt4/src/app/hero.service.ts index b7b2d1506814c..0fcb0b462f255 100644 --- a/aio/content/examples/toh-pt4/src/app/hero.service.ts +++ b/aio/content/examples/toh-pt4/src/app/hero.service.ts @@ -25,7 +25,7 @@ export class HeroService { // #docregion getHeroes, getHeroes-1 getHeroes(): Observable { // #enddocregion getHeroes-1 - // Todo: send the message _after_ fetching the heroes + // TODO: send the message _after_ fetching the heroes this.messageService.add('HeroService: fetched heroes'); // #docregion getHeroes-1 return of(HEROES); diff --git a/aio/content/examples/toh-pt5/src/app/hero.service.ts b/aio/content/examples/toh-pt5/src/app/hero.service.ts index 71f8571983629..151520c6207e5 100644 --- a/aio/content/examples/toh-pt5/src/app/hero.service.ts +++ b/aio/content/examples/toh-pt5/src/app/hero.service.ts @@ -13,14 +13,14 @@ export class HeroService { constructor(private messageService: MessageService) { } getHeroes(): Observable { - // Todo: send the message _after_ fetching the heroes + // TODO: send the message _after_ fetching the heroes this.messageService.add('HeroService: fetched heroes'); return of(HEROES); } // #docregion getHero getHero(id: number): Observable { - // Todo: send the message _after_ fetching the hero + // TODO: send the message _after_ fetching the hero this.messageService.add(`HeroService: fetched hero id=${id}`); return of(HEROES.find(hero => hero.id === id)); } diff --git a/aio/content/examples/universal/tsconfig.json b/aio/content/examples/universal/tsconfig.json deleted file mode 100644 index a6c016bf38ad7..0000000000000 --- a/aio/content/examples/universal/tsconfig.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "compileOnSave": false, - "compilerOptions": { - "outDir": "./dist/out-tsc", - "sourceMap": true, - "declaration": false, - "moduleResolution": "node", - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "target": "es5", - "typeRoots": [ - "node_modules/@types" - ], - "lib": [ - "es2017", - "dom" - ] - } -} diff --git a/aio/content/guide/aot-compiler.md b/aio/content/guide/aot-compiler.md index c491e55023908..a314c98a02aef 100644 --- a/aio/content/guide/aot-compiler.md +++ b/aio/content/guide/aot-compiler.md @@ -103,7 +103,7 @@ You can control your app compilation by providing template compiler options in t This option tells the compiler not to produce `.metadata.json` files. The option is `false` by default. -`.metadata.json` files contain infomration needed by the template compiler from a `.ts` +`.metadata.json` files contain information needed by the template compiler from a `.ts` file that is not included in the `.d.ts` file produced by the TypeScript compiler. This information contains, for example, the content of annotations (such as a component's template) which TypeScript emits to the `.js` file but not to the `.d.ts` file. @@ -204,7 +204,7 @@ JavaScript with [JsDoc](http://usejsdoc.org/) comments needed by the ### *annotationsAs* Use this option to modify how the Angular specific annotations are emitted to improve tree-shaking. Non-Angular -annotations and decorators are unnaffected. Default is `static fields`. +annotations and decorators are unaffected. Default is `static fields`. value | description ----------------|------------------------------------------------------------- diff --git a/aio/content/guide/architecture-components.md b/aio/content/guide/architecture-components.md new file mode 100644 index 0000000000000..4e9d61ac47369 --- /dev/null +++ b/aio/content/guide/architecture-components.md @@ -0,0 +1,180 @@ +# Introduction to components + +Component + +A _component_ controls a patch of screen called a *view*. For example, individual components define and control each of the following views from the [Tutorial](tutorial/index): + +* The app root with the navigation links. +* The list of heroes. +* The hero editor. + +You define a component's application logic—what it does to support the view—inside a class. The class interacts with the view through an API of properties and methods. + +For example, the `HeroListComponent` has a `heroes` property that returns an array of heroes that it acquires from a service. `HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list. + + + +Angular creates, updates, and destroys components as the user moves through the application. Your app can take action at each moment in this lifecycle through optional [lifecycle hooks](guide/lifecycle-hooks), like `ngOnInit()`. + +
+ +## Component metadata + +Metadata + +The `@Component` decorator identifies the class immediately below it as a component class, and specifies its metadata. In the example code below, you can see that `HeroListComponent` is just a class, with no special Angular notation or syntax at all. It's not a component until you mark it as one with the `@Component` decorator. + +The metadata for a component tells Angular where to get the major building blocks it needs to create and present the component and its view. In particular, it associates a _template_ with the component, either directly with inline code, or by reference. Together, the component and its template describe a _view_. + +In addition to containing or pointing to the template, the `@Component` metadata configures, for example, how the component can be referenced in HTML and what services it requires. + + Here's an example of basic metadata for `HeroListComponent`: + + + + This example shows some of the most useful `@Component` configuration options: + +* `selector`: A CSS selector that tells Angular to create and insert an instance of this component wherever it finds the corresponding tag in template HTML. For example, if an app's HTML contains ``, then +Angular inserts an instance of the `HeroListComponent` view between those tags. + +* `templateUrl`: The module-relative address of this component's HTML template. Alternatively, you can provide the HTML template inline, as the value of the `template` property. This template defines the component's _host view_. + +* `providers`: An array of **dependency injection providers** for services that the component requires. In the example, this tells Angular that the component's constructor requires a `HeroService` instance +in order to get the list of heroes to display. + +
+ +## Templates and views + +Template + +You define a component's view with its companion template. A template is a form of HTML that tells Angular how to render the component. + +Views are typically arranged hierarchically, allowing you to modify or show and hide entire UI sections or pages as a unit. The template immediately associated with a component defines that component's _host view_. The component can also define a _view hierarchy_, which contains _embedded views_, hosted by other components. + +
+Component tree +
+ +A view hierarchy can include views from components in the same NgModule, but it also can (and often does) include views from components that are defined in different NgModules. + +## Template syntax + +A template looks like regular HTML, except that it also contains Angular [template syntax](guide/template-syntax), which alters the HTML based on your app's logic and the state of app and DOM data. Your template can use _data binding_ to coordinate the app and DOM data, _pipes_ to transform data before it is displayed, and _directives_ to apply app logic to what gets displayed. + +For example, here is a template for the Tutorial's `HeroListComponent`: + + + +This template uses typical HTML elements like `

` and `

`, and also includes Angular template-syntax elements, `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and ``. The template-syntax elements tell Angular how to render the HTML to the screen, using program logic and data. + +* The `*ngFor` directive tells Angular to iterate over a list. +* The `{{hero.name}}`, `(click)`, and `[hero]` bind program data to and from the DOM, responding to user input. See more about [data binding](#data-binding) below. +* The `` tag in the example is an element that represents a new component, `HeroDetailComponent`. The `HeroDetailComponent` (code not shown) is a child component of the `HeroListComponent` that defines the Hero-detail view. Notice how custom components like this mix seamlessly with native HTML in the same layouts. + +### Data binding + +Without a framework, you would be responsible for pushing data values into the HTML controls and turning user responses into actions and value updates. Writing such push/pull logic by hand is tedious, error-prone, and a nightmare to read, as any experienced jQuery programmer can attest. + +Angular supports *two-way data binding*, a mechanism for coordinating parts of a template with parts of a component. Add binding markup to the template HTML to tell Angular how to connect both sides. + +The following diagram shows the four forms of data binding markup. Each form has a direction—to the DOM, from the DOM, or in both directions. + +

+Data Binding +
+ +This example from the `HeroListComponent` template uses three of these forms: + + + +* The `{{hero.name}}` [*interpolation*](guide/displaying-data#interpolation) +displays the component's `hero.name` property value within the `
  • ` element. + +* The `[hero]` [*property binding*](guide/template-syntax#property-binding) passes the value of `selectedHero` from +the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`. + +* The `(click)` [*event binding*](guide/user-input#binding-to-user-input-events) calls the component's `selectHero` method when the user clicks a hero's name. + +**Two-way data binding** is an important fourth form that combines property and event binding in a single notation. Here's an example from the `HeroDetailComponent` template that uses two-way data binding with the `ngModel` directive: + + + +In two-way binding, a data property value flows to the input box from the component as with property binding. +The user's changes also flow back to the component, resetting the property to the latest value, +as with event binding. + +Angular processes *all* data bindings once per JavaScript event cycle, +from the root of the application component tree through all child components. + +
    + Data Binding +
    + +Data binding plays an important role in communication between a template and its component, and is also important for communication between parent and child components. + +
    + Parent/Child binding +
    + +### Pipes + + Angular pipes let you declare display-value transformations in your template HTML. A class with the `@Pipe` decorator defines a function that transforms input values to output values for display in a view. + + Angular defines various pipes, such as the [date](https://angular.io/api/common/DatePipe) pipe and [currency](https://angular.io/api/common/CurrencyPipe) pipe; for a complete list, see the [Pipes API list](https://angular.io/api?type=pipe). You can also define new pipes. + + To specify a value transformation in an HTML template, use the [pipe operator (|)](https://angular.io/guide/template-syntax#pipe): + + `{{interpolated_value | pipe_name}}` + + You can chain pipes, sending the output of one pipe function to be transformed by another pipe function. A pipe can also take arguments that control how it performs its transformation. For example, you can pass the desired format to the `date` pipe: + + ``` + +

    Today is {{today | date}}

    + + +

    The date is {{today | date:'fullDate'}}

    + + +

    The time is {{today | date:'shortTime'}}

    +``` + +
    + +### Directives + +Directives + +Angular templates are *dynamic*. When Angular renders them, it transforms the DOM according to the instructions given by *directives*. A directive is a class with a `@Directive` decorator. + +A component is technically a directive - but components are so distinctive and central to Angular applications that Angular defines the `@Component` decorator, which extends the `@Directive` decorator with template-oriented features. + +There are two kinds of directives besides components: _structural_ and _attribute_ directives. Just as for components, the metadata for a directive associates the class with a `selector` that you use to insert it into HTML. In templates, directives typically appear within an element tag as attributes, either by name or as the target of an assignment or a binding. + +#### Structural directives + +Structural directives alter layout by adding, removing, and replacing elements in DOM. The example template uses two built-in structural directives to add application logic to how the view is rendered: + + + + * [`*ngFor`](guide/displaying-data#ngFor) is an iterative; it tells Angular to stamp out one `
  • ` per hero in the `heroes` list. + * [`*ngIf`](guide/displaying-data#ngIf) is a conditional; it includes the `HeroDetail` component only if a selected hero exists. + +#### Attribute directives + +Attribute directives alter the appearance or behavior of an existing element. +In templates they look like regular HTML attributes, hence the name. + +The `ngModel` directive, which implements two-way data binding, is an example of an attribute directive. `ngModel` modifies the behavior of an existing element (typically an ``) by setting its display value property and responding to change events. + + + +Angular has more pre-defined directives that either alter the layout structure +(for example, [ngSwitch](guide/template-syntax#ngSwitch)) +or modify aspects of DOM elements and components +(for example, [ngStyle](guide/template-syntax#ngStyle) and [ngClass](guide/template-syntax#ngClass)). + +You can also write your own directives. Components such as `HeroListComponent` are one kind of custom directive. You can also create custom structural and attribute directives. + + \ No newline at end of file diff --git a/aio/content/guide/architecture-modules.md b/aio/content/guide/architecture-modules.md new file mode 100644 index 0000000000000..5e0af13e811bc --- /dev/null +++ b/aio/content/guide/architecture-modules.md @@ -0,0 +1,107 @@ +# Introduction to modules + +Module + +Angular apps are modular and Angular has its own modularity system called _NgModules_. An NgModule is a container for a cohesive block of code dedicated to an application domain, a workflow, or a closely related set of capabilities. It can contain components, service providers, and other code files whose scope is defined by the containing NgModule. It can import functionality that is exported from other NgModules, and export selected functionality for use by other NgModules. + +Every Angular app has at least one NgModule class, [the _root module_](guide/bootstrapping), which is conventionally named `AppModule` and resides in a file named `app.module.ts`. You launch your app by *bootstrapping* the root NgModule. + +While a small application might have only one NgModule, most apps have many more _feature modules_. The _root_ NgModule for an app is so named because it can include child NgModules in a hierarchy of any depth. + +## NgModule metadata + +An NgModule is defined as a class decorated with `@NgModule`. The `@NgModule` decorator is a function that takes a single metadata object, whose properties describe the module. The most important properties are as follows. + +* `declarations`—The [components](guide/architecture-components), _directives_, and _pipes_ that belong to this NgModule. + +* `exports`—The subset of declarations that should be visible and usable in the _component templates_ of other NgModules. + +* `imports`—Other modules whose exported classes are needed by component templates declared in _this_ NgModule. + +* `providers`—Creators of [services](guide/architecture-services) that this NgModule contributes to the global collection of services; they become accessible in all parts of the app. (You can also specify providers at the component level, which is often preferred.) + +* `bootstrap`—The main application view, called the _root component_, which hosts all other app views. Only the _root NgModule_ should set this `bootstrap` property. + +Here's a simple root NgModule definition: + + + +
    + + The `export` of `AppComponent` is just to show how to export; it isn't actually necessary in this example. A root NgModule has no reason to _export_ anything because other modules don't need to _import_ the root NgModule. + +
    + +## NgModules and components + +NgModules provide a _compilation context_ for their components. A root NgModule always has a root component that is created during bootstrap, but any NgModule can include any number of additional components, which can be loaded through the router or created through the template. The components that belong to an NgModule share a compilation context. + +
    + +Component compilation context + +
    + +
    + +A component and its template together define a _view_. A component can contain a _view hierarchy_, which allows you to define arbitrarily complex areas of the screen that can be created, modified, and destroyed as a unit. A view hierarchy can mix views defined in components that belong to different NgModules. This is often the case, especially for UI libraries. + +
    + +View hierarchy + +
    + +
    + +When you create a component, it is associated directly with a single view, called the _host view_. The host view can be the root of a view hierarchy, which can contain _embedded views_, which are in turn the host views of other components. Those components can be in the same NgModule, or can be imported from other NgModules. Views in the tree can be nested to any depth. + +
    + The hierarchical structure of views is a key factor in the way Angular detects and responds to changes in the DOM and app data. +
    + +## NgModules and JavaScript modules + +The NgModule system is different from and unrelated to the JavaScript (ES2015) module system for managing collections of JavaScript objects. These are two different and _complementary_ module systems. You can use them both to write your apps. + +In JavaScript each _file_ is a module and all objects defined in the file belong to that module. +The module declares some objects to be public by marking them with the `export` key word. +Other JavaScript modules use *import statements* to access public objects from other modules. + + + + + + + +## Angular libraries + +Component + +Angular ships as a collection of JavaScript modules. You can think of them as library modules. Each Angular library name begins with the `@angular` prefix. Install them with the `npm` package manager and import parts of them with JavaScript `import` statements. + +
    + +For example, import Angular's `Component` decorator from the `@angular/core` library like this: + + + +You also import NgModules from Angular _libraries_ using JavaScript import statements: + + + +In the example of the simple root module above, the application module needs material from within the `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this. + + + +In this way you're using both the Angular and JavaScript module systems _together_. Although it's easy to confuse the two systems, which share the common vocabulary of "imports" and "exports", you will become familiar with the different contexts in which they are used. + +
    + + Learn more from the [NgModules](guide/ngmodules) page. + +
    + +
    diff --git a/aio/content/guide/architecture-next-steps.md b/aio/content/guide/architecture-next-steps.md new file mode 100644 index 0000000000000..713d206c68ea3 --- /dev/null +++ b/aio/content/guide/architecture-next-steps.md @@ -0,0 +1,48 @@ +# Next steps: tools and techniques + +Once you have understood the basic building blocks, you can begin to learn more about the features and tools that are available to help you develop and deliver Angular applications. Angular provides a lot more features and services that are covered in this documentation. + +#### Responsive programming tools + + * [Lifecycle hooks](guide/lifecycle-hooks): Tap into key moments in the lifetime of a component, from its creation to its destruction, by implementing the lifecycle hook interfaces. + + * [Observables and event processing](guide/observables): How to use observables with components and services to publish and subscribe to messages of any type, such as user-interaction events and asynchronous operation results. + +#### Client-server interaction tools + + * [HTTP](guide/http): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client. + + * [Server-side Rendering](guide/universal): Angular Universal generates static application pages on the server through server-side rendering (SSR). This allows you to run your Angular app on the server in order to improve performance and show the first page quickly on mobile and low-powered devices, and also facilitate web crawlers. + + * [Service Workers](guide/service-worker-intro): A service worker is a script that runs in the web browser and manages caching for an application. Service workers function as a network proxy. They intercept outgoing HTTP requests and can, for example, deliver a cached response if one is available. You can significantly improve the user experience by using a service worker to reduce dependency on the network. + +#### Domain-specific libraries + + * [Animations](guide/animations): Animate component behavior +without deep knowledge of animation techniques or CSS with Angular's animation library. + + * [Forms](guide/forms): Support complex data entry scenarios with HTML-based validation and dirty checking. + +#### Support for the development cycle + + * [Testing Platform](guide/testing): Run unit tests on your application parts as they interact with the Angular framework. + + * [Internationalization](guide/i18n): Angular's internationalization (i18n) tools can help you make your app available in multiple languages. + + * [Compilation](guide/aot-compiler): Angular provides just-in-time (JIT) compilation for the development environment, and ahead-of-time (AOT) compilation for the production environment. + + * [Security guidelines](guide/security): Learn about Angular's built-in protections against common web-app vulnerabilities and attacks such as cross-site scripting attacks. + +#### Setup and deployment tools + + * [Setup for local development](guide/setup): Learn how to set up a new project for development with QuickStart. + + * [Installation](guide/npm-packages): The [Angular CLI](https://cli.angular.io/), Angular applications, and Angular itself depend on features and functionality provided by libraries that are available as [npm](https://docs.npmjs.com/) packages. + + * [Typescript Configuration](guide/typescript-configuration): TypeScript is the primary language for Angular application development. + + * [Browser support](guide/browser-support): Learn how to make your apps compatible across a wide range of browsers. + + * [Deployment](guide/deployment): Learn techniques for deploying your Angular application to a remote server. + +
    diff --git a/aio/content/guide/architecture-services.md b/aio/content/guide/architecture-services.md new file mode 100644 index 0000000000000..7e73d7b3993b7 --- /dev/null +++ b/aio/content/guide/architecture-services.md @@ -0,0 +1,76 @@ +# Introduction to services and dependency injection + +Service + +_Service_ is a broad category encompassing any value, function, or feature that an app needs. A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well. +
    + +Angular distinguishes components from services in order to increase modularity and reusability. + +* By separating a component's view-related functionality from other kinds of processing, you can make your component classes lean and efficient. Ideally, a component's job is to enable the user experience and nothing more. It should present properties and methods for data binding, in order to mediate between the view (rendered by the template) and the application logic (which often includes some notion of a _model_). + +* A component should not need to define things like how to fetch data from the server, validate user input, or log directly to the console. Instead, it can delegate such tasks to services. By defining that kind of processing task in an injectable service class, you make it available to any component. You can also make your app more adaptable by injecting different providers of the same kind of service, as appropriate in different circumstances. + +Angular doesn't *enforce* these principles. Angular does help you *follow* these principles by making it easy to factor your +application logic into services and make those services available to components through *dependency injection*. + +## Service examples + +Here's an example of a service class that logs to the browser console: + + + +Services can depend on other services. For example, here's a `HeroService` that depends on the `Logger` service, and also uses `BackendService` to get heroes. That service in turn might depend on the `HttpClient` service to fetch heroes asynchronously from a server. + + + +
    + +## Dependency injection + +Service + +Components consume services; that is, you can *inject* a service into a component, giving the component access to that service class. + +To define a class as a service in Angular, use the `@Injectable` decorator to provide the metadata that allows Angular to inject it into a component as a *dependency*. + +Similarly, use the `@Injectable` decorator to indicate that a component or other class (such as another service, a pipe, or an NgModule) _has_ a dependency. A dependency doesn't have to be a service—it could be a function, for example, or a value. + +*Dependency injection* (often called DI) is wired into the Angular framework and used everywhere to provide new components with the services or other things they need. + +* The *injector* is the main mechanism. You don't have to create an Angular injector. Angular creates an application-wide injector for you during the bootstrap process. + +* The injector maintains a *container* of dependency instances that it has already created, and reuses them if possible. + +* A *provider* is a recipe for creating a dependency. For a service, this is typically the service class itself. For any dependency you need in your app, you must register a provider with the app's injector, so that the injector can use it to create new instances. + +When Angular creates a new instance of a component class, it determines which services or other dependencies that component needs by looking at the types of its constructor parameters. For example, the constructor of `HeroListComponent` needs a `HeroService`: + + + +When Angular discovers that a component depends on a service, it first checks if the injector already has any existing instances of that service. If a requested service instance does not yet exist, the injector makes one using the registered provider, and adds it to the injector before returning the service to Angular. + +When all requested services have been resolved and returned, Angular can call the component's constructor with those services as arguments. + +The process of `HeroService` injection looks something like this: + +
    + Service +
    + +### Providing services + +You must register at least one *provider* of any service you are going to use. You can register providers in modules or in components. + +* When you add providers to the [root module](guide/architecture-modules), the same instance of a service is available to all components in your app. + + + +* When you register a provider at the component level, you get a new instance of the +service with each new instance of that component. At the component level, register a service provider in the `providers` property of the `@Component` metadata: + + + +For more detailed information, see the [Dependency Injection](guide/dependency-injection) section. + +
    diff --git a/aio/content/guide/architecture.md b/aio/content/guide/architecture.md index 0203cb2902013..e1d37bff6cd47 100644 --- a/aio/content/guide/architecture.md +++ b/aio/content/guide/architecture.md @@ -1,533 +1,137 @@ -# Architecture Overview +# Architecture overview -Angular is a framework for building client applications in HTML and -either JavaScript or a language like TypeScript that compiles to JavaScript. +Angular is a platform and framework for building client applications in HTML and TypeScript. +Angular is itself written in TypeScript. It implements core and optional functionality as a set of TypeScript libraries that you import into your apps. -The framework consists of several libraries, some of them core and some optional. +The basic building blocks of an Angular application are _NgModules_, which provide a compilation context for _components_. NgModules collect related code into functional sets; an Angular app is defined by a set of NgModules. An app always has at least a _root module_ that enables bootstrapping, and typically has many more _feature modules_. -You write Angular applications by composing HTML *templates* with Angularized markup, -writing *component* classes to manage those templates, adding application logic in *services*, -and boxing components and services in *modules*. +* Components define *views*, which are sets of screen elements that Angular can choose among and modify according to your program logic and data. Every app has at least a root component. -Then you launch the app by *bootstrapping* the _root module_. -Angular takes over, presenting your application content in a browser and -responding to user interactions according to the instructions you've provided. +* Components use *services*, which provide specific functionality not directly related to views. Service providers can be *injected* into components as *dependencies*, making your code modular, reusable, and efficient. -Of course, there is more to it than this. -You'll learn the details in the pages that follow. For now, focus on the big picture. +Both components and services are simply classes, with *decorators* that mark their type and provide metadata that tells Angular how to use them. -
    - overview -
    +* The metadata for a component class associates it with a *template* that defines a view. A template combines ordinary HTML with Angular *directives* and *binding markup* that allow Angular to modify the HTML before rendering it for display. -
    - - The code referenced on this page is available as a . +* The metadata for a service class provides the information Angular needs to make it available to components through *Dependency Injection (DI)*. -
    +An app's components typically define many views, arranged hierarchically. Angular provides the `Router` service to help you define navigation paths among views. The router provides sophisticated in-browser navigational capabilities. ## Modules -Component +Angular defines the `NgModule`, which differs from and complements the JavaScript (ES2015) module. An NgModule declares a compilation context for a set of components that is dedicated to an application domain, a workflow, or a closely related set of capabilities. An NgModule can associate its components with related code, such as services, to form functional units. +Every Angular app has a _root module_, conventionally named `AppModule`, which provides the bootstrap mechanism that launches the application. An app typically contains many functional modules. -Angular apps are modular and Angular has its own modularity system called _NgModules_. +Like JavaScript modules, NgModules can import functionality from other NgModules, and allow their own functionality to be exported and used by other NgModules. For example, to use the router service in your app, you import the `Router` NgModule. -NgModules are a big deal. -This page introduces modules; the [NgModules](guide/ngmodules) pages -relating to NgModules covers them in detail. - -
    - -Every Angular app has at least one NgModule class, [the _root module_](guide/bootstrapping "Bootstrapping"), -conventionally named `AppModule`. - -While the _root module_ may be the only module in a small application, most apps have many more -_feature modules_, each a cohesive block of code dedicated to an application domain, -a workflow, or a closely related set of capabilities. - -An NgModule, whether a _root_ or _feature_, is a class with an `@NgModule` decorator. +Organizing your code into distinct functional modules helps in managing development of complex applications, and in designing for reusability. In addition, this technique lets you take advantage of _lazy-loading_—that is, loading modules on demand—in order to minimize the amount of code that needs to be loaded at startup.
    - Decorators are functions that modify JavaScript classes. - Angular has many decorators that attach metadata to classes so that it knows - what those classes mean and how they should work. - - Learn more about decorators on the web. + For a more detailed discussion, see [Introduction to modules](guide/architecture-modules).
    -`NgModule` is a decorator function that takes a single metadata object whose properties describe the module. -The most important properties are: -* `declarations` - the _view classes_ that belong to this module. -Angular has three kinds of view classes: [components](guide/architecture#components), [directives](guide/architecture#directives), and [pipes](guide/pipes). - -* `exports` - the subset of declarations that should be visible and usable in the component [templates](guide/architecture#templates) of other modules. - -* `imports` - other modules whose exported classes are needed by component templates declared in _this_ module. - -* `providers` - creators of [services](guide/architecture#services) that this module contributes to -the global collection of services; they become accessible in all parts of the app. - -* `bootstrap` - the main application view, called the _root component_, -that hosts all other app views. Only the _root module_ should set this `bootstrap` property. +## Components -Here's a simple root module: +Every Angular application has at least one component, the *root component* that connects a component hierarchy with the page DOM. Each component defines a class that contains application data and logic, and is associated with an HTML *template* that defines a view to be displayed in a target environment. - +The `@Component` decorator identifies the class immediately below it as a component, and provides the template and related component-specific metadata.
    - The `export` of `AppComponent` is just to show how to use the `exports` array to export a component; it isn't actually necessary in this example. A root module has no reason to _export_ anything because other components don't need to _import_ the root module. - -
    - -Launch an application by _bootstrapping_ its root module. -During development you're likely to bootstrap the `AppModule` in a `main.ts` file like this one. - - - -### NgModules vs. JavaScript modules - -The NgModule — a class decorated with `@NgModule` — is a fundamental feature of Angular. + Decorators are functions that modify JavaScript classes. Angular defines a number of such decorators that attach specific kinds of metadata to classes, so that it knows what those classes mean and how they should work. -JavaScript also has its own module system for managing collections of JavaScript objects. -It's completely different and unrelated to the NgModule system. + Learn more about decorators on the web. -In JavaScript each _file_ is a module and all objects defined in the file belong to that module. -The module declares some objects to be public by marking them with the `export` key word. -Other JavaScript modules use *import statements* to access public objects from other modules. - - - - - - -These are two different and _complementary_ module systems. Use them both to write your apps. - -### Angular libraries - -Component - -Angular ships as a collection of JavaScript modules. You can think of them as library modules. - -Each Angular library name begins with the `@angular` prefix. - -You install them with the **npm** package manager and import parts of them with JavaScript `import` statements. - -
    - -For example, import Angular's `Component` decorator from the `@angular/core` library like this: +### Templates, directives, and data binding - +A template combines HTML with Angular markup that can modify the HTML elements before they are displayed. +Template *directives* provide program logic, and *binding markup* connects your application data and the document object model (DOM). -You also import NgModules from Angular _libraries_ using JavaScript import statements: +* *Event binding* lets your app respond to user input in the target environment by updating your application data. +* *Property binding* lets you interpolate values that are computed from your application data into the HTML. - +Before a view is displayed, Angular evaluates the directives and resolves the binding syntax in the template to modify the HTML elements and the DOM, according to your program data and logic. Angular supports *two-way data binding*, meaning that changes in the DOM, such as user choices, can also be reflected back into your program data. -In the example of the simple root module above, the application module needs material from within that `BrowserModule`. To access that material, add it to the `@NgModule` metadata `imports` like this. - - - -In this way you're using both the Angular and JavaScript module systems _together_. - -It's easy to confuse the two systems because they share the common vocabulary of "imports" and "exports". -Hang in there. The confusion yields to clarity with time and experience. +Your templates can also use *pipes* to improve the user experience by transforming values for display. Use pipes to display, for example, dates and currency values in a way appropriate to the user's locale. Angular provides predefined pipes for common transformations, and you can also define your own.
    - Learn more from the [NgModules](guide/ngmodules) page. + For a more detailed discussion of these concepts, see [Introduction to components](guide/architecture-components).
    -
    - -## Components - -Component - -A _component_ controls a patch of screen called a *view*. - -For example, the following views are controlled by components: - -* The app root with the navigation links. -* The list of heroes. -* The hero editor. - -You define a component's application logic—what it does to support the view—inside a class. -The class interacts with the view through an API of properties and methods. - -{@a component-code} - -For example, this `HeroListComponent` has a `heroes` property that returns an array of heroes -that it acquires from a service. -`HeroListComponent` also has a `selectHero()` method that sets a `selectedHero` property when the user clicks to choose a hero from that list. - - - -Angular creates, updates, and destroys components as the user moves through the application. -Your app can take action at each moment in this lifecycle through optional [lifecycle hooks](guide/lifecycle-hooks), like `ngOnInit()` declared above. - -
    - -## Templates - -Template - -You define a component's view with its companion **template**. A template is a form of HTML -that tells Angular how to render the component. - -A template looks like regular HTML, except for a few differences. Here is a -template for our `HeroListComponent`: - - - -Although this template uses typical HTML elements like `

    ` and `

    `, it also has some differences. Code like `*ngFor`, `{{hero.name}}`, `(click)`, `[hero]`, and `` uses Angular's [template syntax](guide/template-syntax). - -In the last line of the template, the `` tag is a custom element that represents a new component, `HeroDetailComponent`. - -The `HeroDetailComponent` is a *different* component than the `HeroListComponent` you've been reviewing. -The `HeroDetailComponent` (code not shown) presents facts about a particular hero, the -hero that the user selects from the list presented by the `HeroListComponent`. -The `HeroDetailComponent` is a **child** of the `HeroListComponent`. - -Metadata - -Notice how `` rests comfortably among native HTML elements. Custom components mix seamlessly with native HTML in the same layouts. - -


    - -## Metadata - -Metadata - -Metadata tells Angular how to process a class. - -
    - -[Looking back at the code](guide/architecture#component-code) for `HeroListComponent`, you can see that it's just a class. -There is no evidence of a framework, no "Angular" in it at all. - -In fact, `HeroListComponent` really is *just a class*. It's not a component until you *tell Angular about it*. - -To tell Angular that `HeroListComponent` is a component, attach **metadata** to the class. - -In TypeScript, you attach metadata by using a **decorator**. -Here's some metadata for `HeroListComponent`: - - - -Here is the `@Component` decorator, which identifies the class -immediately below it as a component class. - -The `@Component` decorator takes a required configuration object with the -information Angular needs to create and present the component and its view. - -Here are a few of the most useful `@Component` configuration options: - -* `selector`: CSS selector that tells Angular to create and insert an instance of this component -where it finds a `` tag in *parent* HTML. -For example, if an app's HTML contains ``, then -Angular inserts an instance of the `HeroListComponent` view between those tags. - -* `templateUrl`: module-relative address of this component's HTML template, shown [above](guide/architecture#templates). - -* `providers`: array of **dependency injection providers** for services that the component requires. -This is one way to tell Angular that the component's constructor requires a `HeroService` -so it can get the list of heroes to display. - -Metadata - -The metadata in the `@Component` tells Angular where to get the major building blocks you specify for the component. - -The template, metadata, and component together describe a view. - -Apply other metadata decorators in a similar fashion to guide Angular behavior. -`@Injectable`, `@Input`, and `@Output` are a few of the more popular decorators. - -
    - -The architectural takeaway is that you must add metadata to your code -so that Angular knows what to do. - -
    +{@a dependency-injection} -## Data binding -Without a framework, you would be responsible for pushing data values into the HTML controls and turning user responses -into actions and value updates. Writing such push/pull logic by hand is tedious, error-prone, and a nightmare to -read as any experienced jQuery programmer can attest. +## Services and dependency injection -Data Binding +For data or logic that is not associated with a specific view, and that you want to share across components, you create a *service* class. A service class definition is immediately preceded by the `@Injectable` decorator. The decorator provides the metadata that allows your service to be *injected* into client components as a dependency. -Angular supports **data binding**, -a mechanism for coordinating parts of a template with parts of a component. -Add binding markup to the template HTML to tell Angular how to connect both sides. - -As the diagram shows, there are four forms of data binding syntax. Each form has a direction — to the DOM, from the DOM, or in both directions. - -
    - -The `HeroListComponent` [example](guide/architecture#templates) template has three forms: - - - -* The `{{hero.name}}` [*interpolation*](guide/displaying-data#interpolation) -displays the component's `hero.name` property value within the `
  • ` element. - -* The `[hero]` [*property binding*](guide/template-syntax#property-binding) passes the value of `selectedHero` from -the parent `HeroListComponent` to the `hero` property of the child `HeroDetailComponent`. - -* The `(click)` [*event binding*](guide/user-input#click) calls the component's `selectHero` method when the user clicks a hero's name. - -**Two-way data binding** is an important fourth form -that combines property and event binding in a single notation, using the `ngModel` directive. -Here's an example from the `HeroDetailComponent` template: - - - -In two-way binding, a data property value flows to the input box from the component as with property binding. -The user's changes also flow back to the component, resetting the property to the latest value, -as with event binding. - -Angular processes *all* data bindings once per JavaScript event cycle, -from the root of the application component tree through all child components. - -
    - Data Binding -
    - -Data binding plays an important role in communication between a template and its component. - -
    - Parent/Child binding -
    - -Data binding is also important for communication between parent and child components. - -
    - -## Directives - -Parent child - -Angular templates are *dynamic*. When Angular renders them, it transforms the DOM -according to the instructions given by **directives**. - -A directive is a class with a `@Directive` decorator. -A component is a *directive-with-a-template*; -a `@Component` decorator is actually a `@Directive` decorator extended with template-oriented features. + *Dependency injection* (or DI) lets you keep your component classes lean and efficient. They don't fetch data from the server, validate user input, or log directly to the console; they delegate such tasks to services.
    - While **a component is technically a directive**, - components are so distinctive and central to Angular applications that this architectural overview separates components from directives. + For a more detailed discusssion, see [Introduction to services and DI](guide/architecture-services).
    -Two *other* kinds of directives exist: _structural_ and _attribute_ directives. - -They tend to appear within an element tag as attributes do, -sometimes by name but more often as the target of an assignment or a binding. - -**Structural** directives alter layout by adding, removing, and replacing elements in DOM. - -The [example template](guide/architecture#templates) uses two built-in structural directives: - - - -* [`*ngFor`](guide/displaying-data#ngFor) tells Angular to stamp out one `
  • ` per hero in the `heroes` list. -* [`*ngIf`](guide/displaying-data#ngIf) includes the `HeroDetail` component only if a selected hero exists. - -**Attribute** directives alter the appearance or behavior of an existing element. -In templates they look like regular HTML attributes, hence the name. - -The `ngModel` directive, which implements two-way data binding, is -an example of an attribute directive. `ngModel` modifies the behavior of -an existing element (typically an ``) -by setting its display value property and responding to change events. - - - -Angular has a few more directives that either alter the layout structure -(for example, [ngSwitch](guide/template-syntax#ngSwitch)) -or modify aspects of DOM elements and components -(for example, [ngStyle](guide/template-syntax#ngStyle) and [ngClass](guide/template-syntax#ngClass)). - -Of course, you can also write your own directives. Components such as -`HeroListComponent` are one kind of custom directive. - - - -
    - -## Services - -Service - -_Service_ is a broad category encompassing any value, function, or feature that your application needs. +### Routing -Almost anything can be a service. -A service is typically a class with a narrow, well-defined purpose. It should do something specific and do it well. -
    +The Angular `Router` NgModule provides a service that lets you define a navigation path among the different application states and view hierarchies in your app. It is modeled on the familiar browser navigation conventions: -Examples include: +* Enter a URL in the address bar and the browser navigates to a corresponding page. +* Click links on the page and the browser navigates to a new page. +* Click the browser's back and forward buttons and the browser navigates backward and forward through the history of pages you've seen. -* logging service -* data service -* message bus -* tax calculator -* application configuration +The router maps URL-like paths to views instead of pages. When a user performs an action, such as clicking a link, that would load a new page in the browser, the router intercepts the browser's behavior, and shows or hides view hierarchies. -There is nothing specifically _Angular_ about services. Angular has no definition of a service. -There is no service base class, and no place to register a service. +If the router determines that the current application state requires particular functionality, and the module that defines it has not been loaded, the router can _lazy-load_ the module on demand. -Yet services are fundamental to any Angular application. Components are big consumers of services. +The router interprets a link URL according to your app's view navigation rules and data state. You can navigate to new views when the user clicks a button, selects from a drop box, or in response to some other stimulus from any source. The Router logs activity in the browser's history journal, so the back and forward buttons work as well. -Here's an example of a service class that logs to the browser console: +To define navigation rules, you associate *navigation paths* with your components. A path uses a URL-like syntax that integrates your program data, in much the same way that template syntax integrates your views with your program data. You can then apply program logic to choose which views to show or to hide, in response to user input and your own access rules. - +
    -Here's a `HeroService` that uses a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to fetch heroes. -The `HeroService` depends on the `Logger` service and another `BackendService` that handles the server communication grunt work. + For a more detailed discussion, see [Routing and navigation](guide/router). - - -Services are everywhere. - -Component classes should be lean. They don't fetch data from the server, -validate user input, or log directly to the console. -They delegate such tasks to services. - -A component's job is to enable the user experience and nothing more. It mediates between the view (rendered by the template) -and the application logic (which often includes some notion of a _model_). -A good component presents properties and methods for data binding. -It delegates everything nontrivial to services. - -Angular doesn't *enforce* these principles. -It won't complain if you write a "kitchen sink" component with 3000 lines. - -Angular does help you *follow* these principles by making it easy to factor your -application logic into services and make those services available to components through *dependency injection*. +

    -## Dependency injection - -Service - -_Dependency injection_ is a way to supply a new instance of a class -with the fully-formed dependencies it requires. Most dependencies are services. -Angular uses dependency injection to provide new components with the services they need. - -
    - -Angular can tell which services a component needs by looking at the types of its constructor parameters. -For example, the constructor of your `HeroListComponent` needs a `HeroService`: - - - +## What's next -When Angular creates a component, it first asks an **injector** for -the services that the component requires. - -An injector maintains a container of service instances that it has previously created. -If a requested service instance is not in the container, the injector makes one and adds it to the container -before returning the service to Angular. -When all requested services have been resolved and returned, -Angular can call the component's constructor with those services as arguments. -This is *dependency injection*. - -The process of `HeroService` injection looks a bit like this: +You've learned the basics about the main building blocks of an Angular application. The following diagram shows how these basic pieces are related.
    - Service + overview
    -If the injector doesn't have a `HeroService`, how does it know how to make one? - -In brief, you must have previously registered a **provider** of the `HeroService` with the injector. -A provider is something that can create or return a service, typically the service class itself. - -You can register providers in modules or in components. - -In general, add providers to the [root module](guide/architecture#modules) so that -the same instance of a service is available everywhere. - - - -Alternatively, register at a component level in the `providers` property of the `@Component` metadata: - - - -Registering at a component level means you get a new instance of the -service with each new instance of that component. +* Together, a component and template define an Angular view. + * A decorator on a component class adds the metadata, including a pointer to the associated template. + * Directives and binding markup in a component's template modify views based on program data and logic. +* The dependency injector provides services to a component, such as the router service that lets you define navigation among views. - +Each of these subjects is introduced in more detail in the following pages. -Points to remember about dependency injection: +* [Modules](guide/architecture-modules) +* [Components](guide/architecture-components) + * [Templates](guide/architecture-components#templates-and-views) + * [Metadata](guide/architecture-components#component-metadata) + * [Data binding](guide/architecture-components#data-binding) + * [Directives](guide/architecture-components#directives) + * [Pipes](guide/architecture-components#pipes) +* [Services and dependency injection](guide/architecture-services) -* Dependency injection is wired into the Angular framework and used everywhere. - -* The *injector* is the main mechanism. - * An injector maintains a *container* of service instances that it created. - * An injector can create a new service instance from a *provider*. - -* A *provider* is a recipe for creating a service. - -* Register *providers* with injectors. - -
    - -## Wrap up - -You've learned the basics about the eight main building blocks of an Angular application: - -* [Modules](guide/architecture#modules) -* [Components](guide/architecture#components) -* [Templates](guide/architecture#templates) -* [Metadata](guide/architecture#metadata) -* [Data binding](guide/architecture#data-binding) -* [Directives](guide/architecture#directives) -* [Services](guide/architecture#services) -* [Dependency injection](guide/architecture#dependency-injection) - -That's a foundation for everything else in an Angular application, -and it's more than enough to get going. -But it doesn't include everything you need to know. - -Here is a brief, alphabetical list of other important Angular features and services. -Most of them are covered in this documentation (or soon will be). - -> [**Animations**](guide/animations): Animate component behavior -without deep knowledge of animation techniques or CSS with Angular's animation library. - -> **Change detection**: The change detection documentation will cover how Angular decides that a component property value has changed, -when to update the screen, and how it uses **zones** to intercept asynchronous activity and run its change detection strategies. - -> **Events**: The events documentation will cover how to use components and services to raise events with mechanisms for -publishing and subscribing to events. - -> [**Forms**](guide/forms): Support complex data entry scenarios with HTML-based validation and dirty checking. - -> [**HTTP**](guide/http): Communicate with a server to get data, save data, and invoke server-side actions with an HTTP client. - -> [**Lifecycle hooks**](guide/lifecycle-hooks): Tap into key moments in the lifetime of a component, from its creation to its destruction, -by implementing the lifecycle hook interfaces. - -> [**Pipes**](guide/pipes): Use pipes in your templates to improve the user experience by transforming values for display. Consider this `currency` pipe expression: -> -> > `price | currency:'USD':true` -> -> It displays a price of 42.33 as `$42.33`. +
    -> [**Router**](guide/router): Navigate from page to page within the client - application and never leave the browser. + Note that the code referenced on these pages is available as a . +
    -> [**Testing**](guide/testing): Run unit tests on your application parts as they interact with the Angular framework -using the _Angular Testing Platform_. +When you are familiar with these fundamental building blocks, you can explore them in more detail in the documentation. To learn about more tools and techniques that are available to help you build and deploy Angular applications, see [Next steps](guide/architecture-next-steps). + diff --git a/aio/content/guide/cheatsheet.md b/aio/content/guide/cheatsheet.md index 7bdce13171d47..f5104b885767b 100644 --- a/aio/content/guide/cheatsheet.md +++ b/aio/content/guide/cheatsheet.md @@ -311,11 +311,11 @@ so the @Directive configuration applies to components as well

    ngAfterViewInit() { ... } -

    Called after ngAfterContentInit when the component's view has been initialized. Applies to components only.

    +

    Called after ngAfterContentInit when the component's views and child views / the view that a directive is in has been initialized.

    ngAfterViewChecked() { ... } -

    Called after every check of the component's view. Applies to components only.

    +

    Called after every check of the component's views and child views / the view that a directive is in.

    ngOnDestroy() { ... } diff --git a/aio/content/guide/docs-style-guide.md b/aio/content/guide/docs-style-guide.md index 37cd6b9f5cd4d..86ccdf7ebc143 100644 --- a/aio/content/guide/docs-style-guide.md +++ b/aio/content/guide/docs-style-guide.md @@ -740,7 +740,7 @@ Adding `` to the page generates the two default lin 2. a link that downloads that sample. -Clicking the first link opens the code sample in a new browser tab in the "embedded Stackblitz" style. +Clicking the first link opens the code sample on StackBlitz in a new browser tab. You can change the appearance and behavior of the live example with attributes and classes. @@ -810,16 +810,13 @@ You can embed the Stackblitz within the guide page itself by adding the `embedde For performance reasons, the Stackblitz does not start right away. The reader sees an image instead. Clicking the image starts the sometimes-slow process of launching the embedded Stackblitz within an iframe on the page. -You usually replace the default Stackblitz image with a custom image that better represents the sample. -Store that image in the `content/images` directory in a folder with a name matching the corresponding example folder. - -Here's an embedded live example for this guide. It has a custom image created from a snapshot of the running app, overlayed with `content/images/Stackblitz/unused/click-to-run.png`. +Here's an embedded live example for this guide. ```html - + ``` - + {@a anchors} diff --git a/aio/content/images/guide/architecture/compilation-context.png b/aio/content/images/guide/architecture/compilation-context.png new file mode 100644 index 0000000000000..1fdff50285413 Binary files /dev/null and b/aio/content/images/guide/architecture/compilation-context.png differ diff --git a/aio/content/images/guide/architecture/view-hierarchy.png b/aio/content/images/guide/architecture/view-hierarchy.png new file mode 100644 index 0000000000000..1127e48f2e059 Binary files /dev/null and b/aio/content/images/guide/architecture/view-hierarchy.png differ diff --git a/aio/content/images/guide/docs-style-guide/docs-style-guide-plunker.png b/aio/content/images/guide/docs-style-guide/docs-style-guide-plunker.png deleted file mode 100644 index 22cd657e3e688..0000000000000 Binary files a/aio/content/images/guide/docs-style-guide/docs-style-guide-plunker.png and /dev/null differ diff --git a/aio/content/images/stackblitz/unused/click-to-run.png b/aio/content/images/stackblitz/unused/click-to-run.png deleted file mode 100644 index d9211dc255d65..0000000000000 Binary files a/aio/content/images/stackblitz/unused/click-to-run.png and /dev/null differ diff --git a/aio/content/navigation.json b/aio/content/navigation.json index 243524326fddf..90cabaaad6c64 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -125,13 +125,39 @@ "tooltip": "The fundamentals of Angular", "children": [ { - "url": "guide/architecture", "title": "Architecture", - "tooltip": "The basic building blocks of Angular applications." + "tooltip": "The basic building blocks of Angular applications.", + "children": [ + { + "url": "guide/architecture", + "title": "Architecture Overview", + "tooltip": "Basic building blocks of Angular applications." + }, + { + "url": "guide/architecture-modules", + "title": "Intro to Modules", + "tooltip": "About NgModules." + }, + { + "url": "guide/architecture-components", + "title": "Intro to Components", + "tooltip": "About Components, Templates, and Views." + }, + { + "url": "guide/architecture-services", + "title": "Intro to Services and DI", + "tooltip": "About services and dependency injection." + }, + { + "url": "guide/architecture-next-steps", + "title": "Next Steps", + "tooltip": "Beyond the basics." + } + ] }, { - "title": "Template & Data Binding", - "tooltip": "Template & Data Binding", + "title": "Components & Templates", + "tooltip": "Building dynamic views with data binding", "children": [ { "url": "guide/displaying-data", @@ -602,6 +628,10 @@ { "title": "中文版", "url": "https://angular.cn/" + }, + { + "title": "日本語版", + "url": "https://angular.jp/" } ] } diff --git a/aio/content/tutorial/toh-pt6.md b/aio/content/tutorial/toh-pt6.md index 36d7294e38105..a9b07e2bb564a 100644 --- a/aio/content/tutorial/toh-pt6.md +++ b/aio/content/tutorial/toh-pt6.md @@ -43,13 +43,15 @@ If you're _coding along_ with this tutorial, stay here and add the *In-memory We -Install the *In-memory Web API* package from _npm_ +Install the *In-memory Web API* package from _npm_. + +**Note:** This package's version is locked to `v0.5` to maintain compatibility with the current release of `@angular/cli`. - npm install angular-in-memory-web-api --save + npm install angular-in-memory-web-api@0.5 --save -Import the `InMemoryWebApiModule` and the `InMemoryDataService` class, +Import the `HttpClientInMemoryWebApiModule` and the `InMemoryDataService` class, which you will create in a moment. -Add the `InMemoryWebApiModule` to the `@NgModule.imports` array— +Add the `HttpClientInMemoryWebApiModule` to the `@NgModule.imports` array— _after importing the `HttpClient`_, —while configuring it with the `InMemoryDataService`. diff --git a/aio/e2e/app.e2e-spec.ts b/aio/e2e/app.e2e-spec.ts index 0f0fa2aefd08e..dba15ab86db2e 100644 --- a/aio/e2e/app.e2e-spec.ts +++ b/aio/e2e/app.e2e-spec.ts @@ -162,15 +162,12 @@ describe('site App', function() { describe('404 page', () => { it('should add or remove the "noindex" meta tag depending upon the validity of the page', () => { page.navigateTo(''); - expect(element(by.css('meta[name="googlebot"]')).isPresent()).toBeFalsy(); expect(element(by.css('meta[name="robots"]')).isPresent()).toBeFalsy(); page.navigateTo('does/not/exist'); - expect(element(by.css('meta[name="googlebot"][content="noindex"]')).isPresent()).toBeTruthy(); expect(element(by.css('meta[name="robots"][content="noindex"]')).isPresent()).toBeTruthy(); page.click(page.getTopMenuLink('features')); - expect(element(by.css('meta[name="googlebot"]')).isPresent()).toBeFalsy(); expect(element(by.css('meta[name="robots"]')).isPresent()).toBeFalsy(); }); diff --git a/aio/ngsw-manifest.json b/aio/ngsw-manifest.json index 35584cc0676ba..538b0ce425b93 100644 --- a/aio/ngsw-manifest.json +++ b/aio/ngsw-manifest.json @@ -10,7 +10,6 @@ }, "static.ignore": [ "\\.js\\.map$", - "^(?:/|\\\\)assets(?:/|\\\\)images(?:/|\\\\).*(?:/|\\\\)_unused(?:/|\\\\)", "^(?:/|\\\\)generated(?:/|\\\\)(?:docs(?:/|\\\\)(?!api(?:/|\\\\)api-list\\.json).*|images(?:/|\\\\)(?!marketing(?:/|\\\\)).*|live-examples|zips)(?:/|\\\\)" ], "static.versioned": [ @@ -19,7 +18,7 @@ "routing": { "index": "/index.html", "routes": { - "^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing|api/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?$|.*\\.[^\/.]+$)": { + "^(?!/styleguide|/docs/.|(?:/guide/(?:cli-quickstart|metadata|ngmodule|service-worker-(?:getstart|comm|configref)|learning-angular)|/news)(?:\\.html|/)?$|/testing|/api/(?:.+/[^/]+-|platform-browser/AnimationDriver|testing/|api/|animate/|(?:common/(?:NgModel|Control|MaxLengthValidator))|(?:[^/]+/)?(?:NgFor(?:$|-)|AnimationStateDeclarationMetadata|CORE_DIRECTIVES|PLATFORM_PIPES|DirectiveMetadata|HTTP_PROVIDERS))|.*/stackblitz(?:\\.html)?$|.*\\.[^\/.]+$)": { "match": "regex" } } diff --git a/aio/package.json b/aio/package.json index a9d0d17e50f51..60f7a26e22d31 100644 --- a/aio/package.json +++ b/aio/package.json @@ -6,6 +6,8 @@ "author": "Angular", "license": "MIT", "scripts": { + "preinstall": "node ../tools/yarn/check-yarn.js", + "postinstall": "node tools/cli-patches/patch.js && uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map", "aio-use-local": "node tools/ng-packages-installer overwrite . --debug --ignore-packages @angular/service-worker", "aio-use-npm": "node tools/ng-packages-installer restore .", "aio-check-local": "node tools/ng-packages-installer check .", @@ -17,10 +19,9 @@ "build-local": "yarn ~~build", "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", + "pree2e": "yarn check-env && yarn update-webdriver", "e2e": "ng e2e --no-webdriver-update", "e2e-prod": "yarn e2e --environment=dev --target=production", - "preinstall": "node ../tools/yarn/check-yarn.js", "presetup": "yarn install --frozen-lockfile && yarn ~~check-env && yarn boilerplate:remove", "setup": "yarn aio-use-npm && yarn example-use-npm", "postsetup": "yarn boilerplate:add && yarn build-ie-polyfills && yarn docs", @@ -44,7 +45,7 @@ "docs-watch": "node tools/transforms/authors-package/watchr.js", "docs-lint": "eslint --ignore-path=\"tools/transforms/.eslintignore\" tools/transforms", "docs-test": "node tools/transforms/test.js", - "deployment-config-test": "jasmine-ts tests/deployment/**/*.spec.ts", + "deployment-config-test": "jasmine-ts tests/deployment-config/unit/**/*.spec.ts", "firebase-utils-test": "jasmine-ts tools/firebase-test-utils/*.spec.ts", "tools-lint": "tslint -c \"tools/tslint.json\" \"tools/firebase-test-utils/**/*.ts\"", "tools-test": "./scripts/deploy-to-firebase.test.sh && yarn docs-test && yarn boilerplate:test && jasmine tools/ng-packages-installer/index.spec.js && yarn firebase-utils-test", @@ -57,12 +58,11 @@ "generate-zips": "node ./tools/example-zipper/generateZips", "sw-manifest": "ngu-sw-manifest --dist dist --in ngsw-manifest.json --out dist/ngsw-manifest.json", "sw-copy": "cp node_modules/@angular/service-worker/bundles/worker-basic.min.js dist/", - "postinstall": "node tools/cli-patches/patch.js && uglifyjs node_modules/lunr/lunr.js -c -m -o src/assets/js/lunr.min.js --source-map", "build-ie-polyfills": "node node_modules/webpack/bin/webpack.js -p src/ie-polyfills.js src/generated/ie-polyfills.min.js", + "update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG", "~~check-env": "node scripts/check-environment", "~~build": "ng build --target=production --environment=stable -sm", - "post~~build": "yarn sw-manifest && yarn sw-copy", - "~~update-webdriver": "webdriver-manager update --standalone false --gecko false $CHROMEDRIVER_VERSION_ARG" + "post~~build": "yarn sw-manifest && yarn sw-copy" }, "engines": { "node": ">=8.9.1 <9.0.0", diff --git a/aio/scripts/deploy-preview.sh b/aio/scripts/deploy-preview.sh index 5701f2a3ce69d..70b3e2ee8411a 100755 --- a/aio/scripts/deploy-preview.sh +++ b/aio/scripts/deploy-preview.sh @@ -30,7 +30,6 @@ readonly relevantChangedFilesCount=$(git diff --name-only $TRAVIS_COMMIT_RANGE | yarn build fi tar --create --gzip --directory "$INPUT_DIR" --file "$OUTPUT_FILE" . - yarn payload-size # Deploy to staging readonly output=$( @@ -53,4 +52,7 @@ readonly relevantChangedFilesCount=$(git diff --name-only $TRAVIS_COMMIT_RANGE | if [[ $httpCode -ne 202 ]] && [[ "$isHidden" != "true" ]]; then yarn test-pwa-score "$DEPLOYED_URL" "$MIN_PWA_SCORE" fi + + # Check the bundle sizes. + yarn payload-size ) diff --git a/aio/scripts/payload.sh b/aio/scripts/payload.sh index 189b8022e45a4..0ab4b73fb91de 100755 --- a/aio/scripts/payload.sh +++ b/aio/scripts/payload.sh @@ -8,5 +8,8 @@ readonly parentDir=$(dirname $thisDir) # Track payload size functions source ../scripts/ci/payload-size.sh +# Provide node_modules from aio +NODE_MODULES_BIN=$PROJECT_ROOT/aio/node_modules/.bin/ + trackPayloadSize "aio" "dist/*.js" true true "${thisDir}/_payload-limits.json" diff --git a/aio/scripts/test-production.sh b/aio/scripts/test-production.sh new file mode 100755 index 0000000000000..71988b136a638 --- /dev/null +++ b/aio/scripts/test-production.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set +x -eu -o pipefail + +( + readonly thisDir="$(cd $(dirname ${BASH_SOURCE[0]}); pwd)" + readonly aioDir="$(realpath $thisDir/..)" + + readonly appPtorConf="$aioDir/tests/e2e/protractor.conf.js" + readonly cfgPtorConf="$aioDir/tests/deployment-config/e2e/protractor.conf.js" + readonly minPwaScore="95" + readonly urls=( + "https://angular.io/" + "https://next.angular.io" + ) + + cd "$aioDir" + + # Install dependencies. + echo -e "\nInstalling dependencies in '$aioDir'...\n-----" + yarn install --frozen-lockfile + yarn update-webdriver + + # Run checks for all URLs. + for url in "${urls[@]}"; do + echo -e "\nChecking '$url'...\n-----" + + # Run e2e tests. + yarn protractor "$appPtorConf" --baseUrl "$url" + + # Run deployment config tests. + yarn protractor "$cfgPtorConf" --baseUrl "$url" + + # Run PWA-score tests. + yarn test-pwa-score "$url" "$minPwaScore" + done + + echo -e "\nAll checks passed!" +) diff --git a/aio/src/app/embedded/api/api-list.component.ts b/aio/src/app/embedded/api/api-list.component.ts index 2dbeda2016226..616a6de7b461b 100644 --- a/aio/src/app/embedded/api/api-list.component.ts +++ b/aio/src/app/embedded/api/api-list.component.ts @@ -81,7 +81,7 @@ export class ApiListComponent implements OnInit { this.initializeSearchCriteria(); } - // Todo: may need to debounce as the original did + // TODO: may need to debounce as the original did // although there shouldn't be any perf consequences if we don't setQuery(query: string) { this.setSearchCriteria({query: (query || '').toLowerCase().trim() }); diff --git a/aio/src/app/embedded/api/api.service.ts b/aio/src/app/embedded/api/api.service.ts index 32ee99babaccc..18f71aca84f7f 100644 --- a/aio/src/app/embedded/api/api.service.ts +++ b/aio/src/app/embedded/api/api.service.ts @@ -76,7 +76,7 @@ export class ApiService implements OnDestroy { .subscribe( sections => this.sectionsSubject.next(sections), (err: HttpErrorResponse) => { - // Todo: handle error + // TODO: handle error this.logger.error(err); throw err; // rethrow for now. } diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts index 6643f1d5e0939..f2668dae20ba8 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.spec.ts @@ -1,6 +1,6 @@ import { ComponentRef } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { Title, Meta } from '@angular/platform-browser'; +import { Meta, Title } from '@angular/platform-browser'; import { Observable } from 'rxjs/Observable'; import { of } from 'rxjs/observable/of'; @@ -415,21 +415,18 @@ describe('DocViewerComponent', () => { expect(addTitleAndTocSpy).toHaveBeenCalledTimes(4); }); - it('should remove "noindex" meta tags if the document is valid', async () => { + it('should remove the "noindex" meta tag if the document is valid', async () => { await doRender('foo', 'bar'); - expect(TestBed.get(Meta).removeTag).toHaveBeenCalledWith('name="googlebot"'); expect(TestBed.get(Meta).removeTag).toHaveBeenCalledWith('name="robots"'); }); - it('should add "noindex" meta tags if the document is 404', async () => { + it('should add the "noindex" meta tag if the document is 404', async () => { await doRender('missing', FILE_NOT_FOUND_ID); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); - it('should add "noindex" meta tags if the document fetching fails', async () => { + it('should add a "noindex" meta tag if the document fetching fails', async () => { await doRender('error', FETCHING_ERROR_ID); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); }); @@ -558,7 +555,6 @@ describe('DocViewerComponent', () => { [jasmine.any(Error)] ]); expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'foo': ${error.stack}`); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); @@ -580,7 +576,6 @@ describe('DocViewerComponent', () => { [jasmine.any(Error)] ]); expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'bar': ${error.stack}`); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); @@ -602,7 +597,6 @@ describe('DocViewerComponent', () => { [jasmine.any(Error)] ]); expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'baz': ${error.stack}`); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); @@ -624,7 +618,6 @@ describe('DocViewerComponent', () => { [jasmine.any(Error)] ]); expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'qux': ${error.stack}`); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); @@ -643,7 +636,6 @@ describe('DocViewerComponent', () => { [jasmine.any(Error)] ]); expect(logger.output.error[0][0].message).toEqual(`[DocViewer] Error preparing document 'qux': ${error}`); - expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'googlebot', content: 'noindex' }); expect(TestBed.get(Meta).addTag).toHaveBeenCalledWith({ name: 'robots', content: 'noindex' }); }); }); diff --git a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts index d1ea0efcf7429..7ddf04d8a7a5b 100644 --- a/aio/src/app/layout/doc-viewer/doc-viewer.component.ts +++ b/aio/src/app/layout/doc-viewer/doc-viewer.component.ts @@ -169,10 +169,8 @@ export class DocViewerComponent implements DoCheck, OnDestroy { */ private setNoIndex(val: boolean) { if (val) { - this.metaService.addTag({ name: 'googlebot', content: 'noindex' }); this.metaService.addTag({ name: 'robots', content: 'noindex' }); } else { - this.metaService.removeTag('name="googlebot"'); this.metaService.removeTag('name="robots"'); } } diff --git a/aio/src/app/shared/location.service.ts b/aio/src/app/shared/location.service.ts index 762a96838c06b..dadf337c749aa 100644 --- a/aio/src/app/shared/location.service.ts +++ b/aio/src/app/shared/location.service.ts @@ -36,7 +36,7 @@ export class LocationService { swUpdates.updateActivated.subscribe(() => this.swUpdateActivated = true); } - // TODO?: ignore if url-without-hash-or-search matches current location? + // TODO: ignore if url-without-hash-or-search matches current location? go(url: string|null|undefined) { if (!url) { return; } url = this.stripSlashes(url); diff --git a/aio/src/app/sw-updates/sw-updates.service.spec.ts b/aio/src/app/sw-updates/sw-updates.service.spec.ts index ff4bb5b714801..b7346c1a8519d 100644 --- a/aio/src/app/sw-updates/sw-updates.service.spec.ts +++ b/aio/src/app/sw-updates/sw-updates.service.spec.ts @@ -1,4 +1,4 @@ -import { ReflectiveInjector } from '@angular/core'; +import { ApplicationRef, ReflectiveInjector } from '@angular/core'; import { fakeAsync, tick } from '@angular/core/testing'; import { NgServiceWorker } from '@angular/service-worker'; import { Subject } from 'rxjs/Subject'; @@ -9,23 +9,26 @@ import { SwUpdatesService } from './sw-updates.service'; describe('SwUpdatesService', () => { let injector: ReflectiveInjector; + let appRef: MockApplicationRef; let service: SwUpdatesService; let sw: MockNgServiceWorker; let checkInterval: number; // Helpers // NOTE: - // Because `SwUpdatesService` uses the `debounceTime` operator, it needs to be instantiated - // inside the `fakeAsync` zone (when `fakeAsync` is used for the test). Thus, we can't run - // `setup()` in a `beforeEach()` block. We use the `run()` helper to call `setup()` inside each - // test's zone. + // Because `SwUpdatesService` uses the `debounceTime` operator, it needs to be instantiated and + // destroyed inside the `fakeAsync` zone (when `fakeAsync` is used for the test). Thus, we can't + // run `setup()`/`tearDown()` in `beforeEach()`/`afterEach()` blocks. We use the `run()` helper + // to call them inside each test's zone. const setup = () => { injector = ReflectiveInjector.resolveAndCreate([ + { provide: ApplicationRef, useClass: MockApplicationRef }, { provide: Logger, useClass: MockLogger }, { provide: NgServiceWorker, useClass: MockNgServiceWorker }, SwUpdatesService ]); + appRef = injector.get(ApplicationRef); service = injector.get(SwUpdatesService); sw = injector.get(NgServiceWorker); checkInterval = (service as any).checkInterval; @@ -42,11 +45,18 @@ describe('SwUpdatesService', () => { expect(service).toBeTruthy(); })); - it('should immediately check for updates when instantiated', run(() => { + it('should start checking for updates when instantiated (once the app stabilizes)', run(() => { + expect(sw.checkForUpdate).not.toHaveBeenCalled(); + + appRef.isStable.next(false); + expect(sw.checkForUpdate).not.toHaveBeenCalled(); + + appRef.isStable.next(true); expect(sw.checkForUpdate).toHaveBeenCalled(); })); it('should schedule a new check if there is no update available', fakeAsync(run(() => { + appRef.isStable.next(true); sw.checkForUpdate.calls.reset(); sw.$$checkForUpdateSubj.next(false); @@ -58,6 +68,7 @@ describe('SwUpdatesService', () => { }))); it('should activate new updates immediately', fakeAsync(run(() => { + appRef.isStable.next(true); sw.checkForUpdate.calls.reset(); sw.$$checkForUpdateSubj.next(true); @@ -69,6 +80,7 @@ describe('SwUpdatesService', () => { }))); it('should not pass a specific version to `NgServiceWorker.activateUpdate()`', fakeAsync(run(() => { + appRef.isStable.next(true); sw.$$checkForUpdateSubj.next(true); tick(checkInterval); @@ -76,6 +88,7 @@ describe('SwUpdatesService', () => { }))); it('should schedule a new check after activating the update', fakeAsync(run(() => { + appRef.isStable.next(true); sw.checkForUpdate.calls.reset(); sw.$$checkForUpdateSubj.next(true); @@ -103,6 +116,7 @@ describe('SwUpdatesService', () => { describe('when destroyed', () => { it('should not schedule a new check for update (after current check)', fakeAsync(run(() => { + appRef.isStable.next(true); sw.checkForUpdate.calls.reset(); service.ngOnDestroy(); @@ -113,6 +127,7 @@ describe('SwUpdatesService', () => { }))); it('should not schedule a new check for update (after activating an update)', fakeAsync(run(() => { + appRef.isStable.next(true); sw.checkForUpdate.calls.reset(); sw.$$checkForUpdateSubj.next(true); @@ -141,6 +156,10 @@ describe('SwUpdatesService', () => { }); // Mocks +class MockApplicationRef { + isStable = new Subject(); +} + class MockLogger { log = jasmine.createSpy('MockLogger.log'); } diff --git a/aio/src/app/sw-updates/sw-updates.service.ts b/aio/src/app/sw-updates/sw-updates.service.ts index 8cc3bdf4d6e1f..c642935cf13cf 100644 --- a/aio/src/app/sw-updates/sw-updates.service.ts +++ b/aio/src/app/sw-updates/sw-updates.service.ts @@ -1,14 +1,14 @@ -import { Injectable, OnDestroy } from '@angular/core'; +import { ApplicationRef, Injectable, OnDestroy } from '@angular/core'; import { NgServiceWorker } from '@angular/service-worker'; -import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; -import 'rxjs/add/observable/of'; import 'rxjs/add/operator/concat'; import 'rxjs/add/operator/debounceTime'; +import 'rxjs/add/operator/defaultIfEmpty'; +import 'rxjs/add/operator/do'; import 'rxjs/add/operator/filter'; +import 'rxjs/add/operator/first'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/startWith'; -import 'rxjs/add/operator/take'; import 'rxjs/add/operator/takeUntil'; import { Logger } from 'app/shared/logger.service'; @@ -29,18 +29,20 @@ import { Logger } from 'app/shared/logger.service'; @Injectable() export class SwUpdatesService implements OnDestroy { private checkInterval = 1000 * 60 * 60 * 6; // 6 hours - private onDestroy = new Subject(); - private checkForUpdateSubj = new Subject(); + private onDestroy = new Subject(); + private checkForUpdateSubj = new Subject(); updateActivated = this.sw.updates .takeUntil(this.onDestroy) .do(evt => this.log(`Update event: ${JSON.stringify(evt)}`)) .filter(({type}) => type === 'activation') .map(({version}) => version); - constructor(private logger: Logger, private sw: NgServiceWorker) { - this.checkForUpdateSubj - .debounceTime(this.checkInterval) - .startWith(null) + constructor(appRef: ApplicationRef, private logger: Logger, private sw: NgServiceWorker) { + const appIsStable$ = appRef.isStable.first(v => v); + const checkForUpdates$ = this.checkForUpdateSubj.debounceTime(this.checkInterval).startWith(undefined); + + appIsStable$ + .concat(checkForUpdates$) .takeUntil(this.onDestroy) .subscribe(() => this.checkForUpdate()); } @@ -60,7 +62,8 @@ export class SwUpdatesService implements OnDestroy { this.sw.checkForUpdate() // Temp workaround for https://github.com/angular/mobile-toolkit/pull/137. // TODO (gkalpak): Remove once #137 is fixed. - .concat(Observable.of(false)).take(1) + .defaultIfEmpty(false) + .first() .do(v => this.log(`Update available: ${v}`)) .subscribe(v => v ? this.activateUpdate() : this.scheduleCheckForUpdate()); } diff --git a/aio/src/assets/images/backgrounds/_unused/browser-background-template.png b/aio/src/assets/images/backgrounds/_unused/browser-background-template.png deleted file mode 100644 index 94391aa40f852..0000000000000 Binary files a/aio/src/assets/images/backgrounds/_unused/browser-background-template.png and /dev/null differ diff --git a/aio/src/assets/images/backgrounds/_unused/lon-paper.png b/aio/src/assets/images/backgrounds/_unused/lon-paper.png deleted file mode 100644 index 08280f64cd5f9..0000000000000 Binary files a/aio/src/assets/images/backgrounds/_unused/lon-paper.png and /dev/null differ diff --git a/aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.png b/aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.png deleted file mode 100644 index 9646b7ed7d02e..0000000000000 Binary files a/aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.png and /dev/null differ diff --git a/aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.svg b/aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.svg deleted file mode 100644 index f761ed5bb43fb..0000000000000 --- a/aio/src/assets/images/backgrounds/_unused/ng-pattern-lg.svg +++ /dev/null @@ -1 +0,0 @@ -ng-pattern \ No newline at end of file diff --git a/aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.png b/aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.png deleted file mode 100644 index 12b5763e9169a..0000000000000 Binary files a/aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.png and /dev/null differ diff --git a/aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.svg b/aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.svg deleted file mode 100644 index f3c49ba5db3d2..0000000000000 --- a/aio/src/assets/images/backgrounds/_unused/ng-pattern-sm.svg +++ /dev/null @@ -1 +0,0 @@ -ng-pattern-sm \ No newline at end of file diff --git a/aio/src/assets/images/backgrounds/_unused/sf-paper.png b/aio/src/assets/images/backgrounds/_unused/sf-paper.png deleted file mode 100644 index a04478d824de5..0000000000000 Binary files a/aio/src/assets/images/backgrounds/_unused/sf-paper.png and /dev/null differ diff --git a/aio/src/assets/images/backgrounds/_unused/super-hero-large.png b/aio/src/assets/images/backgrounds/_unused/super-hero-large.png deleted file mode 100644 index aebfc154aa212..0000000000000 Binary files a/aio/src/assets/images/backgrounds/_unused/super-hero-large.png and /dev/null differ diff --git a/aio/src/assets/images/favicons/_unused/apple-touch-icon-152x152.png b/aio/src/assets/images/favicons/_unused/apple-touch-icon-152x152.png deleted file mode 100644 index 2bf6efacbe8d7..0000000000000 Binary files a/aio/src/assets/images/favicons/_unused/apple-touch-icon-152x152.png and /dev/null differ diff --git a/aio/src/assets/images/favicons/_unused/apple-touch-icon-180x180.png b/aio/src/assets/images/favicons/_unused/apple-touch-icon-180x180.png deleted file mode 100644 index 1e87fd95803cc..0000000000000 Binary files a/aio/src/assets/images/favicons/_unused/apple-touch-icon-180x180.png and /dev/null differ diff --git a/aio/src/assets/images/favicons/_unused/apple-touch-icon-precomposed.png b/aio/src/assets/images/favicons/_unused/apple-touch-icon-precomposed.png deleted file mode 100644 index 2b2644ae1fe91..0000000000000 Binary files a/aio/src/assets/images/favicons/_unused/apple-touch-icon-precomposed.png and /dev/null differ diff --git a/aio/src/assets/images/favicons/_unused/apple-touch-icon.png b/aio/src/assets/images/favicons/_unused/apple-touch-icon.png deleted file mode 100644 index 1e87fd95803cc..0000000000000 Binary files a/aio/src/assets/images/favicons/_unused/apple-touch-icon.png and /dev/null differ diff --git a/aio/src/assets/images/home/_unused/joyful-development.gif b/aio/src/assets/images/home/_unused/joyful-development.gif deleted file mode 100644 index 95f1acad3d30b..0000000000000 Binary files a/aio/src/assets/images/home/_unused/joyful-development.gif and /dev/null differ diff --git a/aio/src/assets/images/home/_unused/joyful-development.jpg b/aio/src/assets/images/home/_unused/joyful-development.jpg deleted file mode 100644 index 97a6b87068a6b..0000000000000 Binary files a/aio/src/assets/images/home/_unused/joyful-development.jpg and /dev/null differ diff --git a/aio/src/assets/images/home/_unused/joyful-development.png b/aio/src/assets/images/home/_unused/joyful-development.png deleted file mode 100644 index 603d3dde13f13..0000000000000 Binary files a/aio/src/assets/images/home/_unused/joyful-development.png and /dev/null differ diff --git a/aio/src/assets/images/home/_unused/loved-by-millions.png b/aio/src/assets/images/home/_unused/loved-by-millions.png deleted file mode 100644 index 5ec8bf8b658d5..0000000000000 Binary files a/aio/src/assets/images/home/_unused/loved-by-millions.png and /dev/null differ diff --git a/aio/src/assets/images/home/_unused/responsive-framework.png b/aio/src/assets/images/home/_unused/responsive-framework.png deleted file mode 100644 index b169f4a23048a..0000000000000 Binary files a/aio/src/assets/images/home/_unused/responsive-framework.png and /dev/null differ diff --git a/aio/src/assets/images/icons/_unused/clippy.svg b/aio/src/assets/images/icons/_unused/clippy.svg deleted file mode 100644 index e1b17035905df..0000000000000 --- a/aio/src/assets/images/icons/_unused/clippy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/aio/src/assets/images/icons/_unused/favicon.ico b/aio/src/assets/images/icons/_unused/favicon.ico deleted file mode 100644 index 8081c7ceaf2be..0000000000000 Binary files a/aio/src/assets/images/icons/_unused/favicon.ico and /dev/null differ diff --git a/aio/src/assets/images/icons/_unused/ic_keyboard_arrow_down_black_24px.svg b/aio/src/assets/images/icons/_unused/ic_keyboard_arrow_down_black_24px.svg deleted file mode 100644 index b4fcfad0b3d62..0000000000000 --- a/aio/src/assets/images/icons/_unused/ic_keyboard_arrow_down_black_24px.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/aio/src/assets/images/logos/_unused/anglebrackets/anglebrackets.png b/aio/src/assets/images/logos/_unused/anglebrackets/anglebrackets.png deleted file mode 100644 index d1eec22f77ffa..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/anglebrackets/anglebrackets.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/anglebrackets/devintersection.png b/aio/src/assets/images/logos/_unused/anglebrackets/devintersection.png deleted file mode 100644 index d1ac2afa53d43..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/anglebrackets/devintersection.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-med.png b/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-med.png deleted file mode 100644 index 3fffaa36d8912..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-med.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-small.png b/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-small.png deleted file mode 100644 index ed8ee74e428cd..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-small.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-text.png b/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-text.png deleted file mode 100644 index 632aac93c45a3..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo-text.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo.png b/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo.png deleted file mode 100644 index 41c0ccebddac6..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/angularconnect/angularconnect-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/google/gdd-logo.png b/aio/src/assets/images/logos/_unused/google/gdd-logo.png deleted file mode 100644 index 981aa4536267f..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/google/gdd-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/html5/html5.png b/aio/src/assets/images/logos/_unused/html5/html5.png deleted file mode 100644 index 399b5f3731620..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/html5/html5.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/html5/html5.svg b/aio/src/assets/images/logos/_unused/html5/html5.svg deleted file mode 100644 index 14e2fc25b8af6..0000000000000 --- a/aio/src/assets/images/logos/_unused/html5/html5.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - html5 logossvg - Created with Sketch. - - - - - - - - - - - \ No newline at end of file diff --git a/aio/src/assets/images/logos/_unused/html5/html5@2x.png b/aio/src/assets/images/logos/_unused/html5/html5@2x.png deleted file mode 100644 index beadd4861e674..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/html5/html5@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/128.png b/aio/src/assets/images/logos/_unused/inverse/shield/128.png deleted file mode 100644 index e5f9bd95d04dc..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/128.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/128@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/128@2x.png deleted file mode 100644 index 687dbdeb87e48..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/128@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/16.png b/aio/src/assets/images/logos/_unused/inverse/shield/16.png deleted file mode 100644 index 09e52b8d833b2..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/16.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/16@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/16@2x.png deleted file mode 100644 index 5b0414e72efa4..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/16@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/22.png b/aio/src/assets/images/logos/_unused/inverse/shield/22.png deleted file mode 100644 index a3cc3d56e4f96..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/22.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/22@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/22@2x.png deleted file mode 100644 index 6670ba63ead26..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/22@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/22svg.svg b/aio/src/assets/images/logos/_unused/inverse/shield/22svg.svg deleted file mode 100644 index 682797233c8cc..0000000000000 --- a/aio/src/assets/images/logos/_unused/inverse/shield/22svg.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - 22svg - Created with Sketch. - - - - - - - \ No newline at end of file diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/256.png b/aio/src/assets/images/logos/_unused/inverse/shield/256.png deleted file mode 100644 index ce81d0bcf728d..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/256.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/256@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/256@2x.png deleted file mode 100644 index 7985316c598ba..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/256@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/32.png b/aio/src/assets/images/logos/_unused/inverse/shield/32.png deleted file mode 100644 index b87ffc9b684fe..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/32.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/32@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/32@2x.png deleted file mode 100644 index 501db67c84aeb..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/32@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/48.png b/aio/src/assets/images/logos/_unused/inverse/shield/48.png deleted file mode 100644 index a38f92fb3ce63..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/48.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/48@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/48@2x.png deleted file mode 100644 index 4df575d95c024..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/48@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/shield-large.png b/aio/src/assets/images/logos/_unused/inverse/shield/shield-large.png deleted file mode 100644 index 6c468adf8b6eb..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/shield-large.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/shield-large.svg b/aio/src/assets/images/logos/_unused/inverse/shield/shield-large.svg deleted file mode 100644 index 619666c456094..0000000000000 --- a/aio/src/assets/images/logos/_unused/inverse/shield/shield-large.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - shield-large - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/aio/src/assets/images/logos/_unused/inverse/shield/shield-large@2x.png b/aio/src/assets/images/logos/_unused/inverse/shield/shield-large@2x.png deleted file mode 100644 index 8ea0c02a74201..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/inverse/shield/shield-large@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/license/open-source.png b/aio/src/assets/images/logos/_unused/license/open-source.png deleted file mode 100644 index a3798cd954341..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/license/open-source.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/license/open-source@2x.png b/aio/src/assets/images/logos/_unused/license/open-source@2x.png deleted file mode 100644 index 153e1b58c3bd4..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/license/open-source@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/ng-conf/ng-conf-shield.png b/aio/src/assets/images/logos/_unused/ng-conf/ng-conf-shield.png deleted file mode 100644 index f3313eafad0c7..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/ng-conf/ng-conf-shield.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/ng-conf/ng-conf-shield@2x.png b/aio/src/assets/images/logos/_unused/ng-conf/ng-conf-shield@2x.png deleted file mode 100644 index 397689d399f33..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/ng-conf/ng-conf-shield@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/_unused/ng-europe/ng-europe-logo.png b/aio/src/assets/images/logos/_unused/ng-europe/ng-europe-logo.png deleted file mode 100644 index 838156b4ff774..0000000000000 Binary files a/aio/src/assets/images/logos/_unused/ng-europe/ng-europe-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/angular-banner-logo-grey.png b/aio/src/assets/images/logos/angular/_unused/angular-banner-logo-grey.png deleted file mode 100644 index 3804ac10641a8..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/angular-banner-logo-grey.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/angular-gde-logo.png b/aio/src/assets/images/logos/angular/_unused/angular-gde-logo.png deleted file mode 100644 index f98257e70af3d..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/angular-gde-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/angular-gde-logo@2x.png b/aio/src/assets/images/logos/angular/_unused/angular-gde-logo@2x.png deleted file mode 100644 index adf4f22e9ecac..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/angular-gde-logo@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/angular-logo-banner.png b/aio/src/assets/images/logos/angular/_unused/angular-logo-banner.png deleted file mode 100644 index 751ee050b729f..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/angular-logo-banner.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/logo-nav-small.png b/aio/src/assets/images/logos/angular/_unused/logo-nav-small.png deleted file mode 100644 index 9a6676c0e6777..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/logo-nav-small.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/logo-nav.png b/aio/src/assets/images/logos/angular/_unused/logo-nav.png deleted file mode 100644 index 8cfa4f7b418e8..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/logo-nav.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/shield-large.png b/aio/src/assets/images/logos/angular/_unused/shield-large.png deleted file mode 100644 index 1e488b1a4946d..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/shield-large.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/shield-large@2x.png b/aio/src/assets/images/logos/angular/_unused/shield-large@2x.png deleted file mode 100644 index e6377579b79b5..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/shield-large@2x.png and /dev/null differ diff --git a/aio/src/assets/images/logos/angular/_unused/shield-with-beta.png b/aio/src/assets/images/logos/angular/_unused/shield-with-beta.png deleted file mode 100644 index 31694e2b9679c..0000000000000 Binary files a/aio/src/assets/images/logos/angular/_unused/shield-with-beta.png and /dev/null differ diff --git a/aio/src/assets/images/logos/misc/_unused/github-logo.png b/aio/src/assets/images/logos/misc/_unused/github-logo.png deleted file mode 100644 index 1da3ef6d8b7c4..0000000000000 Binary files a/aio/src/assets/images/logos/misc/_unused/github-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/misc/_unused/gitter-logo.png b/aio/src/assets/images/logos/misc/_unused/gitter-logo.png deleted file mode 100644 index 6309c98b426fe..0000000000000 Binary files a/aio/src/assets/images/logos/misc/_unused/gitter-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/misc/_unused/reddit-logo.png b/aio/src/assets/images/logos/misc/_unused/reddit-logo.png deleted file mode 100644 index 003c9305dd277..0000000000000 Binary files a/aio/src/assets/images/logos/misc/_unused/reddit-logo.png and /dev/null differ diff --git a/aio/src/assets/images/logos/misc/_unused/stackoverflow-logo.png b/aio/src/assets/images/logos/misc/_unused/stackoverflow-logo.png deleted file mode 100644 index fa0a9777d1dc3..0000000000000 Binary files a/aio/src/assets/images/logos/misc/_unused/stackoverflow-logo.png and /dev/null differ diff --git a/aio/src/index.html b/aio/src/index.html index 4350c2aa1963d..ac46c11ec9a74 100644 --- a/aio/src/index.html +++ b/aio/src/index.html @@ -33,8 +33,6 @@ diff --git a/aio/src/main.ts b/aio/src/main.ts index 40593a4b231c3..7ae67fe0fbf50 100644 --- a/aio/src/main.ts +++ b/aio/src/main.ts @@ -1,5 +1,6 @@ import { enableProdMode, ApplicationRef } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import 'rxjs/add/operator/first'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; @@ -11,7 +12,7 @@ if (environment.production) { platformBrowserDynamic().bootstrapModule(AppModule).then(ref => { if (environment.production && 'serviceWorker' in (navigator as any)) { const appRef: ApplicationRef = ref.injector.get(ApplicationRef); - appRef.isStable.first().subscribe(() => { + appRef.isStable.first(v => v).subscribe(() => { (navigator as any).serviceWorker.register('/worker-basic.min.js'); }); } diff --git a/aio/src/styles/2-modules/_scrollbar.scss b/aio/src/styles/2-modules/_scrollbar.scss index 6f9eddc09e4c8..dd9b27e9bd0ac 100644 --- a/aio/src/styles/2-modules/_scrollbar.scss +++ b/aio/src/styles/2-modules/_scrollbar.scss @@ -3,7 +3,7 @@ body::-webkit-scrollbar, mat-sidenav.sidenav::-webkit-scrollbar, .mat-sidenav-co width: 6px; } -body::-webkit-scrollbar-track, mat-sidenav.sidenav::-webkit-scrollbar-trac, .mat-sidenav-content::-webkit-scrollbar-trac { +body::-webkit-scrollbar-track, mat-sidenav.sidenav::-webkit-scrollbar-track, .mat-sidenav-content::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); } @@ -17,7 +17,7 @@ body::-webkit-scrollbar-thumb, mat-sidenav.sidenav::-webkit-scrollbar-thumb, .ma width: 4px; } -.search-results::-webkit-scrollbar-track, .toc-container::-webkit-scrollbar-trac { +.search-results::-webkit-scrollbar-track, .toc-container::-webkit-scrollbar-track { -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); } diff --git a/aio/tests/deployment-config/e2e/protractor.conf.js b/aio/tests/deployment-config/e2e/protractor.conf.js new file mode 100644 index 0000000000000..72a1894efbb2b --- /dev/null +++ b/aio/tests/deployment-config/e2e/protractor.conf.js @@ -0,0 +1,52 @@ +// Protractor configuration file, see link for more information +// https://github.com/angular/protractor/blob/master/lib/config.ts + +exports.config = { + allScriptsTimeout: 11000, + specs: [ + './*.e2e-spec.ts' + ], + capabilities: { + browserName: 'chrome', + // For Travis + chromeOptions: { + binary: process.env.CHROME_BIN, + args: ['--no-sandbox'] + } + }, + directConnect: true, + framework: 'jasmine', + jasmineNodeOpts: { + showColors: true, + defaultTimeoutInterval: 30000, + print: function() {} + }, + params: { + sitemapUrls: [], + legacyUrls: [], + }, + beforeLaunch() { + const {register} = require('ts-node'); + register({}); + }, + onPrepare() { + const {SpecReporter} = require('jasmine-spec-reporter'); + const {browser} = require('protractor'); + const {loadLegacyUrls, loadRemoteSitemapUrls} = require('../shared/helpers'); + + return Promise.all([ + browser.getProcessedConfig(), + loadRemoteSitemapUrls(browser.baseUrl), + loadLegacyUrls(), + ]).then(([config, sitemapUrls, legacyUrls]) => { + if (sitemapUrls.length <= 100) { + throw new Error(`Too few sitemap URLs. (Expected: >100 | Found: ${sitemapUrls.length})`); + } else if (legacyUrls.length <= 100) { + throw new Error(`Too few legacy URLs. (Expected: >100 | Found: ${legacyUrls.length})`); + } + + Object.assign(config.params, {sitemapUrls, legacyUrls}); + jasmine.getEnv().addReporter(new SpecReporter({spec: {displayStacktrace: true}})); + }); + } +}; diff --git a/aio/tests/deployment-config/e2e/testDeployedRedirection.e2e-spec.ts b/aio/tests/deployment-config/e2e/testDeployedRedirection.e2e-spec.ts new file mode 100644 index 0000000000000..9c7b8d41d0e1f --- /dev/null +++ b/aio/tests/deployment-config/e2e/testDeployedRedirection.e2e-spec.ts @@ -0,0 +1,50 @@ +import { browser } from 'protractor'; + +describe(browser.baseUrl, () => { + const sitemapUrls = browser.params.sitemapUrls; + const legacyUrls = browser.params.legacyUrls; + const goTo = async url => { + // Go to the specified URL and then unregister the ServiceWorker + // to ensure subsequent requests are passed through to the server. + await browser.get(url); + await browser.executeAsyncScript(cb => navigator.serviceWorker + .getRegistrations() + .then(regs => Promise.all(regs.map(reg => reg.unregister()))) + .then(cb)); + }; + + beforeAll(async done => { + // Make an initial request to unregister the ServiceWorker. + await goTo(browser.baseUrl); + done(); + }); + + beforeEach(() => browser.waitForAngularEnabled(false)); + afterEach(() => browser.waitForAngularEnabled(true)); + + describe('(with sitemap URLs)', () => { + sitemapUrls.forEach((url, i) => { + it(`should not redirect '${url}' (${i + 1}/${sitemapUrls.length})`, async () => { + await goTo(url); + + const expectedUrl = browser.baseUrl + url; + const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, ''); + + expect(actualUrl).toBe(expectedUrl); + }); + }); + }); + + describe('(with legacy URLs)', () => { + legacyUrls.forEach(([fromUrl, toUrl], i) => { + it(`should redirect '${fromUrl}' to '${toUrl}' (${i + 1}/${legacyUrls.length})`, async () => { + await goTo(fromUrl); + + const expectedUrl = (/^http/.test(toUrl) ? '' : browser.baseUrl.replace(/\/$/, '')) + toUrl; + const actualUrl = (await browser.getCurrentUrl()).replace(/\?.*$/, ''); + + expect(actualUrl).toBe(expectedUrl); + }); + }); + }); +}); diff --git a/aio/tests/deployment/URLS_TO_REDIRECT.txt b/aio/tests/deployment-config/shared/URLS_TO_REDIRECT.txt similarity index 99% rename from aio/tests/deployment/URLS_TO_REDIRECT.txt rename to aio/tests/deployment-config/shared/URLS_TO_REDIRECT.txt index cdc6575d20098..c4d9ee2d4f491 100644 --- a/aio/tests/deployment/URLS_TO_REDIRECT.txt +++ b/aio/tests/deployment-config/shared/URLS_TO_REDIRECT.txt @@ -1,3 +1,5 @@ +/api/animate/AnimationBuilder /api/animations/AnimationBuilder +/api/animate/CssAnimationBuilder /api/animations/CssAnimationBuilder /api/api/core/ElementRef /api/core/ElementRef /api/common/Control-class /api/forms/FormControl /api/common/CORE_DIRECTIVES /api/common/CommonModule diff --git a/aio/tests/deployment-config/shared/helpers.ts b/aio/tests/deployment-config/shared/helpers.ts new file mode 100644 index 0000000000000..d50a6ae375a4f --- /dev/null +++ b/aio/tests/deployment-config/shared/helpers.ts @@ -0,0 +1,76 @@ +import { resolve } from 'canonical-path'; +import { load as loadJson } from 'cjson'; +import { readFileSync } from 'fs'; +import { get as httpGet } from 'http'; +import { get as httpsGet } from 'https'; + +import { FirebaseRedirector, FirebaseRedirectConfig } from '../../../tools/firebase-test-utils/FirebaseRedirector'; + + +const AIO_DIR = resolve(__dirname, '../../..'); + +export function getRedirector() { + return new FirebaseRedirector(loadRedirects()); +} + +export function loadRedirects(): FirebaseRedirectConfig[] { + const pathToFirebaseJSON = `${AIO_DIR}/firebase.json`; + const contents = loadJson(pathToFirebaseJSON); + return contents.hosting.redirects; +} + +export function loadLegacyUrls() { + const pathToLegacyUrls = `${__dirname}/URLS_TO_REDIRECT.txt`; + const urls = readFileSync(pathToLegacyUrls, 'utf8').split('\n').map(line => line.split('\t')); + return urls; +} + +export function loadLocalSitemapUrls() { + const pathToSiteMap = `${AIO_DIR}/src/generated/sitemap.xml`; + const xml = readFileSync(pathToSiteMap, 'utf8'); + return extractSitemapUrls(xml); +} + +export async function loadRemoteSitemapUrls(host: string) { + const urlToSiteMap = `${host}/generated/sitemap.xml`; + const get = /^https:/.test(host) ? httpsGet : httpGet; + + const xml = await new Promise((resolve, reject) => { + let responseText = ''; + get(urlToSiteMap, res => res + .on('data', chunk => responseText += chunk) + .on('end', () => resolve(responseText)) + .on('error', reject)); + }); + + // Currently, all sitemaps use `angular.io` as host in URLs (which is fine since we only use the + // sitemap `angular.io`). See also `aio/src/extra-files/*/robots.txt`. + return extractSitemapUrls(xml, 'https://angular.io/'); +} + +export function loadSWRoutes() { + const pathToSWManifest = `${AIO_DIR}/ngsw-manifest.json`; + const contents = loadJson(pathToSWManifest); + const routes = contents.routing.routes; + return Object.keys(routes).map(route => { + const routeConfig = routes[route]; + switch (routeConfig.match) { + case 'exact': + return (url) => url === route; + case 'prefix': + return (url) => url.startsWith(route); + case 'regex': + const regex = new RegExp(route); + return (url) => regex.test(url); + default: + throw new Error(`unknown route config: ${route} - ${routeConfig.match}`); + } + }); +} + +// Private functions +function extractSitemapUrls(xml: string, host = '%%DEPLOYMENT_HOST%%') { + const urls: string[] = []; + xml.replace(/([^<]+)<\/loc>/g, (_, loc) => urls.push(loc.replace(host, '')) as any); + return urls; +} diff --git a/aio/tests/deployment/testFirebaseRedirection.spec.ts b/aio/tests/deployment-config/unit/testFirebaseRedirection.spec.ts similarity index 85% rename from aio/tests/deployment/testFirebaseRedirection.spec.ts rename to aio/tests/deployment-config/unit/testFirebaseRedirection.spec.ts index 19b687456d242..d50f81f32db77 100644 --- a/aio/tests/deployment/testFirebaseRedirection.spec.ts +++ b/aio/tests/deployment-config/unit/testFirebaseRedirection.spec.ts @@ -1,8 +1,8 @@ -import { getRedirector, loadLegacyUrls, loadRedirects, loadSitemapUrls } from './helpers'; +import { getRedirector, loadLegacyUrls, loadLocalSitemapUrls, loadRedirects } from '../shared/helpers'; describe('firebase.json redirect config', () => { describe('with sitemap urls', () => { - loadSitemapUrls().forEach(url => { + loadLocalSitemapUrls().forEach(url => { it('should not redirect any urls in the sitemap', () => { expect(getRedirector().redirect(url)).toEqual(url); }); diff --git a/aio/tests/deployment/testServiceWorkerRoutes.spec.ts b/aio/tests/deployment-config/unit/testServiceWorkerRoutes.spec.ts similarity index 90% rename from aio/tests/deployment/testServiceWorkerRoutes.spec.ts rename to aio/tests/deployment-config/unit/testServiceWorkerRoutes.spec.ts index db119ffecb932..95cec6143d882 100644 --- a/aio/tests/deployment/testServiceWorkerRoutes.spec.ts +++ b/aio/tests/deployment-config/unit/testServiceWorkerRoutes.spec.ts @@ -1,8 +1,8 @@ -import { loadLegacyUrls, loadSitemapUrls, loadSWRoutes } from './helpers'; +import { loadLegacyUrls, loadLocalSitemapUrls, loadSWRoutes } from '../shared/helpers'; describe('service-worker routes', () => { - loadSitemapUrls().forEach(url => { + loadLocalSitemapUrls().forEach(url => { it('should process URLs in the Sitemap', () => { const routes = loadSWRoutes(); expect(routes.some(test => test(url))).toBeTruthy(url); diff --git a/aio/tests/deployment/helpers.ts b/aio/tests/deployment/helpers.ts deleted file mode 100644 index 0347597f285d4..0000000000000 --- a/aio/tests/deployment/helpers.ts +++ /dev/null @@ -1,49 +0,0 @@ -const { readFileSync } = require('fs'); -const path = require('canonical-path'); -const cjson = require('cjson'); - -import { FirebaseRedirector, FirebaseRedirectConfig } from '../../tools/firebase-test-utils/FirebaseRedirector'; - -export function getRedirector() { - return new FirebaseRedirector(loadRedirects()); -} - -export function loadRedirects(): FirebaseRedirectConfig[] { - const pathToFirebaseJSON = path.resolve(__dirname, '../../firebase.json'); - const contents = cjson.load(pathToFirebaseJSON); - return contents.hosting.redirects; -} - -export function loadSitemapUrls() { - const pathToSiteMap = path.resolve(__dirname, '../../src/generated/sitemap.xml'); - const xml = readFileSync(pathToSiteMap, 'utf8'); - const urls: string[] = []; - xml.replace(/([^<]+)<\/loc>/g, (_, loc) => urls.push(loc.replace('%%DEPLOYMENT_HOST%%', ''))); - return urls; -} - -export function loadLegacyUrls() { - const pathToLegacyUrls = path.resolve(__dirname, 'URLS_TO_REDIRECT.txt'); - const urls = readFileSync(pathToLegacyUrls, 'utf8').split('\n').map(line => line.split('\t')); - return urls; -} - -export function loadSWRoutes() { - const pathToSWManifest = path.resolve(__dirname, '../../ngsw-manifest.json'); - const contents = cjson.load(pathToSWManifest); - const routes = contents.routing.routes; - return Object.keys(routes).map(route => { - const routeConfig = routes[route]; - switch (routeConfig.match) { - case 'exact': - return (url) => url === route; - case 'prefix': - return (url) => url.startsWith(route); - case 'regex': - const regex = new RegExp(route); - return (url) => regex.test(url); - default: - throw new Error(`unknown route config: ${route} - ${routeConfig.match}`); - } - }); -} diff --git a/aio/tools/examples/run-example-e2e.js b/aio/tools/examples/run-example-e2e.js index d45a8acea240d..63320c5b6729a 100644 --- a/aio/tools/examples/run-example-e2e.js +++ b/aio/tools/examples/run-example-e2e.js @@ -147,7 +147,7 @@ function runProtractorSystemJS(prepPromise, appDir, appRunSpawnInfo, outputFile) let transpileError = false; // Start protractor. - + console.log(`\n\n=========== Running aio example tests for: ${appDir}`); const spawnInfo = spawnExt('yarn', ['protractor', PROTRACTOR_CONFIG_FILENAME, `--specs=${specFilename}`, @@ -199,6 +199,7 @@ function runProtractorAoT(appDir, outputFile) { // All protractor output is appended to the outputFile. // CLI version function runE2eTestsCLI(appDir, outputFile) { + console.log(`\n\n=========== Running aio example tests for: ${appDir}`); // `--preserve-symlinks` is needed due the symlinked `node_modules/` in each example. // `--no-webdriver-update` is needed to preserve the ChromeDriver version already installed. const args = ['e2e', '--preserve-symlinks', '--no-webdriver-update']; diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index 1cbd5ec562257..9c3c69b6372f5 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -97,9 +97,10 @@ To run tests: $ ./test.sh node # Run all angular tests on node $ ./test.sh browser # Run all angular tests in browser + $ ./test.sh browserNoRouter # Optionally run all angular tests without router in browser -$ ./test.sh tools # Run angular tooling (not framework) tests +$ ./test.sh router # Optionally run only the router tests in browser ``` You should execute the 3 test suites before submitting a PR to github. @@ -111,17 +112,6 @@ All the tests are executed on our Continuous Integration infrastructure and a PR - CircleCI fails if your code is not formatted properly, - Travis CI fails if any of the test suites described above fails. -## Update the public API tests - -If you happen to modify the public API of Angular, API golden files must be updated using: - -``` shell -$ gulp public-api:update -``` - -Note: The command `gulp public-api:enforce` fails when the API doesn't match the golden files. Make sure to rebuild -the project before trying to verify after an API change. - ## Formatting your source code Angular uses [clang-format](http://clang.llvm.org/docs/ClangFormat.html) to format the source code. If the source code diff --git a/modules/rollup-test/hello-world.html b/modules/rollup-test/hello-world.html deleted file mode 100644 index a704403f4afd7..0000000000000 --- a/modules/rollup-test/hello-world.html +++ /dev/null @@ -1 +0,0 @@ -hello remote world! diff --git a/modules/rollup-test/hello_world.js b/modules/rollup-test/hello_world.js deleted file mode 100644 index a187d9e5c3d18..0000000000000 --- a/modules/rollup-test/hello_world.js +++ /dev/null @@ -1,19 +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 - */ - -var HelloWorldComponent = ng.core.Component({ - selector: 'hello-world', - //template: 'hello world!!!' - templateUrl: 'hello-world.html' -}).Class({ - constructor: function() {} -}); - - - -ng.platformBrowserDynamic.bootstrap(HelloWorldComponent); diff --git a/modules/rollup-test/hello_world.ts b/modules/rollup-test/hello_world.ts deleted file mode 100644 index 832361d203d8a..0000000000000 --- a/modules/rollup-test/hello_world.ts +++ /dev/null @@ -1,24 +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 {Component, NgModule} from '@angular/core'; -import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; -import {BrowserModule} from '@angular/platform-browser'; - -@Component({selector: 'hello-world', template: 'hello world!!!'}) -class HelloWorldComponent { -} - -@NgModule({ - declarations: [HelloWorldComponent], - bootstrap: [HelloWorldComponent], - imports: [BrowserModule] -}) -class ExampleModule {} - -platformBrowserDynamic().bootstrapModule(ExampleModule); \ No newline at end of file diff --git a/modules/rollup-test/index-bundle.html b/modules/rollup-test/index-bundle.html deleted file mode 100644 index c9f273fbf22cf..0000000000000 --- a/modules/rollup-test/index-bundle.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - -loading.. - - - - - - - - - - - diff --git a/modules/rollup-test/index-systemjs.html b/modules/rollup-test/index-systemjs.html deleted file mode 100644 index 1a2cef3e2284a..0000000000000 --- a/modules/rollup-test/index-systemjs.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - -loading.. - - - - - - - diff --git a/modules/rollup-test/index.html b/modules/rollup-test/index.html deleted file mode 100644 index aaf5739ad1280..0000000000000 --- a/modules/rollup-test/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - -loading.. - - - - - diff --git a/modules/rollup-test/package.json b/modules/rollup-test/package.json deleted file mode 100644 index 9c85f88073900..0000000000000 --- a/modules/rollup-test/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "rollup-test", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "compile": "tsc -p tsconfig.json", - "compile-system": "tsc -p tsconfig-system.json", - "bundle": "rollup -f iife -c rollup.config.js -o dist/bundle.es2015.js", - "es5": "tsc --out dist/bundle.js --target es5 --allowJs dist/bundle.es2015.js", - "minify": "uglifyjs --screw-ie8 --compress --mangle --in-source-map ./dist/bundle.js.map --source-map ./dist/bindle.min.js.map --source-map-include-sources --output ./dist/bundle.min.js ./dist/bundle.js", - "build": "npm run compile && npm run bundle && npm run es5 && npm run minify", - "serve": "python -m SimpleHTTPServer 8000" - }, - "author": "", - "license": "ISC", - "dependencies": { - "@igorminar/md-button": "^1.1.0", - "@igorminar/platform-browser": "^0.1.0" - }, - "devDependencies": { - "rollup": "^0.26.0", - "rollup-plugin-node-resolve": "^1.5.0", - "rollup-plugin-typescript": "^0.7.3", - "rollup-plugin-uglify": "^0.3.1", - "rxjs-es": "^5.0.0-rc.4", - "typescript": "^1.9.0-dev.20160423", - "uglify-js": "^2.6.2" - }, - "repository": { - "type": "git", - "url": "https://github.com/angular/angular.git" - } -} diff --git a/modules/rollup-test/rollup.config.js b/modules/rollup-test/rollup.config.js deleted file mode 100644 index be89d0546eb0f..0000000000000 --- a/modules/rollup-test/rollup.config.js +++ /dev/null @@ -1,42 +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 typescript from 'rollup-plugin-typescript'; -// import tsc from 'typescript' - -import nodeResolve from 'rollup-plugin-node-resolve'; - -class RollupNG2 { - constructor(options){ - this.options = options; - } - resolveId(id, from){ - //console.log(id, from); - // if(id.startsWith('angular2/')){ - // return `${__dirname}/vendor/angular2/${id.split('angular2/').pop()}.js`; - // } - if(id.startsWith('rxjs/')){ - return `${__dirname}/node_modules/rxjs-es/${id.replace('rxjs/', '')}.js`; - } - } -} - - -const rollupNG2 = (config) => new RollupNG2(config); - - -export default { - entry: 'dist/hello_world.js', - //entry: 'hello_world.ts', - sourceMap: true, - plugins: [ - //typescript({typescript: tsc, target: 'es5', declaration: false}), - rollupNG2(), - nodeResolve({ jsnext: true, main: true }), - ] -}; diff --git a/modules/rollup-test/tsconfig-system.json b/modules/rollup-test/tsconfig-system.json deleted file mode 100644 index a8a7ec2e6dd4f..0000000000000 --- a/modules/rollup-test/tsconfig-system.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "declaration": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "system", - "moduleResolution": "node", - "outDir": "./dist/", - "rootDir": ".", - "sourceMap": true, - "sourceRoot": ".", - "target": "es5" - }, - "files": [ - "hello_world.ts", - "../@angular/typings/es6-collections/es6-collections.d.ts", - "../@angular/typings/es6-promise/es6-promise.d.ts" - ] -} diff --git a/modules/rollup-test/tsconfig.json b/modules/rollup-test/tsconfig.json deleted file mode 100644 index e89d4dff0b0f6..0000000000000 --- a/modules/rollup-test/tsconfig.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "declaration": true, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, - "module": "es2015", - "moduleResolution": "node", - "outDir": "./dist/", - "rootDir": ".", - "sourceMap": true, - "sourceRoot": ".", - "target": "es2015" - }, - "files": [ - "hello_world.ts" - ] -} diff --git a/modules/tsconfig.json b/modules/tsconfig.json index 0c680709d2b22..d5992a365aab0 100644 --- a/modules/tsconfig.json +++ b/modules/tsconfig.json @@ -28,8 +28,7 @@ "benchmarks/src/old", "benchmarks/src/**/index_aot.ts", "benchmarks_external", - "payload_tests", - "rollup-test" + "payload_tests" ], "angularCompilerOptions": { "skipTemplateCodegen": true diff --git a/package.json b/package.json index 33cc9fb271893..dd1cfe78d7db5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-srcs", - "version": "5.2.9", + "version": "5.2.10", "private": true, "branchPattern": "2.0.*", "description": "Angular - a web framework for modern web apps", @@ -100,7 +100,7 @@ "source-map-support": "0.4.18", "systemjs": "0.18.10", "ts-api-guardian": "^0.3.0", - "tsickle": "0.26.0", + "tsickle": "^0.27.2", "tslint": "5.7.0", "tslint-eslint-rules": "4.1.1", "tsutils": "2.20.0", diff --git a/packages/animations/browser/src/render/transition_animation_engine.ts b/packages/animations/browser/src/render/transition_animation_engine.ts index b483cbc219209..d00d0d6b6fbad 100644 --- a/packages/animations/browser/src/render/transition_animation_engine.ts +++ b/packages/animations/browser/src/render/transition_animation_engine.ts @@ -665,7 +665,16 @@ export class TransitionAnimationEngine { // code does not contain any animation code in it, but it is // just being called so that the node is marked as being inserted if (namespaceId) { - this._fetchNamespace(namespaceId).insertNode(element, parent); + const ns = this._fetchNamespace(namespaceId); + // This if-statement is a workaround for router issue #21947. + // The router sometimes hits a race condition where while a route + // is being instantiated a new navigation arrives, triggering leave + // animation of DOM that has not been fully initialized, until this + // is resolved, we need to handle the scenario when DOM is not in a + // consistent state during the animation. + if (ns) { + ns.insertNode(element, parent); + } } // only *directives and host elements are inserted before diff --git a/packages/benchpress/src/runner.ts b/packages/benchpress/src/runner.ts index e33e6e4f2cfd1..7ba28dbf8a330 100644 --- a/packages/benchpress/src/runner.ts +++ b/packages/benchpress/src/runner.ts @@ -73,7 +73,7 @@ export class Runner { // This might still create instances twice. We are creating a new injector with all the // providers. // Only WebDriverAdapter is reused. - // TODO vsavkin consider changing it when toAsyncFactory is added back or when child + // TODO(vsavkin): consider changing it when toAsyncFactory is added back or when child // injectors are handled better. const injector = Injector.create([ sampleProviders, {provide: Options.CAPABILITIES, useValue: capabilities}, diff --git a/packages/common/http/src/client.ts b/packages/common/http/src/client.ts index 896974ddb1bfd..77c82fe4dbb30 100644 --- a/packages/common/http/src/client.ts +++ b/packages/common/http/src/client.ts @@ -15,7 +15,7 @@ import {map} from 'rxjs/operator/map'; import {HttpHandler} from './backend'; import {HttpHeaders} from './headers'; -import {HttpParams, HttpParamsOptions} from './params'; +import {HttpParams} from './params'; import {HttpRequest} from './request'; import {HttpEvent, HttpResponse} from './response'; @@ -364,7 +364,7 @@ export class HttpClient { if (options.params instanceof HttpParams) { params = options.params; } else { - params = new HttpParams({ fromObject: options.params } as HttpParamsOptions); + params = new HttpParams({fromObject: options.params}); } } diff --git a/packages/common/http/src/params.ts b/packages/common/http/src/params.ts index 170f151fe645a..555f45efd2ce5 100755 --- a/packages/common/http/src/params.ts +++ b/packages/common/http/src/params.ts @@ -73,21 +73,6 @@ interface Update { op: 'a'|'d'|'s'; } -/** Options used to construct an `HttpParams` instance. */ -export interface HttpParamsOptions { - /** - * String representation of the HTTP params in URL-query-string format. Mutually exclusive with - * `fromObject`. - */ - fromString?: string; - - /** Object map of the HTTP params. Mutally exclusive with `fromString`. */ - fromObject?: {[param: string]: string | string[]}; - - /** Encoding codec used to parse and serialize the params. */ - encoder?: HttpParameterCodec; -} - /** * An HTTP request/response body that represents serialized parameters, * per the MIME type `application/x-www-form-urlencoded`. @@ -102,7 +87,19 @@ export class HttpParams { private updates: Update[]|null = null; private cloneFrom: HttpParams|null = null; - constructor(options: HttpParamsOptions = {} as HttpParamsOptions) { + constructor(options = {} as { + /** + * String representation of the HTTP params in URL-query-string format. Mutually exclusive with + * `fromObject`. + */ + fromString?: string; + + /** Object map of the HTTP params. Mutally exclusive with `fromString`. */ + fromObject?: {[param: string]: string | string[]}; + + /** Encoding codec used to parse and serialize the params. */ + encoder?: HttpParameterCodec; + }) { this.encoder = options.encoder || new HttpUrlEncodingCodec(); if (!!options.fromString) { if (!!options.fromObject) { @@ -186,7 +183,7 @@ export class HttpParams { } private clone(update: Update): HttpParams { - const clone = new HttpParams({ encoder: this.encoder } as HttpParamsOptions); + const clone = new HttpParams({encoder: this.encoder}); clone.cloneFrom = this.cloneFrom || this; clone.updates = (this.updates || []).concat([update]); return clone; diff --git a/packages/common/src/directives/ng_class.ts b/packages/common/src/directives/ng_class.ts index 9acc430fde067..1eae99666f4c1 100644 --- a/packages/common/src/directives/ng_class.ts +++ b/packages/common/src/directives/ng_class.ts @@ -49,15 +49,16 @@ export class NgClass implements DoCheck { @Input('class') set klass(v: string) { - this._applyInitialClasses(true); + this._removeClasses(this._initialClasses); this._initialClasses = typeof v === 'string' ? v.split(/\s+/) : []; - this._applyInitialClasses(false); - this._applyClasses(this._rawClass, false); + this._applyClasses(this._initialClasses); + this._applyClasses(this._rawClass); } @Input() set ngClass(v: string|string[]|Set|{[klass: string]: any}) { - this._cleanupClasses(this._rawClass); + this._removeClasses(this._rawClass); + this._applyClasses(this._initialClasses); this._iterableDiffer = null; this._keyValueDiffer = null; @@ -87,11 +88,6 @@ export class NgClass implements DoCheck { } } - private _cleanupClasses(rawClassVal: string[]|{[klass: string]: any}): void { - this._applyClasses(rawClassVal, true); - this._applyInitialClasses(false); - } - private _applyKeyValueChanges(changes: KeyValueChanges): void { changes.forEachAddedItem((record) => this._toggleClass(record.key, record.currentValue)); changes.forEachChangedItem((record) => this._toggleClass(record.key, record.currentValue)); @@ -115,19 +111,34 @@ export class NgClass implements DoCheck { changes.forEachRemovedItem((record) => this._toggleClass(record.item, false)); } - private _applyInitialClasses(isCleanup: boolean) { - this._initialClasses.forEach(klass => this._toggleClass(klass, !isCleanup)); + /** + * Applies a collection of CSS classes to the DOM element. + * + * For argument of type Set and Array CSS class names contained in those collections are always + * added. + * For argument of type Map CSS class name in the map's key is toggled based on the value (added + * for truthy and removed for falsy). + */ + private _applyClasses(rawClassVal: string[]|Set|{[klass: string]: any}) { + if (rawClassVal) { + if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) { + (rawClassVal).forEach((klass: string) => this._toggleClass(klass, true)); + } else { + Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, !!rawClassVal[klass])); + } + } } - private _applyClasses( - rawClassVal: string[]|Set|{[klass: string]: any}, isCleanup: boolean) { + /** + * Removes a collection of CSS classes from the DOM element. This is mostly useful for cleanup + * purposes. + */ + private _removeClasses(rawClassVal: string[]|Set|{[klass: string]: any}) { if (rawClassVal) { if (Array.isArray(rawClassVal) || rawClassVal instanceof Set) { - (rawClassVal).forEach((klass: string) => this._toggleClass(klass, !isCleanup)); + (rawClassVal).forEach((klass: string) => this._toggleClass(klass, false)); } else { - Object.keys(rawClassVal).forEach(klass => { - if (rawClassVal[klass] != null) this._toggleClass(klass, !isCleanup); - }); + Object.keys(rawClassVal).forEach(klass => this._toggleClass(klass, false)); } } } diff --git a/packages/common/test/directives/ng_class_spec.ts b/packages/common/test/directives/ng_class_spec.ts index 4db790b5b7652..e0efe7900bf5f 100644 --- a/packages/common/test/directives/ng_class_spec.ts +++ b/packages/common/test/directives/ng_class_spec.ts @@ -290,6 +290,17 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing'; detectChangesAndExpectClassName(`init foo`); })); + it('should co-operate with the interpolated class attribute when interpolation changes', + async(() => { + fixture = createTestComponent( + `
    `); + + detectChangesAndExpectClassName(`foo small`); + + getComponent().strExpr = 'bar'; + detectChangesAndExpectClassName(`bar small`); + })); + it('should co-operate with the class attribute and binding to it', async(() => { fixture = createTestComponent(`
    `); diff --git a/packages/compiler-cli/src/transformers/node_emitter.ts b/packages/compiler-cli/src/transformers/node_emitter.ts index ae0eb2a6633d2..dfa8996249006 100644 --- a/packages/compiler-cli/src/transformers/node_emitter.ts +++ b/packages/compiler-cli/src/transformers/node_emitter.ts @@ -55,13 +55,28 @@ export class TypeScriptNodeEmitter { // NodeEmitterVisitor type RecordedNode = (T & { __recorded: any; }) | null; +function escapeLiteral(value: string): string { + return value.replace(/(\"|\\)/g, '\\$1').replace(/(\n)|(\r)/g, function(v, n, r) { + return n ? '\\n' : '\\r'; + }); +} + function createLiteral(value: any) { if (value === null) { return ts.createNull(); } else if (value === undefined) { return ts.createIdentifier('undefined'); } else { - return ts.createLiteral(value); + const result = ts.createLiteral(value); + if (ts.isStringLiteral(result) && result.text.indexOf('\\') >= 0) { + // Hack to avoid problems cause indirectly by: + // https://github.com/Microsoft/TypeScript/issues/20192 + // This avoids the string escaping normally performed for a string relying on that + // TypeScript just emits the text raw for a numeric literal. + (result as any).kind = ts.SyntaxKind.NumericLiteral; + result.text = `"${escapeLiteral(result.text)}"`; + } + return result; } } diff --git a/packages/compiler-cli/test/transformers/node_emitter_spec.ts b/packages/compiler-cli/test/transformers/node_emitter_spec.ts index d589402cfbd9c..b728c793f076e 100644 --- a/packages/compiler-cli/test/transformers/node_emitter_spec.ts +++ b/packages/compiler-cli/test/transformers/node_emitter_spec.ts @@ -176,6 +176,14 @@ describe('TypeScriptNodeEmitter', () => { ]).toStmt()) .replace(/\s+/gm, '')) .toEqual(`({someKey:1,a:"a","b":"b","*":"star"});`); + + // Regressions #22774 + expect(emitStmt(o.literal('\\0025BC').toStmt())).toEqual('"\\\\0025BC";'); + expect(emitStmt(o.literal('"some value"').toStmt())).toEqual('"\\"some value\\"";'); + expect(emitStmt(o.literal('"some \\0025BC value"').toStmt())) + .toEqual('"\\"some \\\\0025BC value\\"";'); + expect(emitStmt(o.literal('\n \\0025BC \n ').toStmt())).toEqual('"\\n \\\\0025BC \\n ";'); + expect(emitStmt(o.literal('\r \\0025BC \r ').toStmt())).toEqual('"\\r \\\\0025BC \\r ";'); }); it('should support blank literals', () => { diff --git a/packages/compiler/src/i18n/serializers/xml_helper.ts b/packages/compiler/src/i18n/serializers/xml_helper.ts index 805592f7f55a3..27a8d10c5a8f7 100644 --- a/packages/compiler/src/i18n/serializers/xml_helper.ts +++ b/packages/compiler/src/i18n/serializers/xml_helper.ts @@ -54,7 +54,7 @@ export class Declaration implements Node { constructor(unescapedAttrs: {[k: string]: string}) { Object.keys(unescapedAttrs).forEach((k: string) => { - this.attrs[k] = _escapeXml(unescapedAttrs[k]); + this.attrs[k] = escapeXml(unescapedAttrs[k]); }); } @@ -74,7 +74,7 @@ export class Tag implements Node { public name: string, unescapedAttrs: {[k: string]: string} = {}, public children: Node[] = []) { Object.keys(unescapedAttrs).forEach((k: string) => { - this.attrs[k] = _escapeXml(unescapedAttrs[k]); + this.attrs[k] = escapeXml(unescapedAttrs[k]); }); } @@ -83,7 +83,7 @@ export class Tag implements Node { export class Text implements Node { value: string; - constructor(unescapedValue: string) { this.value = _escapeXml(unescapedValue); } + constructor(unescapedValue: string) { this.value = escapeXml(unescapedValue); } visit(visitor: IVisitor): any { return visitor.visitText(this); } } @@ -100,7 +100,8 @@ const _ESCAPED_CHARS: [RegExp, string][] = [ [/>/g, '>'], ]; -function _escapeXml(text: string): string { +// Escape `_ESCAPED_CHARS` characters in the given text with encoded entities +export function escapeXml(text: string): string { return _ESCAPED_CHARS.reduce( (text: string, entry: [RegExp, string]) => text.replace(entry[0], entry[1]), text); } diff --git a/packages/compiler/src/i18n/translation_bundle.ts b/packages/compiler/src/i18n/translation_bundle.ts index c1c5daf68cf7a..242ff966d88ae 100644 --- a/packages/compiler/src/i18n/translation_bundle.ts +++ b/packages/compiler/src/i18n/translation_bundle.ts @@ -14,6 +14,7 @@ import {Console} from '../util'; import * as i18n from './i18n_ast'; import {I18nError} from './parse_util'; import {PlaceholderMapper, Serializer} from './serializers/serializer'; +import {escapeXml} from './serializers/xml_helper'; /** @@ -88,7 +89,11 @@ class I18nToHtmlVisitor implements i18n.Visitor { }; } - visitText(text: i18n.Text, context?: any): string { return text.value; } + visitText(text: i18n.Text, context?: any): string { + // `convert()` uses an `HtmlParser` to return `html.Node`s + // we should then make sure that any special characters are escaped + return escapeXml(text.value); + } visitContainer(container: i18n.Container, context?: any): any { return container.children.map(n => n.visit(this)).join(''); diff --git a/packages/compiler/test/i18n/integration_common.ts b/packages/compiler/test/i18n/integration_common.ts index 400365f9ad82d..11027cbf75886 100644 --- a/packages/compiler/test/i18n/integration_common.ts +++ b/packages/compiler/test/i18n/integration_common.ts @@ -47,7 +47,8 @@ export function validateHtml( expectHtml(el, '#i18n-3b') .toBe( '

    avec des espaces réservés

    '); - expectHtml(el, '#i18n-4').toBe('

    '); + expectHtml(el, '#i18n-4') + .toBe('

    '); expectHtml(el, '#i18n-5').toBe('

    '); expectHtml(el, '#i18n-6').toBe('

    '); @@ -117,7 +118,7 @@ export const HTML = `
    with
    nested
    placeholders
    -

    +

    diff --git a/packages/compiler/test/i18n/integration_xliff2_spec.ts b/packages/compiler/test/i18n/integration_xliff2_spec.ts index 657f8141f5bb6..e7f82d09ccd6d 100644 --- a/packages/compiler/test/i18n/integration_xliff2_spec.ts +++ b/packages/compiler/test/i18n/integration_xliff2_spec.ts @@ -95,6 +95,12 @@ const XLIFF2_TOMERGE = ` sur des balises non traductibles + + + <b>bold</b> + <b>gras</b> + + on translatable node @@ -267,6 +273,14 @@ const XLIFF2_EXTRACTED = ` on not translatable node + + + file.ts:14 + + + <b>bold</b> + + file.ts:15 diff --git a/packages/compiler/test/i18n/integration_xliff_spec.ts b/packages/compiler/test/i18n/integration_xliff_spec.ts index ba347cc1bb756..5c8daf498440d 100644 --- a/packages/compiler/test/i18n/integration_xliff_spec.ts +++ b/packages/compiler/test/i18n/integration_xliff_spec.ts @@ -85,6 +85,10 @@ const XLIFF_TOMERGE = ` on not translatable node sur des balises non traductibles + + <b>bold</b> + <b>gras</b> + on translatable node sur des balises traductibles @@ -215,6 +219,13 @@ const XLIFF_EXTRACTED = ` 14 + + <b>bold</b> + + file.ts + 14 + + on translatable node diff --git a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts index 33f9543da4b62..51b074700f6c1 100644 --- a/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts +++ b/packages/compiler/test/i18n/integration_xmb_xtb_spec.ts @@ -63,6 +63,7 @@ const XTB = ` avec des espaces réservés <div>avec <div>des espaces réservés</div> imbriqués</div> sur des balises non traductibles + <b>gras</b> sur des balises traductibles {VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {beaucoup}} @@ -93,6 +94,7 @@ const XMB = `file.ts:3i18n attribu file.ts:9file.ts:10<i>with placeholders</i> file.ts:11<div>with <div>nested</div> placeholders</div> file.ts:14on not translatable node + file.ts:14<b>bold</b> file.ts:15on translatable node file.ts:20file.ts:37{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<b>many</b>} } file.ts:22,24 diff --git a/packages/compiler/test/i18n/translation_bundle_spec.ts b/packages/compiler/test/i18n/translation_bundle_spec.ts index 6da0e99456960..cac903ecca40e 100644 --- a/packages/compiler/test/i18n/translation_bundle_spec.ts +++ b/packages/compiler/test/i18n/translation_bundle_spec.ts @@ -10,8 +10,10 @@ import {MissingTranslationStrategy} from '@angular/core'; import * as i18n from '../../src/i18n/i18n_ast'; import {TranslationBundle} from '../../src/i18n/translation_bundle'; +import * as html from '../../src/ml_parser/ast'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../src/parse_util'; import {serializeNodes} from '../ml_parser/ast_serializer_spec'; + import {_extractMessages} from './i18n_parser_spec'; { @@ -22,13 +24,24 @@ import {_extractMessages} from './i18n_parser_spec'; const span = new ParseSourceSpan(startLocation, endLocation); const srcNode = new i18n.Text('src', span); - it('should translate a plain message', () => { + it('should translate a plain text', () => { const msgMap = {foo: [new i18n.Text('bar', null !)]}; const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); expect(serializeNodes(tb.get(msg))).toEqual(['bar']); }); + it('should translate html-like plain text', () => { + const msgMap = {foo: [new i18n.Text('

    bar

    ', null !)]}; + const tb = new TranslationBundle(msgMap, null, (_) => 'foo'); + const msg = new i18n.Message([srcNode], {}, {}, 'm', 'd', 'i'); + const nodes = tb.get(msg); + expect(nodes.length).toEqual(1); + const textNode: html.Text = nodes[0] as any; + expect(textNode instanceof html.Text).toEqual(true); + expect(textNode.value).toBe('

    bar

    '); + }); + it('should translate a message with placeholder', () => { const msgMap = { foo: [ diff --git a/packages/core/src/change_detection/differs/default_iterable_differ.ts b/packages/core/src/change_detection/differs/default_iterable_differ.ts index dc2c08ec18cbb..edbf0ac3d8a5d 100644 --- a/packages/core/src/change_detection/differs/default_iterable_differ.ts +++ b/packages/core/src/change_detection/differs/default_iterable_differ.ts @@ -248,7 +248,7 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan this._removalsHead = this._removalsTail = null; this._identityChangesHead = this._identityChangesTail = null; - // todo(vicb) when assert gets supported + // TODO(vicb): when assert gets supported // assert(!this.isDirty); } } @@ -420,11 +420,11 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan this._insertAfter(record, prevRecord, index); if (this._additionsTail === null) { - // todo(vicb) + // TODO(vicb): // assert(this._additionsHead === null); this._additionsTail = this._additionsHead = record; } else { - // todo(vicb) + // TODO(vicb): // assert(_additionsTail._nextAdded === null); // assert(record._nextAdded === null); this._additionsTail = this._additionsTail._nextAdded = record; @@ -436,14 +436,14 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan _insertAfter( record: IterableChangeRecord_, prevRecord: IterableChangeRecord_|null, index: number): IterableChangeRecord_ { - // todo(vicb) + // TODO(vicb): // assert(record != prevRecord); // assert(record._next === null); // assert(record._prev === null); const next: IterableChangeRecord_|null = prevRecord === null ? this._itHead : prevRecord._next; - // todo(vicb) + // TODO(vicb): // assert(next != record); // assert(prevRecord != record); record._next = next; @@ -482,7 +482,7 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan const prev = record._prev; const next = record._next; - // todo(vicb) + // TODO(vicb): // assert((record._prev = null) === null); // assert((record._next = null) === null); @@ -502,7 +502,7 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan /** @internal */ _addToMoves(record: IterableChangeRecord_, toIndex: number): IterableChangeRecord_ { - // todo(vicb) + // TODO(vicb): // assert(record._nextMoved === null); if (record.previousIndex === toIndex) { @@ -510,11 +510,11 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan } if (this._movesTail === null) { - // todo(vicb) + // TODO(vicb): // assert(_movesHead === null); this._movesTail = this._movesHead = record; } else { - // todo(vicb) + // TODO(vicb): // assert(_movesTail._nextMoved === null); this._movesTail = this._movesTail._nextMoved = record; } @@ -531,12 +531,12 @@ export class DefaultIterableDiffer implements IterableDiffer, IterableChan record._nextRemoved = null; if (this._removalsTail === null) { - // todo(vicb) + // TODO(vicb): // assert(_removalsHead === null); this._removalsTail = this._removalsHead = record; record._prevRemoved = null; } else { - // todo(vicb) + // TODO(vicb): // assert(_removalsTail._nextRemoved === null); // assert(record._nextRemoved === null); record._prevRemoved = this._removalsTail; @@ -607,7 +607,7 @@ class _DuplicateItemRecordList { record._nextDup = null; record._prevDup = null; } else { - // todo(vicb) + // TODO(vicb): // assert(record.item == _head.item || // record.item is num && record.item.isNaN && _head.item is num && _head.item.isNaN); this._tail !._nextDup = record; @@ -636,7 +636,7 @@ class _DuplicateItemRecordList { * Returns whether the list of duplicates is empty. */ remove(record: IterableChangeRecord_): boolean { - // todo(vicb) + // TODO(vicb): // assert(() { // // verify that the record being removed is in the list. // for (IterableChangeRecord_ cursor = _head; cursor != null; cursor = cursor._nextDup) { diff --git a/packages/core/src/metadata/directives.ts b/packages/core/src/metadata/directives.ts index f2dc5d9e13cc4..c95d803dae424 100644 --- a/packages/core/src/metadata/directives.ts +++ b/packages/core/src/metadata/directives.ts @@ -148,7 +148,7 @@ export interface Directive { * @Component({ * selector: 'app', * template: ` - * + * * ` * }) * class App {} @@ -834,7 +834,7 @@ export interface InputDecorator { * @Component({ * selector: 'app', * template: ` - * + * * ` * }) * diff --git a/packages/core/src/util/lang.ts b/packages/core/src/util/lang.ts index 1d577314f75d1..2bfa62ccfd5ec 100644 --- a/packages/core/src/util/lang.ts +++ b/packages/core/src/util/lang.ts @@ -21,6 +21,6 @@ export function isPromise(obj: any): obj is Promise { * Determine if the argument is an Observable */ export function isObservable(obj: any | Observable): obj is Observable { - // TODO use Symbol.observable when https://github.com/ReactiveX/rxjs/issues/2415 will be resolved + // TODO: use Symbol.observable when https://github.com/ReactiveX/rxjs/issues/2415 will be resolved return !!obj && typeof obj.subscribe === 'function'; } diff --git a/packages/core/test/change_detection/differs/default_iterable_differ_spec.ts b/packages/core/test/change_detection/differs/default_iterable_differ_spec.ts index cdb62e7990ab1..e1d8f09063f3f 100644 --- a/packages/core/test/change_detection/differs/default_iterable_differ_spec.ts +++ b/packages/core/test/change_detection/differs/default_iterable_differ_spec.ts @@ -23,7 +23,7 @@ class ComplexItem { toString() { return `{id: ${this.id}, color: ${this.color}}`; } } -// todo(vicb): UnmodifiableListView / frozen object when implemented +// TODO(vicb): UnmodifiableListView / frozen object when implemented { describe('iterable differ', function() { describe('DefaultIterableDiffer', function() { diff --git a/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts b/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts index cff3397cb5322..bba585f5b069e 100644 --- a/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts +++ b/packages/core/test/change_detection/differs/default_keyvalue_differ_spec.ts @@ -11,7 +11,7 @@ import {DefaultKeyValueDiffer, DefaultKeyValueDifferFactory} from '@angular/core import {kvChangesAsString, testChangesAsString} from '../../change_detection/util'; -// todo(vicb): Update the code & tests for object equality +// TODO(vicb): Update the code & tests for object equality { describe('keyvalue differ', function() { describe('DefaultKeyValueDiffer', function() { diff --git a/packages/forms/src/directives/shared.ts b/packages/forms/src/directives/shared.ts index c261a9a44d989..ab02409fab55e 100644 --- a/packages/forms/src/directives/shared.ts +++ b/packages/forms/src/directives/shared.ts @@ -185,9 +185,13 @@ export function selectValueAccessor( dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor|null { if (!valueAccessors) return null; + if (!Array.isArray(valueAccessors)) + _throwError(dir, 'Value accessor was not provided as an array for form control with'); + let defaultAccessor: ControlValueAccessor|undefined = undefined; let builtinAccessor: ControlValueAccessor|undefined = undefined; let customAccessor: ControlValueAccessor|undefined = undefined; + valueAccessors.forEach((v: ControlValueAccessor) => { if (v.constructor === DefaultValueAccessor) { defaultAccessor = v; diff --git a/packages/forms/test/directives_spec.ts b/packages/forms/test/directives_spec.ts index dd81a8b4f07b7..81033d237c4b6 100644 --- a/packages/forms/test/directives_spec.ts +++ b/packages/forms/test/directives_spec.ts @@ -55,6 +55,12 @@ function asyncValidator(expected: any, timeout = 0) { it('should throw when given an empty array', () => { expect(() => selectValueAccessor(dir, [])).toThrowError(); }); + it('should throw when accessor is not provided as array', () => { + expect(() => selectValueAccessor(dir, {} as any[])) + .toThrowError( + `Value accessor was not provided as an array for form control with unspecified name attribute`); + }); + it('should return the default value accessor when no other provided', () => { expect(selectValueAccessor(dir, [defaultAccessor])).toEqual(defaultAccessor); }); diff --git a/packages/forms/test/spies.ts b/packages/forms/test/spies.ts index e956a4821c474..1f6b3940c7558 100644 --- a/packages/forms/test/spies.ts +++ b/packages/forms/test/spies.ts @@ -16,6 +16,6 @@ export class SpyChangeDetectorRef extends SpyObject { } } -export class SpyNgControl extends SpyObject {} +export class SpyNgControl extends SpyObject { path = []; } export class SpyValueAccessor extends SpyObject { writeValue: any; } diff --git a/packages/http/testing/src/mock_backend.ts b/packages/http/testing/src/mock_backend.ts index 109865bd4f601..4450f4ff2a3d9 100644 --- a/packages/http/testing/src/mock_backend.ts +++ b/packages/http/testing/src/mock_backend.ts @@ -20,7 +20,7 @@ import {take} from 'rxjs/operator/take'; * @deprecated use @angular/common/http instead */ export class MockConnection implements Connection { - // TODO Name `readyState` should change to be more generic, and states could be made to be more + // TODO: Name `readyState` should change to be more generic, and states could be made to be more // descriptive than ResourceLoader states. /** * Describes the state of the connection, based on `XMLHttpRequest.readyState`, but with diff --git a/packages/language-service/src/types.ts b/packages/language-service/src/types.ts index 2666d3bfa31f9..ccb3447fe1004 100644 --- a/packages/language-service/src/types.ts +++ b/packages/language-service/src/types.ts @@ -39,7 +39,7 @@ export interface TemplateSource { /** * The version of the source. As files are modified the version should change. That is, if the - * `LanguageService` requesting template infomration for a source file and that file has changed + * `LanguageService` requesting template information for a source file and that file has changed * since the last time the host was asked for the file then this version string should be * different. No assumptions are made about the format of this string. * @@ -324,7 +324,7 @@ export interface HoverTextSection { } /** - * Hover infomration for a symbol at the hover location. + * Hover information for a symbol at the hover location. */ export interface Hover { /** diff --git a/packages/platform-webworker/src/web_workers/shared/post_message_bus.ts b/packages/platform-webworker/src/web_workers/shared/post_message_bus.ts index f7c11f5bc8c27..4de44d4301e88 100644 --- a/packages/platform-webworker/src/web_workers/shared/post_message_bus.ts +++ b/packages/platform-webworker/src/web_workers/shared/post_message_bus.ts @@ -12,7 +12,7 @@ import {MessageBus, MessageBusSink, MessageBusSource} from './message_bus'; -// TODO(jteplitz602) Replace this with the definition in lib.webworker.d.ts(#3492) +// TODO(jteplitz602): Replace this with the definition in lib.webworker.d.ts(#3492) export interface PostMessageTarget { postMessage: (message: any, transfer?: [ArrayBuffer]) => void; } diff --git a/packages/platform-webworker/src/worker_app.ts b/packages/platform-webworker/src/worker_app.ts index 7476a6547590f..07bf264d647f7 100644 --- a/packages/platform-webworker/src/worker_app.ts +++ b/packages/platform-webworker/src/worker_app.ts @@ -33,7 +33,7 @@ export function errorHandler(): ErrorHandler { } -// TODO(jteplitz602) remove this and compile with lib.webworker.d.ts (#3492) +// TODO(jteplitz602): remove this and compile with lib.webworker.d.ts (#3492) const _postMessage = { postMessage: (message: any, transferrables?: [ArrayBuffer]) => { (postMessage)(message, transferrables); diff --git a/packages/router/test/create_router_state.spec.ts b/packages/router/test/create_router_state.spec.ts index 2c1f6428a00e6..7404460454422 100644 --- a/packages/router/test/create_router_state.spec.ts +++ b/packages/router/test/create_router_state.spec.ts @@ -21,7 +21,7 @@ describe('create router state', () => { const emptyState = () => createEmptyState( new (UrlTree as any)(new UrlSegmentGroup([], {}), {}, null !), RootComponent); - it('should work create new state', () => { + it('should create new state', () => { const state = createRouterState( reuseStrategy, createState( [ diff --git a/packages/service-worker/worker/src/data.ts b/packages/service-worker/worker/src/data.ts index 5608611888cb1..e1d98f2951af7 100644 --- a/packages/service-worker/worker/src/data.ts +++ b/packages/service-worker/worker/src/data.ts @@ -99,25 +99,7 @@ class LruList { } const url = this.state.tail; - - // Special case if this is the last node. - if (this.state.head === this.state.tail) { - // When removing the last node, both head and tail pointers become null. - this.state.head = null; - this.state.tail = null; - } else { - // Normal node removal. All that needs to be done is to clear the next pointer - // of the previous node and make it the new tail. - const block = this.state.map[url] !; - const previous = this.state.map[block.previous !] !; - this.state.tail = previous.url; - previous.next = block.next; - } - - // In any case, this URL is no longer tracked, so remove it from the count and the - // map of tracked URLs. - delete this.state.map[url]; - this.state.count--; + this.remove(url); // This URL has been successfully evicted. return url; @@ -145,6 +127,8 @@ class LruList { const next = this.state.map[node.next !] !; next.previous = null; this.state.head = next.url; + node.next = null; + delete this.state.map[url]; this.state.count--; return true; } @@ -167,6 +151,9 @@ class LruList { this.state.tail = node.previous !; } + node.next = null; + node.previous = null; + delete this.state.map[url]; // Count the removal. this.state.count--; diff --git a/packages/service-worker/worker/src/driver.ts b/packages/service-worker/worker/src/driver.ts index b4e046539f8b4..bc21c4564c495 100644 --- a/packages/service-worker/worker/src/driver.ts +++ b/packages/service-worker/worker/src/driver.ts @@ -31,8 +31,8 @@ const IDLE_THRESHOLD = 5000; const SUPPORTED_CONFIG_VERSION = 1; const NOTIFICATION_OPTION_NAMES = [ - 'actions', 'body', 'dir', 'icon', 'lang', 'renotify', 'requireInteraction', 'tag', 'vibrate', - 'data' + 'actions', 'badge', 'body', 'dir', 'icon', 'lang', 'renotify', 'requireInteraction', 'tag', + 'vibrate', 'data' ]; interface LatestEntry { @@ -94,6 +94,12 @@ export class Driver implements Debuggable, UpdateSource { */ private scheduledNavUpdateCheck: boolean = false; + /** + * Keep track of whether we have logged an invalid `only-if-cached` request. + * (See `.onFetch()` for details.) + */ + private loggedInvalidOnlyIfCachedRequest: boolean = false; + /** * A scheduler which manages a queue of tasks that need to be executed when the SW is * not doing any other work (not processing any other requests). @@ -156,12 +162,13 @@ export class Driver implements Debuggable, UpdateSource { * asynchronous execution that eventually resolves for respondWith() and waitUntil(). */ private onFetch(event: FetchEvent): void { + const req = event.request; + // The only thing that is served unconditionally is the debug page. - if (this.adapter.parseUrl(event.request.url, this.scope.registration.scope).path === - '/ngsw/state') { + if (this.adapter.parseUrl(req.url, this.scope.registration.scope).path === '/ngsw/state') { // Allow the debugger to handle the request, but don't affect SW state in any // other way. - event.respondWith(this.debugger.handleFetch(event.request)); + event.respondWith(this.debugger.handleFetch(req)); return; } @@ -177,6 +184,24 @@ export class Driver implements Debuggable, UpdateSource { return; } + // When opening DevTools in Chrome, a request is made for the current URL (and possibly related + // resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request + // will eventually fail, because `only-if-cached` is only allowed to be used with + // `mode: 'same-origin'`. + // This is likely a bug in Chrome DevTools. Avoid handling such requests. + // (See also https://github.com/angular/angular/issues/22362.) + // TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools). + if ((req.cache as string) === 'only-if-cached' && req.mode !== 'same-origin') { + // Log the incident only the first time it happens, to avoid spamming the logs. + if (!this.loggedInvalidOnlyIfCachedRequest) { + this.loggedInvalidOnlyIfCachedRequest = true; + this.debugger.log( + `Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`, + `Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`); + } + return; + } + // Past this point, the SW commits to handling the request itself. This could still // fail (and result in `state` being set to `SAFE_MODE`), but even in that case the // SW will still deliver a response. @@ -607,16 +632,22 @@ export class Driver implements Debuggable, UpdateSource { /** * Retrieve a copy of the latest manifest from the server. + * Return `null` if `ignoreOfflineError` is true (default: false) and the server or client are + * offline (detected as response status 504). */ - private async fetchLatestManifest(): Promise { + private async fetchLatestManifest(ignoreOfflineError?: false): Promise; + private async fetchLatestManifest(ignoreOfflineError: true): Promise; + private async fetchLatestManifest(ignoreOfflineError = false): Promise { const res = await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random())); if (!res.ok) { if (res.status === 404) { await this.deleteAllCaches(); await this.scope.registration.unregister(); + } else if (res.status === 504 && ignoreOfflineError) { + return null; } - throw new Error('Manifest fetch failed!'); + throw new Error(`Manifest fetch failed! (status: ${res.status})`); } this.lastUpdateCheck = this.adapter.time; return res.json(); @@ -728,7 +759,15 @@ export class Driver implements Debuggable, UpdateSource { async checkForUpdate(): Promise { let hash: string = '(unknown)'; try { - const manifest = await this.fetchLatestManifest(); + const manifest = await this.fetchLatestManifest(true); + + if (manifest === null) { + // Client or server offline. Unable to check for updates at this time. + // Continue to service clients (existing and new). + this.debugger.log('Check for update aborted. (Client or server offline.)'); + return false; + } + hash = hashManifest(manifest); // Check whether this is really an update. @@ -972,4 +1011,4 @@ function errorToString(error: any): string { } else { return `${error}`; } -} \ No newline at end of file +} diff --git a/packages/service-worker/worker/test/happy_spec.ts b/packages/service-worker/worker/test/happy_spec.ts index 994e6f326dfd2..caacc9a839dd3 100644 --- a/packages/service-worker/worker/test/happy_spec.ts +++ b/packages/service-worker/worker/test/happy_spec.ts @@ -170,7 +170,11 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); let driver: Driver; beforeEach(() => { - server.clearRequests(); + server.reset(); + serverUpdate.reset(); + server404.reset(); + brokenServer.reset(); + scope = new SwTestHarnessBuilder().withServerState(server).build(); driver = new Driver(scope, scope, new CacheDatabase(scope, scope)); }); @@ -275,39 +279,6 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); serverUpdate.assertNoOtherRequests(); }); - async_it('updates to new content when requested', async() => { - expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); - await driver.initialized; - - const client = scope.clients.getMock('default') !; - expect(client.messages).toEqual([]); - - scope.updateServerState(serverUpdate); - expect(await driver.checkForUpdate()).toEqual(true); - serverUpdate.assertSawRequestFor('ngsw.json'); - serverUpdate.assertSawRequestFor('/foo.txt'); - serverUpdate.assertSawRequestFor('/redirected.txt'); - serverUpdate.assertNoOtherRequests(); - - expect(client.messages).toEqual([{ - type: 'UPDATE_AVAILABLE', - current: {hash: manifestHash, appData: {version: 'original'}}, - available: {hash: manifestUpdateHash, appData: {version: 'update'}}, - }]); - - // Default client is still on the old version of the app. - expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); - - // Sending a new client id should result in the updated version being returned. - expect(await makeRequest(scope, '/foo.txt', 'new')).toEqual('this is foo v2'); - - // Of course, the old version should still work. - expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); - - expect(await makeRequest(scope, '/bar.txt')).toEqual('this is bar'); - serverUpdate.assertNoOtherRequests(); - }); - async_it('updates a specific client to new content on request', async() => { expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); await driver.initialized; @@ -403,8 +374,8 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); serverUpdate.clearRequests(); scope = new SwTestHarnessBuilder() - .withServerState(serverUpdate) .withCacheState(scope.caches.dehydrate()) + .withServerState(serverUpdate) .build(); driver = new Driver(scope, scope, new CacheDatabase(scope, scope)); @@ -478,6 +449,10 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); await driver.initialized; serverUpdate.assertNoOtherRequests(); + let keys = await scope.caches.keys(); + let hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:${manifestHash}:`)); + expect(hasOriginalCaches).toEqual(true); + scope.clients.remove('default'); scope.advance(12000); @@ -487,10 +462,9 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); driver = new Driver(scope, scope, new CacheDatabase(scope, scope)); expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo v2'); - const oldManifestHash = sha1(JSON.stringify(manifest)); - const keys = await scope.caches.keys(); - const hasOldCaches = keys.some(name => name.startsWith(oldManifestHash + ':')); - expect(hasOldCaches).toEqual(false); + keys = await scope.caches.keys(); + hasOriginalCaches = keys.some(name => name.startsWith(`ngsw:${manifestHash}:`)); + expect(hasOriginalCaches).toEqual(false); }); async_it('shows notifications for push notifications', async() => { @@ -542,6 +516,17 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); expect(await scope.caches.keys()).toEqual([]); }); + async_it('does not unregister or change state when offline (i.e. manifest 504s)', async() => { + expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); + await driver.initialized; + server.online = false; + + expect(await driver.checkForUpdate()).toEqual(false); + expect(driver.state).toEqual(DriverReadyState.NORMAL); + expect(scope.unregistered).toBeFalsy(); + expect(await scope.caches.keys()).not.toEqual([]); + }); + describe('unhashed requests', () => { async_beforeEach(async() => { expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); @@ -639,6 +624,7 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); serverUpdate.assertNoOtherRequests(); }); }); + describe('routing', () => { async_beforeEach(async() => { expect(await makeRequest(scope, '/foo.txt')).toEqual('this is foo'); @@ -680,6 +666,7 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); headers: { 'Accept': 'text/plain, text/html, text/css', }, + mode: 'navigate', })).toBeNull(); server.assertSawRequestFor('/baz.html'); }); @@ -721,18 +708,27 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate)); expect(driver.state).toEqual(DriverReadyState.EXISTING_CLIENTS_ONLY); }); + + async_it('ignores invalid `only-if-cached` requests ', async() => { + const requestFoo = (cache: RequestCache | 'only-if-cached', mode: RequestMode) => + makeRequest(scope, '/foo.txt', undefined, {cache, mode}); + + expect(await requestFoo('default', 'no-cors')).toBe('this is foo'); + expect(await requestFoo('only-if-cached', 'same-origin')).toBe('this is foo'); + expect(await requestFoo('only-if-cached', 'no-cors')).toBeNull(); + }); }); }); })(); async function makeRequest( - scope: SwTestHarness, url: string, clientId?: string, init?: Object): Promise { - const [resPromise, done] = scope.handleFetch(new MockRequest(url, init), clientId || 'default'); + scope: SwTestHarness, url: string, clientId = 'default', init?: Object): Promise { + const [resPromise, done] = scope.handleFetch(new MockRequest(url, init), clientId); await done; const res = await resPromise; - scope.clients.add(clientId || 'default'); + scope.clients.add(clientId); if (res !== undefined && res.ok) { return res.text(); } return null; -} \ No newline at end of file +} diff --git a/packages/service-worker/worker/testing/fetch.ts b/packages/service-worker/worker/testing/fetch.ts index 3a55f84a6ac43..7d77b91c1fedb 100644 --- a/packages/service-worker/worker/testing/fetch.ts +++ b/packages/service-worker/worker/testing/fetch.ts @@ -108,6 +108,9 @@ export class MockRequest extends MockBody implements Request { Object.keys(headers).forEach(header => { this.headers.set(header, headers[header]); }); } } + if (init.cache !== undefined) { + this.cache = init.cache; + } if (init.mode !== undefined) { this.mode = init.mode; } @@ -168,4 +171,4 @@ export class MockResponse extends MockBody implements Response { return new MockResponse( this._body, {status: this.status, statusText: this.statusText, headers: this.headers}); } -} \ No newline at end of file +} diff --git a/packages/service-worker/worker/testing/mock.ts b/packages/service-worker/worker/testing/mock.ts index 4ab6502464322..208e75f9adbf5 100644 --- a/packages/service-worker/worker/testing/mock.ts +++ b/packages/service-worker/worker/testing/mock.ts @@ -96,6 +96,7 @@ export class MockServerState { private gate: Promise = Promise.resolve(); private resolve: Function|null = null; private resolveNextRequest: Function; + online = true; nextRequest: Promise; constructor(private resources: Map, private errors: Set) { @@ -108,6 +109,10 @@ export class MockServerState { await this.gate; + if (!this.online) { + throw new Error('Offline.'); + } + if (req.credentials === 'include') { return new MockResponse(null, {status: 0, statusText: '', type: 'opaque'}); } @@ -171,6 +176,7 @@ export class MockServerState { this.nextRequest = new Promise(resolve => { this.resolveNextRequest = resolve; }); this.gate = Promise.resolve(); this.resolve = null; + this.online = true; } } diff --git a/packages/upgrade/src/common/downgrade_component_adapter.ts b/packages/upgrade/src/common/downgrade_component_adapter.ts index 6ba0238675c10..7d15d0c4a5b89 100644 --- a/packages/upgrade/src/common/downgrade_component_adapter.ts +++ b/packages/upgrade/src/common/downgrade_component_adapter.ts @@ -25,6 +25,7 @@ export class DowngradeComponentAdapter { private componentRef: ComponentRef; private component: any; private changeDetector: ChangeDetectorRef; + private viewChangeDetector: ChangeDetectorRef; constructor( private element: angular.IAugmentedJQuery, private attrs: angular.IAttributes, @@ -60,6 +61,7 @@ export class DowngradeComponentAdapter { this.componentRef = this.componentFactory.create(childInjector, projectableNodes, this.element[0]); + this.viewChangeDetector = this.componentRef.injector.get(ChangeDetectorRef); this.changeDetector = this.componentRef.changeDetectorRef; this.component = this.componentRef.instance; @@ -139,6 +141,8 @@ export class DowngradeComponentAdapter { (this.component).ngOnChanges(inputChanges !); } + this.viewChangeDetector.markForCheck(); + // If opted out of propagating digests, invoke change detection when inputs change. if (!propagateDigest) { detectChanges(); @@ -168,45 +172,43 @@ export class DowngradeComponentAdapter { const outputs = this.componentFactory.outputs || []; for (let j = 0; j < outputs.length; j++) { const output = new PropertyBinding(outputs[j].propName, outputs[j].templateName); - let expr: string|null = null; - let assignExpr = false; - const bindonAttr = output.bindonAttr.substring(0, output.bindonAttr.length - 6); const bracketParenAttr = `[(${output.bracketParenAttr.substring(2, output.bracketParenAttr.length - 8)})]`; - + // order below is important - first update bindings then evaluate expressions + if (attrs.hasOwnProperty(bindonAttr)) { + this.subscribeToOutput(output, attrs[bindonAttr], true); + } + if (attrs.hasOwnProperty(bracketParenAttr)) { + this.subscribeToOutput(output, attrs[bracketParenAttr], true); + } if (attrs.hasOwnProperty(output.onAttr)) { - expr = attrs[output.onAttr]; - } else if (attrs.hasOwnProperty(output.parenAttr)) { - expr = attrs[output.parenAttr]; - } else if (attrs.hasOwnProperty(bindonAttr)) { - expr = attrs[bindonAttr]; - assignExpr = true; - } else if (attrs.hasOwnProperty(bracketParenAttr)) { - expr = attrs[bracketParenAttr]; - assignExpr = true; + this.subscribeToOutput(output, attrs[output.onAttr]); } - - if (expr != null && assignExpr != null) { - const getter = this.$parse(expr); - const setter = getter.assign; - if (assignExpr && !setter) { - throw new Error(`Expression '${expr}' is not assignable!`); - } - const emitter = this.component[output.prop] as EventEmitter; - if (emitter) { - emitter.subscribe({ - next: assignExpr ? (v: any) => setter !(this.scope, v) : - (v: any) => getter(this.scope, {'$event': v}) - }); - } else { - throw new Error( - `Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`); - } + if (attrs.hasOwnProperty(output.parenAttr)) { + this.subscribeToOutput(output, attrs[output.parenAttr]); } } } + private subscribeToOutput(output: PropertyBinding, expr: string, isAssignment: boolean = false) { + const getter = this.$parse(expr); + const setter = getter.assign; + if (isAssignment && !setter) { + throw new Error(`Expression '${expr}' is not assignable!`); + } + const emitter = this.component[output.prop] as EventEmitter; + if (emitter) { + emitter.subscribe({ + next: isAssignment ? (v: any) => setter !(this.scope, v) : + (v: any) => getter(this.scope, {'$event': v}) + }); + } else { + throw new Error( + `Missing emitter '${output.prop}' on component '${getComponentName(this.componentFactory.componentType)}'!`); + } + } + registerCleanup() { const destroyComponentRef = this.wrapCallback(() => this.componentRef.destroy()); let destroyed = false; diff --git a/packages/upgrade/src/dynamic/upgrade_adapter.ts b/packages/upgrade/src/dynamic/upgrade_adapter.ts index 00c52dff49215..d41b49cf6b9d1 100644 --- a/packages/upgrade/src/dynamic/upgrade_adapter.ts +++ b/packages/upgrade/src/dynamic/upgrade_adapter.ts @@ -389,8 +389,9 @@ export class UpgradeAdapter { const originalResumeBootstrap: () => void = windowAngular.resumeBootstrap; windowAngular.resumeBootstrap = function() { windowAngular.resumeBootstrap = originalResumeBootstrap; - windowAngular.resumeBootstrap.apply(this, arguments); + const r = windowAngular.resumeBootstrap.apply(this, arguments); resolve(); + return r; }; } else { resolve(); diff --git a/packages/upgrade/src/static/upgrade_module.ts b/packages/upgrade/src/static/upgrade_module.ts index e93305e0dfcf1..6a7f57960bb1d 100644 --- a/packages/upgrade/src/static/upgrade_module.ts +++ b/packages/upgrade/src/static/upgrade_module.ts @@ -69,7 +69,8 @@ import {NgAdapterInjector} from './util'; * in AngularJS templates must use kebab-case, while AngularJS templates must use camelCase. * c. However the template binding syntax will always use the Angular style, e.g. square * brackets (`[...]`) for property binding. - * 8. AngularJS is always bootstrapped first and owns the root component. + * 8. Angular is bootstrapped first; AngularJS is bootstrapped second. AngularJS always owns the + * root component of the application. * 9. The new application is running in an Angular zone, and therefore it no longer needs calls * to * `$apply()`. @@ -268,7 +269,7 @@ export class UpgradeModule { windowAngular.resumeBootstrap = function() { let args = arguments; windowAngular.resumeBootstrap = originalResumeBootstrap; - ngZone.run(() => { windowAngular.resumeBootstrap.apply(this, args); }); + return ngZone.run(() => windowAngular.resumeBootstrap.apply(this, args)); }; } } diff --git a/packages/upgrade/test/dynamic/upgrade_spec.ts b/packages/upgrade/test/dynamic/upgrade_spec.ts index 48d34128348ef..159f05c4ae80d 100644 --- a/packages/upgrade/test/dynamic/upgrade_spec.ts +++ b/packages/upgrade/test/dynamic/upgrade_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, OnDestroy, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; +import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, OnDestroy, Output, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; import {async, fakeAsync, flushMicrotasks, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -434,6 +434,55 @@ withEachNg1Version(() => { })); + it('should support two-way binding and event listener', async(() => { + const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); + const listenerSpy = jasmine.createSpy('$rootScope.listener'); + const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { + $rootScope['value'] = 'world'; + $rootScope['listener'] = listenerSpy; + }); + + @Component({selector: 'ng2', template: `model: {{model}};`}) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + @Input() model = '?'; + @Output() modelChange = new EventEmitter(); + + ngOnChanges(changes: SimpleChanges) { + switch (this.ngOnChangesCount++) { + case 0: + expect(changes.model.currentValue).toBe('world'); + this.modelChange.emit('newC'); + break; + case 1: + expect(changes.model.currentValue).toBe('newC'); + break; + default: + throw new Error('Called too many times! ' + JSON.stringify(changes)); + } + } + } + + ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2Component)); + + @NgModule({declarations: [Ng2Component], imports: [BrowserModule]}) + class Ng2Module { + ngDoBootstrap() {} + } + + const element = html(` +
    + + | value: {{value}} +
    + `); + adapter.bootstrap(element, ['ng1']).ready((ref) => { + expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); + expect(listenerSpy).toHaveBeenCalledWith('newC'); + ref.dispose(); + }); + })); + it('should initialize inputs in time for `ngOnChanges`', async(() => { const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module)); @@ -2860,6 +2909,29 @@ withEachNg1Version(() => { }, 100); })); + it('should propagate return value of resumeBootstrap', fakeAsync(() => { + @NgModule({imports: [BrowserModule]}) + class MyNg2Module { + } + + const adapter: UpgradeAdapter = new UpgradeAdapter(MyNg2Module); + const ng1Module = angular.module('ng1', []); + let a1Injector: angular.IInjectorService|undefined; + ng1Module.run([ + '$injector', function($injector: angular.IInjectorService) { a1Injector = $injector; } + ]); + + const element = html('
    '); + window.name = 'NG_DEFER_BOOTSTRAP!' + window.name; + + adapter.bootstrap(element, [ng1Module.name]).ready((ref) => { ref.dispose(); }); + + tick(100); + + const value = (window).angular.resumeBootstrap(); + expect(value).toBe(a1Injector); + })); + it('should wait for ng2 testability', async(() => { @NgModule({imports: [BrowserModule]}) class MyNg2Module { diff --git a/packages/upgrade/test/static/integration/downgrade_component_spec.ts b/packages/upgrade/test/static/integration/downgrade_component_spec.ts index 2f977c78e818b..810d604c34fa8 100644 --- a/packages/upgrade/test/static/integration/downgrade_component_spec.ts +++ b/packages/upgrade/test/static/integration/downgrade_component_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, ElementRef, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, SimpleChanges, destroyPlatform} from '@angular/core'; +import {ChangeDetectionStrategy, ChangeDetectorRef, Compiler, Component, ComponentFactoryResolver, Directive, ElementRef, EventEmitter, Injector, Input, NgModule, NgModuleRef, OnChanges, OnDestroy, Output, SimpleChanges, destroyPlatform} from '@angular/core'; import {async, fakeAsync, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; @@ -148,6 +148,103 @@ withEachNg1Version(() => { }); })); + it('should bind properties to onpush components', async(() => { + const ng1Module = + angular.module('ng1', []).value('$exceptionHandler', (err: any) => { + throw err; + }).run(($rootScope: angular.IScope) => { + $rootScope['dataB'] = 'B'; + }); + + @Component({ + selector: 'ng2', + inputs: ['oneWayB'], + template: 'oneWayB: {{oneWayB}}', + changeDetection: ChangeDetectionStrategy.OnPush + }) + + class Ng2Component { + ngOnChangesCount = 0; + oneWayB = '?'; + } + + ng1Module.directive('ng2', downgradeComponent({ + component: Ng2Component, + })); + + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const element = html(` +
    + +
    `); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(multiTrim(document.body.textContent)).toEqual('oneWayB: B'); + $apply(upgrade, 'dataB= "everyone"'); + expect(multiTrim(document.body.textContent)).toEqual('oneWayB: everyone'); + }); + })); + + it('should support two-way binding and event listener', async(() => { + const listenerSpy = jasmine.createSpy('$rootScope.listener'); + const ng1Module = angular.module('ng1', []).run(($rootScope: angular.IScope) => { + $rootScope['value'] = 'world'; + $rootScope['listener'] = listenerSpy; + }); + + @Component({selector: 'ng2', template: `model: {{model}};`}) + class Ng2Component implements OnChanges { + ngOnChangesCount = 0; + @Input() model = '?'; + @Output() modelChange = new EventEmitter(); + + ngOnChanges(changes: SimpleChanges) { + switch (this.ngOnChangesCount++) { + case 0: + expect(changes.model.currentValue).toBe('world'); + this.modelChange.emit('newC'); + break; + case 1: + expect(changes.model.currentValue).toBe('newC'); + break; + default: + throw new Error('Called too many times! ' + JSON.stringify(changes)); + } + } + } + + ng1Module.directive('ng2', downgradeComponent({component: Ng2Component})); + + @NgModule({ + declarations: [Ng2Component], + entryComponents: [Ng2Component], + imports: [BrowserModule, UpgradeModule] + }) + class Ng2Module { + ngDoBootstrap() {} + } + + const element = html(` +
    + + | value: {{value}} +
    + `); + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module).then((upgrade) => { + expect(multiTrim(element.textContent)).toEqual('model: newC; | value: newC'); + expect(listenerSpy).toHaveBeenCalledWith('newC'); + }); + })); + it('should run change-detection on every digest (by default)', async(() => { let ng2Component: Ng2Component; diff --git a/packages/upgrade/test/static/integration/testability_spec.ts b/packages/upgrade/test/static/integration/testability_spec.ts index 73d9712e54cd0..3ceeb0ab9d9a2 100644 --- a/packages/upgrade/test/static/integration/testability_spec.ts +++ b/packages/upgrade/test/static/integration/testability_spec.ts @@ -8,7 +8,7 @@ import {NgModule, Testability, destroyPlatform} from '@angular/core'; import {NgZone} from '@angular/core/src/zone/ng_zone'; -import {fakeAsync, tick} from '@angular/core/testing'; +import {fakeAsync, flush, tick} from '@angular/core/testing'; import {BrowserModule} from '@angular/platform-browser'; import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {UpgradeModule} from '@angular/upgrade/static'; @@ -48,6 +48,25 @@ withEachNg1Version(() => { expect(stayedInTheZone).toEqual(true); })); + it('should propagate return value of resumeBootstrap', fakeAsync(() => { + const ng1Module = angular.module('ng1', []); + let a1Injector: angular.IInjectorService|undefined; + ng1Module.run([ + '$injector', function($injector: angular.IInjectorService) { a1Injector = $injector; } + ]); + const element = html('
    '); + window.name = 'NG_DEFER_BOOTSTRAP!' + window.name; + + bootstrap(platformBrowserDynamic(), Ng2Module, element, ng1Module); + + tick(100); + + const value = (window).angular.resumeBootstrap(); + expect(value).toBe(a1Injector); + + flush(); + })); + it('should wait for ng2 testability', fakeAsync(() => { const ng1Module = angular.module('ng1', []); const element = html('
    '); diff --git a/scripts/ci/env.sh b/scripts/ci/env.sh index d74cede5e6403..fa20d39e9adb7 100755 --- a/scripts/ci/env.sh +++ b/scripts/ci/env.sh @@ -106,7 +106,7 @@ if [[ ${TRAVIS:-} ]]; then setEnvVar BROWSER_STACK_USERNAME angularteam1 # not using use setEnvVar so that we don't print the key - export BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB + export BROWSER_STACK_ACCESS_KEY=CaXMeMHD9pr5PHg8N7Jq setEnvVar CHROME_BIN ${HOME}/.chrome/chromium/chrome-linux/chrome setEnvVar BROWSER_PROVIDER_READY_FILE /tmp/angular-build/browser-provider-tunnel-init.lock fi diff --git a/scripts/ci/payload-size.sh b/scripts/ci/payload-size.sh index ec242a610cf74..28dfd7346e080 100644 --- a/scripts/ci/payload-size.sh +++ b/scripts/ci/payload-size.sh @@ -3,6 +3,7 @@ set -eu -o pipefail readonly PROJECT_NAME="angular-payload-size" +NODE_MODULES_BIN=$PROJECT_ROOT/node_modules/.bin/ # Calculate the size of target file uncompressed size, gzip7 size, gzip9 size # Write to global variable $payloadData, $filename @@ -88,7 +89,7 @@ uploadData() { # WARNING: FIREBASE_TOKEN should NOT be printed. set +x - $PROJECT_ROOT/node_modules/.bin/firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$ANGULAR_PAYLOAD_FIREBASE_TOKEN" $dbPath + $NODE_MODULES_BIN/firebase database:update --data "$payloadData" --project $PROJECT_NAME --confirm --token "$ANGULAR_PAYLOAD_FIREBASE_TOKEN" $dbPath fi } diff --git a/scripts/ci/test-js.sh b/scripts/ci/test-js.sh index 0257ab1ae3b31..67db3eafcf005 100755 --- a/scripts/ci/test-js.sh +++ b/scripts/ci/test-js.sh @@ -9,7 +9,7 @@ source ${thisDir}/_travis-fold.sh # Run unit tests for our tools/ directory travisFoldStart "test.unit.tools" - # TODO(i) could this be rolled into the tools tests above? why is it separate? + # TODO(i): could this be rolled into the tools tests above? why is it separate? travisFoldStart "test.unit.validate-commit-message" ( cd tools/validate-commit-message diff --git a/scripts/github/merge-pr b/scripts/github/merge-pr index 752f86fcdf75f..98aa478eaee14 100755 --- a/scripts/github/merge-pr +++ b/scripts/github/merge-pr @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -u -e -o pipefail diff --git a/scripts/github/push-upstream b/scripts/github/push-upstream index 076ffcc856008..a802808856662 100755 --- a/scripts/github/push-upstream +++ b/scripts/github/push-upstream @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -u -e -o pipefail diff --git a/scripts/github/rebase-pr b/scripts/github/rebase-pr index 77cda6cd51321..b87dbe815feee 100755 --- a/scripts/github/rebase-pr +++ b/scripts/github/rebase-pr @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -u -e -o pipefail diff --git a/scripts/release/post-check b/scripts/release/post-check new file mode 100755 index 0000000000000..9d46036abaee4 --- /dev/null +++ b/scripts/release/post-check @@ -0,0 +1,16 @@ + +#!/usr/bin/env bash + +# use for PATCH releases +# Verify that all packages are published with correct version: + +mkdir tmp +cd tmp +npm init -y +npm install typescript@2.6 rxjs@5.5.0 zone.js@0.8.10 @angular/{animations,core,common,forms,router,platform-browser,platform-browser-dynamic,platform-webworker,platform-webworker-dynamic,platform-server,service-worker,compiler,compiler-cli,upgrade,language-service} --save +cd .. +rm -rf tmp + +echo "CHECK THE DIST-TAGS" + +grep '"name": "@angular' $(find packages -name package.json) | grep -o '@angular/[^/"]*' | sort -u | xargs -n 1 -I% npm show \% name dist-tags diff --git a/scripts/release/post-check-next b/scripts/release/post-check-next new file mode 100755 index 0000000000000..e10d46b9a54f6 --- /dev/null +++ b/scripts/release/post-check-next @@ -0,0 +1,16 @@ + +#!/usr/bin/env bash + +# use for BETA and RC releases +# Verify that all packages are published with correct version: + +mkdir tmp +cd tmp +npm init -y +npm install typescript@2.6 rxjs@5.5.0 zone.js@0.8.10 @angular/{animations,core,common,forms,router,platform-browser,platform-browser-dynamic,platform-webworker,platform-webworker-dynamic,platform-server,compiler,compiler-cli,language-service,service-worker,upgrade}@next --save +cd .. +rm -rf tmp + +echo "CHECK THE DIST-TAGS" + +grep '"name": "@angular' $(find packages -name package.json) | grep -o '@angular/[^/"]*' | sort -u | xargs -n 1 -I% npm show \% name dist-tags \ No newline at end of file diff --git a/scripts/release/pre-check b/scripts/release/pre-check new file mode 100755 index 0000000000000..33281cb0305ce --- /dev/null +++ b/scripts/release/pre-check @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +# Verify peer deps constraints and package.json before publishing to npm +# There should be no npm errors + +mkdir tmp +cd tmp +npm init -y +npm install --save ../dist/packages-dist/* zone.js@0.8.10 rxjs@5.5.0 typescript@2.6 +cd .. +rm -rf ./tmp \ No newline at end of file diff --git a/scripts/release/publish b/scripts/release/publish new file mode 100755 index 0000000000000..204be53eee957 --- /dev/null +++ b/scripts/release/publish @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Use for PATCH releases +# Publish all packages in `dist/packages-dist` to npm + +(cd dist/packages-dist; for p in `ls .`; do npm publish --access public $p; done) \ No newline at end of file diff --git a/scripts/release/publish-next b/scripts/release/publish-next new file mode 100755 index 0000000000000..e1d0f21f171d8 --- /dev/null +++ b/scripts/release/publish-next @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +# Use for BETA and RC releases +# Publish all packages in `dist/packages-dist` to npm (as next) + +(cd dist/packages-dist; for p in `ls .`; do npm publish --access public --tag next $p; done) \ No newline at end of file diff --git a/test.sh b/test.sh index 7699a6ee4f13a..5eb7fa9964e94 100755 --- a/test.sh +++ b/test.sh @@ -6,8 +6,8 @@ if [ $# -eq 0 ] then echo "Angular test runner. (No platform specified)" echo - echo "./test.sh [node|browser|browserNoRouter|router|tools] [--debug]" - echo "(--debug flag only relevant to 'node' and 'tools' testing - see https://github.com/angular/angular/blob/master/docs/DEBUG.md)" + echo "./test.sh [node|browser|browserNoRouter|router] [--debug]" + echo "(--debug flag only relevant to 'node' testing - see https://github.com/angular/angular/blob/master/docs/DEBUG.md)" echo else cd `dirname $0` diff --git a/tools/bazel.rc b/tools/bazel.rc index 800920282e2c8..5fdf4ba0d8ab3 100644 --- a/tools/bazel.rc +++ b/tools/bazel.rc @@ -38,9 +38,6 @@ build --workspace_status_command=./tools/bazel_stamp_vars.sh # Prints eg. "ng_module rule //foo:bar" rather than just "//foo:bar" query --output=label_kind -# Don't print every dependency in :node_modules, for example -query --noimplicit_deps - # By default, failing tests don't print any output, it goes to the log file test --test_output=errors diff --git a/tools/gulp-tasks/lint.js b/tools/gulp-tasks/lint.js index 9f6cebd4a4f87..ddc1ba05742c9 100644 --- a/tools/gulp-tasks/lint.js +++ b/tools/gulp-tasks/lint.js @@ -13,7 +13,7 @@ module.exports = (gulp) => () => { const path = require('path'); return gulp .src([ - // todo(vicb): add .js files when supported + // TODO(vicb): add .js files when supported // see https://github.com/palantir/tslint/pull/1515 './modules/**/*.ts', './modules/**/*.js', @@ -34,7 +34,7 @@ module.exports = (gulp) => () => { '!**/*.externs.js', // Ignore generated files due to lack of copyright header - // todo(alfaproject): make generated files lintable + // TODO(alfaproject): make generated files lintable '!**/*.d.ts', '!**/*.ngfactory.ts', ]) diff --git a/tools/public_api_guard/common/http.d.ts b/tools/public_api_guard/common/http.d.ts index fd066926afc45..d341eadb45d70 100644 --- a/tools/public_api_guard/common/http.d.ts +++ b/tools/public_api_guard/common/http.d.ts @@ -1580,7 +1580,13 @@ export interface HttpParameterCodec { /** @stable */ export declare class HttpParams { - constructor(options?: HttpParamsOptions); + constructor(options?: { + fromString?: string | undefined; + fromObject?: { + [param: string]: string | string[]; + } | undefined; + encoder?: HttpParameterCodec | undefined; + }); append(param: string, value: string): HttpParams; delete(param: string, value?: string): HttpParams; get(param: string): string | null; diff --git a/yarn.lock b/yarn.lock index 11193581a676c..1857204265dc9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6539,7 +6539,7 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@0.4.18, source-map-support@^0.4.2, source-map-support@~0.4.0: +source-map-support@0.4.18, source-map-support@~0.4.0: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" dependencies: @@ -6551,6 +6551,12 @@ source-map-support@0.4.3: dependencies: source-map "^0.5.3" +source-map-support@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.3.tgz#2b3d5fff298cfa4d1afd7d4352d569e9a0158e76" + dependencies: + source-map "^0.6.0" + source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" @@ -6571,6 +6577,10 @@ source-map@^0.4.4, source-map@~0.4.1: dependencies: amdefine ">=0.0.4" +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + sparkles@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" @@ -7124,14 +7134,14 @@ ts-api-guardian@^0.3.0: diff "^3.4.0" minimist "^1.2.0" -tsickle@0.26.0: - version "0.26.0" - resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.26.0.tgz#40b30a2dd6abcb33b182e37596674bd1cfe4039c" +tsickle@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/tsickle/-/tsickle-0.27.2.tgz#f33d46d046f73dd5c155a37922e422816e878736" dependencies: minimist "^1.2.0" mkdirp "^0.5.1" - source-map "^0.5.6" - source-map-support "^0.4.2" + source-map "^0.6.0" + source-map-support "^0.5.0" tslib@^1.0.0, tslib@^1.7.1: version "1.7.1" @@ -7952,5 +7962,5 @@ zip-stream@~0.6.0: readable-stream "~1.0.26" zone.js@^0.8.12: - version "0.8.17" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.17.tgz#4c5e5185a857da8da793daf3919371c5a36b2a0b" + version "0.8.20" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.20.tgz#a218c48db09464b19ff6fc8f0d4bb5b1046e185d"