从源码看angular/material2 中 dialog模块的实现方法(2)

目前版本的angular关于在动态创建的组件中注入服务还存在一个注意点,就是直接创建出的组件无法使用隐式的依赖注入,也就是说,直接在组件的 constructor 中声明服务对象的实例是不起作用的,而必须先注入 Injector ,再使用这个 Injector 把注入的服务都 get 出来:

private 服务;

constructor( private injector: Injector // private 服务: 服务类 // 这样是无效的 ) { this.服务 = injector.get('服务类名'); }

解决的办法是不直接创建出组件来注入服务,而是先创建一个指令,再在这个指令中创建组件并注入服务使用,这时隐式的依赖注入就又有效了,material2就是这么干的:

<ng-template cdkPortalHost></ng-template>

其中的 cdkPortalHost 指令就是用来后续创建组件的。

所以创建这么一个弹窗容器组件,用户就感觉不到这一点,很顺利的像普通组件一样注入服务并使用。

创建弹窗容器的核心方法在 dom-portal-host.ts 中:

attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> { // 创建工厂 let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component); let componentRef: ComponentRef<T>; if (portal.viewContainerRef) { componentRef = portal.viewContainerRef.createComponent( componentFactory, portal.viewContainerRef.length, portal.injector || portal.viewContainerRef.parentInjector); this.setDisposeFn(() => componentRef.destroy()); // 暂不知道为何有指定宿主后面还要把它添加到宿主元素DOM中 } else { componentRef = componentFactory.create(portal.injector || this._defaultInjector); this._appRef.attachView(componentRef.hostView); this.setDisposeFn(() => { this._appRef.detachView(componentRef.hostView); componentRef.destroy(); }); // 到这一步创建出了经angular处理的DOM } // 将创建的弹窗容器组件直接append到弹出层DOM中 this._hostDomElement.appendChild(this._getComponentRootNode(componentRef)); // 返回组件的引用 return componentRef; }

所做的事情无非就是动态创建组件的四步曲:

创建工厂

使用工厂创建组件

将组件整合进AppRef(同时设置一个移除的方法)

在DOM中插入这个组件的原始节点

弹窗内容

从上文可以知道,得到的弹窗容器组件中存在一个宿主指令,实际上是在这个宿主指令中创建弹窗内容组件。进入宿主指令的代码可以找到 attachComponentPortal 方法:

attachComponentPortal<T>(portal: ComponentPortal<T>): ComponentRef<T> { portal.setAttachedHost(this); // If the portal specifies an origin, use that as the logical location of the component // in the application tree. Otherwise use the location of this PortalHost. // 如果入口已经有宿主则使用那个宿主 // 否则使用 PortalHost 作为宿主 let viewContainerRef = portal.viewContainerRef != null ? portal.viewContainerRef : this._viewContainerRef; // 在宿主上动态创建组件的代码 let componentFactory = this._componentFactoryResolver.resolveComponentFactory(portal.component); let ref = viewContainerRef.createComponent( // 使用 ViewContainerRef 动态创建组件到当前视图容器(也就是弹窗容器指令) componentFactory, viewContainerRef.length, portal.injector || viewContainerRef.parentInjector ); super.setDisposeFn(() => ref.destroy()); this._portal = portal; return ref; }

最后这一步就非常明了了,正是官方文档中使用的动态创建组件的方式(ViewContainerRef),至此弹窗已经成功弹出到界面中了。

弹窗的关闭

还有最后一个要注意的点就是弹窗如何关闭,从上文可以知道应该要先执行关闭动画,然后才能销毁弹窗,material2的弹窗容器组件添加了一堆节点:

host: { 'class': 'mat-dialog-container', 'tabindex': '-1', '[attr.role]': '_config?.role', '[attr.aria-labelledby]': '_ariaLabelledBy', '[attr.aria-describedby]': '_config?.ariaDescribedBy || null', '[@slideDialog]': '_state', '(@slideDialog.start)': '_onAnimationStart($event)', '(@slideDialog.done)': '_onAnimationDone($event)', }

其中需要关注的就是material2在容器组件中添加了一个动画叫 slideDialog ,并为其设置了动画事件,现在关注动画完成事件的回调:

_onAnimationDone(event: AnimationEvent) { if (event.toState === 'enter') { this._trapFocus(); } else if (event.toState === 'exit') { this._restoreFocus(); } this._animationStateChanged.emit(event); this._isAnimating = false; }

这里发射了这个事件,并在 MatDialogRef 中订阅:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wywsgs.html