refactor(ivy): move hostVars
/hostAttrs
from instruction to DirectiveDef
(#34683)
This change moves information from instructions to declarative position: - `ɵɵallocHostVars(vars)` => `DirectiveDef.hostVars` - `ɵɵelementHostAttrs(attrs)` => `DirectiveDef.hostAttrs` When merging directives it is necessary to know about `hostVars` and `hostAttrs`. Before this change the information was stored in the `hostBindings` function. This was problematic, because in order to get to the information the `hostBindings` would have to be executed. In order for `hostBindings` to be executed the directives would have to be instantiated. This means that the directive instantiation would happen before we had knowledge about the `hostAttrs` and as a result the directive could observe in the constructor that not all of the `hostAttrs` have been applied. This further complicates the runtime as we have to apply `hostAttrs` in parts over many invocations. `ɵɵallocHostVars` was unnecessarily complicated because it would have to update the `LView` (and Blueprint) while existing directives are already executing. By moving it out of `hostBindings` function we can access it statically and we can create correct `LView` (and Blueprint) in a single pass. This change only changes how the instructions are generated, but does not change the runtime much. (We cheat by emulating the old behavior by calling `ɵɵallocHostVars` and `ɵɵelementHostAttrs`) Subsequent change will refactor the runtime to take advantage of the static information. PR Close #34683
This commit is contained in:
@ -7,6 +7,8 @@
|
||||
*/
|
||||
|
||||
import {Component, ContentChildren, Directive, EventEmitter, HostBinding, HostListener, Input, OnChanges, Output, QueryList, ViewChildren} from '@angular/core';
|
||||
import {ivyEnabled} from '@angular/core/src/ivy_switch';
|
||||
import {getDirectiveDef} from '@angular/core/src/render3/definition';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {onlyInIvy} from '@angular/private/testing';
|
||||
@ -42,6 +44,86 @@ describe('inheritance', () => {
|
||||
}).toThrowError('Directives cannot inherit Components');
|
||||
});
|
||||
|
||||
describe('multiple children', () => {
|
||||
it('should ensure that multiple child classes don\'t cause multiple parent execution', () => {
|
||||
// Assume this inheritance:
|
||||
// Base
|
||||
// |
|
||||
// Super
|
||||
// / \
|
||||
// Sub1 Sub2
|
||||
//
|
||||
// In the above case:
|
||||
// 1. Sub1 as will walk the inheritance Sub1, Super, Base
|
||||
// 2. Sub2 as will walk the inheritance Sub2, Super, Base
|
||||
//
|
||||
// Notice that Super, Base will get walked twice. Because inheritance works by wrapping parent
|
||||
// hostBindings function in a delegate which calls the hostBindings of the directive as well
|
||||
// as super, we need to ensure that we don't double wrap the hostBindings function. Doing so
|
||||
// would result in calling the hostBindings multiple times (unnecessarily). This would be
|
||||
// especially an issue if we have a lot of sub-classes (as is common in component libraries)
|
||||
const log: string[] = [];
|
||||
|
||||
@Directive({selector: '[superDir]'})
|
||||
class BaseDirective {
|
||||
@HostBinding('style.background-color')
|
||||
get backgroundColor() {
|
||||
log.push('Base.backgroundColor');
|
||||
return 'white';
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: '[superDir]'})
|
||||
class SuperDirective extends BaseDirective {
|
||||
@HostBinding('style.color')
|
||||
get color() {
|
||||
log.push('Super.color');
|
||||
return 'blue';
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: '[subDir1]'})
|
||||
class Sub1Directive extends SuperDirective {
|
||||
@HostBinding('style.height')
|
||||
get height() {
|
||||
log.push('Sub1.height');
|
||||
return '200px';
|
||||
}
|
||||
}
|
||||
|
||||
@Directive({selector: '[subDir2]'})
|
||||
class Sub2Directive extends SuperDirective {
|
||||
@HostBinding('style.width')
|
||||
get width() {
|
||||
log.push('Sub2.width');
|
||||
return '100px';
|
||||
}
|
||||
}
|
||||
|
||||
@Component({template: `<div subDir1 subDir2></div>`})
|
||||
class App {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [App, Sub1Directive, Sub2Directive, SuperDirective],
|
||||
});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges(false); // Don't check for no changes (so that assertion does not need
|
||||
// to worry about it.)
|
||||
|
||||
expect(log).toEqual([
|
||||
'Base.backgroundColor', 'Super.color', 'Sub1.height', //
|
||||
'Base.backgroundColor', 'Super.color', 'Sub2.width', //
|
||||
]);
|
||||
if (ivyEnabled) {
|
||||
expect(getDirectiveDef(BaseDirective) !.hostVars).toEqual(1);
|
||||
expect(getDirectiveDef(SuperDirective) !.hostVars).toEqual(2);
|
||||
expect(getDirectiveDef(Sub1Directive) !.hostVars).toEqual(3);
|
||||
expect(getDirectiveDef(Sub2Directive) !.hostVars).toEqual(3);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('ngOnChanges', () => {
|
||||
it('should be inherited when super is a directive', () => {
|
||||
const log: string[] = [];
|
||||
|
Reference in New Issue
Block a user