mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ed39ebaaf | |||
| 091f0a5aaa | |||
| 315606e02c | |||
| 5ea373d184 | |||
| 6e36bb7b20 | |||
| 3b2fb23805 | |||
| bd2eecb4de | |||
| 3d351a4f5f | |||
| 5492fada21 | |||
| fd4f9acbcf | |||
| 48528a86e1 | |||
| 80364def27 | |||
| 1803beb4d5 | |||
| 3bcba8a570 | |||
| 84542d8ae7 | |||
| 17cb3ec565 | |||
| 015878afe6 | |||
| 2af58622c1 | |||
| 7ffd10541d | |||
| 481b099d82 | |||
| 49c4b0fa92 | |||
| b8b6b1d27a | |||
| 892b5ba950 | |||
| bd15110c7d | |||
| 2250082fd7 | |||
| 87316c52db | |||
| 606b76d9bb | |||
| 3d0b1b8184 | |||
| 261fd16780 | |||
| 104cc42f6d |
+30
-1
@@ -1,3 +1,32 @@
|
||||
<a name="2.4.5"></a>
|
||||
## [2.4.5](https://github.com/angular/angular/compare/2.4.4...2.4.5) (2017-01-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler:** [i18n] XMB/XTB placeholder names can contain only A-Z, 0-9, _n ([5492fad](https://github.com/angular/angular/commit/5492fad))
|
||||
* **compiler:** fix regexp to support firefox 31 ([#14082](https://github.com/angular/angular/issues/14082)) ([bd2eecb](https://github.com/angular/angular/commit/bd2eecb)), closes [#14029](https://github.com/angular/angular/issues/14029) [#13900](https://github.com/angular/angular/issues/13900)
|
||||
* **core:** export animation classes required for Renderer impl ([#14002](https://github.com/angular/angular/issues/14002)) ([fd4f9ac](https://github.com/angular/angular/commit/fd4f9ac)), closes [#14001](https://github.com/angular/angular/issues/14001)
|
||||
* **upgrade:** ensure upgraded injector is initialized early enough ([#14065](https://github.com/angular/angular/issues/14065)) ([3b2fb23](https://github.com/angular/angular/commit/3b2fb23)), closes [#13811](https://github.com/angular/angular/issues/13811)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.4"></a>
|
||||
## [2.4.4](https://github.com/angular/angular/compare/2.4.3...2.4.4) (2017-01-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **animations:** fix internal jscompiler issue and AOT quoting ([#13798](https://github.com/angular/angular/issues/13798)) ([261fd16](https://github.com/angular/angular/commit/261fd16))
|
||||
* **common:** support numeric value as discrete cases for NgPlural ([#13876](https://github.com/angular/angular/issues/13876)) ([3d0b1b8](https://github.com/angular/angular/commit/3d0b1b8))
|
||||
* **http:** don't create a blob out of ArrayBuffer when type is application/octet-stream ([#13992](https://github.com/angular/angular/issues/13992)) ([015878a](https://github.com/angular/angular/commit/015878a)), closes [#13973](https://github.com/angular/angular/issues/13973)
|
||||
* **router:** enable loadChildren with function in aot ([#13909](https://github.com/angular/angular/issues/13909)) ([2af5862](https://github.com/angular/angular/commit/2af5862)), closes [#11075](https://github.com/angular/angular/issues/11075)
|
||||
* **router:** routerLinkActive should not throw when not initialized ([#13273](https://github.com/angular/angular/issues/13273)) ([49c4b0f](https://github.com/angular/angular/commit/49c4b0f)), closes [#13270](https://github.com/angular/angular/issues/13270)
|
||||
* **security:** allow calc and gradient functions. ([#13943](https://github.com/angular/angular/issues/13943)) ([bd15110](https://github.com/angular/angular/commit/bd15110))
|
||||
* **upgrade:** detect async downgrade component changes ([#13812](https://github.com/angular/angular/issues/13812)) ([2250082](https://github.com/angular/angular/commit/2250082)), closes [#6385](https://github.com/angular/angular/issues/6385) [#6385](https://github.com/angular/angular/issues/6385) [#10660](https://github.com/angular/angular/issues/10660) [#12318](https://github.com/angular/angular/issues/12318) [#12034](https://github.com/angular/angular/issues/12034)
|
||||
|
||||
|
||||
|
||||
<a name="2.4.3"></a>
|
||||
## [2.4.3](https://github.com/angular/angular/compare/2.4.2...2.4.3) (2017-01-11)
|
||||
|
||||
@@ -26,7 +55,7 @@
|
||||
* **common:** allow null/undefined values for `NgForTrackBy` ([6be55cc](https://github.com/angular/angular/commit/6be55cc)), closes [#13641](https://github.com/angular/angular/issues/13641)
|
||||
* **compiler:** don’t throw when using `ANALYZE_FOR_ENTRY_COMPONENTS` with user classes ([#13679](https://github.com/angular/angular/issues/13679)) ([230e33f](https://github.com/angular/angular/commit/230e33f)), closes [#13565](https://github.com/angular/angular/issues/13565)
|
||||
* **compiler:** query `<template>` elements before their children. ([#13677](https://github.com/angular/angular/issues/13677)) ([1cd73c7](https://github.com/angular/angular/commit/1cd73c7)), closes [#13118](https://github.com/angular/angular/issues/13118) [#13167](https://github.com/angular/angular/issues/13167)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645)
|
||||
* **compiler:** allow "." in attribute selectors ([#13653](https://github.com/angular/angular/issues/13653)) ([29ffdfd](https://github.com/angular/angular/commit/29ffdfd)), closes [#13645](https://github.com/angular/angular/issues/13645) [#13982](https://github.com/angular/angular/issues/13982)
|
||||
* **core:** animations no longer silently exits if the element is not apart of the DOM ([#13763](https://github.com/angular/angular/issues/13763)) ([f1cde43](https://github.com/angular/angular/commit/f1cde43))
|
||||
* **core:** animations should blend in all previously transitioned styles into next animation if interrupted ([#13148](https://github.com/angular/angular/issues/13148)) ([b245b92](https://github.com/angular/angular/commit/b245b92))
|
||||
* **core:** remove reference to "Angular 2" in dev mode warning ([#13751](https://github.com/angular/angular/issues/13751)) ([21f5f05](https://github.com/angular/angular/commit/21f5f05))
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
machine:
|
||||
node:
|
||||
version: 5.4.1
|
||||
version: 6.6.0
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g npm@3.6.0
|
||||
- npm install -g npm@3.5.3
|
||||
|
||||
test:
|
||||
override:
|
||||
|
||||
@@ -103,6 +103,7 @@ export class NgPluralCase {
|
||||
constructor(
|
||||
@Attribute('ngPluralCase') public value: string, template: TemplateRef<Object>,
|
||||
viewContainer: ViewContainerRef, @Host() ngPlural: NgPlural) {
|
||||
ngPlural.addCase(value, new SwitchView(viewContainer, template));
|
||||
const isANumber: boolean = !isNaN(Number(value));
|
||||
ngPlural.addCase(isANumber ? `=${value}` : value, new SwitchView(viewContainer, template));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import {InvalidPipeArgumentError} from './invalid_pipe_argument_error';
|
||||
* Where:
|
||||
* - `expression` is a date object or a number (milliseconds since UTC epoch) or an ISO string
|
||||
* (https://www.w3.org/TR/NOTE-datetime).
|
||||
* - `format` indicates which date/time components to include. The format can be predifined as
|
||||
* - `format` indicates which date/time components to include. The format can be predefined as
|
||||
* shown below or custom as shown in the table.
|
||||
* - `'medium'`: equivalent to `'yMMMdjms'` (e.g. `Sep 3, 2010, 12:05:08 PM` for `en-US`)
|
||||
* - `'short'`: equivalent to `'yMdjm'` (e.g. `9/3/2010, 12:05 PM` for `en-US`)
|
||||
|
||||
@@ -12,7 +12,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {expect} from '@angular/platform-browser/testing/matchers';
|
||||
|
||||
export function main() {
|
||||
describe('switch', () => {
|
||||
describe('ngPlural', () => {
|
||||
let fixture: ComponentFixture<any>;
|
||||
|
||||
function getComponent(): TestComponent { return fixture.componentInstance; }
|
||||
@@ -47,6 +47,22 @@ export function main() {
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
it('should display the template according to the exact numeric value', async(() => {
|
||||
const template = '<div>' +
|
||||
'<ul [ngPlural]="switchValue">' +
|
||||
'<template ngPluralCase="0"><li>you have no messages.</li></template>' +
|
||||
'<template ngPluralCase="1"><li>you have one message.</li></template>' +
|
||||
'</ul></div>';
|
||||
|
||||
fixture = createTestComponent(template);
|
||||
|
||||
getComponent().switchValue = 0;
|
||||
detectChangesAndExpectText('you have no messages.');
|
||||
|
||||
getComponent().switchValue = 1;
|
||||
detectChangesAndExpectText('you have one message.');
|
||||
}));
|
||||
|
||||
// https://github.com/angular/angular/issues/9868
|
||||
// https://github.com/angular/angular/issues/9882
|
||||
it('should not throw when ngPluralCase contains expressions', async(() => {
|
||||
|
||||
@@ -36,29 +36,6 @@ export class CodeGenerator {
|
||||
public host: ts.CompilerHost, private compiler: compiler.AotCompiler,
|
||||
private ngCompilerHost: CompilerHost) {}
|
||||
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
private calculateEmitPath(filePath: string): string {
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
|
||||
codegen(): Promise<any> {
|
||||
return this.compiler
|
||||
.compileAll(this.program.getSourceFiles().map(
|
||||
@@ -66,7 +43,7 @@ export class CodeGenerator {
|
||||
.then(generatedModules => {
|
||||
generatedModules.forEach(generatedModule => {
|
||||
const sourceFile = this.program.getSourceFile(generatedModule.srcFileUrl);
|
||||
const emitPath = this.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const emitPath = this.ngCompilerHost.calculateEmitPath(generatedModule.genFileUrl);
|
||||
const source = GENERATED_META_FILES.test(emitPath) ? generatedModule.source :
|
||||
PREAMBLE + generatedModule.source;
|
||||
this.host.writeFile(emitPath, source, false, () => {}, [sourceFile]);
|
||||
|
||||
@@ -261,6 +261,29 @@ export class CompilerHost implements AotCompilerHost {
|
||||
this.options.generateCodeForLibraries === false ? GENERATED_OR_DTS_FILES : GENERATED_FILES;
|
||||
return !excludeRegex.test(filePath);
|
||||
}
|
||||
|
||||
calculateEmitPath(filePath: string): string {
|
||||
// Write codegen in a directory structure matching the sources.
|
||||
let root = this.options.basePath;
|
||||
for (const eachRootDir of this.options.rootDirs || []) {
|
||||
if (this.options.trace) {
|
||||
console.error(`Check if ${filePath} is under rootDirs element ${eachRootDir}`);
|
||||
}
|
||||
if (path.relative(eachRootDir, filePath).indexOf('.') !== 0) {
|
||||
root = eachRootDir;
|
||||
}
|
||||
}
|
||||
|
||||
// transplant the codegen path to be inside the `genDir`
|
||||
let relativePath: string = path.relative(root, filePath);
|
||||
while (relativePath.startsWith('..' + path.sep)) {
|
||||
// Strip out any `..` path such as: `../node_modules/@foo` as we want to put everything
|
||||
// into `genDir`.
|
||||
relativePath = relativePath.substr(3);
|
||||
}
|
||||
|
||||
return path.join(this.options.genDir, relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
export class CompilerHostContextAdapter {
|
||||
|
||||
@@ -52,7 +52,7 @@ export class Extractor {
|
||||
|
||||
extract(rootFiles: string[]): Promise<MessageBundle> {
|
||||
const programSymbols = extractProgramSymbols(this.staticSymbolResolver, rootFiles, this.host);
|
||||
const {ngModuleByPipeOrDirective, files, ngModules} =
|
||||
const {files, ngModules} =
|
||||
analyzeAndValidateNgModules(programSymbols, this.host, this.metadataResolver);
|
||||
return Promise
|
||||
.all(ngModules.map(
|
||||
|
||||
@@ -111,8 +111,14 @@ export class PlaceholderRegistry {
|
||||
private _hashClosingTag(tag: string): string { return this._hashTag(`/${tag}`, {}, false); }
|
||||
|
||||
private _generateUniqueName(base: string): string {
|
||||
const next = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = next ? next + 1 : 1;
|
||||
return next ? `${base}_${next}` : base;
|
||||
const seen = this._placeHolderNameCounts.hasOwnProperty(base);
|
||||
if (!seen) {
|
||||
this._placeHolderNameCounts[base] = 1;
|
||||
return base;
|
||||
}
|
||||
|
||||
const id = this._placeHolderNameCounts[base];
|
||||
this._placeHolderNameCounts[base] = id + 1;
|
||||
return `${base}_${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,26 @@
|
||||
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
export interface Serializer {
|
||||
write(messages: i18n.Message[]): string;
|
||||
export abstract class Serializer {
|
||||
abstract write(messages: i18n.Message[]): string;
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
abstract load(content: string, url: string): {[msgId: string]: i18n.Node[]};
|
||||
|
||||
digest(message: i18n.Message): string;
|
||||
abstract digest(message: i18n.Message): string;
|
||||
|
||||
// Creates a name mapper, see `PlaceholderMapper`
|
||||
// Returning `null` means that no name mapping is used.
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper { return null; }
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PlaceholderMapper` converts placeholder names from internal to serialized representation and
|
||||
* back.
|
||||
*
|
||||
* It should be used for serialization format that put constraints on the placeholder names.
|
||||
*/
|
||||
export interface PlaceholderMapper {
|
||||
toPublicName(internalName: string): string;
|
||||
|
||||
toInternalName(publicName: string): string;
|
||||
}
|
||||
@@ -27,7 +27,7 @@ const _UNIT_TAG = 'trans-unit';
|
||||
|
||||
// http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html
|
||||
// http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html
|
||||
export class Xliff implements Serializer {
|
||||
export class Xliff extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const visitor = new _WriteVisitor();
|
||||
const visited: {[id: string]: boolean} = {};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import {decimalDigest} from '../digest';
|
||||
import * as i18n from '../i18n_ast';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import * as xml from './xml_helper';
|
||||
|
||||
const _MESSAGES_TAG = 'messagebundle';
|
||||
@@ -37,7 +37,7 @@ const _DOCTYPE = `<!ELEMENT messagebundle (msg)*>
|
||||
|
||||
<!ELEMENT ex (#PCDATA)>`;
|
||||
|
||||
export class Xmb implements Serializer {
|
||||
export class Xmb extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
const exampleVisitor = new ExampleVisitor();
|
||||
const visitor = new _Visitor();
|
||||
@@ -51,6 +51,8 @@ export class Xmb implements Serializer {
|
||||
if (visited[id]) return;
|
||||
visited[id] = true;
|
||||
|
||||
const mapper = this.createNameMapper(message);
|
||||
|
||||
const attrs: {[k: string]: string} = {id};
|
||||
|
||||
if (message.description) {
|
||||
@@ -62,7 +64,8 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
rootNode.children.push(
|
||||
new xml.CR(2), new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes)));
|
||||
new xml.CR(2),
|
||||
new xml.Tag(_MESSAGE_TAG, attrs, visitor.serialize(message.nodes, {mapper})));
|
||||
});
|
||||
|
||||
rootNode.children.push(new xml.CR());
|
||||
@@ -82,22 +85,29 @@ export class Xmb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
class _Visitor implements i18n.Visitor {
|
||||
visitText(text: i18n.Text, context?: any): xml.Node[] { return [new xml.Text(text.value)]; }
|
||||
visitText(text: i18n.Text, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [new xml.Text(text.value)];
|
||||
}
|
||||
|
||||
visitContainer(container: i18n.Container, context?: any): xml.Node[] {
|
||||
visitContainer(container: i18n.Container, ctx: any): xml.Node[] {
|
||||
const nodes: xml.Node[] = [];
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this)));
|
||||
container.children.forEach((node: i18n.Node) => nodes.push(...node.visit(this, ctx)));
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, context?: any): xml.Node[] {
|
||||
visitIcu(icu: i18n.Icu, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const nodes = [new xml.Text(`{${icu.expressionPlaceholder}, ${icu.type}, `)];
|
||||
|
||||
Object.keys(icu.cases).forEach((c: string) => {
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this), new xml.Text(`} `));
|
||||
nodes.push(new xml.Text(`${c} {`), ...icu.cases[c].visit(this, ctx), new xml.Text(`} `));
|
||||
});
|
||||
|
||||
nodes.push(new xml.Text(`}`));
|
||||
@@ -105,30 +115,34 @@ class _Visitor implements i18n.Visitor {
|
||||
return nodes;
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, context?: any): xml.Node[] {
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const startEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`<${ph.tag}>`)]);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.startName}, [startEx]);
|
||||
let name = ctx.mapper.toPublicName(ph.startName);
|
||||
const startTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [startEx]);
|
||||
if (ph.isVoid) {
|
||||
// void tags have no children nor closing tags
|
||||
return [startTagPh];
|
||||
}
|
||||
|
||||
const closeEx = new xml.Tag(_EXEMPLE_TAG, {}, [new xml.Text(`</${ph.tag}>`)]);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name: ph.closeName}, [closeEx]);
|
||||
name = ctx.mapper.toPublicName(ph.closeName);
|
||||
const closeTagPh = new xml.Tag(_PLACEHOLDER_TAG, {name}, [closeEx]);
|
||||
|
||||
return [startTagPh, ...this.serialize(ph.children), closeTagPh];
|
||||
return [startTagPh, ...this.serialize(ph.children, ctx), closeTagPh];
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): xml.Node[] {
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name: ph.name})];
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
const name = ctx.mapper.toPublicName(ph.name);
|
||||
return [new xml.Tag(_PLACEHOLDER_TAG, {name})];
|
||||
}
|
||||
|
||||
serialize(nodes: i18n.Node[]): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this)));
|
||||
serialize(nodes: i18n.Node[], ctx: {mapper: PlaceholderMapper}): xml.Node[] {
|
||||
return [].concat(...nodes.map(node => node.visit(this, ctx)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,3 +172,69 @@ class ExampleVisitor implements xml.IVisitor {
|
||||
visitDeclaration(decl: xml.Declaration): void {}
|
||||
visitDoctype(doctype: xml.Doctype): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* XMB/XTB placeholders can only contain A-Z, 0-9 and _
|
||||
*
|
||||
* Because such restrictions do not exist on placeholder names generated locally, the
|
||||
* `PlaceholderMapper` is used to convert internal names to XMB names when the XMB file is
|
||||
* serialized and back from XTB to internal names when an XTB is loaded.
|
||||
*/
|
||||
export class XmbPlaceholderMapper implements PlaceholderMapper, i18n.Visitor {
|
||||
private internalToXmb: {[k: string]: string} = {};
|
||||
private xmbToNextId: {[k: string]: number} = {};
|
||||
private xmbToInternal: {[k: string]: string} = {};
|
||||
|
||||
// create a mapping from the message
|
||||
constructor(message: i18n.Message) { message.nodes.forEach(node => node.visit(this)); }
|
||||
|
||||
toPublicName(internalName: string): string {
|
||||
return this.internalToXmb.hasOwnProperty(internalName) ? this.internalToXmb[internalName] :
|
||||
null;
|
||||
}
|
||||
|
||||
toInternalName(publicName: string): string {
|
||||
return this.xmbToInternal.hasOwnProperty(publicName) ? this.xmbToInternal[publicName] : null;
|
||||
}
|
||||
|
||||
visitText(text: i18n.Text, ctx?: any): any { return null; }
|
||||
|
||||
visitContainer(container: i18n.Container, ctx?: any): any {
|
||||
container.children.forEach(child => child.visit(this));
|
||||
}
|
||||
|
||||
visitIcu(icu: i18n.Icu, ctx?: any): any {
|
||||
Object.keys(icu.cases).forEach(k => { icu.cases[k].visit(this); });
|
||||
}
|
||||
|
||||
visitTagPlaceholder(ph: i18n.TagPlaceholder, ctx?: any): any {
|
||||
this.addPlaceholder(ph.startName);
|
||||
ph.children.forEach(child => child.visit(this));
|
||||
this.addPlaceholder(ph.closeName);
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, ctx?: any): any { this.addPlaceholder(ph.name); }
|
||||
|
||||
// XMB placeholders could only contains A-Z, 0-9 and _
|
||||
private addPlaceholder(internalName: string): void {
|
||||
if (!internalName || this.internalToXmb.hasOwnProperty(internalName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let xmbName = internalName.toUpperCase().replace(/[^A-Z0-9_]/g, '_');
|
||||
|
||||
if (this.xmbToInternal.hasOwnProperty(xmbName)) {
|
||||
// Create a new XMB when it has already been used
|
||||
const nextId = this.xmbToNextId[xmbName];
|
||||
this.xmbToNextId[xmbName] = nextId + 1;
|
||||
xmbName = `${xmbName}_${nextId}`;
|
||||
} else {
|
||||
this.xmbToNextId[xmbName] = 1;
|
||||
}
|
||||
|
||||
this.internalToXmb[internalName] = xmbName;
|
||||
this.xmbToInternal[xmbName] = internalName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ import {XmlParser} from '../../ml_parser/xml_parser';
|
||||
import * as i18n from '../i18n_ast';
|
||||
import {I18nError} from '../parse_util';
|
||||
|
||||
import {Serializer} from './serializer';
|
||||
import {digest} from './xmb';
|
||||
import {PlaceholderMapper, Serializer} from './serializer';
|
||||
import {XmbPlaceholderMapper, digest} from './xmb';
|
||||
|
||||
const _TRANSLATIONS_TAG = 'translationbundle';
|
||||
const _TRANSLATION_TAG = 'translation';
|
||||
const _PLACEHOLDER_TAG = 'ph';
|
||||
|
||||
export class Xtb implements Serializer {
|
||||
export class Xtb extends Serializer {
|
||||
write(messages: i18n.Message[]): string { throw new Error('Unsupported'); }
|
||||
|
||||
load(content: string, url: string): {[msgId: string]: i18n.Node[]} {
|
||||
@@ -43,6 +43,10 @@ export class Xtb implements Serializer {
|
||||
}
|
||||
|
||||
digest(message: i18n.Message): string { return digest(message); }
|
||||
|
||||
createNameMapper(message: i18n.Message): PlaceholderMapper {
|
||||
return new XmbPlaceholderMapper(message);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract messages as xml nodes from the xtb file
|
||||
|
||||
@@ -11,7 +11,7 @@ import {HtmlParser} from '../ml_parser/html_parser';
|
||||
|
||||
import * as i18n from './i18n_ast';
|
||||
import {I18nError} from './parse_util';
|
||||
import {Serializer} from './serializers/serializer';
|
||||
import {PlaceholderMapper, Serializer} from './serializers/serializer';
|
||||
|
||||
/**
|
||||
* A container for translated messages
|
||||
@@ -21,16 +21,20 @@ export class TranslationBundle {
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
public digest: (m: i18n.Message) => string) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest);
|
||||
public digest: (m: i18n.Message) => string,
|
||||
public mapperFactory?: (m: i18n.Message) => PlaceholderMapper) {
|
||||
this._i18nToHtml = new I18nToHtmlVisitor(_i18nNodesByMsgId, digest, mapperFactory);
|
||||
}
|
||||
|
||||
// Creates a `TranslationBundle` by parsing the given `content` with the `serializer`.
|
||||
static load(content: string, url: string, serializer: Serializer): TranslationBundle {
|
||||
const i18nNodesByMsgId = serializer.load(content, url);
|
||||
const digestFn = (m: i18n.Message) => serializer.digest(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn);
|
||||
const mapperFactory = (m: i18n.Message) => serializer.createNameMapper(m);
|
||||
return new TranslationBundle(i18nNodesByMsgId, digestFn, mapperFactory);
|
||||
}
|
||||
|
||||
// Returns the translation as HTML nodes from the given source message.
|
||||
get(srcMsg: i18n.Message): html.Node[] {
|
||||
const html = this._i18nToHtml.convert(srcMsg);
|
||||
|
||||
@@ -46,15 +50,17 @@ export class TranslationBundle {
|
||||
|
||||
class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
private _srcMsg: i18n.Message;
|
||||
private _srcMsgStack: i18n.Message[] = [];
|
||||
private _contextStack: {msg: i18n.Message, mapper: (name: string) => string}[] = [];
|
||||
private _errors: I18nError[] = [];
|
||||
private _mapper: (name: string) => string;
|
||||
|
||||
constructor(
|
||||
private _i18nNodesByMsgId: {[msgId: string]: i18n.Node[]} = {},
|
||||
private _digest: (m: i18n.Message) => string) {}
|
||||
private _digest: (m: i18n.Message) => string,
|
||||
private _mapperFactory: (m: i18n.Message) => PlaceholderMapper) {}
|
||||
|
||||
convert(srcMsg: i18n.Message): {nodes: html.Node[], errors: I18nError[]} {
|
||||
this._srcMsgStack.length = 0;
|
||||
this._contextStack.length = 0;
|
||||
this._errors.length = 0;
|
||||
// i18n to text
|
||||
const text = this._convertToText(srcMsg);
|
||||
@@ -88,7 +94,7 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
}
|
||||
|
||||
visitPlaceholder(ph: i18n.Placeholder, context?: any): string {
|
||||
const phName = ph.name;
|
||||
const phName = this._mapper(ph.name);
|
||||
if (this._srcMsg.placeholders.hasOwnProperty(phName)) {
|
||||
return this._srcMsg.placeholders[phName];
|
||||
}
|
||||
@@ -105,14 +111,26 @@ class I18nToHtmlVisitor implements i18n.Visitor {
|
||||
|
||||
visitIcuPlaceholder(ph: i18n.IcuPlaceholder, context?: any): any { throw 'unreachable code'; }
|
||||
|
||||
/**
|
||||
* Convert a source message to a translated text string:
|
||||
* - text nodes are replaced with their translation,
|
||||
* - placeholders are replaced with their content,
|
||||
* - ICU nodes are converted to ICU expressions.
|
||||
*/
|
||||
private _convertToText(srcMsg: i18n.Message): string {
|
||||
const digest = this._digest(srcMsg);
|
||||
const mapper = this._mapperFactory ? this._mapperFactory(srcMsg) : null;
|
||||
|
||||
if (this._i18nNodesByMsgId.hasOwnProperty(digest)) {
|
||||
this._srcMsgStack.push(this._srcMsg);
|
||||
this._contextStack.push({msg: this._srcMsg, mapper: this._mapper});
|
||||
this._srcMsg = srcMsg;
|
||||
this._mapper = (name: string) => mapper ? mapper.toInternalName(name) : name;
|
||||
|
||||
const nodes = this._i18nNodesByMsgId[digest];
|
||||
const text = nodes.map(node => node.visit(this)).join('');
|
||||
this._srcMsg = this._srcMsgStack.pop();
|
||||
const context = this._contextStack.pop();
|
||||
this._srcMsg = context.msg;
|
||||
this._mapper = context.mapper;
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,10 +9,11 @@
|
||||
import {getHtmlTagDefinition} from './ml_parser/html_tags';
|
||||
|
||||
const _SELECTOR_REGEXP = new RegExp(
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
'(?:\\[([.-\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\:not\\()|' + //":not("
|
||||
'([-\\w]+)|' + // "tag"
|
||||
'(?:\\.([-\\w]+))|' + // ".class"
|
||||
// "-" should appear first in the regexp below as FF31 parses "[.-\w]" as a range
|
||||
'(?:\\[([-.\\w*]+)(?:=([^\\]]*))?\\])|' + // "[name]", "[name=value]"
|
||||
'(\\))|' + // ")"
|
||||
'(\\s*,\\s*)', // ","
|
||||
'g');
|
||||
|
||||
@@ -404,8 +404,9 @@ function createViewTopLevelStmts(view: CompileView, targetStatements: o.Statemen
|
||||
o.literal(view.component.template.ngContentSelectors.length),
|
||||
ViewEncapsulationEnum.fromValue(view.component.template.encapsulation),
|
||||
view.styles,
|
||||
o.literalMap(view.animations.map(
|
||||
(entry): [string, o.Expression] => [entry.name, entry.fnExp])),
|
||||
o.literalMap(
|
||||
view.animations.map((entry): [string, o.Expression] => [entry.name, entry.fnExp]),
|
||||
null, true),
|
||||
]))
|
||||
.toDeclStmt(o.importType(createIdentifier(Identifiers.RenderComponentType))));
|
||||
}
|
||||
|
||||
@@ -182,6 +182,12 @@ const XTB = `
|
||||
<ph name="START_TAG_DIV_1"/><ph name="ICU"/><ph name="CLOSE_TAG_DIV"></ph>
|
||||
</translation>
|
||||
<translation id="1491627405349178954">ca <ph name="START_BOLD_TEXT"/>devrait<ph name="CLOSE_BOLD_TEXT"/> marcher</translation>
|
||||
<translation id="i18n16">avec un ID explicite</translation>
|
||||
<translation id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {un} =2 {deux} other {<ph
|
||||
name="START_BOLD_TEXT"><ex><b></ex></ph>beaucoup<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</translation>
|
||||
<translation id="4085484936881858615">{VAR_PLURAL, plural, =0 {Pas de réponse} =1 {une réponse} other {<ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> réponse} }</translation>
|
||||
<translation id="4035252431381981115">FOO<ph name="START_LINK"><ex><a></ex></ph>BAR<ph name="CLOSE_LINK"><ex></a></ex></ph></translation>
|
||||
<translation id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></translation>
|
||||
</translationbundle>`;
|
||||
|
||||
// unused, for reference only
|
||||
@@ -210,6 +216,11 @@ const XMB = `
|
||||
<ph name="START_TAG_DIV_1"><ex><div></ex></ph><ph name="ICU"/><ph name="CLOSE_TAG_DIV"><ex></div></ex></ph>
|
||||
</msg>
|
||||
<msg id="1491627405349178954">it <ph name="START_BOLD_TEXT"><ex><b></ex></ph>should<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph> work</msg>
|
||||
<msg id="i18n16">with an explicit ID</msg>
|
||||
<msg id="i18n17">{VAR_PLURAL, plural, =0 {zero} =1 {one} =2 {two} other {<ph name="START_BOLD_TEXT"><ex><b></ex></ph>many<ph name="CLOSE_BOLD_TEXT"><ex></b></ex></ph>} }</msg>
|
||||
<msg id="4085484936881858615" desc="desc">{VAR_PLURAL, plural, =0 {Found no results} =1 {Found one result} other {Found <ph name="INTERPOLATION"><ex>INTERPOLATION</ex></ph> results} }</msg>
|
||||
<msg id="4035252431381981115">foo<ph name="START_LINK"><ex><a></ex></ph>bar<ph name="CLOSE_LINK"><ex></a></ex></ph></msg>
|
||||
<msg id="5339604010413301604"><ph name="MAP_NAME"><ex>MAP_NAME</ex></ph></msg>;
|
||||
</messagebundle>`;
|
||||
|
||||
const HTML = `
|
||||
@@ -262,4 +273,6 @@ const HTML = `
|
||||
}</div>
|
||||
|
||||
<div i18n id="i18n-18">foo<a i18n-title title="in a translatable section">bar</a></div>
|
||||
|
||||
<div i18n>{{ 'test' //i18n(ph="map name") }}</div>
|
||||
`;
|
||||
|
||||
@@ -42,7 +42,7 @@ export function main(): void {
|
||||
});
|
||||
}
|
||||
|
||||
class _TestSerializer implements Serializer {
|
||||
class _TestSerializer extends Serializer {
|
||||
write(messages: i18n.Message[]): string {
|
||||
return messages.map(msg => `${serializeNodes(msg.nodes)} (${msg.meaning}|${msg.description})`)
|
||||
.join('//');
|
||||
|
||||
@@ -8,6 +8,16 @@
|
||||
|
||||
import {AnimationStyles} from './animation_styles';
|
||||
|
||||
/**
|
||||
* `AnimationKeyframe` consists of a series of styles (contained within {@link AnimationStyles
|
||||
* `AnimationStyles`})
|
||||
* and an offset value indicating when those styles are applied within the `duration/delay/easing`
|
||||
* timings.
|
||||
* `AnimationKeyframe` is mostly an internal class which is designed to be used alongside {@link
|
||||
* Renderer#animate-anchor `Renderer.animate`}.
|
||||
*
|
||||
* @experimental Animation support is experimental
|
||||
*/
|
||||
export class AnimationKeyframe {
|
||||
constructor(public offset: number, public styles: AnimationStyles) {}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,20 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// having an import prevents dgeni from truncating out
|
||||
// the class description in the docs. DO NOT REMOVE.
|
||||
import {isPresent} from '../facade/lang';
|
||||
|
||||
/**
|
||||
* `AnimationStyles` consists of a collection of key/value maps containing CSS-based style data
|
||||
* that can either be used as initial styling data or apart of a series of keyframes within an
|
||||
* animation.
|
||||
* This class is mostly internal, and it is designed to be used alongside
|
||||
* {@link AnimationKeyframe `AnimationKeyframe`} and {@link Renderer#animate-anchor
|
||||
* `Renderer.animate`}.
|
||||
*
|
||||
* @experimental Animation support is experimental
|
||||
*/
|
||||
export class AnimationStyles {
|
||||
constructor(public styles: {[key: string]: string | number}[]) {}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import {isPromise} from '../src/util/lang';
|
||||
|
||||
import {ApplicationInitStatus} from './application_init';
|
||||
import {APP_BOOTSTRAP_LISTENER, PLATFORM_INITIALIZER} from './application_tokens';
|
||||
import {ChangeDetectorRef} from './change_detection/change_detector_ref';
|
||||
import {Console} from './console';
|
||||
import {Injectable, Injector, OpaqueToken, Optional, Provider, ReflectiveInjector} from './di';
|
||||
import {CompilerFactory, CompilerOptions} from './linker/compiler';
|
||||
|
||||
@@ -35,4 +35,6 @@ export * from './core_private_export';
|
||||
export * from './animation/metadata';
|
||||
export {AnimationTransitionEvent} from './animation/animation_transition_event';
|
||||
export {AnimationPlayer} from './animation/animation_player';
|
||||
export {AnimationStyles} from './animation/animation_styles';
|
||||
export {AnimationKeyframe} from './animation/animation_keyframe';
|
||||
export {Sanitizer, SecurityContext} from './security';
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Determine if the argument is shaped like a Promise
|
||||
*/
|
||||
export function isPromise(obj: any): obj is Promise<any> {
|
||||
// allow any Promise/A+ compliant thenable.
|
||||
// It's up to the caller to ensure that obj.then conforms to the spec
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* Convenience to throw an Error with 'unimplemented' as the message.
|
||||
*/
|
||||
export function unimplemented(): any {
|
||||
throw new Error('unimplemented');
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ export class Request extends Body {
|
||||
case 'text/html':
|
||||
return ContentType.TEXT;
|
||||
case 'application/octet-stream':
|
||||
return ContentType.BLOB;
|
||||
return this._body instanceof ArrayBuffer ? ContentType.ARRAY_BUFFER : ContentType.BLOB;
|
||||
default:
|
||||
return this.detectContentTypeFromBody();
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export class Request extends Body {
|
||||
return ContentType.BLOB;
|
||||
} else if (this._body instanceof ArrayBuffer) {
|
||||
return ContentType.ARRAY_BUFFER;
|
||||
} else if (this._body && typeof this._body == 'object') {
|
||||
} else if (this._body && typeof this._body === 'object') {
|
||||
return ContentType.JSON;
|
||||
} else {
|
||||
return ContentType.TEXT;
|
||||
@@ -167,4 +167,4 @@ const noop = function() {};
|
||||
const w = typeof window == 'object' ? window : noop;
|
||||
const FormData = (w as any /** TODO #9100 */)['FormData'] || noop;
|
||||
const Blob = (w as any /** TODO #9100 */)['Blob'] || noop;
|
||||
const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
|
||||
export const ArrayBuffer = (w as any /** TODO #9100 */)['ArrayBuffer'] || noop;
|
||||
|
||||
@@ -36,7 +36,7 @@ import {Headers} from './headers';
|
||||
*/
|
||||
export class Response extends Body {
|
||||
/**
|
||||
* One of "basic", "cors", "default", "error, or "opaque".
|
||||
* One of "basic", "cors", "default", "error", or "opaque".
|
||||
*
|
||||
* Defaults to "default".
|
||||
*/
|
||||
|
||||
@@ -11,7 +11,7 @@ import {describe, expect, it} from '@angular/core/testing/testing_internal';
|
||||
import {RequestOptions} from '../src/base_request_options';
|
||||
import {ContentType} from '../src/enums';
|
||||
import {Headers} from '../src/headers';
|
||||
import {Request} from '../src/static_request';
|
||||
import {ArrayBuffer, Request} from '../src/static_request';
|
||||
|
||||
export function main() {
|
||||
describe('Request', () => {
|
||||
@@ -76,6 +76,17 @@ export function main() {
|
||||
|
||||
expect(req.detectContentType()).toEqual(ContentType.BLOB);
|
||||
});
|
||||
|
||||
it('should not create a blob out of ArrayBuffer', () => {
|
||||
const req = new Request(new RequestOptions({
|
||||
url: 'test',
|
||||
method: 'GET',
|
||||
body: new ArrayBuffer(1),
|
||||
headers: new Headers({'content-type': 'application/octet-stream'})
|
||||
}));
|
||||
|
||||
expect(req.detectContentType()).toEqual(ContentType.ARRAY_BUFFER);
|
||||
});
|
||||
});
|
||||
|
||||
it('should return empty string if no body is present', () => {
|
||||
|
||||
@@ -96,7 +96,9 @@ export class WebAnimationsPlayer implements AnimationPlayer {
|
||||
|
||||
/** @internal */
|
||||
_triggerWebAnimation(element: any, keyframes: any[], options: any): DomAnimatePlayer {
|
||||
return <DomAnimatePlayer>element.animate(keyframes, options);
|
||||
// jscompiler doesn't seem to know animate is a native property because it's not fully
|
||||
// supported yet across common browsers (we polyfill it for Edge/Safari) [CL #143630929]
|
||||
return <DomAnimatePlayer>element['animate'](keyframes, options);
|
||||
}
|
||||
|
||||
get domPlayer() { return this._player; }
|
||||
|
||||
@@ -30,9 +30,14 @@ import {sanitizeUrl} from './url_sanitizer';
|
||||
const VALUES = '[-,."\'%_!# a-zA-Z0-9]+';
|
||||
const TRANSFORMATION_FNS = '(?:matrix|translate|scale|rotate|skew|perspective)(?:X|Y|3d)?';
|
||||
const COLOR_FNS = '(?:rgb|hsl)a?';
|
||||
const FN_ARGS = '\\([-0-9.%, a-zA-Z]+\\)';
|
||||
const SAFE_STYLE_VALUE =
|
||||
new RegExp(`^(${VALUES}|(?:${TRANSFORMATION_FNS}|${COLOR_FNS})${FN_ARGS})$`, 'g');
|
||||
const GRADIENTS = '(?:repeating-)?(?:linear|radial)-gradient';
|
||||
const CSS3_FNS = '(?:calc|attr)';
|
||||
const FN_ARGS = '\\([-0-9.%, #a-zA-Z]+\\)';
|
||||
const SAFE_STYLE_VALUE = new RegExp(
|
||||
`^(${VALUES}|` +
|
||||
`(?:${TRANSFORMATION_FNS}|${COLOR_FNS}|${GRADIENTS}|${CSS3_FNS})` +
|
||||
`${FN_ARGS})$`,
|
||||
'g');
|
||||
|
||||
/**
|
||||
* Matches a `url(...)` value with an arbitrary argument as long as it does
|
||||
|
||||
@@ -39,6 +39,16 @@ export function main() {
|
||||
expectSanitize('translateX(12px, -5px)').toEqual('translateX(12px, -5px)');
|
||||
expectSanitize('scale3d(1, 1, 2)').toEqual('scale3d(1, 1, 2)');
|
||||
});
|
||||
t.it('accepts gradients', () => {
|
||||
expectSanitize('linear-gradient(to bottom, #fg34a1, #bada55)')
|
||||
.toEqual('linear-gradient(to bottom, #fg34a1, #bada55)');
|
||||
expectSanitize('repeating-radial-gradient(ellipse cover, black, red, black, red)')
|
||||
.toEqual('repeating-radial-gradient(ellipse cover, black, red, black, red)');
|
||||
});
|
||||
t.it('accepts calc', () => { expectSanitize('calc(90%-123px)').toEqual('calc(90%-123px)'); });
|
||||
t.it('accepts attr', () => {
|
||||
expectSanitize('attr(value string)').toEqual('attr(value string)');
|
||||
});
|
||||
t.it('sanitizes URLs', () => {
|
||||
expectSanitize('url(foo/bar.png)').toEqual('url(foo/bar.png)');
|
||||
expectSanitize('url( foo/bar.png\n )').toEqual('url( foo/bar.png\n )');
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/core';
|
||||
import {NgModuleFactory, Type} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {PRIMARY_OUTLET} from './shared';
|
||||
import {UrlSegment, UrlSegmentGroup} from './url_tree';
|
||||
@@ -310,7 +310,8 @@ export type ResolveData = {
|
||||
* See {@link Routes} for more details.
|
||||
* @stable
|
||||
*/
|
||||
export type LoadChildrenCallback = () => Type<any>| Promise<Type<any>>| Observable<Type<any>>;
|
||||
export type LoadChildrenCallback = () =>
|
||||
Type<any>| NgModuleFactory<any>| Promise<Type<any>>| Observable<Type<any>>;
|
||||
|
||||
/**
|
||||
* @whatItDoes The type of `loadChildren`.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AfterContentInit, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer} from '@angular/core';
|
||||
import {AfterContentInit, ChangeDetectorRef, ContentChildren, Directive, ElementRef, Input, OnChanges, OnDestroy, QueryList, Renderer, SimpleChanges} from '@angular/core';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
|
||||
import {NavigationEnd, Router} from '../router';
|
||||
@@ -14,6 +14,7 @@ import {NavigationEnd, Router} from '../router';
|
||||
import {RouterLink, RouterLinkWithHref} from './router_link';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @whatItDoes Lets you add a CSS class to an element when the link's route becomes active.
|
||||
*
|
||||
@@ -89,10 +90,13 @@ export class RouterLinkActive implements OnChanges,
|
||||
|
||||
private classes: string[] = [];
|
||||
private subscription: Subscription;
|
||||
private active: boolean = false;
|
||||
|
||||
@Input() routerLinkActiveOptions: {exact: boolean} = {exact: false};
|
||||
|
||||
constructor(private router: Router, private element: ElementRef, private renderer: Renderer) {
|
||||
constructor(
|
||||
private router: Router, private element: ElementRef, private renderer: Renderer,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
this.subscription = router.events.subscribe(s => {
|
||||
if (s instanceof NavigationEnd) {
|
||||
this.update();
|
||||
@@ -100,35 +104,34 @@ export class RouterLinkActive implements OnChanges,
|
||||
});
|
||||
}
|
||||
|
||||
get isActive(): boolean { return this.hasActiveLink(); }
|
||||
get isActive(): boolean { return this.active; }
|
||||
|
||||
ngAfterContentInit(): void {
|
||||
this.links.changes.subscribe(s => this.update());
|
||||
this.linksWithHrefs.changes.subscribe(s => this.update());
|
||||
this.links.changes.subscribe(_ => this.update());
|
||||
this.linksWithHrefs.changes.subscribe(_ => this.update());
|
||||
this.update();
|
||||
}
|
||||
|
||||
@Input()
|
||||
set routerLinkActive(data: string[]|string) {
|
||||
if (Array.isArray(data)) {
|
||||
this.classes = <any>data;
|
||||
} else {
|
||||
this.classes = data.split(' ');
|
||||
}
|
||||
const classes = Array.isArray(data) ? data : data.split(' ');
|
||||
this.classes = classes.filter(c => !!c);
|
||||
}
|
||||
|
||||
ngOnChanges(changes: {}): any { this.update(); }
|
||||
ngOnDestroy(): any { this.subscription.unsubscribe(); }
|
||||
ngOnChanges(changes: SimpleChanges): void { this.update(); }
|
||||
ngOnDestroy(): void { this.subscription.unsubscribe(); }
|
||||
|
||||
private update(): void {
|
||||
if (!this.links || !this.linksWithHrefs || !this.router.navigated) return;
|
||||
const hasActiveLinks = this.hasActiveLinks();
|
||||
|
||||
const isActive = this.hasActiveLink();
|
||||
this.classes.forEach(c => {
|
||||
if (c) {
|
||||
this.renderer.setElementClass(this.element.nativeElement, c, isActive);
|
||||
}
|
||||
});
|
||||
// react only when status has changed to prevent unnecessary dom updates
|
||||
if (this.active !== hasActiveLinks) {
|
||||
this.active = hasActiveLinks;
|
||||
this.classes.forEach(
|
||||
c => this.renderer.setElementClass(this.element.nativeElement, c, hasActiveLinks));
|
||||
this.cdr.detectChanges();
|
||||
}
|
||||
}
|
||||
|
||||
private isLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
|
||||
@@ -136,7 +139,7 @@ export class RouterLinkActive implements OnChanges,
|
||||
router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
|
||||
}
|
||||
|
||||
private hasActiveLink(): boolean {
|
||||
private hasActiveLinks(): boolean {
|
||||
return this.links.some(this.isLinkActive(this.router)) ||
|
||||
this.linksWithHrefs.some(this.isLinkActive(this.router));
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ export interface CanActivateChild {
|
||||
* {
|
||||
* path: 'team/:id',
|
||||
* component: TeamCmp,
|
||||
* canActivate: ['canDeactivateTeam']
|
||||
* canDeactivate: ['canDeactivateTeam']
|
||||
* }
|
||||
* ])
|
||||
* ],
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModuleFactory} from '@angular/core';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {fromPromise} from 'rxjs/observable/fromPromise';
|
||||
import {of } from 'rxjs/observable/of';
|
||||
@@ -126,7 +127,8 @@ export function andObservables(observables: Observable<Observable<any>>): Observ
|
||||
return every.call(merged$, (result: any) => result === true);
|
||||
}
|
||||
|
||||
export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Observable<T> {
|
||||
export function wrapIntoObservable<T>(value: T | NgModuleFactory<T>| Promise<T>| Observable<T>):
|
||||
Observable<T> {
|
||||
if (value instanceof Observable) {
|
||||
return value;
|
||||
}
|
||||
@@ -136,4 +138,4 @@ export function wrapIntoObservable<T>(value: T | Promise<T>| Observable<T>): Obs
|
||||
}
|
||||
|
||||
return of (value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2032,6 +2032,8 @@ describe('Integration', () => {
|
||||
@Component({
|
||||
template: `<a routerLink="/team" routerLinkActive #rla="routerLinkActive"></a>
|
||||
<p>{{rla.isActive}}</p>
|
||||
<span *ngIf="rla.isActive"></span>
|
||||
<span [ngClass]="{'highlight': rla.isActive}"></span>
|
||||
<router-outlet></router-outlet>`
|
||||
})
|
||||
class ComponentWithRouterLink {
|
||||
@@ -2051,15 +2053,15 @@ describe('Integration', () => {
|
||||
}
|
||||
]);
|
||||
|
||||
const f = TestBed.createComponent(ComponentWithRouterLink);
|
||||
const fixture = TestBed.createComponent(ComponentWithRouterLink);
|
||||
router.navigateByUrl('/team');
|
||||
advance(f);
|
||||
expect(() => advance(fixture)).not.toThrow();
|
||||
|
||||
const paragraph = f.nativeElement.querySelector('p');
|
||||
const paragraph = fixture.nativeElement.querySelector('p');
|
||||
expect(paragraph.textContent).toEqual('true');
|
||||
|
||||
router.navigateByUrl('/otherteam');
|
||||
advance(f);
|
||||
advance(fixture);
|
||||
|
||||
expect(paragraph.textContent).toEqual('false');
|
||||
}));
|
||||
|
||||
@@ -150,10 +150,12 @@ export class UpgradeModule {
|
||||
*/
|
||||
bootstrap(
|
||||
element: Element, modules: string[] = [], config?: any /*angular.IAngularBootstrapConfig*/) {
|
||||
const INIT_MODULE_NAME = UPGRADE_MODULE_NAME + '.init';
|
||||
|
||||
// Create an ng1 module to bootstrap
|
||||
const upgradeModule =
|
||||
const initModule =
|
||||
angular
|
||||
.module(UPGRADE_MODULE_NAME, modules)
|
||||
.module(INIT_MODULE_NAME, [])
|
||||
|
||||
.value(INJECTOR_KEY, this.injector)
|
||||
|
||||
@@ -205,6 +207,8 @@ export class UpgradeModule {
|
||||
}
|
||||
]);
|
||||
|
||||
const upgradeModule = angular.module(UPGRADE_MODULE_NAME, [INIT_MODULE_NAME].concat(modules));
|
||||
|
||||
// Make sure resumeBootstrap() only exists if the current bootstrap is deferred
|
||||
const windowAngular = (window as any /** TODO #???? */)['angular'];
|
||||
windowAngular.resumeBootstrap = undefined;
|
||||
|
||||
@@ -577,10 +577,6 @@ export class UpgradeAdapter {
|
||||
})
|
||||
.then((ref: NgModuleRef<any>) => {
|
||||
this.moduleRef = ref;
|
||||
let subscription = this.ngZone.onMicrotaskEmpty.subscribe({
|
||||
next: (_: any) => this.ngZone.runOutsideAngular(() => rootScope.$evalAsync())
|
||||
});
|
||||
rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
|
||||
this.ngZone.run(() => {
|
||||
if (rootScopePrototype) {
|
||||
rootScopePrototype.$apply = original$applyFn; // restore original $apply
|
||||
@@ -591,7 +587,12 @@ export class UpgradeAdapter {
|
||||
}
|
||||
});
|
||||
})
|
||||
.then(() => this.ng2BootstrapDeferred.resolve(ng1Injector), onError);
|
||||
.then(() => this.ng2BootstrapDeferred.resolve(ng1Injector), onError)
|
||||
.then(() => {
|
||||
let subscription =
|
||||
this.ngZone.onMicrotaskEmpty.subscribe({next: () => rootScope.$digest()});
|
||||
rootScope.$on('$destroy', () => { subscription.unsubscribe(); });
|
||||
});
|
||||
})
|
||||
.catch((e) => this.ng2BootstrapDeferred.reject(e));
|
||||
}
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgModule, OpaqueToken, destroyPlatform} from '@angular/core';
|
||||
import {Injector, NgModule, OpaqueToken, destroyPlatform} from '@angular/core';
|
||||
import {async} from '@angular/core/testing';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
|
||||
import * as angular from '@angular/upgrade/src/angular_js';
|
||||
import {$INJECTOR, INJECTOR_KEY} from '@angular/upgrade/src/aot/constants';
|
||||
import {UpgradeModule, downgradeInjectable} from '@angular/upgrade/static';
|
||||
|
||||
import {bootstrap, html} from '../test_helpers';
|
||||
@@ -76,5 +77,27 @@ export function main() {
|
||||
expect(ng2Injector.get(Ng1Service)).toBe('ng1 service value');
|
||||
});
|
||||
}));
|
||||
|
||||
it('should initialize the upgraded injector before application run blocks are executed',
|
||||
async(() => {
|
||||
let runBlockTriggered = false;
|
||||
|
||||
const ng1Module = angular.module('ng1Module', []).run([
|
||||
INJECTOR_KEY,
|
||||
function(injector: Injector) {
|
||||
runBlockTriggered = true;
|
||||
expect(injector.get($INJECTOR)).toBeDefined();
|
||||
}
|
||||
]);
|
||||
|
||||
@NgModule({imports: [BrowserModule, UpgradeModule]})
|
||||
class Ng2Module {
|
||||
ngDoBootstrap() {}
|
||||
}
|
||||
|
||||
bootstrap(platformBrowserDynamic(), Ng2Module, html('<div>'), ng1Module).then(() => {
|
||||
expect(runBlockTriggered).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ChangeDetectorRef, Class, Component, EventEmitter, NO_ERRORS_SCHEMA, NgModule, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core';
|
||||
import {ChangeDetectorRef, Class, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgZone, 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';
|
||||
@@ -18,7 +18,89 @@ export function main() {
|
||||
beforeEach(() => destroyPlatform());
|
||||
afterEach(() => destroyPlatform());
|
||||
|
||||
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
||||
describe('(basic use)', () => {
|
||||
it('should have angular 1 loaded', () => expect(angular.version.major).toBe(1));
|
||||
|
||||
it('should instantiate ng2 in ng1 template and project content', async(() => {
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
||||
const Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
|
||||
constructor: function() {}
|
||||
});
|
||||
|
||||
const element =
|
||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
||||
const Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||
}).Class({constructor: function Ng2() {}});
|
||||
|
||||
const Ng2Module = NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||
imports: [BrowserModule],
|
||||
}).Class({constructor: function Ng2Module() {}});
|
||||
|
||||
ng1Module.directive('ng1', () => {
|
||||
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
|
||||
});
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
|
||||
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('supports the compilerOptions argument', async(() => {
|
||||
const platformRef = platformBrowserDynamic();
|
||||
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
|
||||
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
const Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'NG2' }}(<ng-content></ng-content>)`
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
const element =
|
||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
||||
|
||||
const Ng2AppModule =
|
||||
NgModule({
|
||||
declarations: [Ng2],
|
||||
imports: [BrowserModule],
|
||||
}).Class({constructor: function Ng2AppModule() {}, ngDoBootstrap: function() {}});
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect((platformRef as any)._bootstrapModuleWithZone)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(Function), {providers: []}, jasmine.any(Object),
|
||||
jasmine.any(Function));
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('bootstrap errors', () => {
|
||||
let adapter: UpgradeAdapter;
|
||||
@@ -51,98 +133,18 @@ export function main() {
|
||||
}));
|
||||
|
||||
it('should output an error message to the console and re-throw', fakeAsync(() => {
|
||||
let consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
|
||||
const consoleErrorSpy: jasmine.Spy = spyOn(console, 'error');
|
||||
expect(() => {
|
||||
adapter.bootstrap(html('<ng2></ng2>'), ['ng1']);
|
||||
flushMicrotasks();
|
||||
}).toThrowError();
|
||||
let args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
||||
const args: any[] = consoleErrorSpy.calls.mostRecent().args;
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
expect(args.length).toBeGreaterThan(0);
|
||||
expect(args[0]).toEqual(jasmine.any(Error));
|
||||
}));
|
||||
});
|
||||
|
||||
it('should instantiate ng2 in ng1 template and project content', async(() => {
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
||||
const Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'NG2' }}(<ng-content></ng-content>)`,
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
const Ng2Module = NgModule({declarations: [Ng2], imports: [BrowserModule]}).Class({
|
||||
constructor: function() {}
|
||||
});
|
||||
|
||||
const element =
|
||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2Module);
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(document.body.textContent).toEqual('ng1[NG2(~ng-content~)]');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should instantiate ng1 in ng2 template and project content', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
|
||||
const Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'ng2(' }}<ng1>{{'transclude'}}</ng1>{{ ')' }}`,
|
||||
}).Class({constructor: function Ng2() {}});
|
||||
|
||||
const Ng2Module = NgModule({
|
||||
declarations: [adapter.upgradeNg1Component('ng1'), Ng2],
|
||||
imports: [BrowserModule],
|
||||
}).Class({constructor: function Ng2Module() {}});
|
||||
|
||||
ng1Module.directive('ng1', () => {
|
||||
return {transclude: true, template: '{{ "ng1" }}(<ng-transclude></ng-transclude>)'};
|
||||
});
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
|
||||
const element = html('<div>{{\'ng1(\'}}<ng2></ng2>{{\')\'}}</div>');
|
||||
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect(document.body.textContent).toEqual('ng1(ng2(ng1(transclude)))');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('supports the compilerOptions argument', async(() => {
|
||||
const platformRef = platformBrowserDynamic();
|
||||
spyOn(platformRef, '_bootstrapModuleWithZone').and.callThrough();
|
||||
|
||||
const ng1Module = angular.module('ng1', []);
|
||||
const Ng2 = Component({
|
||||
selector: 'ng2',
|
||||
template: `{{ 'NG2' }}(<ng-content></ng-content>)`
|
||||
}).Class({constructor: function() {}});
|
||||
|
||||
const element =
|
||||
html('<div>{{ \'ng1[\' }}<ng2>~{{ \'ng-content\' }}~</ng2>{{ \']\' }}</div>');
|
||||
|
||||
const Ng2AppModule =
|
||||
NgModule({
|
||||
declarations: [Ng2],
|
||||
imports: [BrowserModule],
|
||||
}).Class({constructor: function Ng2AppModule() {}, ngDoBootstrap: function() {}});
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(Ng2AppModule, {providers: []});
|
||||
ng1Module.directive('ng2', adapter.downgradeNg2Component(Ng2));
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
expect((platformRef as any)._bootstrapModuleWithZone)
|
||||
.toHaveBeenCalledWith(
|
||||
jasmine.any(Function), {providers: []}, jasmine.any(Object),
|
||||
jasmine.any(Function));
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('scope/component change-detection', () => {
|
||||
it('should interleave scope and component expressions', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
@@ -184,6 +186,87 @@ export function main() {
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
it('should propagate changes to a downgraded component inside the ngZone', async(() => {
|
||||
let appComponent: AppComponent;
|
||||
let upgradeRef: UpgradeAdapterRef;
|
||||
|
||||
@Component({selector: 'my-app', template: '<my-child [value]="value"></my-child>'})
|
||||
class AppComponent {
|
||||
value: number;
|
||||
constructor() { appComponent = this; }
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-child',
|
||||
template: '<div>{{valueFromPromise}}',
|
||||
})
|
||||
class ChildComponent {
|
||||
valueFromPromise: number;
|
||||
@Input()
|
||||
set value(v: number) { expect(NgZone.isInAngularZone()).toBe(true); }
|
||||
|
||||
constructor(private zone: NgZone) {}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['value'].isFirstChange()) return;
|
||||
|
||||
this.zone.onMicrotaskEmpty.subscribe(() => {
|
||||
expect(element.textContent).toEqual('5');
|
||||
upgradeRef.dispose();
|
||||
});
|
||||
|
||||
Promise.resolve().then(() => this.valueFromPromise = changes['value'].currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [AppComponent, ChildComponent], imports: [BrowserModule]})
|
||||
class Ng2Module {
|
||||
}
|
||||
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
const ng1Module = angular.module('ng1', []).directive(
|
||||
'myApp', adapter.downgradeNg2Component(AppComponent));
|
||||
|
||||
const element = html('<my-app></my-app>');
|
||||
|
||||
adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
upgradeRef = ref;
|
||||
appComponent.value = 5;
|
||||
});
|
||||
}));
|
||||
|
||||
// This test demonstrates https://github.com/angular/angular/issues/6385
|
||||
// which was invalidly fixed by https://github.com/angular/angular/pull/6386
|
||||
// it('should not trigger $digest from an async operation in a watcher', async(() => {
|
||||
// @Component({selector: 'my-app', template: ''})
|
||||
// class AppComponent {
|
||||
// }
|
||||
|
||||
// @NgModule({declarations: [AppComponent], imports: [BrowserModule]})
|
||||
// class Ng2Module {
|
||||
// }
|
||||
|
||||
// const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
// const ng1Module = angular.module('ng1', []).directive(
|
||||
// 'myApp', adapter.downgradeNg2Component(AppComponent));
|
||||
|
||||
// const element = html('<my-app></my-app>');
|
||||
|
||||
// adapter.bootstrap(element, ['ng1']).ready((ref) => {
|
||||
// let doTimeout = false;
|
||||
// let timeoutId: number;
|
||||
// ref.ng1RootScope.$watch(() => {
|
||||
// if (doTimeout && !timeoutId) {
|
||||
// timeoutId = window.setTimeout(function() {
|
||||
// timeoutId = null;
|
||||
// }, 10);
|
||||
// }
|
||||
// });
|
||||
// doTimeout = true;
|
||||
// });
|
||||
// }));
|
||||
});
|
||||
|
||||
describe('downgrade ng2 component', () => {
|
||||
@@ -388,6 +471,31 @@ export function main() {
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should allow attribute selectors for components in ng2', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
||||
const ng1Module = angular.module('myExample', []);
|
||||
|
||||
@Component({selector: '[works]', template: 'works!'})
|
||||
class WorksComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'root-component', template: 'It <div works></div>'})
|
||||
class RootComponent {
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], declarations: [RootComponent, WorksComponent]})
|
||||
class MyNg2Module {
|
||||
}
|
||||
|
||||
ng1Module.directive('rootComponent', adapter.downgradeNg2Component(RootComponent));
|
||||
|
||||
document.body.innerHTML = '<root-component></root-component>';
|
||||
adapter.bootstrap(document.body.firstElementChild, ['myExample']).ready((ref) => {
|
||||
expect(multiTrim(document.body.textContent)).toEqual('It works!');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
});
|
||||
|
||||
describe('upgrade ng1 component', () => {
|
||||
@@ -1627,31 +1735,6 @@ export function main() {
|
||||
}));
|
||||
});
|
||||
|
||||
it('should allow attribute selectors for components in ng2', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => MyNg2Module));
|
||||
const ng1Module = angular.module('myExample', []);
|
||||
|
||||
@Component({selector: '[works]', template: 'works!'})
|
||||
class WorksComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'root-component', template: 'It <div works></div>'})
|
||||
class RootComponent {
|
||||
}
|
||||
|
||||
@NgModule({imports: [BrowserModule], declarations: [RootComponent, WorksComponent]})
|
||||
class MyNg2Module {
|
||||
}
|
||||
|
||||
ng1Module.directive('rootComponent', adapter.downgradeNg2Component(RootComponent));
|
||||
|
||||
document.body.innerHTML = '<root-component></root-component>';
|
||||
adapter.bootstrap(document.body.firstElementChild, ['myExample']).ready((ref) => {
|
||||
expect(multiTrim(document.body.textContent)).toEqual('It works!');
|
||||
ref.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
describe('examples', () => {
|
||||
it('should verify UpgradeAdapter example', async(() => {
|
||||
const adapter: UpgradeAdapter = new UpgradeAdapter(forwardRef(() => Ng2Module));
|
||||
|
||||
+2637
-1185
File diff suppressed because it is too large
Load Diff
Generated
+2639
-1187
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "angular-srcs",
|
||||
"version": "2.4.3",
|
||||
"version": "2.4.5",
|
||||
"private": true,
|
||||
"branchPattern": "2.0.*",
|
||||
"description": "Angular 2 - a web framework for modern web apps",
|
||||
@@ -79,7 +79,7 @@
|
||||
"source-map-support": "^0.4.2",
|
||||
"systemjs": "0.18.10",
|
||||
"ts-api-guardian": "0.1.4",
|
||||
"tsickle": "^0.2.1",
|
||||
"tsickle": "^0.2.4",
|
||||
"tslint": "^4.1.1",
|
||||
"tslint-eslint-rules": "^3.1.0",
|
||||
"typescript": "^2.0.2",
|
||||
|
||||
+17
@@ -44,6 +44,13 @@ export declare class AnimationGroupMetadata extends AnimationWithStepsMetadata {
|
||||
constructor(_steps: AnimationMetadata[]);
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class AnimationKeyframe {
|
||||
offset: number;
|
||||
styles: AnimationStyles;
|
||||
constructor(offset: number, styles: AnimationStyles);
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class AnimationKeyframesSequenceMetadata extends AnimationMetadata {
|
||||
steps: AnimationStyleMetadata[];
|
||||
@@ -106,6 +113,16 @@ export declare class AnimationStyleMetadata extends AnimationMetadata {
|
||||
}>, offset?: number);
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class AnimationStyles {
|
||||
styles: {
|
||||
[key: string]: string | number;
|
||||
}[];
|
||||
constructor(styles: {
|
||||
[key: string]: string | number;
|
||||
}[]);
|
||||
}
|
||||
|
||||
/** @experimental */
|
||||
export declare class AnimationTransitionEvent {
|
||||
fromState: string;
|
||||
|
||||
+4
-4
@@ -85,7 +85,7 @@ export interface ExtraOptions {
|
||||
export declare type LoadChildren = string | LoadChildrenCallback;
|
||||
|
||||
/** @stable */
|
||||
export declare type LoadChildrenCallback = () => Type<any> | Promise<Type<any>> | Observable<Type<any>>;
|
||||
export declare type LoadChildrenCallback = () => Type<any> | NgModuleFactory<any> | Promise<Type<any>> | Observable<Type<any>>;
|
||||
|
||||
/** @stable */
|
||||
export declare class NavigationCancel {
|
||||
@@ -262,10 +262,10 @@ export declare class RouterLinkActive implements OnChanges, OnDestroy, AfterCont
|
||||
routerLinkActiveOptions: {
|
||||
exact: boolean;
|
||||
};
|
||||
constructor(router: Router, element: ElementRef, renderer: Renderer);
|
||||
constructor(router: Router, element: ElementRef, renderer: Renderer, cdr: ChangeDetectorRef);
|
||||
ngAfterContentInit(): void;
|
||||
ngOnChanges(changes: {}): any;
|
||||
ngOnDestroy(): any;
|
||||
ngOnChanges(changes: SimpleChanges): void;
|
||||
ngOnDestroy(): void;
|
||||
}
|
||||
|
||||
/** @stable */
|
||||
|
||||
Reference in New Issue
Block a user