fix(elements): fire custom element output events during component initialization (#37570)
Previously, event listeners for component output events attached on an Angular custom element before inserting it into the DOM (i.e. before instantiating the underlying component) didn't fire for events emitted during initialization lifecycle hooks, such as `ngAfterContentInit`, `ngAfterViewInit`, `ngOnChanges` (initial call) and `ngOnInit`. The reason was that `NgElementImpl` [subscribed to events][1] _after_ calling [ngElementStrategy#connect()][2], which is where the [initial change detection][3] takes place (running the initialization lifecycle hooks). This commit fixes this by: 1. Ensuring `ComponentNgElementStrategy#events` is defined and available for subscribing to, even before instantiating the component. 2. Changing `NgElementImpl` to subscribe to `NgElementStrategy#events` (if available) before calling `NgElementStrategy#connect()` (which initializes the component instance) if available. 3. Falling back to the old behavior (subscribing to `events` after calling `connect()` for strategies that do not initialize `events` before their `connect()` is run). NOTE: By falling back to the old behavior when `NgElementStrategy#events` is not initialized before calling `NgElementStrategy#connect()`, we avoid breaking existing custom `NgElementStrategy` implementations (with @remackgeek's [ElementZoneStrategy][4] being a commonly used example). Jira issue: [FW-2010](https://angular-team.atlassian.net/browse/FW-2010) [1]:c0143cb2ab/packages/elements/src/create-custom-element.ts (L167-L170)
[2]:c0143cb2ab/packages/elements/src/create-custom-element.ts (L164)
[3]:c0143cb2ab/packages/elements/src/component-factory-strategy.ts (L158)
[4]:f1b6699495/projects/elements-zone-strategy/src/lib/element-zone-strategy.ts
Fixes #36141 PR Close #37570
This commit is contained in:

committed by
Andrew Kushnir

parent
78a44768e0
commit
eba9dd364e
@ -41,6 +41,33 @@ describe('ComponentFactoryNgElementStrategy', () => {
|
||||
expect(strategyFactory.create(injector)).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('before connected', () => {
|
||||
it('should allow subscribing to output events', () => {
|
||||
const events: NgElementStrategyEvent[] = [];
|
||||
strategy.events.subscribe(e => events.push(e));
|
||||
|
||||
// No events before connecting (since `componentRef` is not even on the strategy yet).
|
||||
componentRef.instance.output1.next('output-1a');
|
||||
componentRef.instance.output1.next('output-1b');
|
||||
componentRef.instance.output2.next('output-2a');
|
||||
expect(events).toEqual([]);
|
||||
|
||||
// No events upon connecting (since events are not cached/played back).
|
||||
strategy.connect(document.createElement('div'));
|
||||
expect(events).toEqual([]);
|
||||
|
||||
// Events emitted once connected.
|
||||
componentRef.instance.output1.next('output-1c');
|
||||
componentRef.instance.output1.next('output-1d');
|
||||
componentRef.instance.output2.next('output-2b');
|
||||
expect(events).toEqual([
|
||||
{name: 'templateOutput1', value: 'output-1c'},
|
||||
{name: 'templateOutput1', value: 'output-1d'},
|
||||
{name: 'templateOutput2', value: 'output-2b'},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('after connected', () => {
|
||||
beforeEach(() => {
|
||||
// Set up an initial value to make sure it is passed to the component
|
||||
|
Reference in New Issue
Block a user