Angular 2 Material. Same MdIcon that works in one

2019-08-01 14:24发布

I just upgraded from ng2 rc4 with material2 alpha6 to ng2 rc5 with material 2 alpha7-2. A new error pops up on code that worked before when I use <md-icon>.

I cannot see the full error text because instead I get Observable_1.Observable.throw is not a function error (the app crashes), with the top lines in the stacktrace being:

MdIconRegistry.prototype.getNamedSvgIcon
    @angular2-material/icon/icon-registry.js:180:16
MdIcon.prototype.ngOnChanges
    @angular2-material/icon/icon.js:107:17

If I console.log() the id of the icon sought just above icon-registry.js @ line 180, I see ic_lightbulb_outline_24px. This icon is present in my svg sprite, but it displayed properly before I upgraded today to material 2 alpha 7-2. Here is its section in the svg sprite:

...<svg viewBox="..." id="ic_lightbulb_outline_24px"><path d="..."/></svg>...

The template:

<md-icon svgIcon="ic_lightbulb_outline_24px"></md-icon>

In my main AppComponent I have

this._iconRegistry.addSvgIconSet('src/icons/sprite.defs.svg');

I've also imported MdIconModule into the main AppModule. What's really puzzling is that

  • the same icon from the same sprite file works on other components and
  • this worked before the upgrade!

Two more pieces of info:

  • this problem only affects lazy-loaded modules
  • The same template component with <md-icon> will work fine if it's used on the template of a component loaded at app launch, but will fail with this error if it's used on the template of a component that was lazy-loaded

I've built a Plunkr demonstrating the problem. You'll notice that the eagerly loaded components (AppComponent and HomeComponent) are able to display the icon. However, LazyLoadedComponent cannot.

Below is the full stacktrace:

MdIconRegistry.prototype.getNamedSvgIcon /@angular2-material/icon/icon-registry.js:180:16
MdIcon.prototype.ngOnChanges /@angular2-material/icon/icon.js:107:17
anonymous/_View_UserProfileComponent6.prototype.detectChangesInternal@UserProfileComponent.ngfactory.js:1318:29
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectContentChildrenChanges /@angular/core//bundles/core.umd.js:12604:17
anonymous/_View_UserProfileComponent1.prototype.detectChangesInternal@UserProfileComponent.ngfactory.js:294:3
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectContentChildrenChanges /@angular/core//bundles/core.umd.js:12604:17
anonymous/_View_UserProfileComponent0.prototype.detectChangesInternal@UserProfileComponent.ngfactory.js:37:3
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectViewChildrenChanges /@angular/core//bundles/core.umd.js:12612:17
anonymous/_View_UserProfileComponent_Host0.prototype.detectChangesInternal@UserProfileComponent.ngfactory.js:28:3
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectContentChildrenChanges /@angular/core//bundles/core.umd.js:12604:17
AppView</AppView.prototype.detectChangesInternal /@angular/core//bundles/core.umd.js:12596:13
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectViewChildrenChanges /@angular/core//bundles/core.umd.js:12612:17
AppView</AppView.prototype.detectChangesInternal /@angular/core//bundles/core.umd.js:12597:13
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectContentChildrenChanges /@angular/core//bundles/core.umd.js:12604:17
anonymous/_View_AppComponent0.prototype.detectChangesInternal@AppComponent.ngfactory.js:445:3
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
AppView</AppView.prototype.detectViewChildrenChanges /@angular/core//bundles/core.umd.js:12612:17
anonymous/_View_AppComponent_Host0.prototype.detectChangesInternal@AppComponent.ngfactory.js:30:3
AppView</AppView.prototype.detectChanges /@angular/core//bundles/core.umd.js:12586:13
ViewRef_</ViewRef_.prototype.detectChanges /@angular/core//bundles/core.umd.js:10804:58
ApplicationRef_</ApplicationRef_.prototype.tick/< /@angular/core//bundles/core.umd.js:10191:79
ApplicationRef_</ApplicationRef_.prototype.tick /@angular/core//bundles/core.umd.js:10191:17
ApplicationRef_/<.next/< /@angular/core//bundles/core.umd.js:10095:103
Zone</ZoneDelegate</ZoneDelegate.prototype.invoke /zone.js/dist/zone.js:323:20
NgZoneImpl/this.inner<.onInvoke /@angular/core//bundles/core.umd.js:9245:36
Zone</ZoneDelegate</ZoneDelegate.prototype.invoke /zone.js/dist/zone.js:322:20
Zone</Zone</Zone.prototype.run /zone.js/dist/zone.js:216:25
NgZoneImpl</NgZoneImpl.prototype.runInner /@angular/core//bundles/core.umd.js:9276:64
NgZone</NgZone.prototype.run /@angular/core//bundles/core.umd.js:9505:55
ApplicationRef_/<.next /@angular/core//bundles/core.umd.js:10095:73
EventEmitter</EventEmitter.prototype.subscribe/schedulerFn< /@angular/core//bundles/core.umd.js:9168:58
SafeSubscriber.prototype.__tryOrUnsub /rxjs/Subscriber.js:225:13
SafeSubscriber.prototype.next /rxjs/Subscriber.js:174:17
Subscriber.prototype._next /rxjs/Subscriber.js:124:9
Subscriber.prototype.next /rxjs/Subscriber.js:88:13
Subject.prototype._finalNext /rxjs/Subject.js:128:13
Subject.prototype._next /rxjs/Subject.js:120:13
Subject.prototype.next /rxjs/Subject.js:77:9
EventEmitter</EventEmitter.prototype.emit /@angular/core//bundles/core.umd.js:9156:58
NgZone</NgZone.prototype._checkStable /@angular/core//bundles/core.umd.js:9415:25
NgZone/this._zoneImpl<.onLeave /@angular/core//bundles/core.umd.js:9387:21
NgZoneImpl/this.inner<.onInvokeTask /@angular/core//bundles/core.umd.js:9239:29
Zone</ZoneDelegate</ZoneDelegate.prototype.invokeTask /zone.js/dist/zone.js:355:24
Zone</Zone</Zone.prototype.runTask /zone.js/dist/zone.js:256:29
ZoneTask/this.invoke /zone.js/dist/zone.js:423:29

Any ideas?

(cross-posted on github)

1条回答
成全新的幸福
2楼-- · 2019-08-01 15:00

(this is a reproduction of my post here because the two issues are really the same)

I figured out that because MdIconModule itself has MdIconRegistry service in its providers array, every time another module imports it, a new instance of the service is provided. As a result, all components that are loaded at bootstrap time and that belong to the same AppModule share the same instance of this service. However, components loaded later (via lazy-loading) have a different instance of the service and as a result cannot see the icons registered at bootstrap time.

With help from James, I've used the workaround of a specially crafted module that does not use MdIconModule at all, but rather declares the MdIcon class alone. I then provide the MdIconRegistry service separately, and only to the root AppComponent. The result is that there is only one instance of the service app-wide and icons registered at bootstrap time are available everywhere.

Modified MdIconFixedModule

@NgModule({
    imports: [CommonModule, HttpModule],
    declarations: [MdIcon],
    exports: [MdIcon],
    providers: [],//leave empty to avoid multiple instances of MdIconRegistry
})
export class MdIconFixedModule {
    static forRoot() {
        return {
            ngModule: MdIconFixedModule,
            //will be available only to whoever calls .forRoot()
            providers: [MdIconRegistry] 
        };
    }
}

Modules that need to just use icons can import MdIconFixedModule as this doesn't contain the MdIconRegistry. The AppModule that also needs to register icons imports MdIconFixedModule.forRoot() which does contain the service.

The details of this implementation can be seen here.

This limitation of ng-Material Modules is slotted to be fixed with the alpha 8 release.

查看更多
登录 后发表回答