Skip to content

Commit 2f36a95

Browse files
karaAndrewKushnir
authored andcommitted
fix(ivy): providers should not be inherited (angular#27013)
PR Close angular#27013
1 parent 83c9bff commit 2f36a95

File tree

9 files changed

+144
-33
lines changed

9 files changed

+144
-33
lines changed

packages/core/src/render3/features/inherit_definition_feature.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
146146
const features = superDef.features;
147147
if (features) {
148148
for (const feature of features) {
149-
if (feature && feature !== InheritDefinitionFeature) {
149+
if (feature && feature.ngInherit) {
150150
(feature as DirectiveDefFeature)(definition);
151151
}
152152
}

packages/core/src/render3/features/ng_onchanges_feature.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {SimpleChange} from '../../change_detection/change_detection_util';
1010
import {OnChanges, SimpleChanges} from '../../metadata/lifecycle_hooks';
11-
import {DirectiveDef} from '../interfaces/definition';
11+
import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition';
1212

1313
const PRIVATE_PREFIX = '__ngOnChanges_';
1414

@@ -106,6 +106,10 @@ export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
106106
definition.doCheck = onChangesWrapper(definition.doCheck);
107107
}
108108

109+
// This option ensures that the ngOnChanges lifecycle hook will be inherited
110+
// from superclasses (in InheritDefinitionFeature).
111+
(NgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
112+
109113
function onChangesWrapper(delegateHook: (() => void) | null) {
110114
return function(this: OnChangesExpando) {
111115
const simpleChanges = this[PRIVATE_PREFIX];

packages/core/src/render3/interfaces/definition.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,16 @@ export interface PipeDef<T> {
307307

308308
export type PipeDefWithMeta<T, Name extends string> = PipeDef<T>;
309309

310-
export type DirectiveDefFeature = <T>(directiveDef: DirectiveDef<T>) => void;
311-
export type ComponentDefFeature = <T>(componentDef: ComponentDef<T>) => void;
310+
export interface DirectiveDefFeature {
311+
<T>(directiveDef: DirectiveDef<T>): void;
312+
ngInherit?: true;
313+
}
314+
315+
export interface ComponentDefFeature {
316+
<T>(componentDef: ComponentDef<T>): void;
317+
ngInherit?: true;
318+
}
319+
312320

313321
/**
314322
* Type used for directiveDefs on component definition.

packages/core/test/bundling/animation_world/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@
137137
{
138138
"name": "NgModuleRef"
139139
},
140+
{
141+
"name": "NgOnChangesFeature"
142+
},
140143
{
141144
"name": "NodeInjector$1"
142145
},
@@ -161,6 +164,9 @@
161164
{
162165
"name": "PARENT_INJECTOR"
163166
},
167+
{
168+
"name": "PRIVATE_PREFIX"
169+
},
164170
{
165171
"name": "QUERIES"
166172
},
@@ -188,6 +194,9 @@
188194
{
189195
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
190196
},
197+
{
198+
"name": "SimpleChange"
199+
},
191200
{
192201
"name": "SimpleKeyframePlayer"
193202
},
@@ -944,6 +953,9 @@
944953
{
945954
"name": "noop"
946955
},
956+
{
957+
"name": "onChangesWrapper"
958+
},
947959
{
948960
"name": "pointers"
949961
},

packages/core/test/bundling/hello_world/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@
8383
{
8484
"name": "NO_PARENT_INJECTOR"
8585
},
86+
{
87+
"name": "NgOnChangesFeature"
88+
},
8689
{
8790
"name": "NodeInjectorFactory"
8891
},
@@ -95,6 +98,9 @@
9598
{
9699
"name": "PARENT_INJECTOR"
97100
},
101+
{
102+
"name": "PRIVATE_PREFIX"
103+
},
98104
{
99105
"name": "QUERIES"
100106
},
@@ -107,6 +113,9 @@
107113
{
108114
"name": "SANITIZER"
109115
},
116+
{
117+
"name": "SimpleChange"
118+
},
110119
{
111120
"name": "TVIEW"
112121
},
@@ -383,6 +392,9 @@
383392
{
384393
"name": "noop"
385394
},
395+
{
396+
"name": "onChangesWrapper"
397+
},
386398
{
387399
"name": "postProcessBaseDirective"
388400
},

packages/core/test/bundling/hello_world_r2/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@
281281
{
282282
"name": "NgModuleRef$1"
283283
},
284+
{
285+
"name": "NgOnChangesFeature"
286+
},
284287
{
285288
"name": "NgZone"
286289
},
@@ -329,6 +332,9 @@
329332
{
330333
"name": "PLATFORM_INITIALIZER"
331334
},
335+
{
336+
"name": "PRIVATE_PREFIX"
337+
},
332338
{
333339
"name": "PlatformLocation"
334340
},
@@ -383,6 +389,9 @@
383389
{
384390
"name": "Self"
385391
},
392+
{
393+
"name": "SimpleChange"
394+
},
386395
{
387396
"name": "SkipSelf"
388397
},
@@ -1142,6 +1151,9 @@
11421151
{
11431152
"name": "observable"
11441153
},
1154+
{
1155+
"name": "onChangesWrapper"
1156+
},
11451157
{
11461158
"name": "onEnter"
11471159
},

packages/core/test/bundling/injection/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
{
3333
"name": "NULL_INJECTOR$2"
3434
},
35+
{
36+
"name": "NgOnChangesFeature"
37+
},
3538
{
3639
"name": "NullInjector"
3740
},
@@ -44,6 +47,9 @@
4447
{
4548
"name": "PARAMETERS"
4649
},
50+
{
51+
"name": "PRIVATE_PREFIX"
52+
},
4753
{
4854
"name": "R3Injector"
4955
},
@@ -53,6 +59,9 @@
5359
{
5460
"name": "Self"
5561
},
62+
{
63+
"name": "SimpleChange"
64+
},
5665
{
5766
"name": "SkipSelf"
5867
},
@@ -152,6 +161,9 @@
152161
{
153162
"name": "makeRecord"
154163
},
164+
{
165+
"name": "onChangesWrapper"
166+
},
155167
{
156168
"name": "providerToFactory"
157169
},

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,9 @@
131131
{
132132
"name": "NgModuleRef"
133133
},
134+
{
135+
"name": "NgOnChangesFeature"
136+
},
134137
{
135138
"name": "NodeInjector$1"
136139
},
@@ -155,6 +158,9 @@
155158
{
156159
"name": "PARENT_INJECTOR"
157160
},
161+
{
162+
"name": "PRIVATE_PREFIX"
163+
},
158164
{
159165
"name": "QUERIES"
160166
},
@@ -182,6 +188,9 @@
182188
{
183189
"name": "SWITCH_VIEW_CONTAINER_REF_FACTORY"
184190
},
191+
{
192+
"name": "SimpleChange"
193+
},
185194
{
186195
"name": "SkipSelf"
187196
},
@@ -962,6 +971,9 @@
962971
{
963972
"name": "noop"
964973
},
974+
{
975+
"name": "onChangesWrapper"
976+
},
965977
{
966978
"name": "pointers"
967979
},

packages/core/test/render3/Inherit_definition_feature_spec.ts

Lines changed: 68 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {EventEmitter, Output} from '../../src/core';
10-
import {EMPTY} from '../../src/render3/definition';
11-
import {InheritDefinitionFeature} from '../../src/render3/features/inherit_definition_feature';
12-
import {ComponentDef, DirectiveDef, RenderFlags, defineBase, defineComponent, defineDirective} from '../../src/render3/index';
9+
import {Inject, InjectionToken} from '../../src/core';
10+
import {ComponentDef, DirectiveDef, InheritDefinitionFeature, NgOnChangesFeature, ProvidersFeature, RenderFlags, defineBase, defineComponent, defineDirective, directiveInject, element} from '../../src/render3/index';
11+
import {ComponentFixture, createComponent} from './render_util';
1312

1413
describe('InheritDefinitionFeature', () => {
1514
it('should inherit lifecycle hooks', () => {
@@ -457,50 +456,90 @@ describe('InheritDefinitionFeature', () => {
457456
}).toThrowError('Directives cannot inherit Components');
458457
});
459458

460-
it('should run inherited features', () => {
461-
const log: any[] = [];
459+
it('should inherit ngOnChanges', () => {
460+
const log: string[] = [];
461+
let subDir !: SubDirective;
462462

463463
class SuperDirective {
464+
someInput = '';
465+
466+
ngOnChanges() { log.push('on changes!'); }
467+
464468
static ngDirectiveDef = defineDirective({
465469
type: SuperDirective,
466470
selectors: [['', 'superDir', '']],
467471
factory: () => new SuperDirective(),
468-
features: [
469-
(arg: any) => { log.push('super1', arg); },
470-
(arg: any) => { log.push('super2', arg); },
471-
]
472+
features: [NgOnChangesFeature],
473+
inputs: {someInput: 'someInput'}
472474
});
473475
}
474476

475477
class SubDirective extends SuperDirective {
476-
@Output()
477-
baz = new EventEmitter();
478+
static ngDirectiveDef = defineDirective({
479+
type: SubDirective,
480+
selectors: [['', 'subDir', '']],
481+
factory: () => subDir = new SubDirective(),
482+
features: [InheritDefinitionFeature],
483+
});
484+
}
485+
486+
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
487+
if (rf & RenderFlags.Create) {
488+
element(0, 'div', ['subDir', '']);
489+
}
490+
}, 1, 0, [SubDirective]);
491+
492+
const fixture = new ComponentFixture(App);
493+
expect(log).toEqual(['on changes!']);
494+
});
495+
496+
it('should NOT inherit providers', () => {
497+
let otherDir !: OtherDirective;
498+
499+
const SOME_DIRS = new InjectionToken('someDirs');
478500

479-
@Output()
480-
qux = new EventEmitter();
501+
// providers: [{ provide: SOME_DIRS, useClass: SuperDirective, multi: true }]
502+
class SuperDirective {
503+
static ngDirectiveDef = defineDirective({
504+
type: SuperDirective,
505+
selectors: [['', 'superDir', '']],
506+
factory: () => new SuperDirective(),
507+
features: [ProvidersFeature([{provide: SOME_DIRS, useClass: SuperDirective, multi: true}])],
508+
});
509+
}
481510

511+
// providers: [{ provide: SOME_DIRS, useClass: SubDirective, multi: true }]
512+
class SubDirective extends SuperDirective {
482513
static ngDirectiveDef = defineDirective({
483514
type: SubDirective,
484515
selectors: [['', 'subDir', '']],
485516
factory: () => new SubDirective(),
486-
features: [InheritDefinitionFeature, (arg: any) => { log.push('sub1', arg); }]
517+
features: [
518+
ProvidersFeature([{provide: SOME_DIRS, useClass: SubDirective, multi: true}]),
519+
InheritDefinitionFeature
520+
],
487521
});
488522
}
489523

490-
const superDef = SuperDirective.ngDirectiveDef as DirectiveDef<any>;
491-
const subDef = SubDirective.ngDirectiveDef as DirectiveDef<any>;
524+
class OtherDirective {
525+
constructor(@Inject(SOME_DIRS) public dirs: any) {}
526+
527+
static ngDirectiveDef = defineDirective({
528+
type: OtherDirective,
529+
selectors: [['', 'otherDir', '']],
530+
factory: () => otherDir = new OtherDirective(directiveInject(SOME_DIRS)),
531+
});
532+
}
533+
534+
/** <div otherDir subDir></div> */
535+
const App = createComponent('app', (rf: RenderFlags, ctx: any) => {
536+
if (rf & RenderFlags.Create) {
537+
element(0, 'div', ['otherDir', '', 'subDir', '']);
538+
}
539+
}, 1, 0, [OtherDirective, SubDirective, SuperDirective]);
492540

493-
expect(log).toEqual([
494-
'super1',
495-
superDef,
496-
'super2',
497-
superDef,
498-
'super1',
499-
subDef,
500-
'super2',
501-
subDef,
502-
'sub1',
503-
subDef,
504-
]);
541+
const fixture = new ComponentFixture(App);
542+
expect(otherDir.dirs.length).toEqual(1);
543+
expect(otherDir.dirs[0] instanceof SubDirective).toBe(true);
505544
});
506545
});

0 commit comments

Comments
 (0)