Skip to content

Commit

Permalink
feat: custom separator icon, custom template and i18n
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Modified API for component and service.
  • Loading branch information
udayvunnam committed Nov 5, 2019
1 parent fc6e35e commit f11980e
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 40 deletions.
15 changes: 15 additions & 0 deletions projects/xng-breadcrumb/src/lib/auto-label.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
name: 'autoLabel'
})
export class AutoLabelPipe implements PipeTransform {
transform(breadcrumbList: any, shouldautoGenerate: boolean, ...args: any[]): any {
if (shouldautoGenerate) {
return breadcrumbList;
} else {
return breadcrumbList.filter(breadcrumb => !breadcrumb.isAutoGeneratedLabel);
}
return null;
}
}
6 changes: 3 additions & 3 deletions projects/xng-breadcrumb/src/lib/breadcrumb.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<nav aria-label="breadcrumb" class="xng-breadcrumb-root">
<nav aria-label="breadcrumb" class="xng-breadcrumb-root" [ngClass]="class">
<ol class="xng-breadcrumb-list">
<ng-container *ngFor="let breadcrumb of breadcrumbs$ | async; last as isLast; first as isFirst">
<ng-container *ngFor="let breadcrumb of breadcrumbs$ | async | autoLabel: autoGenerate; last as isLast; first as isFirst">
<li class="xng-breadcrumb-item">
<a *ngIf="!isLast" [routerLink]="[breadcrumb.routeLink]" class="xng-breadcrumb-link">
<ng-container
Expand All @@ -17,7 +17,7 @@
</label>
</li>

<li class="xng-breadcrumb-separator" aria-hidden="true" role="separator" *ngIf="!isLast">
<li *ngIf="!isLast" class="xng-breadcrumb-separator" aria-hidden="true" role="separator">
<ng-container *ngTemplateOutlet="separatorTemplate"></ng-container>
<ng-container *ngIf="!separatorTemplate">{{ separator }}</ng-container>
</li>
Expand Down
14 changes: 10 additions & 4 deletions projects/xng-breadcrumb/src/lib/breadcrumb.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, ContentChild, Input, OnInit, TemplateRef } from '@angular/core';
import { Component, ContentChild, Input, OnInit, TemplateRef, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs';
import { BreadcrumbItemDirective } from './breadcrumb-item.directive';
import { BreadcrumbService } from './breadcrumb.service';
Expand All @@ -7,7 +7,8 @@ import { Breadcrumb } from './breadcrumb';
@Component({
selector: 'xng-breadcrumb',
templateUrl: './breadcrumb.component.html',
styleUrls: ['./breadcrumb.component.scss']
styleUrls: ['./breadcrumb.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class BreadcrumbComponent implements OnInit {
breadcrumbs$: Observable<Breadcrumb[]>;
Expand All @@ -28,7 +29,13 @@ export class BreadcrumbComponent implements OnInit {
* If true, breacrumb is auto generated even without any mapping label
* Default label is same as route segment
*/
@Input() defaultMapping = true;
@Input() autoGenerate = true;

/**
* custom class provided by consumer to increase specificity
* This will benefit to override styles that are conflicting
*/
@Input() class = '';

/**
* separator between breadcrumbs, defaults to '/'.
Expand Down Expand Up @@ -56,7 +63,6 @@ export class BreadcrumbComponent implements OnInit {
constructor(private breadcrumbService: BreadcrumbService) {}

ngOnInit() {
this.breadcrumbService.defaultMapping = this.defaultMapping;
this.breadcrumbs$ = this.breadcrumbService.breadcrumbs$;
}
}
3 changes: 2 additions & 1 deletion projects/xng-breadcrumb/src/lib/breadcrumb.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { BreadcrumbItemDirective } from './breadcrumb-item.directive';
import { BreadcrumbComponent } from './breadcrumb.component';
import { AutoLabelPipe } from './auto-label.pipe';

@NgModule({
declarations: [BreadcrumbComponent, BreadcrumbItemDirective],
declarations: [BreadcrumbComponent, BreadcrumbItemDirective, AutoLabelPipe],
imports: [CommonModule, RouterModule],
exports: [BreadcrumbComponent, BreadcrumbItemDirective]
})
Expand Down
74 changes: 42 additions & 32 deletions projects/xng-breadcrumb/src/lib/breadcrumb.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ export class BreadcrumbService {
private pathParamRegexIdentifier = '/:[^/]+';
private pathParamRegexReplacer = '/[^/]+';

/**
* If true, breacrumb is auto generated even without any mapping label
* Default label is same as route segment
*/
public defaultMapping = true;

constructor(private activatedRoute: ActivatedRoute, private router: Router) {
this.setBaseBreadcrumb();
this.detectRouteChanges();
Expand Down Expand Up @@ -87,14 +81,21 @@ export class BreadcrumbService {
private setBaseBreadcrumb() {
const baseConfig = this.router.config.find(pathConfig => pathConfig.path === '');
if (baseConfig && baseConfig.data) {
const { label, alias, skip = false, info } = this.getBreadcrumbOptions(baseConfig.data);
let { label, alias, skip = false, info } = this.getBreadcrumbOptions(baseConfig.data);

let isAutoGeneratedLabel = false;
if (typeof label !== 'string' && !label) {
label = '';
isAutoGeneratedLabel = true;
}

this.baseBreadcrumb = {
label,
alias,
skip,
info,
routeLink: this.baseHref
routeLink: this.baseHref,
isAutoGeneratedLabel
};
}
}
Expand Down Expand Up @@ -126,7 +127,8 @@ export class BreadcrumbService {
return this.prepareBreadcrumbList(activatedRoute.firstChild, routeLinkPrefix);
}
// remove breadcrumb items that needs to be hidden or don't have a label
const breacrumbsToShow = this.currentBreadcrumbs.filter(breadcrumb => !breadcrumb.skip);
const breacrumbsToShow = this.currentBreadcrumbs.filter(item => !item.skip);

this.breadcrumbs.next(breacrumbsToShow);
}

Expand All @@ -138,12 +140,14 @@ export class BreadcrumbService {
const routeLink = `${routeLinkPrefix}${resolvedPath}`;

let { label, alias, skip, info } = this.getFromStore(breadcrumb.alias, routeLink);
let isAutoGeneratedLabel = false;

if (typeof label !== 'string') {
if (typeof breadcrumb.label === 'string') {
label = breadcrumb.label;
} else if (this.defaultMapping) {
} else {
label = resolvedPath;
isAutoGeneratedLabel = true;
}
}

Expand All @@ -152,7 +156,8 @@ export class BreadcrumbService {
alias: alias || breadcrumb.alias,
skip: skip || breadcrumb.skip,
info: info || breadcrumb.info,
routeLink
routeLink,
isAutoGeneratedLabel
};
}

Expand Down Expand Up @@ -220,39 +225,44 @@ export class BreadcrumbService {
* Update current breadcrumb definition and emit a new stream of breadcrumbs
* Also update the store to reuse dynamic declarations
*/
private updateStore(spec) {
const { alias, routeLink, routeRegex, ...rest } = spec;

let breadcrumbItemIndex;
let storeItemIndex;

// identify macthing breadcrumb and store item
if (alias) {
breadcrumbItemIndex = this.currentBreadcrumbs.findIndex(item => alias === item.alias);
storeItemIndex = this.dynamicBreadcrumbStore.findIndex(item => alias === item.alias);
} else if (routeLink) {
breadcrumbItemIndex = this.currentBreadcrumbs.findIndex(item => routeLink === item.routeLink);
storeItemIndex = this.dynamicBreadcrumbStore.findIndex(item => routeLink === item.routeLink);
} else if (routeRegex) {
breadcrumbItemIndex = this.currentBreadcrumbs.findIndex(item => new RegExp(routeRegex).test(item.routeLink + '/'));
storeItemIndex = this.dynamicBreadcrumbStore.findIndex(item => routeRegex === item.routeRegex);
}
private updateStore(breadcrumb) {
const { breadcrumbItemIndex, storeItemIndex } = this.getBreadcrumbIndexes(breadcrumb);

// if breadcrumb is present in current breadcrumbs update it and emit new stream
if (breadcrumbItemIndex > -1) {
this.currentBreadcrumbs[breadcrumbItemIndex] = { ...this.currentBreadcrumbs[breadcrumbItemIndex], ...rest };
const breacrumbsToShow = this.currentBreadcrumbs.filter(breadcrumb => !breadcrumb.skip);
this.currentBreadcrumbs[breadcrumbItemIndex] = { ...this.currentBreadcrumbs[breadcrumbItemIndex], ...breadcrumb };
const breacrumbsToShow = this.currentBreadcrumbs.filter(item => !item.skip);
this.breadcrumbs.next([...breacrumbsToShow]);
}

// If the store already has this route definition update it, else add
if (storeItemIndex > -1) {
this.dynamicBreadcrumbStore[storeItemIndex] = { ...this.dynamicBreadcrumbStore[storeItemIndex], ...rest };
this.dynamicBreadcrumbStore[storeItemIndex] = { ...this.dynamicBreadcrumbStore[storeItemIndex], ...breadcrumb };
} else {
this.dynamicBreadcrumbStore.push({ alias, routeLink, routeRegex, ...rest });
this.dynamicBreadcrumbStore.push(breadcrumb);
}
}

private getBreadcrumbIndexes(breadcrumb): any {
const { alias, routeLink, routeRegex } = breadcrumb;
let indexMap = {};
// identify macthing breadcrumb and store item
if (alias) {
indexMap = this.getBreadcrumbIndexesByType('alias', alias);
} else if (routeLink) {
indexMap = this.getBreadcrumbIndexesByType('routeLink', routeLink);
} else if (routeRegex) {
indexMap = this.getBreadcrumbIndexesByType('routeRegex', routeRegex);
}
return indexMap;
}

private getBreadcrumbIndexesByType(key: string, value: string) {
const breadcrumbItemIndex = this.currentBreadcrumbs.findIndex(item => value === item[key]);
const storeItemIndex = this.dynamicBreadcrumbStore.findIndex(item => value === item[key]);
return { breadcrumbItemIndex, storeItemIndex };
}

private resolvePathParam(path: string, activatedRoute: ActivatedRoute) {
// if the path segment is a route param, read the param value from url
if (path.startsWith(this.pathParamPrefix)) {
Expand Down
5 changes: 5 additions & 0 deletions projects/xng-breadcrumb/src/lib/breadcrumb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@ export interface Breadcrumb {
* path '/mentor/:id' becomes routeRegex '/mentor/[^/]+', which can be matched against when needed
*/
routeRegex?: string;
/**
* isAutoGeneratedLabel has to be present at component level but not at the service,
* since we may need to support multiple breadcrumb components in same app
*/
isAutoGeneratedLabel?: boolean;
}

0 comments on commit f11980e

Please sign in to comment.