refactor(ivy): Switch styling to new reconcile algorithm (#34616)

NOTE: This change must be reverted with previous deletes so that it code remains in build-able state.

This change deletes old styling code and replaces it with a simplified styling algorithm.

The mental model for the new algorithm is:
- Create a linked list of styling bindings in the order of priority. All styling bindings ere executed in compiled order and than a linked list of bindings is created in priority order.
- Flush the style bindings at the end of `advance()` instruction. This implies that there are two flush events. One at the end of template `advance` instruction in the template. Second one at the end of `hostBindings` `advance` instruction when processing host bindings (if any).
- Each binding instructions effectively updates the string to represent the string at that location. Because most of the bindings are additive, this is a cheap strategy in most cases. In rare cases the strategy requires removing tokens from the styling up to this point. (We expect that to be rare case)S Because, the bindings are presorted in the order of priority, it is safe to resume the processing of the concatenated string from the last change binding.

PR Close #34616
This commit is contained in:
Miško Hevery
2019-12-17 15:40:37 -08:00
parent 1ccf3e54f7
commit 49e8028f26
60 changed files with 2439 additions and 1413 deletions

View File

@ -157,4 +157,11 @@ export class NgClass implements DoCheck {
});
}
}
// TODO(misko): Delete this code after angula/flex-layout stops depending on private APIs
// We need to export this to make angular/flex-layout happy
// https://github.com/angular/flex-layout/blob/ec7b57eb6adf59ecfdfff1de5ccf1ab2f6652ed3/src/lib/extended/class/class.ts#L9
setClass(value: string) { this.klass = value; }
setNgClass(value: any) { this.ngClass = value; }
applyChanges() { this.ngDoCheck(); }
}

View File

@ -85,4 +85,10 @@ export class NgStyle implements DoCheck {
changes.forEachAddedItem((record) => this._setStyle(record.key, record.currentValue));
changes.forEachChangedItem((record) => this._setStyle(record.key, record.currentValue));
}
// TODO(misko): Delete this code after angula/flex-layout stops depending on private APIs
// We need to export this to make angular/flex-layout happy
// https://github.com/angular/flex-layout/blob/ec7b57eb6adf59ecfdfff1de5ccf1ab2f6652ed3/src/lib/extended/class/class.ts#L9
setNgStyle(value: any) { this.ngStyle = value; }
applyChanges() { this.ngDoCheck(); }
}

View File

@ -6,5 +6,11 @@
* found in the LICENSE file at https://angular.io/license
*/
// TODO(misko): Delete this code after angula/flex-layout stops depending on private APIs
// We need to export this to make angular/flex-layout happy
// https://github.com/angular/flex-layout/blob/ec7b57eb6adf59ecfdfff1de5ccf1ab2f6652ed3/src/lib/extended/class/class.ts#L9
export {NgClass as ɵNgClassImpl, NgClass as ɵNgClassR2Impl} from './directives/ng_class';
export {NgStyle as ɵNgStyleR2Impl} from './directives/ng_style';
export {DomAdapter as ɵDomAdapter, getDOM as ɵgetDOM, setRootDomAdapter as ɵsetRootDomAdapter} from './dom_adapter';
export {BrowserPlatformLocation as ɵBrowserPlatformLocation} from './location/platform_location';

View File

@ -196,7 +196,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
fixture = createTestComponent(`<div [ngClass]="['foo', {}]"></div>`);
expect(() => fixture !.detectChanges())
.toThrowError(
/NgClass can only toggle CSS classes expressed as strings, got: \[object Object\]/);
/NgClass can only toggle CSS classes expressed as strings, got \[object Object\]/);
});
});
@ -372,6 +372,27 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
detectChangesAndExpectClassName('color-red');
});
it('should allow classes with trailing and leading spaces in [ngClass]', () => {
@Component({
template: `
<div leading-space [ngClass]="{' foo': applyClasses}"></div>
<div trailing-space [ngClass]="{'foo ': applyClasses}"></div>
`
})
class Cmp {
applyClasses = true;
}
TestBed.configureTestingModule({declarations: [Cmp]});
const fixture = TestBed.createComponent(Cmp);
fixture.detectChanges();
const leading = fixture.nativeElement.querySelector('[leading-space]');
const trailing = fixture.nativeElement.querySelector('[trailing-space]');
expect(leading.className).toBe('foo');
expect(trailing.className).toBe('foo');
});
});
});
}
@ -379,8 +400,7 @@ import {ComponentFixture, TestBed, async} from '@angular/core/testing';
@Component({selector: 'test-cmp', template: ''})
class TestComponent {
condition: boolean = true;
// TODO(issue/24571): remove '!'.
items !: any[];
items: any[]|undefined;
arrExpr: string[] = ['foo'];
setExpr: Set<string> = new Set<string>();
objExpr: {[klass: string]: any}|null = {'foo': true, 'bar': false};