parent
975c269752
commit
ca40565f9a
|
@ -6,7 +6,7 @@
|
|||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import './ng_dev_mode';
|
||||
|
||||
import {resolveForwardRef} from '../di/forward_ref';
|
||||
import {InjectionToken} from '../di/injection_token';
|
||||
import {Injector} from '../di/injector';
|
||||
|
@ -16,6 +16,7 @@ import {Sanitizer} from '../sanitization/security';
|
|||
import {StyleSanitizeFn} from '../sanitization/style_sanitizer';
|
||||
import {Type} from '../type';
|
||||
import {noop} from '../util/noop';
|
||||
|
||||
import {assertDefined, assertEqual, assertLessThan, assertNotEqual} from './assert';
|
||||
import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
|
||||
|
@ -40,7 +41,7 @@ import {createStylingContextTemplate, renderStyleAndClassBindings, updateClassPr
|
|||
import {BoundPlayerFactory} from './styling/player_factory';
|
||||
import {getStylingContext} from './styling/util';
|
||||
import {NO_CHANGE} from './tokens';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readPatchedLViewData, stringify} from './util';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isDifferent, loadInternal, readElementValue, readPatchedLViewData, stringify} from './util';
|
||||
|
||||
/**
|
||||
* A permanent marker promise which signifies that the current CD tree is
|
||||
|
@ -125,6 +126,12 @@ export function setHostBindings(tView: TView, viewData: LViewData): void {
|
|||
viewData[BINDING_INDEX] = bindingRootIndex;
|
||||
// We must subtract the header offset because the load() instruction
|
||||
// expects a raw, unadjusted index.
|
||||
// <HACK(misko)>: set the `previousOrParentTNode` so that hostBindings functions can
|
||||
// correctly retrieve it. This should be removed once we call the hostBindings function
|
||||
// inline as part of the `RenderFlags.Create` because in that case the value will already be
|
||||
// correctly set.
|
||||
setPreviousOrParentTNode(getTView().data[currentElementIndex + HEADER_OFFSET] as TNode);
|
||||
// </HACK>
|
||||
instruction(currentDirectiveIndex - HEADER_OFFSET, currentElementIndex);
|
||||
currentDirectiveIndex++;
|
||||
}
|
||||
|
@ -1069,17 +1076,21 @@ function generatePropertyAliases(
|
|||
* This instruction is meant to handle the [class.foo]="exp" case
|
||||
*
|
||||
* @param index The index of the element to update in the data array
|
||||
* @param className Name of class to toggle. Because it is going to DOM, this is not subject to
|
||||
* @param classIndex Index of class to toggle. Because it is going to DOM, this is not subject to
|
||||
* renaming as part of minification.
|
||||
* @param value A value indicating if a given class should be added or removed.
|
||||
* @param directiveIndex the index for the directive that is attempting to change styling.
|
||||
*/
|
||||
export function elementClassProp(
|
||||
index: number, stylingIndex: number, value: boolean | PlayerFactory,
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory,
|
||||
directiveIndex?: number): void {
|
||||
if (directiveIndex != undefined) {
|
||||
return hackImplementationOfElementClassProp(
|
||||
index, classIndex, value, directiveIndex); // proper supported in next PR
|
||||
}
|
||||
const val =
|
||||
(value instanceof BoundPlayerFactory) ? (value as BoundPlayerFactory<boolean>) : (!!value);
|
||||
updateElementClassProp(getStylingContext(index, getViewData()), stylingIndex, val);
|
||||
updateElementClassProp(getStylingContext(index, getViewData()), classIndex, val);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1115,7 +1126,13 @@ export function elementStyling(
|
|||
classDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleSanitizer?: StyleSanitizeFn | null, directiveIndex?: number): void {
|
||||
if (directiveIndex) return; // supported in next PR
|
||||
if (directiveIndex !== undefined) {
|
||||
getCreationMode() &&
|
||||
hackImplementationOfElementStyling(
|
||||
classDeclarations || null, styleDeclarations || null, styleSanitizer || null,
|
||||
directiveIndex); // supported in next PR
|
||||
return;
|
||||
}
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
const inputData = initializeTNodeInputs(tNode);
|
||||
|
||||
|
@ -1159,7 +1176,9 @@ export function elementStyling(
|
|||
* @param directiveIndex the index for the directive that is attempting to change styling.
|
||||
*/
|
||||
export function elementStylingApply(index: number, directiveIndex?: number): void {
|
||||
if (directiveIndex) return; // supported in next PR
|
||||
if (directiveIndex != undefined) {
|
||||
return hackImplementationOfElementStylingApply(index, directiveIndex); // supported in next PR
|
||||
}
|
||||
const viewData = getViewData();
|
||||
const isFirstRender = (viewData[FLAGS] & LViewFlags.CreationMode) !== 0;
|
||||
const totalPlayersQueued = renderStyleAndClassBindings(
|
||||
|
@ -1194,7 +1213,9 @@ export function elementStylingApply(index: number, directiveIndex?: number): voi
|
|||
export function elementStyleProp(
|
||||
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
|
||||
suffix?: string, directiveIndex?: number): void {
|
||||
if (directiveIndex) return; // supported in next PR
|
||||
if (directiveIndex != undefined)
|
||||
return hackImplementationOfElementStyleProp(
|
||||
index, styleIndex, value, suffix, directiveIndex); // supported in next PR
|
||||
let valueToAdd: string|null = null;
|
||||
if (value) {
|
||||
if (suffix) {
|
||||
|
@ -1237,7 +1258,9 @@ export function elementStyleProp(
|
|||
export function elementStylingMap<T>(
|
||||
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
||||
styles?: {[styleName: string]: any} | NO_CHANGE | null, directiveIndex?: number): void {
|
||||
if (directiveIndex) return; // supported in next PR
|
||||
if (directiveIndex != undefined)
|
||||
return hackImplementationOfElementStylingMap(
|
||||
index, classes, styles, directiveIndex); // supported in next PR
|
||||
const viewData = getViewData();
|
||||
const tNode = getTNode(index, viewData);
|
||||
const stylingContext = getStylingContext(index, viewData);
|
||||
|
@ -1250,6 +1273,74 @@ export function elementStylingMap<T>(
|
|||
updateStylingMap(stylingContext, classes, styles);
|
||||
}
|
||||
|
||||
/* START OF HACK BLOCK */
|
||||
/*
|
||||
* HACK
|
||||
* The code below is a quick and dirty implementation of the host style binding so that we can make
|
||||
* progress on TestBed. Once the correct implementation is created this code should be removed.
|
||||
*/
|
||||
interface HostStylingHack {
|
||||
classDeclarations: string[];
|
||||
styleDeclarations: string[];
|
||||
styleSanitizer: StyleSanitizeFn|null;
|
||||
}
|
||||
interface HostStylingHackMap {
|
||||
[directiveIndex: number]: HostStylingHack;
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStyling(
|
||||
classDeclarations: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleDeclarations: (string | boolean | InitialStylingFlags)[] | null,
|
||||
styleSanitizer: StyleSanitizeFn | null, directiveIndex: number): void {
|
||||
const node = getNativeByTNode(getPreviousOrParentTNode(), getViewData());
|
||||
ngDevMode && assertDefined(node, 'expecting parent DOM node');
|
||||
const hostStylingHackMap: HostStylingHackMap =
|
||||
((node as any).hostStylingHack || ((node as any).hostStylingHack = {}));
|
||||
hostStylingHackMap[directiveIndex] = {
|
||||
classDeclarations: hackSquashDeclaration(classDeclarations),
|
||||
styleDeclarations: hackSquashDeclaration(styleDeclarations), styleSanitizer
|
||||
};
|
||||
}
|
||||
|
||||
function hackSquashDeclaration(declarations: (string | boolean | InitialStylingFlags)[] | null):
|
||||
string[] {
|
||||
// assume the array is correct. This should be fine for View Engine compatibility.
|
||||
return declarations || [] as any;
|
||||
}
|
||||
|
||||
function hackImplementationOfElementClassProp(
|
||||
index: number, classIndex: number, value: boolean | PlayerFactory,
|
||||
directiveIndex: number): void {
|
||||
const node = getNativeByIndex(index, getViewData());
|
||||
ngDevMode && assertDefined(node, 'could not locate node');
|
||||
const hostStylingHack: HostStylingHack = (node as any).hostStylingHack[directiveIndex];
|
||||
const className = hostStylingHack.classDeclarations[classIndex];
|
||||
const renderer = getRenderer();
|
||||
if (isProceduralRenderer(renderer)) {
|
||||
value ? renderer.addClass(node, className) : renderer.removeClass(node, className);
|
||||
} else {
|
||||
const classList = (node as HTMLElement).classList;
|
||||
value ? classList.add(className) : classList.remove(className);
|
||||
}
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStylingApply(index: number, directiveIndex?: number): void {
|
||||
// Do nothing because the hack implementation is eager.
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStyleProp(
|
||||
index: number, styleIndex: number, value: string | number | String | PlayerFactory | null,
|
||||
suffix?: string, directiveIndex?: number): void {
|
||||
throw new Error('unimplemented. Should not be needed by ViewEngine compatibility');
|
||||
}
|
||||
|
||||
function hackImplementationOfElementStylingMap<T>(
|
||||
index: number, classes: {[key: string]: any} | string | NO_CHANGE | null,
|
||||
styles?: {[styleName: string]: any} | NO_CHANGE | null, directiveIndex?: number): void {
|
||||
throw new Error('unimplemented. Should not be needed by ViewEngine compatibility');
|
||||
}
|
||||
|
||||
/* END OF HACK BLOCK */
|
||||
//////////////////////////
|
||||
//// Text
|
||||
//////////////////////////
|
||||
|
|
|
@ -785,6 +785,18 @@
|
|||
{
|
||||
"name": "getViewData"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStyling"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStylingApply"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStylingMap"
|
||||
},
|
||||
{
|
||||
"name": "hackSquashDeclaration"
|
||||
},
|
||||
{
|
||||
"name": "hasParentInjector"
|
||||
},
|
||||
|
|
|
@ -818,6 +818,18 @@
|
|||
{
|
||||
"name": "getViewData"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementClassProp"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStyling"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStylingApply"
|
||||
},
|
||||
{
|
||||
"name": "hackSquashDeclaration"
|
||||
},
|
||||
{
|
||||
"name": "hasParentInjector"
|
||||
},
|
||||
|
|
|
@ -1943,6 +1943,18 @@
|
|||
{
|
||||
"name": "globalListener"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementClassProp"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStyling"
|
||||
},
|
||||
{
|
||||
"name": "hackImplementationOfElementStylingApply"
|
||||
},
|
||||
{
|
||||
"name": "hackSquashDeclaration"
|
||||
},
|
||||
{
|
||||
"name": "hasBalancedQuotes"
|
||||
},
|
||||
|
|
|
@ -12,7 +12,6 @@ import {AbstractControl, AsyncValidator, AsyncValidatorFn, COMPOSITION_BUFFER_MO
|
|||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {merge, timer} from 'rxjs';
|
||||
import {tap} from 'rxjs/operators';
|
||||
|
||||
|
@ -713,7 +712,6 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
|||
|
||||
});
|
||||
|
||||
fixmeIvy('Host bindings to styles do not yet work') &&
|
||||
describe('setting status classes', () => {
|
||||
it('should work with single fields', () => {
|
||||
const fixture = initTest(FormControlComp);
|
||||
|
@ -743,9 +741,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
|||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual([
|
||||
'ng-pending', 'ng-pristine', 'ng-untouched'
|
||||
]);
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
@ -759,8 +755,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
|||
expect(sortedClassList(input)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
}));
|
||||
|
||||
it('should work with single fields that combines async and sync validators',
|
||||
fakeAsync(() => {
|
||||
it('should work with single fields that combines async and sync validators', fakeAsync(() => {
|
||||
const fixture = initTest(FormControlComp);
|
||||
const control =
|
||||
new FormControl('', Validators.required, uniqLoginAsyncValidator('good'));
|
||||
|
@ -768,9 +763,7 @@ import {MyInput, MyInputForm} from './value_accessor_integration_spec';
|
|||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual([
|
||||
'ng-invalid', 'ng-pristine', 'ng-untouched'
|
||||
]);
|
||||
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
|
|
@ -12,7 +12,6 @@ import {AbstractControl, AsyncValidator, COMPOSITION_BUFFER_MODE, FormControl, F
|
|||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
import {merge} from 'rxjs';
|
||||
|
||||
import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integration_spec';
|
||||
|
@ -149,7 +148,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
expect(form.value).toEqual({});
|
||||
}));
|
||||
|
||||
fixmeIvy('host bindings do not yet work with classes or styles') &&
|
||||
it('should set status classes with ngModel', async(() => {
|
||||
const fixture = initTest(NgModelForm);
|
||||
fixture.componentInstance.name = 'aa';
|
||||
|
@ -158,16 +156,12 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual([
|
||||
'ng-invalid', 'ng-pristine', 'ng-untouched'
|
||||
]);
|
||||
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual([
|
||||
'ng-invalid', 'ng-pristine', 'ng-touched'
|
||||
]);
|
||||
expect(sortedClassList(input)).toEqual(['ng-invalid', 'ng-pristine', 'ng-touched']);
|
||||
|
||||
input.value = 'updatedValue';
|
||||
dispatchEvent(input, 'input');
|
||||
|
@ -176,7 +170,6 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('host bindings do not yet work with classes or styles') &&
|
||||
it('should set status classes with ngModel and async validators', fakeAsync(() => {
|
||||
|
||||
const fixture = initTest(NgModelAsyncValidation, NgAsyncValidator);
|
||||
|
@ -184,16 +177,12 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
fixture.detectChanges();
|
||||
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
expect(sortedClassList(input)).toEqual([
|
||||
'ng-pending', 'ng-pristine', 'ng-untouched'
|
||||
]);
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(input)).toEqual([
|
||||
'ng-pending', 'ng-pristine', 'ng-touched'
|
||||
]);
|
||||
expect(sortedClassList(input)).toEqual(['ng-pending', 'ng-pristine', 'ng-touched']);
|
||||
|
||||
input.value = 'updatedValue';
|
||||
dispatchEvent(input, 'input');
|
||||
|
@ -204,15 +193,13 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
});
|
||||
}));
|
||||
|
||||
fixmeIvy('host bindings do not yet work with classes or styles') &&
|
||||
it('should set status classes with ngModelGroup and ngForm', async(() => {
|
||||
const fixture = initTest(NgModelGroupForm);
|
||||
fixture.componentInstance.first = '';
|
||||
fixture.detectChanges();
|
||||
|
||||
const form = fixture.debugElement.query(By.css('form')).nativeElement;
|
||||
const modelGroup =
|
||||
fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement;
|
||||
const modelGroup = fixture.debugElement.query(By.css('[ngModelGroup]')).nativeElement;
|
||||
const input = fixture.debugElement.query(By.css('input')).nativeElement;
|
||||
|
||||
// ngModelGroup creates its control asynchronously
|
||||
|
@ -222,9 +209,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
'ng-invalid', 'ng-pristine', 'ng-untouched'
|
||||
]);
|
||||
|
||||
expect(sortedClassList(form)).toEqual([
|
||||
'ng-invalid', 'ng-pristine', 'ng-untouched'
|
||||
]);
|
||||
expect(sortedClassList(form)).toEqual(['ng-invalid', 'ng-pristine', 'ng-untouched']);
|
||||
|
||||
dispatchEvent(input, 'blur');
|
||||
fixture.detectChanges();
|
||||
|
@ -238,9 +223,7 @@ import {NgModelCustomComp, NgModelCustomWrapper} from './value_accessor_integrat
|
|||
dispatchEvent(input, 'input');
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(sortedClassList(modelGroup)).toEqual([
|
||||
'ng-dirty', 'ng-touched', 'ng-valid'
|
||||
]);
|
||||
expect(sortedClassList(modelGroup)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
expect(sortedClassList(form)).toEqual(['ng-dirty', 'ng-touched', 'ng-valid']);
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -11,7 +11,6 @@ import {ComponentFixture, TestBed, async, fakeAsync, tick} from '@angular/core/t
|
|||
import {AbstractControl, ControlValueAccessor, FormControl, FormGroup, FormsModule, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgControl, NgForm, NgModel, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {dispatchEvent} from '@angular/platform-browser/testing/src/browser_util';
|
||||
import {fixmeIvy} from '@angular/private/testing';
|
||||
|
||||
{
|
||||
describe('value accessors', () => {
|
||||
|
|
Loading…
Reference in New Issue