Skip to content

Commit 8b181cd

Browse files
committed
Refactored ChangeableItem(s)Directive to use a helper, Updated CommandBarItem and ContextualMenuItem to use new pattern, Changed component property organization, Ran Prettier on touched files
1 parent 0eff80c commit 8b181cd

16 files changed

+355
-346
lines changed

apps/demo/src/app/app.component.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ <h2>Getting up and running...</h2>
9191
</fab-details-list>
9292
</fab-marquee-selection>
9393
<div>{{ selection.count || 0 }} item(s) selected</div>
94-
<fab-default-button (click)="marqueeEnabled = !marqueeEnabled">
95-
Marque {{ marqueeEnabled === false ? 'Disabled' : 'Enabled' }}
94+
<fab-default-button (onClick)="marqueeEnabled = !marqueeEnabled">
95+
Marquee {{ marqueeEnabled === false ? 'Disabled' : 'Enabled' }}
9696
</fab-default-button>
97-
</div>
97+
</div>

apps/demo/src/app/app.component.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ export class AppComponent {
4242
};
4343

4444
detailItems = [
45-
{ 'field1': 'f1content1', 'field2': 'f2content1' },
46-
{ 'field1': 'f1content2', 'field2': 'f2content2' },
47-
{ 'field1': 'f1content3', 'field2': 'f2content3' },
48-
{ 'field1': 'f1content4' },
49-
{ 'field2': 'f2content5' }
50-
]
45+
{ field1: 'f1content1', field2: 'f2content1' },
46+
{ field1: 'f1content2', field2: 'f2content2' },
47+
{ field1: 'f1content3', field2: 'f2content3' },
48+
{ field1: 'f1content4' },
49+
{ field2: 'f2content5' },
50+
];
5151

5252
onNewClicked() {
5353
console.log('New clicked');

libs/fabric/src/lib/components/command-bar/command-bar.component.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ import {
6262
export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarProps>
6363
implements OnChanges<FabCommandBarComponent>, AfterContentInit, OnDestroy {
6464
@ContentChild(CommandBarItemsDirective)
65-
readonly itemsDirective: CommandBarItemsDirective;
65+
readonly itemsDirective?: CommandBarItemsDirective;
6666
@ContentChild(CommandBarFarItemsDirective)
67-
readonly farItemsDirective: CommandBarFarItemsDirective;
67+
readonly farItemsDirective?: CommandBarFarItemsDirective;
6868
@ContentChild(CommandBarOverflowItemsDirective)
69-
readonly overflowItemsDirective: CommandBarOverflowItemsDirective;
69+
readonly overflowItemsDirective?: CommandBarOverflowItemsDirective;
7070

7171
@ViewChild('reactNode')
7272
protected reactNodeRef: ElementRef;
@@ -188,7 +188,7 @@ export class FabCommandBarComponent extends ReactWrapperComponent<ICommandBarPro
188188

189189
// Subscribe for existing items changes
190190
this._subscriptions.push(
191-
directive.onItemChanged.subscribe(({ key, changes }: CommandBarItemChangedPayload) => {
191+
directive.onChildItemChanged.subscribe(({ key, changes }: CommandBarItemChangedPayload) => {
192192
setItems(items => items.map(item => (item.key === key ? mergeItemChanges(item, changes) : item)));
193193
this.markForCheck();
194194
})

libs/fabric/src/lib/components/command-bar/directives/command-bar-items.directives.ts

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import {
5-
AfterContentInit,
6-
ContentChildren,
7-
Directive,
8-
EventEmitter,
9-
OnDestroy,
10-
Output,
11-
QueryList,
12-
} from '@angular/core';
13-
import { Subscription } from 'rxjs';
14-
import { ICommandBarItemOptions } from '../command-bar.component';
15-
import { CommandBarItemChangedPayload, CommandBarItemDirective } from './command-bar-item.directives';
4+
import { ContentChildren, Directive, QueryList } from '@angular/core';
165

17-
export abstract class CommandBarItemsDirectiveBase implements AfterContentInit, OnDestroy {
18-
private readonly _subscriptions: Subscription[] = [];
6+
import { ChangeableItemsDirective } from '../../core/shared/changeable-items.directive';
7+
import { ICommandBarItemOptions } from '../command-bar.component';
8+
import { CommandBarItemDirective } from './command-bar-item.directives';
199

10+
export abstract class CommandBarItemsDirectiveBase extends ChangeableItemsDirective<ICommandBarItemOptions> {
2011
abstract readonly directiveItems: QueryList<CommandBarItemDirective>;
2112

22-
@Output()
23-
readonly onItemChanged = new EventEmitter<CommandBarItemChangedPayload>();
24-
@Output()
25-
readonly onItemsChanged = new EventEmitter<QueryList<CommandBarItemDirective>>();
26-
2713
get items() {
2814
return (
2915
this.directiveItems &&
@@ -38,29 +24,6 @@ export abstract class CommandBarItemsDirectiveBase implements AfterContentInit,
3824
}))
3925
);
4026
}
41-
42-
ngAfterContentInit() {
43-
this._subscriptions.push(
44-
...this.directiveItems.map(directiveItem =>
45-
// Propagate the change to the parent CommandBarComponent
46-
directiveItem.onItemChanged.subscribe(changes => this.onItemChangedHandler(changes))
47-
)
48-
);
49-
50-
this._subscriptions.push(
51-
this.directiveItems.changes.subscribe((newValue: this['directiveItems']) => {
52-
this.onItemsChanged.emit(newValue);
53-
})
54-
);
55-
}
56-
57-
ngOnDestroy() {
58-
this._subscriptions.forEach(subscription => subscription.unsubscribe());
59-
}
60-
61-
private onItemChangedHandler(payload: CommandBarItemChangedPayload) {
62-
this.onItemChanged.emit(payload);
63-
}
6427
}
6528

6629
@Directive({ selector: 'fab-command-bar > items' })

libs/fabric/src/lib/components/contextual-menu/directives/contextual-menu-item.directive.ts

Lines changed: 29 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,23 @@ import {
1212
QueryList,
1313
} from '@angular/core';
1414
import { IContextualMenuItem } from 'office-ui-fabric-react';
15-
import { Subscription } from 'rxjs';
16-
import { OnChanges, TypedChanges } from '../../../declarations/angular/typed-changes';
17-
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
1815

19-
export type ContextualMenuItemChangedPayload = ItemChangedPayload<IContextualMenuItem['key'], IContextualMenuItem>;
16+
import { OnChanges } from '../../../declarations/angular/typed-changes';
17+
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
18+
import { ChangeableItemsHelper, IChangeableItemsContainer } from '../../core/shared/changeable-helper';
19+
import { ChangeableItemDirective } from '../../core/shared/changeable-item.directive';
2020

2121
@Directive({ selector: 'contextual-menu-item' })
22-
export class ContextualMenuItemDirective
23-
implements IContextualMenuItem, OnChanges<ContextualMenuItemDirective>, AfterContentInit, OnDestroy {
22+
export class ContextualMenuItemDirective extends ChangeableItemDirective<IContextualMenuItem>
23+
implements
24+
AfterContentInit,
25+
IChangeableItemsContainer<IContextualMenuItem>,
26+
IContextualMenuItem,
27+
OnChanges<ContextualMenuItemDirective>,
28+
OnDestroy {
2429
@ContentChildren(ContextualMenuItemDirective)
2530
readonly menuItemsDirectives: QueryList<ContextualMenuItemDirective>;
2631

27-
@Input()
28-
key: IContextualMenuItem['key'];
2932
@Input()
3033
componentRef?: IContextualMenuItem['componentRef'];
3134
@Input()
@@ -94,53 +97,31 @@ export class ContextualMenuItemDirective
9497
@Output()
9598
readonly click = new EventEmitter<{ ev?: MouseEvent | KeyboardEvent; item?: IContextualMenuItem }>();
9699

97-
// Directive properties
98100
@Output()
99-
readonly onItemChanged = new EventEmitter<ContextualMenuItemChangedPayload>();
100-
101-
private readonly _subscriptions: Subscription[] = [];
102-
103-
ngOnChanges(changes: TypedChanges<this>) {
104-
this.onItemChanged.emit({
105-
key: this.key,
106-
changes,
107-
});
101+
get onChildItemChanged(): EventEmitter<ItemChangedPayload<string, IContextualMenuItem>> {
102+
return this.changeableItemsHelper && this.changeableItemsHelper.onChildItemChanged;
103+
}
104+
@Input()
105+
get onItemsChanged(): EventEmitter<QueryList<ChangeableItemDirective<IContextualMenuItem>>> {
106+
return this.changeableItemsHelper && this.changeableItemsHelper.onItemsChanged;
108107
}
109108

110-
ngAfterContentInit() {
111-
// @ContentChildren selects host component as well.
112-
// Relevant GitHub issue: https://github.com/angular/angular/issues/10098
113-
const nonSelfMenuItemsDirectives = this.menuItemsDirectives.filter(directive => directive !== this);
114-
if (nonSelfMenuItemsDirectives.length === 0) {
115-
return;
116-
}
117-
118-
const items = nonSelfMenuItemsDirectives.map(directive => this._directiveToContextualMenuItem(directive));
119-
if (!this.subMenuProps) {
120-
this.subMenuProps = { items: items };
121-
} else {
122-
this.subMenuProps.items = items;
123-
}
109+
private changeableItemsHelper: ChangeableItemsHelper<IContextualMenuItem>;
124110

125-
this._subscriptions.push(
126-
this.menuItemsDirectives.changes.subscribe((newValue: this['menuItemsDirectives']) => {
127-
this.onItemChanged.emit({
128-
key: this.key,
129-
changes: {
130-
subMenuProps: {
131-
currentValue: {
132-
...this.subMenuProps,
133-
items: newValue.map(directive => this._directiveToContextualMenuItem(directive)),
134-
},
135-
},
136-
},
137-
});
138-
})
139-
);
111+
ngAfterContentInit() {
112+
this.changeableItemsHelper = new ChangeableItemsHelper(this.menuItemsDirectives, this, nonSelfDirective => {
113+
const items = nonSelfDirective.map(directive => this._directiveToContextualMenuItem(directive as any));
114+
if (!this.subMenuProps) {
115+
this.subMenuProps = { items: items };
116+
} else {
117+
this.subMenuProps.items = items;
118+
}
119+
});
120+
this.changeableItemsHelper.afterContentInit();
140121
}
141122

142123
ngOnDestroy() {
143-
this._subscriptions.forEach(subscription => subscription.unsubscribe());
124+
this.changeableItemsHelper.onDestroy();
144125
}
145126

146127
private _directiveToContextualMenuItem(directive: ContextualMenuItemDirective): IContextualMenuItem {
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { EventEmitter, QueryList } from '@angular/core';
5+
import { Subscription } from 'rxjs';
6+
7+
import { TypedChanges } from '../../../declarations/angular/typed-changes';
8+
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
9+
import { ChangeableItemDirective } from './changeable-item.directive';
10+
11+
/**
12+
* Helper class for single changeable item
13+
*/
14+
export class ChangeableItemHelper<TItem> {
15+
readonly onItemChanged = new EventEmitter<ItemChangedPayload<string, TItem>>();
16+
17+
constructor(private readonly key: string) {}
18+
19+
onChanges(changes: TypedChanges<TItem>) {
20+
this.onItemChanged.emit({ key: this.key, changes });
21+
}
22+
}
23+
24+
/**
25+
* Parent class for wrapper directive for multiple ChangeableItemDirectives
26+
*/
27+
export class ChangeableItemsHelper<TItem> {
28+
readonly onChildItemChanged = new EventEmitter<ItemChangedPayload<string, TItem>>();
29+
readonly onItemsChanged = new EventEmitter<QueryList<ChangeableItemDirective<TItem>>>();
30+
31+
private readonly _subscriptionsMap: { [key: string]: Subscription } = {};
32+
private _changeSubscription: Subscription;
33+
34+
constructor(
35+
private directiveItems: QueryList<ChangeableItemDirective<TItem>>,
36+
private self?: IChangeableItemsContainer<TItem>,
37+
private nonSelfHandler?: (nonSelfDirectives: ChangeableItemDirective<TItem>[]) => void
38+
) {}
39+
40+
afterContentInit() {
41+
this._subscribeNewDirectives();
42+
this._changeSubscription = this.directiveItems.changes.subscribe(newValues => {
43+
this.onItemsChanged.emit(newValues);
44+
this._subscribeNewDirectives();
45+
});
46+
}
47+
48+
onDestroy() {
49+
Object.keys(this._subscriptionsMap).forEach(key => this._subscriptionsMap[key].unsubscribe());
50+
this._changeSubscription.unsubscribe();
51+
}
52+
53+
private _subscribeNewDirectives() {
54+
const nonSelfDirectives = this._handleNonSelfDirectives();
55+
nonSelfDirectives.forEach(directiveItem => {
56+
if (this._subscriptionsMap[directiveItem.key]) {
57+
this._subscriptionsMap[directiveItem.key].unsubscribe();
58+
}
59+
this._subscriptionsMap[directiveItem.key] = directiveItem.onItemChanged.subscribe(changes => {
60+
this._handleNonSelfDirectives();
61+
this.onChildItemChanged.emit(changes);
62+
if (this.self && this.self.onItemChanged) {
63+
this.self.onItemChanged.emit(changes);
64+
}
65+
});
66+
});
67+
}
68+
69+
private _handleNonSelfDirectives() {
70+
const nonSelfDirectives = this.directiveItems.filter(directiveItem => directiveItem !== (this.self as any));
71+
if (this.nonSelfHandler && nonSelfDirectives.length) {
72+
this.nonSelfHandler(nonSelfDirectives);
73+
}
74+
return nonSelfDirectives;
75+
}
76+
}
77+
78+
/**
79+
* Interface for directives that contain changeable items
80+
*/
81+
export interface IChangeableItemsContainer<TItem> {
82+
onChildItemChanged: EventEmitter<ItemChangedPayload<string, TItem>>;
83+
onItemChanged?: EventEmitter<ItemChangedPayload<string, TItem>>;
84+
onItemsChanged: EventEmitter<QueryList<ChangeableItemDirective<TItem>>>;
85+
}

libs/fabric/src/lib/components/core/shared/changeable-item.directive.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,32 @@
22
// Licensed under the MIT License.
33

44
import { EventEmitter, Input, Output } from '@angular/core';
5-
5+
66
import { OnChanges, TypedChanges } from '../../../declarations/angular/typed-changes';
77
import { ItemChangedPayload } from '../../core/declarative/item-changed.payload';
8+
import { ChangeableItemHelper } from './changeable-helper';
89

910
/**
1011
* Parent class for wrapper directive for single item with OnChanges
1112
*/
12-
export abstract class ChangeableItemDirective<TItem>
13-
implements OnChanges<ChangeableItemDirective<TItem>> {
14-
13+
export abstract class ChangeableItemDirective<TItem> implements OnChanges<ChangeableItemDirective<TItem>> {
1514
@Input()
1615
key: string;
1716

1817
@Output()
19-
readonly onItemChanged = new EventEmitter<ItemChangedPayload<string, TItem>>();
18+
get onItemChanged(): EventEmitter<ItemChangedPayload<string, TItem>> {
19+
return this.changeableItemHelper && this.changeableItemHelper.onItemChanged;
20+
}
21+
22+
private changeableItemHelper: ChangeableItemHelper<TItem>;
23+
24+
ngOnInit() {
25+
this.changeableItemHelper = new ChangeableItemHelper(this.key);
26+
}
2027

2128
ngOnChanges(changes: TypedChanges<TItem>) {
22-
this.onItemChanged.emit({ key: this.key, changes });
29+
if (this.changeableItemHelper) {
30+
this.changeableItemHelper.onChanges(changes);
31+
}
2332
}
2433
}

0 commit comments

Comments
 (0)