Skip to content

Commit

Permalink
feat: remove hard dependency on @angular/http
Browse files Browse the repository at this point in the history
* Makes `@angular/http` an optional dependency since it is only being used by the icon module. An error will be thrown only if the user runs into something that requires the `HttpModule`.
* Moves the various icon error classes into `icon-errors.ts`.
* Moves the icon registry provider next to the registry itself, instead of in the same file as the icon directive.

Fixes #2616.
  • Loading branch information
crisbeto committed Apr 12, 2017
1 parent 9d719c5 commit ce74103
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 63 deletions.
4 changes: 1 addition & 3 deletions src/lib/icon/fake-svgs.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {
Response,
ResponseOptions} from '@angular/http';
import {Response, ResponseOptions} from '@angular/http';

/**
* Fake URLs and associated SVG documents used by tests.
Expand Down
43 changes: 43 additions & 0 deletions src/lib/icon/icon-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {MdError} from '../core';

/**
* Exception thrown when attempting to load an icon with a name that cannot be found.
* @docs-private
*/
export class MdIconNameNotFoundError extends MdError {
constructor(iconName: string) {
super(`Unable to find icon with the name "${iconName}"`);
}
}

/**
* Exception thrown when attempting to load SVG content that does not contain the expected
* <svg> tag.
* @docs-private
*/
export class MdIconSvgTagNotFoundError extends MdError {
constructor() {
super('<svg> tag not found');
}
}

/**
* Exception thrown when the consumer attempts to use `<md-icon>` without including @angular/http.
* @docs-private
*/
export class MdIconNoHttpProviderError extends MdError {
constructor() {
super('Could not find Http provider for use with Angular Material icons. ' +
'Please include the HttpModule from @angular/http in your app imports.');
}
}

/**
* Exception thrown when an invalid icon name is passed to an md-icon component.
* @docs-private
*/
export class MdIconInvalidNameError extends MdError {
constructor(iconName: string) {
super(`Invalid icon name: "${iconName}"`);
}
}
56 changes: 28 additions & 28 deletions src/lib/icon/icon-registry.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {Injectable, SecurityContext} from '@angular/core';
import {Injectable, SecurityContext, Optional, SkipSelf} from '@angular/core';
import {SafeResourceUrl, DomSanitizer} from '@angular/platform-browser';
import {Http} from '@angular/http';
import {MdError} from '../core';
import {Observable} from 'rxjs/Observable';
import {
MdIconNameNotFoundError,
MdIconSvgTagNotFoundError,
MdIconNoHttpProviderError,
} from './icon-errors';
import 'rxjs/add/observable/forkJoin';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map';
Expand All @@ -14,27 +18,6 @@ import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';


/**
* Exception thrown when attempting to load an icon with a name that cannot be found.
* @docs-private
*/
export class MdIconNameNotFoundError extends MdError {
constructor(iconName: string) {
super(`Unable to find icon with the name "${iconName}"`);
}
}

/**
* Exception thrown when attempting to load SVG content that does not contain the expected
* <svg> tag.
* @docs-private
*/
export class MdIconSvgTagNotFoundError extends MdError {
constructor() {
super('<svg> tag not found');
}
}

/**
* Configuration for an icon, including the URL and possibly the cached SVG element.
* @docs-private
Expand All @@ -44,9 +27,6 @@ class SvgIconConfig {
constructor(public url: SafeResourceUrl) { }
}

/** Returns the cache key to use for an icon namespace and name. */
const iconKey = (namespace: string, name: string) => namespace + ':' + name;

/**
* Service to register and display icons used by the <md-icon> component.
* - Registers icon URLs by namespace and name.
Expand Down Expand Up @@ -83,7 +63,7 @@ export class MdIconRegistry {
*/
private _defaultFontSetClass = 'material-icons';

constructor(private _http: Http, private _sanitizer: DomSanitizer) {}
constructor(@Optional() private _http: Http, private _sanitizer: DomSanitizer) {}

/**
* Registers an icon by URL in the default namespace.
Expand Down Expand Up @@ -386,7 +366,11 @@ export class MdIconRegistry {
* cached, so future calls with the same URL may not cause another HTTP request.
*/
private _fetchUrl(safeUrl: SafeResourceUrl): Observable<string> {
let url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl);
if (!this._http) {
throw new MdIconNoHttpProviderError();
}

const url = this._sanitizer.sanitize(SecurityContext.RESOURCE_URL, safeUrl);

// Store in-progress fetches to avoid sending a duplicate request for a URL when there is
// already a request in progress for that URL. It's necessary to call share() on the
Expand All @@ -408,8 +392,24 @@ export class MdIconRegistry {
}
}

export function ICON_REGISTRY_PROVIDER_FACTORY(
parentRegistry: MdIconRegistry, http: Http, sanitizer: DomSanitizer) {
return parentRegistry || new MdIconRegistry(http, sanitizer);
}

export const ICON_REGISTRY_PROVIDER = {
// If there is already an MdIconRegistry available, use that. Otherwise, provide a new one.
provide: MdIconRegistry,
deps: [[new Optional(), new SkipSelf(), MdIconRegistry], [new Optional(), Http], DomSanitizer],
useFactory: ICON_REGISTRY_PROVIDER_FACTORY
};

/** Clones an SVGElement while preserving type information. */
function cloneSvg(svg: SVGElement): SVGElement {
return svg.cloneNode(true) as SVGElement;
}

/** Returns the cache key to use for an icon namespace and name. */
function iconKey(namespace: string, name: string) {
return namespace + ':' + name;
}
36 changes: 34 additions & 2 deletions src/lib/icon/icon.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import {inject, async, TestBed} from '@angular/core/testing';
import {SafeResourceUrl, DomSanitizer} from '@angular/platform-browser';
import {XHRBackend} from '@angular/http';
import {HttpModule, XHRBackend} from '@angular/http';
import {MockBackend} from '@angular/http/testing';
import {Component} from '@angular/core';
import {MdIconModule} from './index';
import {MdIconRegistry} from './icon-registry';
import {MdIconNoHttpProviderError} from './icon-errors';
import {getFakeSvgHttpResponse} from './fake-svgs';


Expand Down Expand Up @@ -36,7 +37,7 @@ describe('MdIcon', () => {

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdIconModule.forRoot()],
imports: [HttpModule, MdIconModule.forRoot()],
declarations: [
MdIconColorTestApp,
MdIconLigatureTestApp,
Expand Down Expand Up @@ -394,6 +395,37 @@ describe('MdIcon', () => {
});


describe('MdIcon without HttpModule', () => {
let mdIconRegistry: MdIconRegistry;
let sanitizer: DomSanitizer;

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MdIconModule.forRoot()],
declarations: [MdIconFromSvgNameTestApp],
});

TestBed.compileComponents();
}));

beforeEach(inject([MdIconRegistry, DomSanitizer], (mir: MdIconRegistry, ds: DomSanitizer) => {
mdIconRegistry = mir;
sanitizer = ds;
}));

it('should throw an error when trying to load a remote icon', async() => {
expect(() => {
mdIconRegistry.addSvgIcon('fido', sanitizer.bypassSecurityTrustResourceUrl('dog.svg'));

let fixture = TestBed.createComponent(MdIconFromSvgNameTestApp);

fixture.componentInstance.iconName = 'fido';
fixture.detectChanges();
}).toThrowError(MdIconNoHttpProviderError);
});
});


/** Test components that contain an MdIcon. */
@Component({template: `<md-icon>{{iconName}}</md-icon>`})
class MdIconLigatureTestApp {
Expand Down
26 changes: 2 additions & 24 deletions src/lib/icon/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,10 @@ import {
SimpleChange,
ViewEncapsulation,
AfterViewChecked,
Optional,
SkipSelf,
} from '@angular/core';
import {Http} from '@angular/http';
import {DomSanitizer} from '@angular/platform-browser';
import {MdError} from '../core';
import {MdIconRegistry, MdIconNameNotFoundError} from './icon-registry';
import {MdIconRegistry} from './icon-registry';
import {MdIconNameNotFoundError, MdIconInvalidNameError} from './icon-errors';

/** Exception thrown when an invalid icon name is passed to an md-icon component. */
export class MdIconInvalidNameError extends MdError {
constructor(iconName: string) {
super(`Invalid icon name: "${iconName}"`);
}
}

/**
* Component to display an icon. It can be used in the following ways:
Expand Down Expand Up @@ -246,15 +236,3 @@ export class MdIcon implements OnChanges, OnInit, AfterViewChecked {
}
}
}

export function ICON_REGISTRY_PROVIDER_FACTORY(
parentRegistry: MdIconRegistry, http: Http, sanitizer: DomSanitizer) {
return parentRegistry || new MdIconRegistry(http, sanitizer);
}

export const ICON_REGISTRY_PROVIDER = {
// If there is already an MdIconRegistry available, use that. Otherwise, provide a new one.
provide: MdIconRegistry,
deps: [[new Optional(), new SkipSelf(), MdIconRegistry], Http, DomSanitizer],
useFactory: ICON_REGISTRY_PROVIDER_FACTORY,
};
9 changes: 5 additions & 4 deletions src/lib/icon/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import {NgModule, ModuleWithProviders} from '@angular/core';
import {HttpModule} from '@angular/http';
import {CompatibilityModule} from '../core';
import {MdIcon, ICON_REGISTRY_PROVIDER} from './icon';
import {MdIcon} from './icon';
import {ICON_REGISTRY_PROVIDER} from './icon-registry';


@NgModule({
imports: [HttpModule, CompatibilityModule],
imports: [CompatibilityModule],
exports: [MdIcon, CompatibilityModule],
declarations: [MdIcon],
providers: [ICON_REGISTRY_PROVIDER],
Expand All @@ -22,4 +22,5 @@ export class MdIconModule {


export * from './icon';
export {MdIconRegistry} from './icon-registry';
export * from './icon-errors';
export * from './icon-registry';
3 changes: 1 addition & 2 deletions src/lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
"homepage": "https://github.com/angular/material2#readme",
"peerDependencies": {
"@angular/core": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/http": "^4.0.0"
"@angular/common": "^4.0.0"
}
}

0 comments on commit ce74103

Please sign in to comment.