fix(ivy): update compiler to generate separate creation mode and update mode blocks (#23292)
PR Close #23292
This commit is contained in:

committed by
Victor Berchet

parent
de3ca56769
commit
0d516f1658
@ -149,11 +149,6 @@ let data: any[];
|
||||
*/
|
||||
let directives: any[]|null;
|
||||
|
||||
/**
|
||||
* Points to the next binding index to read or write to.
|
||||
*/
|
||||
let bindingIndex: number;
|
||||
|
||||
/**
|
||||
* When a view is destroyed, listeners need to be released and outputs need to be
|
||||
* unsubscribed. This cleanup array stores both listener data (in chunks of 4)
|
||||
@ -204,7 +199,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||
const oldView: LView = currentView;
|
||||
data = newView && newView.data;
|
||||
directives = newView && newView.directives;
|
||||
bindingIndex = newView && newView.bindingStartIndex || 0;
|
||||
tData = newView && newView.tView.data;
|
||||
creationMode = newView && (newView.flags & LViewFlags.CreationMode) === LViewFlags.CreationMode;
|
||||
firstTemplatePass = newView && newView.tView.firstTemplatePass;
|
||||
@ -212,6 +206,10 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||
cleanup = newView && newView.cleanup;
|
||||
renderer = newView && newView.renderer;
|
||||
|
||||
if (newView && newView.bindingIndex < 0) {
|
||||
newView.bindingIndex = newView.bindingStartIndex;
|
||||
}
|
||||
|
||||
if (host != null) {
|
||||
previousOrParentNode = host;
|
||||
isParent = true;
|
||||
@ -235,6 +233,7 @@ export function leaveView(newView: LView): void {
|
||||
// Views should be clean and in update mode after being checked, so these bits are cleared
|
||||
currentView.flags &= ~(LViewFlags.CreationMode | LViewFlags.Dirty);
|
||||
currentView.lifecycleStage = LifecycleStage.INIT;
|
||||
currentView.bindingIndex = -1;
|
||||
enterView(newView, null);
|
||||
}
|
||||
|
||||
@ -295,7 +294,8 @@ export function createLView<T>(
|
||||
child: null,
|
||||
tail: null,
|
||||
next: null,
|
||||
bindingStartIndex: null,
|
||||
bindingStartIndex: -1,
|
||||
bindingIndex: -1,
|
||||
template: template,
|
||||
context: context,
|
||||
dynamicViewCount: 0,
|
||||
@ -544,7 +544,8 @@ function getRenderFlags(view: LView): RenderFlags {
|
||||
export function elementStart(
|
||||
index: number, name: string, attrs?: string[] | null, localRefs?: string[] | null): RElement {
|
||||
ngDevMode &&
|
||||
assertNull(currentView.bindingStartIndex, 'elements should be created before any bindings');
|
||||
assertEqual(
|
||||
currentView.bindingStartIndex, -1, 'elements should be created before any bindings');
|
||||
|
||||
const native: RElement = renderer.createElement(name);
|
||||
const node: LElementNode = createLNode(index, LNodeType.Element, native !, null);
|
||||
@ -1160,7 +1161,8 @@ export function elementStyle<T>(
|
||||
*/
|
||||
export function text(index: number, value?: any): void {
|
||||
ngDevMode &&
|
||||
assertNull(currentView.bindingStartIndex, 'text nodes should be created before bindings');
|
||||
assertEqual(
|
||||
currentView.bindingStartIndex, -1, 'text nodes should be created before bindings');
|
||||
const textNode = value != null ? createTextNode(value, renderer) : null;
|
||||
const node = createLNode(index, LNodeType.Element, textNode);
|
||||
// Text nodes are self closing.
|
||||
@ -1259,7 +1261,8 @@ function addComponentLogic<T>(index: number, instance: T, def: ComponentDef<T>):
|
||||
export function baseDirectiveCreate<T>(
|
||||
index: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
|
||||
ngDevMode &&
|
||||
assertNull(currentView.bindingStartIndex, 'directives should be created before any bindings');
|
||||
assertEqual(
|
||||
currentView.bindingStartIndex, -1, 'directives should be created before any bindings');
|
||||
ngDevMode && assertPreviousIsParent();
|
||||
|
||||
Object.defineProperty(
|
||||
@ -1381,9 +1384,9 @@ export function createLContainer(
|
||||
export function container(
|
||||
index: number, template?: ComponentTemplate<any>, tagName?: string, attrs?: string[],
|
||||
localRefs?: string[] | null): void {
|
||||
ngDevMode &&
|
||||
assertNull(
|
||||
currentView.bindingStartIndex, 'container nodes should be created before any bindings');
|
||||
ngDevMode && assertEqual(
|
||||
currentView.bindingStartIndex, -1,
|
||||
'container nodes should be created before any bindings');
|
||||
|
||||
const currentParent = isParent ? previousOrParentNode : previousOrParentNode.parent !;
|
||||
const lContainer = createLContainer(currentParent, currentView, template);
|
||||
@ -1976,7 +1979,13 @@ export const NO_CHANGE = {} as NO_CHANGE;
|
||||
* (ie `bind()`, `interpolationX()`, `pureFunctionX()`)
|
||||
*/
|
||||
function initBindings() {
|
||||
bindingIndex = currentView.bindingStartIndex = data.length;
|
||||
ngDevMode && assertEqual(
|
||||
currentView.bindingStartIndex, -1,
|
||||
'Binding start index should only be set once, when null');
|
||||
ngDevMode && assertEqual(
|
||||
currentView.bindingIndex, -1,
|
||||
'Binding index should not yet be set ' + currentView.bindingIndex);
|
||||
currentView.bindingIndex = currentView.bindingStartIndex = data.length;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1985,17 +1994,19 @@ function initBindings() {
|
||||
* @param value Value to diff
|
||||
*/
|
||||
export function bind<T>(value: T | NO_CHANGE): T|NO_CHANGE {
|
||||
if (currentView.bindingStartIndex == null) {
|
||||
if (currentView.bindingStartIndex < 0) {
|
||||
initBindings();
|
||||
return data[bindingIndex++] = value;
|
||||
return data[currentView.bindingIndex++] = value;
|
||||
}
|
||||
|
||||
const changed: boolean = value !== NO_CHANGE && isDifferent(data[bindingIndex], value);
|
||||
const changed: boolean =
|
||||
value !== NO_CHANGE && isDifferent(data[currentView.bindingIndex], value);
|
||||
if (changed) {
|
||||
throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, data[bindingIndex], value);
|
||||
data[bindingIndex] = value;
|
||||
throwErrorIfNoChangesMode(
|
||||
creationMode, checkNoChangesMode, data[currentView.bindingIndex], value);
|
||||
data[currentView.bindingIndex] = value;
|
||||
}
|
||||
bindingIndex++;
|
||||
currentView.bindingIndex++;
|
||||
return changed ? value : NO_CHANGE;
|
||||
}
|
||||
|
||||
@ -2159,26 +2170,28 @@ export function loadDirective<T>(index: number): T {
|
||||
|
||||
/** Gets the current binding value and increments the binding index. */
|
||||
export function consumeBinding(): any {
|
||||
ngDevMode && assertDataInRange(bindingIndex);
|
||||
ngDevMode && assertDataInRange(currentView.bindingIndex);
|
||||
ngDevMode &&
|
||||
assertNotEqual(data[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
|
||||
return data[bindingIndex++];
|
||||
assertNotEqual(
|
||||
data[currentView.bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.');
|
||||
return data[currentView.bindingIndex++];
|
||||
}
|
||||
|
||||
/** Updates binding if changed, then returns whether it was updated. */
|
||||
export function bindingUpdated(value: any): boolean {
|
||||
ngDevMode && assertNotEqual(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.');
|
||||
|
||||
if (currentView.bindingStartIndex == null) {
|
||||
if (currentView.bindingStartIndex < 0) {
|
||||
initBindings();
|
||||
} else if (isDifferent(data[bindingIndex], value)) {
|
||||
throwErrorIfNoChangesMode(creationMode, checkNoChangesMode, data[bindingIndex], value);
|
||||
} else if (isDifferent(data[currentView.bindingIndex], value)) {
|
||||
throwErrorIfNoChangesMode(
|
||||
creationMode, checkNoChangesMode, data[currentView.bindingIndex], value);
|
||||
} else {
|
||||
bindingIndex++;
|
||||
currentView.bindingIndex++;
|
||||
return false;
|
||||
}
|
||||
|
||||
data[bindingIndex++] = value;
|
||||
data[currentView.bindingIndex++] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,16 @@ export interface LView {
|
||||
* will begin reading bindings at the correct point in the array when
|
||||
* we are in update mode.
|
||||
*/
|
||||
bindingStartIndex: number|null;
|
||||
bindingStartIndex: number;
|
||||
|
||||
/**
|
||||
* The binding index we should access next.
|
||||
*
|
||||
* This is stored so that bindings can continue where they left off
|
||||
* if a view is left midway through processing bindings (e.g. if there is
|
||||
* a setter that creates an embedded view, like in ngIf).
|
||||
*/
|
||||
bindingIndex: number;
|
||||
|
||||
/**
|
||||
* When a view is destroyed, listeners need to be released and outputs need to be
|
||||
|
@ -56,8 +56,6 @@ export class TodoStore {
|
||||
|
||||
@Component({
|
||||
selector: 'todo-app',
|
||||
// TODO(misko) remove all `foo && foo.something` once `ViewContainerRef` can separate creation and
|
||||
// update block.
|
||||
// TODO(misko): make this work with `[(ngModel)]`
|
||||
template: `
|
||||
<section class="todoapp">
|
||||
@ -74,18 +72,18 @@ export class TodoStore {
|
||||
(click)="todoStore.setAllTo(toggleall.checked)">
|
||||
<ul class="todo-list">
|
||||
<li *ngFor="let todo of todoStore.todos"
|
||||
[class.completed]="todo && todo.completed"
|
||||
[class.editing]="todo && todo.editing">
|
||||
[class.completed]="todo.completed"
|
||||
[class.editing]="todo.editing">
|
||||
<div class="view">
|
||||
<input class="toggle" type="checkbox"
|
||||
(click)="toggleCompletion(todo)"
|
||||
[checked]="todo && todo.completed">
|
||||
<label (dblclick)="editTodo(todo)">{{todo && todo.title}}</label>
|
||||
[checked]="todo.completed">
|
||||
<label (dblclick)="editTodo(todo)">{{todo.title}}</label>
|
||||
<button class="destroy" (click)="remove(todo)"></button>
|
||||
</div>
|
||||
<input *ngIf="todo && todo.editing"
|
||||
<input *ngIf="todo.editing"
|
||||
class="edit" #editedtodo
|
||||
[value]="todo && todo.title"
|
||||
[value]="todo.title"
|
||||
(blur)="stopEditing(todo, editedtodo.value)"
|
||||
(keyup)="todo.title = $event.target.value"
|
||||
(keyup)="$event.code == 'Enter' && updateEditingTodo(todo, editedtodo.value)"
|
||||
@ -106,7 +104,7 @@ export class TodoStore {
|
||||
</footer>
|
||||
</section>
|
||||
`,
|
||||
// TODO(misko): switch oven to OnPush
|
||||
// TODO(misko): switch over to OnPush
|
||||
// changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ToDoAppComponent {
|
||||
|
@ -11,7 +11,7 @@ import {NgForOfContext} from '@angular/common';
|
||||
import {defineComponent} from '../../src/render3/index';
|
||||
import {bind, container, elementEnd, elementProperty, elementStart, interpolation3, text, textBinding, tick} from '../../src/render3/instructions';
|
||||
import {RenderFlags} from '../../src/render3/interfaces/definition';
|
||||
import {NgForOf} from './common_with_def';
|
||||
import {NgForOf, NgIf} from './common_with_def';
|
||||
import {ComponentFixture} from './render_util';
|
||||
|
||||
describe('@angular/common integration', () => {
|
||||
@ -124,4 +124,65 @@ describe('@angular/common integration', () => {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('ngIf', () => {
|
||||
it('should support sibling ngIfs', () => {
|
||||
class MyApp {
|
||||
showing = true;
|
||||
valueOne = 'one';
|
||||
valueTwo = 'two';
|
||||
|
||||
static ngComponentDef = defineComponent({
|
||||
type: MyApp,
|
||||
factory: () => new MyApp(),
|
||||
selectors: [['my-app']],
|
||||
/**
|
||||
* <div *ngIf="showing">{{ valueOne }}</div>
|
||||
* <div *ngIf="showing">{{ valueTwo }}</div>
|
||||
*/
|
||||
template: (rf: RenderFlags, myApp: MyApp) => {
|
||||
if (rf & RenderFlags.Create) {
|
||||
container(0, templateOne, undefined, ['ngIf', '']);
|
||||
container(1, templateTwo, undefined, ['ngIf', '']);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
elementProperty(0, 'ngIf', bind(myApp.showing));
|
||||
elementProperty(1, 'ngIf', bind(myApp.showing));
|
||||
}
|
||||
|
||||
function templateOne(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(1, bind(myApp.valueOne));
|
||||
}
|
||||
}
|
||||
function templateTwo(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
elementStart(0, 'div');
|
||||
{ text(1); }
|
||||
elementEnd();
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
textBinding(1, bind(myApp.valueTwo));
|
||||
}
|
||||
}
|
||||
},
|
||||
directives: () => [NgIf]
|
||||
});
|
||||
}
|
||||
|
||||
const fixture = new ComponentFixture(MyApp);
|
||||
expect(fixture.html).toEqual('<div>one</div><div>two</div>');
|
||||
|
||||
fixture.component.valueOne = '$$one$$';
|
||||
fixture.component.valueTwo = '$$two$$';
|
||||
fixture.update();
|
||||
expect(fixture.html).toEqual('<div>$$one$$</div><div>$$two$$</div>');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -6,13 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {NgForOf as NgForOfDef} from '@angular/common';
|
||||
import {NgForOf as NgForOfDef, NgIf as NgIfDef} from '@angular/common';
|
||||
import {IterableDiffers} from '@angular/core';
|
||||
|
||||
import {defaultIterableDiffers} from '../../src/change_detection/change_detection';
|
||||
import {DirectiveType, InjectFlags, NgOnChangesFeature, defineDirective, directiveInject, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
|
||||
|
||||
export const NgForOf: DirectiveType<NgForOfDef<any>> = NgForOfDef as any;
|
||||
export const NgIf: DirectiveType<NgIfDef> = NgIfDef as any;
|
||||
|
||||
NgForOf.ngDirectiveDef = defineDirective({
|
||||
type: NgForOfDef,
|
||||
@ -27,3 +28,10 @@ NgForOf.ngDirectiveDef = defineDirective({
|
||||
ngForTemplate: 'ngForTemplate',
|
||||
}
|
||||
});
|
||||
|
||||
(NgIf as any).ngDirectiveDef = defineDirective({
|
||||
type: NgIfDef,
|
||||
selectors: [['', 'ngIf', '']],
|
||||
factory: () => new NgIfDef(injectViewContainerRef(), injectTemplateRef()),
|
||||
inputs: {ngIf: 'ngIf', ngIfThen: 'ngIfThen', ngIfElse: 'ngIfElse'}
|
||||
});
|
||||
|
Reference in New Issue
Block a user