事实上,我确实试图找到一个类似的问题,因为我发现很难接受它之前没有被问过(即使它可能是一个反模式)。让我们从这个组件开始:
@Component({...})
export class MyParent {
myGroups: Observable<Array<MyItemGroup>>;
}
其中MyItemGroup
是:
export class MyItemGroup {
groupDisplayName: string;
group: Observable<Array<MyGroupedItem>>;
}
首先,这种安排是否有某种反模式?是否有另一种分组设计在angular.io中被认为是正确的?为了避免经典答案,“这取决于”,让我们进一步看看:
export class MyGroupedItem {
title: string;
uri: string;
}
在MyParent
级别,我想按MyGroupedItem
过滤title
,当此过滤器将MyItemGroup.group
的数量减少为零时,我想过滤完全退出MyItemGroup
。这有力地告诉我,我必须将mySubject
添加到MyParent
:
@Component({...})
export class MyParent {
myGroups: Observable<Array<MyItemGroup>>;
mySubject: Subject<string> = new Subject<string>();
filterMyGroups(particle: string): void {
this.mySubject.next(particle);
}
}
在这里,我假设我需要与RxJS一起去城镇,将myGroups
和mySubject
结合起来。怎么做到这一点?用户界面将通过此标记引用mySubject
:
<input #myFilter (keyup)="filterMyGroups(myFilter.value)" />
我只能希望俘虏的希望能使我的问题得到理解和明确。
答案 0 :(得分:3)
您不需要将这些项目设置为可观察项目。
您可以做以下事情:
map
数据API observable和搜索过滤器输入发生了变化。然后在其上运行可观察的map
map
将采用字符串(过滤器)和一组数组。map
内,您需要基本的数组映射和过滤
如上所述,您可以获得一组组,因此,您可以:
items
函数返回新的组数组,每个组都相同,除了使用过滤字符串过滤其filter
数组items.length > 0
,这样您只返回@Component({
selector: 'app-parent',
template: `
<p>
Start editing to see some magic happen :)
</p>
<form [formGroup]="form">
<label>
Search
<input type="search" formControlName="search" />
</label>
</form>
<h2>Items</h2>
<app-group
*ngFor="let group of groups$ | async"
[group]="group" ></app-group>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParentComponent {
constructor(
private service: GroupsService
) { }
groupData$ = this.service.getGroups();
form = new FormGroup({
search: new FormControl('')
});
searchFilter$ = this.form.valueChanges.pipe(
// the map gets an arg in shape of `{search: 'value of search input'}`
map((newFormValue: { search: string }) => newFormValue.search)
);
// `combineLatest` returns values from observables passed to it * as array *
groups$ = combineLatest(
this.groupData$,
// We need to show data without waiting for filter change
this.searchFilter$.pipe(startWith(''))
).pipe(
map(([groups, searchFilter]) =>
groups
.map(group => this.filterItemsInGroup(group, searchFilter))
.filter(group => group.items.length > 0)
)
);
private filterItemsInGroup(group: ItemGroup, searchFilter: string) {
if (!searchFilter) {
return group;
}
return {
// Copy all properties of group
...group,
// This is NOT observable filter, this is basic array filtering
items: group.items.filter(item =>
item.title.toLowerCase().indexOf(searchFilter.toLowerCase())
>= 0
)
};
}
}
这是第二步的一些代码:
OnPush
这是一个包含所有部分的完整工作样本:
该示例添加了一些优化(例如将项目分隔到其自己的组件中,并使用trackBy
更改跟踪以获得稍快的输出。可以添加更多优化,例如使用@Component({
selector: 'app-group',
template: `
<h3
class="group-name"
(click)="showItems = ! showItems"
>{{group.displayName}}</h3>
<ng-container *ngIf="showItems">
<app-item
*ngFor="let item of group.items"
[item]="item"
></app-item>
</ng-container>
`
})
export class GroupComponent {
@Input() group: ItemGroup;
showItems = false;
}
。
最初的海报来自Twitter和explained his situation was more complex。
他想要
这实际上使隐藏组的问题更容易,因为您可以在对项目进行分组之前对其进行过滤。
隐藏物品也不一定是这个问题的一部分。如果你的组在它自己的组件中,你可以在该组件中有一个默认隐藏项目的布尔值,然后在组名称点击时将其翻转。
让我们先看看这个位的代码,因为它最简单:
(省略了样式和其他位,但我将链接到下面更新的完整示例)
switchMap
现在让我们回到上市问题。我们需要做的是:
groups$
或其他任何方式将表单连接到服务,并使用项目,搜索过滤器和分组类型获取一个可观察的让我们看看整个代码:
请注意@Component({
selector: 'app-parent',
template: `
<p>
Start editing to see some magic happen :)
</p>
<form [formGroup]="form">
<label>
Group
<select formControlName="groupingKind">
<option
*ngFor="let grouping of GroupingKinds"
value="{{grouping}}">{{grouping}}</option>
</select>
</label>
<label>
Filter
<input type="search" formControlName="search" />
</label>
</form>
<h2>Items</h2>
<app-group
*ngFor="let group of groups$ | async"
[group]="group" ></app-group>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParentComponent {
constructor(
private service: GroupsService
) { }
// To make the template see it
readonly GroupingKinds = Object.keys(GroupingKind)
.map(key => GroupingKind[key]);
form = new FormGroup({
search: new FormControl(''),
// Defaults to ByDate
groupingKind: new FormControl(GroupingKind.ByDate)
});
// This will make a new HTTP request with every form change
// If you don't want that, just switch the order
// of `this.form.valueChanges` and `this.service.getItems()`
groups$ = this.form.valueChanges.pipe(
// initial load
startWith(this.form.value as {search: string, groupingKind: GroupingKind}),
switchMap((form) =>
this.service.getItems().pipe(
// Take every array the `getItems()` returns
// (which is why we use observable `map` not observable `filter`)
// And then transform it into another array
// that happenes to be the same array but filtered
map(items => this.filterItems(items, form.search)),
// Then map the result into group
map(items => this.createGroups(items, form.groupingKind))
)
),
);
private filterItems(items: ItemGroupItem[], searchFilter: string) {
return items.filter(item =>
item.Title.toLowerCase().indexOf(searchFilter.toLowerCase())
>= 0
);
}
private createGroups(src: ItemGroupItem[], groupingKind: GroupingKind) {
const groupsList = [] as ItemGroup[];
src.reduce((groupsObject, item) => {
// Topic groups values are an array, date grouping value is a string,
// So we convert date grouping value to array also for simplicity
const groupNames = groupingKind == GroupingKind.ByTopic
? item.ItemCategory.topics
: [ item.ItemCategory.dateGroup ];
for(const groupName of groupNames) {
if(!groupsObject[groupName]) {
const newGroup: ItemGroup = {
displayName: groupName,
items: []
};
groupsObject[groupName] = newGroup;
groupsList.push(newGroup);
}
groupsObject[groupName].items.push(item);
}
return groupsObject;
}, {} as { [name:string]: ItemGroup} );
return groupsList;
}
}
属性的设置有多简单。唯一的复杂性是真正应用分组。
{{1}}
正如所承诺的,这里有完整的更新样本: