fix(core): allow EmbeddedViewRef context to be updated (#40360)
Currently `EmbeddedViewRef.context` is read-only which means that the only way to update it is to mutate the object which can lead to some undesirable outcomes if the template and the context are provided by an external consumer (see #24515). These changes make the property writeable since there doesn't appear to be a specific reason why it was readonly to begin with. PR Close #40360
This commit is contained in:
parent
ddf7970b78
commit
a3e17190e7
|
@ -301,7 +301,7 @@ export declare class ElementRef<T = any> {
|
|||
}
|
||||
|
||||
export declare abstract class EmbeddedViewRef<C> extends ViewRef {
|
||||
abstract get context(): C;
|
||||
abstract context: C;
|
||||
abstract get rootNodes(): any[];
|
||||
}
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ export abstract class EmbeddedViewRef<C> extends ViewRef {
|
|||
/**
|
||||
* The context for this view, inherited from the anchor element.
|
||||
*/
|
||||
abstract get context(): C;
|
||||
abstract context: C;
|
||||
|
||||
/**
|
||||
* The root nodes for this embedded view.
|
||||
|
|
|
@ -61,6 +61,10 @@ export class ViewRef<T> implements viewEngine_EmbeddedViewRef<T>, viewEngine_Int
|
|||
return this._lView[CONTEXT] as T;
|
||||
}
|
||||
|
||||
set context(value: T) {
|
||||
this._lView[CONTEXT] = value;
|
||||
}
|
||||
|
||||
get destroyed(): boolean {
|
||||
return (this._lView[FLAGS] & LViewFlags.Destroyed) === LViewFlags.Destroyed;
|
||||
}
|
||||
|
|
|
@ -264,6 +264,10 @@ export class ViewRef_ implements EmbeddedViewRef<any>, InternalViewRef {
|
|||
return this._view.context;
|
||||
}
|
||||
|
||||
set context(value: any) {
|
||||
this._view.context = value;
|
||||
}
|
||||
|
||||
get destroyed(): boolean {
|
||||
return (this._view.state & ViewState.Destroyed) !== 0;
|
||||
}
|
||||
|
|
|
@ -273,4 +273,87 @@ describe('TemplateRef', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('context', () => {
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template #templateRef let-name="name">{{name}}</ng-template>
|
||||
<ng-container #containerRef></ng-container>
|
||||
`
|
||||
})
|
||||
class App {
|
||||
@ViewChild('templateRef') templateRef!: TemplateRef<any>;
|
||||
@ViewChild('containerRef', {read: ViewContainerRef}) containerRef!: ViewContainerRef;
|
||||
}
|
||||
|
||||
it('should update if the context of a view ref is mutated', () => {
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const context = {name: 'Frodo'};
|
||||
const viewRef = fixture.componentInstance.templateRef.createEmbeddedView(context);
|
||||
fixture.componentInstance.containerRef.insert(viewRef);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Frodo');
|
||||
|
||||
context.name = 'Bilbo';
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Bilbo');
|
||||
});
|
||||
|
||||
it('should update if the context of a view ref is replaced', () => {
|
||||
TestBed.configureTestingModule({declarations: [App]});
|
||||
const fixture = TestBed.createComponent(App);
|
||||
fixture.detectChanges();
|
||||
const viewRef = fixture.componentInstance.templateRef.createEmbeddedView({name: 'Frodo'});
|
||||
fixture.componentInstance.containerRef.insert(viewRef);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Frodo');
|
||||
|
||||
viewRef.context = {name: 'Bilbo'};
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.nativeElement.textContent).toBe('Bilbo');
|
||||
});
|
||||
|
||||
it('should use the latest context information inside template listeners', () => {
|
||||
const events: string[] = [];
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-template #templateRef let-name="name">
|
||||
<button (click)="log(name)"></button>
|
||||
</ng-template>
|
||||
<ng-container #containerRef></ng-container>
|
||||
`
|
||||
})
|
||||
class ListenerTest {
|
||||
@ViewChild('templateRef') templateRef!: TemplateRef<any>;
|
||||
@ViewChild('containerRef', {read: ViewContainerRef}) containerRef!: ViewContainerRef;
|
||||
|
||||
log(name: string) {
|
||||
events.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [ListenerTest]});
|
||||
const fixture = TestBed.createComponent(ListenerTest);
|
||||
fixture.detectChanges();
|
||||
const viewRef = fixture.componentInstance.templateRef.createEmbeddedView({name: 'Frodo'});
|
||||
fixture.componentInstance.containerRef.insert(viewRef);
|
||||
fixture.detectChanges();
|
||||
|
||||
const button = fixture.nativeElement.querySelector('button');
|
||||
button.click();
|
||||
expect(events).toEqual(['Frodo']);
|
||||
|
||||
viewRef.context = {name: 'Bilbo'};
|
||||
fixture.detectChanges();
|
||||
button.click();
|
||||
expect(events).toEqual(['Frodo', 'Bilbo']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue