欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Angular Material 完全攻略系列 day 27 至 day 34 有大用 有大大用 有大大大用

[Angular Material完全攻略] Day 27 - Angular CDK(3) - Bidirectionality、Layout

今天我们来讲Angular CDK中两个跟版面配置有关的功能,分别是Bidirectionality、Layout。

Bidirectionality主要是用来调整LTR(Left To Right)跟RTL(Right To Left)配置及侦测的工具。

而Layout则是用来侦测浏览器可用的宽度与高度,来判断目前网站使用在什么样的平台上,如果不使用任何其他的RWD工具,Layout可是Angular CDK中实现RWD不可或缺的帮手哩!

 响应式网页设计(RWD)   Responsive Web Design 

开始使用Angular CDK的Bidirectionality

要使用Bidirectionality(之后简称为bidi),需要加入BidiModule

import { BidiModule } from '@angular/cdk/bidi';

@NgModule({
  exports: [
    BidiModule
  ]
})
export class SharedMaterialModule {}






dir     (directory 方向)

dir directive

Bidi模组提供了一个dir的directive,方便我们改变排列方式,也就是LTR和RTL的状态,举个例子,我们在目前画面的toolbar加上两个按钮,来切换LTR和RTL的状态:

<button mat-button *ngIf="bidiMode === 'ltr'" (click)="bidiMode = 'rtl'">LTR</button>
<button mat-button *ngIf="bidiMode === 'rtl'" (click)="bidiMode = 'ltr'">RTL</button>






画面如下:

Bidi

当然,这时候按下去还没有任何反应,只是切换一个变数的资料而已,接着我们在需要改变排列方式,例如

<mat-sidenav-content  [dir]="bidiMode">
  ...
</mat-sidenav-content>






这时候<mat-sidenav-content>里面的内容就可以依照我们想要的去做配置啦!

Change Bidi

dirChange事件

使用dir这个directive之后,会为我们的元件扩充一个dirChange事件,我们可以透过这个事件得知目前bidi状态的改变

<mat-sidenav-content [dir]="bidiMode" (dirChange)="logDirChange($event)">






结果如下:

dirChange event     

Directionality

Directionalitybidi提供的一个service,它的功能非常简单,当某个component注入这个service后,我们就能够过它来知道目前component的排列是LTR还是RTL,当排列方现变更时,也能够过change事件得知;我们先加入一个BidiTestComponent来试试看:

<mat-sidenav-content [dir]="bidiMode" (dirChange)="logDirChange($event)">
  <app-bidi-test></app-bidi-test>
  ...
</mat-sidenav-content>






接着在BidiTestComponent中注入Directionality,并侦测变化

@Component({ })
export class BidiTestComponent implements OnInit {
  constructor(private directionality: Directionality) {}

  ngOnInit() {
    console.log(`目前dir: ${this.directionality.value}`);
    this.directionality.change.subscribe((dir: Direction) => {
      console.log(`component的dir被改變了: ${dir}`);
    });
  }
}






结果如下:

Directionality     

开始使用Angular CDK的Layout

Layout可以帮助我们侦测浏览器大小的变化,进而让我们能依照不同的萤幕大小给予不同的呈现方式,我们须先加入LayoutModule

import { LayoutModule } from '@angular/cdk/layout';

@NgModule({
  exports: [
    LayoutModule
  ]
})
export class SharedMaterialModule {}






使用BreakpointObserver

BreakpointObserver是一个类似media query的萤幕大小侦测器。有两个主要的方法。

isMatched()

我们能使用isMatched()并传入与media query一样的语法,来判断目前的萤幕是否与media query符合:

export class DashboardComponent implements OnInit {
  constructor(private breakpointObserver: BreakpointObserver) {}
  ngOnInit() {
    const isSmallScreen = breakpointObserver.isMatched('(max-width: 599px)');
  }
}






结果如下:

breakpointObserver isMatch

当我们萤幕比较大时,isSmallScreen会是false,而当把萤幕拉小重新整理后,就会看到isSmallScreen变成true啦!

observe

上面使用isMatch()虽然方便,但有时候我们的萤幕大小是动态的,这时候我们就可以使用observe()来判断,我们可以加入多组的media query,当其中一个判断结果改变,就会得到目前状态:

this.breakpointObserver.observe('(orientation: portrait)').subscribe(result => {
  console.log(`{portrait: ${result.matches}`);
});

this.breakpointObserver.observe('(orientation: landscape)').subscribe(result => {
  console.log(`{landscape: ${result.matches}`);
});






结果如下:

breakpointObserver observer

使用内建的breakpoints

Material Design已经有订出一些基本的breakpoints,而Angular Material也有把这些breakpoints也都考量进来了,包含了以下几个breakpoints:

  • Handset

  • Tablet

  • Web

  • HandsetPortrait

  • TabletPortrait

  • WebPortrait

  • HandsetLandscape

  • TabletLandscape

  • WebLandscape

我们可以直接用这些已经设定好的breakpoints,节省我们写media query的时间,如下:

this.breakpointObserver.observe([Breakpoints.HandsetLandscape, Breakpoints.HandsetPortrait])
  .subscribe(result => {
    console.log(`Handset: ${result.matches}`);
  });






结果如下:

Check handset demo

实际应用

透过这种方式,我们就能够针对不同大小的装置,来决定画面该如何呈现了,例如我们之前曾经介绍过的Datepicker元件,我们可以在行动装置时,开启Touch UI模式;非行动装置的大小时就直接显示,这样在不同的画面上,都能以比较适合的方式呈现:

export class SurveyComponent implements OnInit, AfterViewInit {
  isHandeset$: Observable<boolean>;
  
  constructor(private breakpointObserver: BreakpointObserver) {}
  
  ngOnInit() {
    this.isHandset$ = this.breakpointObserver.observe(Breakpoints.Handset).map(match => match.matches);
  }
}






画面上则针对isHandset$来设定touchUi属性:

<mat-form-field>
  <input type="text" name="birthday" matInput placeholder="生日"
         [matDatepicker]="demoDatepicker">
  <mat-datepicker [touchUi]="isHandset$ | async"></mat-datepicker>
</mat-form-field>






结果如下:

RWD datepicker

透过Layout相关的service,要打造RWD的程式也不再是件难事啦!

 响应式网页设计(RWD)   Responsive Web Design 

本日小节

今天我们介绍了两个跟画面配置有关的功能。

Bidirectionality可以帮助我们设定LTR和RTL模式,也能对于模式的切换加以侦测;对于跨国网站来说,这可能会是影响客户来源的一大议题!

而Layout则是用来判断浏览器萤幕大小的变化,在不搭配其他library的情况下,善用Layout,可以让我们的网站符合RWD的精神,在各种不同大小的装置上都能给予最好的显示方式,让网站操作上更加方便!

 响应式网页设计(RWD)   Responsive Web Design 

本日的程式码GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-27-cdk-bidirectionality-layout

分支:day-27-cdk-bidirectionality-layout

相关资源

来自   https://ithelp.ithome.com.tw/articles/10197159



[Angular Material完全攻略] Day 28 - Angular CDK(4) - Observables、Scrolling

今天我们要介绍两个比较简单的Angular CDK功能分类,分别是Observables和Scroll。这两个功能使用上非常简单,但乍看之下使用的机会不高,因此我们也会来稍微偷看一下Angular Material的原始码,看看有什么样让人意想不到的使用情境!

开始使用Angualr CDK的Observables

Angular CDK的Observables分类中,目前只有一个cdkObserveContent,在加入ObserversModule后就可以直接用啦

import { ObserversModule } from '@angular/cdk/observers';

@NgModule({
  exports: [
    ObserversModule
  ]
})
export class SharedMaterialModule {}






使用cdkObserveContent

在自己设计元件时,为了方便客制化,我们常常会元件的内容交给使用元件的人去决定,而元件设计上则使用<ng-content>把外面的内容放到里面的某个位置上,这时候我们可以使用cdkObserveContent这个directive来得知<ng-content>内容的变化,而这个cdkObserveContent本身也是一个@Output事件,签章如下:

@Directive({
  selector: '[cdkObserveContent]',
  exportAs: 'cdkObserveContent',
})
export class CdkObserveContent implements AfterContentInit, OnDestroy {
  @Output('cdkObserveContent') event = new EventEmitter<MutationRecord[]>();
}






参考:https://github.com/angular/material2/blob/5.0.x/src/cdk/observers/observe-content.ts

我们简单的来设计一个元件,并使用看看,就知道变化了!

  1. 先建立一个CdkObserveContentDemoComponent并在画面上加上cdkObserveContent

    <div class="content-wrapper" (cdkObserveContent)="projectContentChanged($event)">
      <ng-content></ng-content>
    </div>
    

           

           

           

           

       

  2. CdkObserveContentDemoComponent对应的程式内容如下:

    @Component({ ... })
    export class CdkObserveContentDemoComponent {
      count = 0;
      projectContentChanged($event: MutationRecord[]) {
        ++this.count;
        console.log(`資料變更,第${this.count}次`);
        console.log($event, this.count);
      }
    }
    

           

           

           

           

           

    MutationRecord是dom的原生物件,可以参考MDN            

来看看结果吧:

cdkObserveContent

可以看到每次<ng-content>内的资料变更时,我们也能立刻得知变化了!

使用debounce避免大量的变更

cdkObserveContent另外提供了一个debounce参数,方便我们在大量变化时减少程式码执行的浪费,只有在边画发生后持续debounce设定的时间内没有再次发生,才会触发cdkObserveContent事件:

<div class="content-wrapper" (cdkObserveContent)="projectContentChanged($event)" debounce="1000">
  <ng-content></ng-content>
</div>






成果如下:

cdkObservableContent debounce

刚按下去变更资料时,事件不会立刻触发,而是1秒内没有再次变更时,才会触发。

使用情境

在Angular Material中,使用的情境目前有3个:Tab、Checkbox和SlideToggle。

应用上来说,大致上都相同,主要都是内容变更时,重新进行资料的调整,或是要求进行变更侦测等行为。

例如<mat-tab-nav>的部分原始码内容:

<div class="mat-tab-links" (cdkObserveContent)="_alignInkBar()">
  <ng-content></ng-content>
  <mat-ink-bar></mat-ink-bar>
</div>






参考:https://github.com/angular/material2/blob/5.0.x/src/lib/tabs/tab-nav-bar/tab-nav-bar.html

一看就知道是内容异动时,要求把tab页签下针对active状态重新调整(ink bar)

或是以<mat-checkbox>来说,可以看到以下的部分原始码内容:

<span class="mat-checkbox-label" #checkboxLabel (cdkObserveContent)="_onLabelTextChange()">






参考:https://github.com/angular/material2/blob/5.0.x/src/lib/checkbox/checkbox.html

而在程式中_onLabelTextChange()做了什么呢?

  /** Method being called whenever the label text changes. */
  _onLabelTextChange() {
    // This method is getting called whenever the label of the checkbox changes.
    // Since the checkbox uses the OnPush strategy we need to notify it about the change
    // that has been recognized by the cdkObserveContent directive.
    this._changeDetectorRef.markForCheck();
  }






参考:https://github.com/angular/material2/blob/5.0.x/src/lib/checkbox/checkbox.ts

简单的说就是因为效能关系,变更侦测的策略为OnPush,所以在内容变更时,主动通知变更啦!

可以看到在打造高效能元件时,cdkObserveContent也是很好的小帮手哩!

开始使用Angualr CDK的Scrolling

在Angular CDK的Scrolling里面,只有一个cdkScrollabledirective和ScrollDispatcherservice,主要都是用来得知scroll是何时发生的,我们需要加入ScrollDispatchModule

import { ScrollDispatchModule } from '@angular/cdk/scrolling';

@NgModule({
  exports: [
    ScrollDispatchModule
  ]
})
export class SharedMaterialModule {}






使用cdkScrollable和ScrollDispatcher

cdkScrollable这个directive在单独使用时,不会有任何感觉,它不会对付加上的元件产生任何变化,也没有任何的@Input()@Output;虽然它能得知元件的scroll状态变更,但无法透过#someTemplate="cdkScrollable"之类的方式管理,而实际上他做的事情很简单,只有两个:

  1. 监听scroll状态

  2. 把自己注册给ScrollDispatcher

使用scrolled()

当我们需要知道某个元件的scroll状态时,必须在元件上加入cdkScrollable,之后再注入ScrollDispatcher来管理,例如:

<mat-sidenav-content cdkScrollable>
  ...
</mat-sidenav-content>






之后在程式码中加入ScrollDispatcher使用:

@Component({ ... })
export class DashboardComponent implements OnInit {
  constructor(private scrollDispatcher: ScrollDispatcher) {}
           
  ngOnInit() { 
    this.scrollDispatcher.scrolled().subscribe((scrollable: CdkScrollable) => {
      console.log('發生scroll了,來源為:');
      console.log(scrollable.getElementRef().nativeElement);
    });
  }
}






结果如下:

ScrollDispatcher

当画面卷动时,就会自动得到一个讯息,并且得知是谁发生的啦!

ScrollDispatcherscrolled()内也能加入一个auditTimeInMs参数,代表的是停止卷动后多久,才触发事件,例如:

this.scrollDispatcher.scrolled(1000).subscribe((scrollable: CdkScrollable) => {
  console.log('發生scroll了,來源為:');
  console.log(scrollable.getElementRef().nativeElement);
});






结果:

ScrollDispatcher with time

使用ancestorScrolled()

除了元件本身的scroll状态之外,我们也能得知某个目标有加入cdkScrollable的祖先scroll状态,也就是以某个元件往祖先找,当祖先有cdkScrollable且产生scroll时,就会发生事件,例如我们直接在某个元件中不加入cdkScrollable,但在程式中直接使用ScrollDispatcherancestorScrolled()来得知外部有cdkScrollable元件的状态:

@Component({ })
export class SomeChildComponent implements OnInit {
  constructor(private scrollDispatcher: ScrollDispatcher, private elementRef: ElementRef) {}

  ngOnInit() {
    this.scrollDispatcher.ancestorScrolled(this.elementRef, 1000).subscribe((scrollable: CdkScrollable) => {
      console.log('祖先發生scroll了,來源為:');
      console.log(scrollable.getElementRef());
    });
  }
}






我们把目前元件的ElementRef丢给ancestorScrolled(),当祖先元件包含cdkScrollable且发生scroll时,我也能够及时收到通知啦!

Dispatcher Ancestor

使用getAncestorScrollContainers()

如果一定需要等到scroll发生时,才知道有什么祖先 显灵发生scroll,那未免也太没效率了,因此我们也能使用getAncestorScrollContainers()取得包含cdkScrollable的祖先。如下:

console.log(this.scrollDispatcher.getAncestorScrollContainers(this.elementRef));






这样操作上就能够更加主动灵活啰!

使用情境

在Angular Material中,目前使用Scrolling的功能只有一个-Tooltip!

Tooltip在滑鼠移到目标上时才会显示,所以当卷动事情发生时,也变相等于滑鼠移开tooltip了,那么会发生什么事呢?tooltip消失也是很正常的,但实际上真的是如此吗?

Tooltip scroll sample

实际上我们可以看到,当scroll发生时,tooltip竟然没有立刻消失!而是等了一小段时间才消失,这么一来我们就可以避免一卷动就看不到tooltip的问题,又不用担心tooltip一直在那里赖着不走 不消失,可以说是非常贴心的小功能。

而透过偷看Angular Material的程式码,?我们能够在Toolip中的程式找到一小段片段如下:

private _createOverlay(): OverlayRef {
  const scrollableAncestors = this._scrollDispatcher.getAncestorScrollContainers(this._elementRef);
  strategy.withScrollableContainers(scrollableAncestors);
}






参考:https://github.com/angular/material2/blob/5.0.x/src/lib/tooltip/tooltip.ts

这里用到的OverlayRef会在后天介绍,但我们可以看到的是,Tooltip把包含cdkScroll的祖先找了出来,虽然我们没仔细去看到里面的实作细节,但也不难猜出tooltip会侦测祖先cdkScroll的状态,再来决定要不要立刻消失哩!

本日小结

今天我们介绍了两个使用上不太容易有感觉的Angular CDK功能分类,分别是Observables和Scroll。这些功能都能让我们发现一些细微的变化发生,进而做一些细部的调整。虽然看起来使用的机会不高,但在打造高效能,注重细节的高品质元件时,这些可都是不可或缺的功能啊!

本日的程式码GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-28-cdk-observables-scrolling     

分支:day-28-cdk-observables-scrolling

相关资源


来自   https://ithelp.ithome.com.tw/articles/10197285


[Angular Material完全攻略] Day 29 - Angular CDK(5) - Portal


接下来我们要介绍Angular CDK中的Portal分类,透过这里面的功能,我们可以很容易的动态切换各种不同的元件和templates,让动态产生内容不再是件麻烦的事情!

其实在Angular中,我们已经能够使用<ng-container>搭配ngTemplateOutletngComponentOutlet来切换不同的template或component了,而Angular CDK的Portal可以想像成是它的简单好用加强版!甚至我们可以透过Portals里面的功能,把元件放在整个Angular程式的控制范围之外,但元件依然还在Angular的控制内,非常的强大!

就让我们继续往下看吧!

开始使用Angualr CDK的Portal

Angular CDK中的Portal类别中提供了一些方便的directives和services,用来产生动态的内容,使用前要先加入PortalModule

import { PortalModule } from '@angular/cdk/portal';

@NgModule({
  exports: [
    PortalModule
  ]
})
export class SharedMaterialModule {}






基本知识

在使用Portal相关功能之前,有两个简单名词要先介绍:

  • Portal:真正要被切换的内容,这些内容会用Portal包起来,如果要切换的内容是一个template reference,则会使用TemplatePortal;如果是元件(component),则使用ComponentPortal

  • PortalOutlet:实际放置内容的地方,如果Portal是插头,那么PortalOutlet就可以想像成是插座

接着我们来实作一个简单的Tab功能,说明Portal如何动态切换内容!

使用cdkPortalOutlet

我们先设计好要放置插座的地方,也就是PortalOutlet,这里我们设计了一个如下的画面

<div class="portal-demo">
  <div class="tabs">
    <button mat-button (click)="changePortal1()">功能1</button>
    <button mat-button (click)="changePortal2()">功能2</button>
    <button mat-button (click)="changePortal3()">功能3</button>
    <button mat-button (click)="changePortal4()">功能4</button>
  </div>
  <div class="tab-content">
    <div [cdkPortalOutlet]="currentPortal"></div>
  </div>
</div>






其中的[cdkPortalOutlet]="currentPortal"就是我们插座放置的位置,接着我们要在程式中宣告一下这个插头currentPortal

@Component({ ... })
export class MainComponent implements OnInit {
  currentPortal: Portal<any>;






之后只要替换掉插头,就可以切换不同内容显示啰。

使用cdkPortal directive

cdkPortal是一个简单的directive,其实就是TemplatePortal衍生的directive版本,透过这个directive,我们可以很容易的透过@ViewChild@ViewChildren取得画面上的portal。

cdkPortal需要放在<ng-template>里面:

<ng-template cdkPortal>
  <p>
    功能1:我放在ng-template + cdkPortal = TemplatePortal裡面
  </p>
</ng-template>






当然,这种语法也能换成语法糖asterisk(*)的方式:

<p *cdkPortal>
  功能2:我放在一般的HTML Element內,加上cdkPortal後變成了一個TemplatePortal
</p>






一般的<ng-template>不要加上cdkPortal可以吗?当然可以,只是需要再加工一下,我们先摆上来:

<ng-template #template>
  功能3:我放在ng-template內,但不是TemplatePortal,要用我要記得先包裝一下
</ng-template>






cdkPortal的用法就这样,很简单吧!

切换不同的cdkPortalOutlet内容

接下来我们要在程式中动态切换cdkPortalOutlet的内容,使用cdkPortal的话,一切都简单很多:

@Component({ ... })
export class MainComponent implements OnInit {
  @ViewChildren(CdkPortal) templatPortals: QueryList<CdkPortal>;
            
  changePortal1() {
    this.currentPortal = this.templatPortals.first;
  }

  changePortal2() {
    this.currentPortal = this.templatPortals.last;
  }
}






只要把加上cdkPortal的直接设定就好啦!

至于一般的TemplateRef该怎么办呢?只要把它用TemplatePortal包装起来就好啦!

@Component({ ... })
export class MainComponent implements OnInit {
  @ViewChild('template') template3: TemplateRef<any>;
  constructor(private viewContainerRef: ViewContainerRef) {}
  ...
  changePortal3() {
    // 使用TemplatePortal把一般的TemplateRef包裝起來
    this.currentPortal = new TemplatePortal(this.template3, this.viewContainerRef);
  }
}






这时候就可以来看看结果啦!

TemplatePortal Baisc

一个简单的tab功能就这样完成啦!

在cdkPortalOutlet中放置元件(component)

刚刚我们示范了一般的template放到PortalOutlet的方法,但template只会放在一般的HTML内,不像元件(component)那么容易被重复使用,有没有办法放置元件呢?答案当然是可以,只要把元件包装成ComponentPortal就可以啰!

changePortal4() {
  this.currentPortal = new ComponentPortal(Portal4Component);
}






温馨提醒:老样子,动态产生的元件记得加入entryComponents。    

成果如下:

ComponentPortal Basic

简单的不得了吧!

为Portal加入其他外部内容

到目前为止,我们已经学会如何轻易且动态的切换内容,但是实务上常常未必那么简单,我们必须让切换的template或component能够理解外部的状态,因此必须传入一些外部资讯给里面的template或component,越复杂的程式越有可能需要这么做,那么要如何才能够加入其他内容呢?

为template加入其他外部内容

在我们建立TemplatePortal时,会需要3个参数,分别是:

  • template: TemplateRef<any>:要传入的template的参考

  • viewContainerRef: ViewContainerRef:画面上的ViewContainerRef,来源可由注入取得

  • context?: any:要传入的外部内容。

因此我们可以在自行建立TemplatePortal时,使用context参数,决定要传入的资讯:

changePortal3() {
  // 使用TemplatePortal把一般的TemplateRef包裝起來
  this.currentPortal = new TemplatePortal(this.template3, this.viewContainerRef, { nameInObject: this.name});
}






这里我们在第3个参数context传入了要加入的内容,接着在<ng-template>上,我们就可以使用let-xxx="yyy"的方式,来取得我们的资讯:

<ng-template #template let-nameInTemplate="nameInObject">
    Hi,{{ nameInTemplate }},功能3:我放在ng-template內,但不是TemplatePortal,要用我要記得先包裝一下
</ng-template>






你可能会问为什么不直接用{{ name }}就好了,实际上这样是最快的方法没错,但有时候要传入的可能是整理后比较复杂的资料,不一定有一个现成的变数放在那边可以接,就会有需要啰。    

let-nameInTemplate="nameInObject"可以想像成是宣告成javascript的let nameInTemplate = context.nameInObject,只是在画面上无法这样直接写而已。

结果如下:

Inject data to template portal

如果是加入cdkPortal的元素呢?只需要设定它的context属性即可:

this.templatPortals.first.context = { nameInObject: this.name };






为component注入其他内容

跟使用template不太一样的是,我们的第三个参数必须是一个Injector,而在component时必须透过注入取得资讯,因此会比较复杂一点,我们接着来看看如何实作吧!

建立要注入的Token
export const PORTAL4_INJECT_DATA = new InjectionToken<any>('portal4-inject-data');






这个PORTAL4_INJECT_DATA就会是我们之后要注入资料的依据。

建立包含自订Token的PortalInjector

如以下程式,我们写了一个_createInjector,来把我们想要注入的token,和当前的Injector,合并成一个新的PortalInjector

@Component({ ... })
export class MainComponent implements OnInit {
  name : string;
  
  constructor(private viewContainerRef: ViewContainerRef, private injector: Injector) {}
  ...
  private _createInjector(): PortalInjector {
    const injectionTokens = new WeakMap();
    injectionTokens.set(PORTAL4_INJECT_DATA, this.name);
    return new PortalInjector(this.injector, injectionTokens);
  }
}






将PortalInjector加入ComponentPortal

之后,在建立ComponentPortal时,只需要把这个PortalInjector也传入即可

changePortal4() {
    const portalInjector = this._createInjector();
    this.currentPortal = new ComponentPortal(Portal4Component, undefined, portalInjector);
  }






建立ComponentPortal的第二个参数一样是ViewContainerRef,但这只有在Angular的管理范围外建立动态元件的时候,需要一个来源ViewContainer,如何在Angular范围外动态建立元件呢?稍后我们会在DomPortalOutlet介绍。

所以这里我们不用传入,只要传入undefined即可。

而在Component内,则可以直接透过注入PORTAL4_INJECT_DATA,来取得对应的资料:

@Component({ ... })
export class Portal4Component implements OnInit {
  get name() {
    return this.data.nameInObject;
  }

  constructor(@Inject(PORTAL4_INJECT_DATA) private data: any) {}

  ngOnInit() {
  }
}






这时候画面上就可以直接使用啦!

Injec data to component portal

在程式中控制cdkPortalOutlet

我们也能在程式中直接控制cdkPortalOutlet要显示什么样的资料,原来的画面只要找得到cdkPortalOutlet的reference就好:     

<div cdkPortalOutlet #portalOutlet="cdkPortalOutlet"></div>






接着就可以在程式中直接使用啦!

@Component({ ... })
export class MainComponent implements OnInit {
  @ViewChild('portalOutlet') portalOutlet: CdkPortalOutlet;
            
  changePortal2() {
    this.portalOutlet.attach(portalOutlet);
  }
}






CdkPortalOutlet有以下几个主要方法:

  • attach():附加一个Portal上去,可以是ComponentPortal,也可以是TemplatePortal

  • attachComponentPortal():附加一个ComponentPortal

  • attachTemplatePortal():附加一个TemplatePortal

  • deatch():把目前附加的Portal拿掉

  • hasAttached():用来检查目前是否有任何Portal被附加上去了

使用DomPortalOutlet

DomPortalOutlet是一个很有趣的插座,他可以帮助我们把插座产生在Angular的管理范围内,以一般的Angular程式来说,就是<app-root>之外,听起来很不可思议吧!立刻来看看该怎么做吧!

注入必要条件

说穿了,DomPortalOutlet就是操作DOM来做些事情,以及把产生的内容丢到一个<app-root>外的插座上,但其实它还是在管理范围内,只是不住在<app-root>里面而已,因此在建立时,还是需要把可以管理他的范围界定起来,这些也是DomPortalOutlet要建立时所相依的类别:

@Component({ ... })
export class MainComponent implements OnInit {
  domPortalOutlet: DomPortalOutlet;

  constructor(
	@Inject(DOCUMENT) private document: any,
    private viewContainerRef: ViewContainerRef,
    private injector: Injector,
    private componentFactoryResolver: ComponentFactoryResolver,
    private appRef: ApplicationRef
  ) {}






以上在建构式注入的,除了document以外,都是建立DomPortalOutlet的必要条件,那么注入document到底是用来干嘛的呢?我们要使用这个物件来建立插座。

在app-root外建立插座

因为超过<app-root>的范围,因此伸手直接去摸DOM基本上是不可避免的,我们可以直接用document来操作DOM,但在这里我们却另外注入了一个document的DOCUMENT token(有点饶舌),这是为什么呢?

一般情况下,我们注入的document,在网页上其实就是window.document,但Angular是一个可以跨不同平台的设计,因此到了其他平台,就不一定了,另外在撰写单元测试时,为了避免单元测试下只有JavaScript而没有DOM,中间垫了一层也是比较好的!也因为如此,虽然我们可以直接使用window.document,但还是选择了使用注入的方式,来隔离相依。

有了这样的概念后,我们就来建立一个插座吧!

createOutletOutOfApp() {
  const element = this.document.createElement('div');
  element.innerHTML = '<br>我在&ltapp-root&gt;之外';
  this.document.body.appendChild(element);
  this.domPortalOutlet = new DomPortalOutlet(element, this.componentFactoryResolver, this.appRef, this.injector);
}






在这边的程式我们直接建立一个<div>element,并把它附加到document.body,也就直接脱离了<app-root>,这是一般JavaScript的写法,指示document有被我们垫了一层而已。

之后我们再使用new DomPortalOutlet()把这个element变成可以被管理的插座。

从这样的程式不难发现,我们不仅可以建立<div>,也能透过getElementById()等等的方式,把<app-root>之外的某个现有HTML,直接变成插座,来插入我们想要的元件,光用想的就觉得潜力无穷啊!

替外部插座插入内容

建立插座后,要插入内容就简单多啦!我们可以使用attachTemplatePortal()来插入TemplatePortal,或是使用attachComponentPortal()来插入ComponentPortal,使用方法跟我们之前使用PortalOutlet一模一样!

addTemplatePortal() {
  this.domPortalOutlet.attachTemplatePortal(this.templatPortals.last);
}






直接来看看结果吧!

DomPortalOutlet

当我们按下产生插座时,在<app-root>外产生了一个插座;按下插入内容后,就把我们想要的Portal插进来啦!

使用情境

在Angular Material中的Dialog,显示dialog时会有一个backdrop,为了避免这个backdrop被其他元件画面干扰,设计时就是使用DomPortalOutlet的技巧,在<app-root>范围外的一个Overlay上(关于Overlay,是另外一个Angular CDK功能,明天会介绍)建立插座,再把dialog放在上面,如下图:

Dialog with DomPortalOutlet sample     

简单的程式码片段如下:

@Injectable()
export class Overlay {
  private _createPaneElement(): HTMLElement {
    const pane = this._document.createElement('div');

    pane.id = `cdk-overlay-${nextUniqueId++}`;
    pane.classList.add('cdk-overlay-pane');
    this._overlayContainer.getContainerElement().appendChild(pane);

    return pane;
  }

  private _createPortalOutlet(pane: HTMLElement): DomPortalOutlet {
    return new DomPortalOutlet(pane, this._componentFactoryResolver, this._appRef, this._injector);
  }
}






可以看到只要是超过<app-root>以外的元件显示应用,都很适合使用DomPortalOutlet呢!

本日小结

今天我们介绍了Angular CDK中动态显示不同画面的功能-Portal,透过cdkPortalcdkPortalOutlet,要动态切换不同的template变成了一件简单的事情;若要动态切换component,也只需要简单的程式码即可达成。同时我们也学到如何让动态的template和component如何与外界沟通,在比较复杂的应用时会非常需要。

如果我们需要在<app-root>之外动态切换不同的template或component,也能够使用DomPortalOutlet达成!这是一个非常酷,让SPA超过SPA不再被一个范围限定死,能够动态的把功能延伸的管理范围之外,又同时不会失去控制,让我们在设计时更加的有弹性!

本日的程式码GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-29-cdk-portal

分支:day-29-cdk-portal

相关资源

来自   https://ithelp.ithome.com.tw/articles/10197393




[Angular Material完全攻略] Day 30 - Angular CDK(6) - Overlay

今天我们要来介绍Angular CDK中的Overlay!Overlay在Angular Material中可以说是随处可见,只要是任何会从萤幕上弹出资讯的功能,如Select、Dialog等等都免不了要使用Overlay;因此也能说Overlay是Angular Material中让画面更具有立体感的大功臣,到底这个功能能帮助我们达到多少目标呢?就让我们继续看下去吧!

首先当然不能忘记,要加入OverlayModule

import { OverlayModule } from '@angular/cdk/overlay';

@NgModule({
  exports: [
    OverlayModule
  ]
})
export class SharedMaterialModule {}






一切的关键-Overlay service

Overlay里面有许多不同大大小小的classes,其中主宰一切的关键,来自于一个service-Overlay,透过这个service,我们可以用来决定一个component或template动态的产生,以及要产生在什么位置,甚至可以"黏在"另外一个元件的旁边!

使用Overlay让物件与物件连结

我们先来小试身手一下,做一个经典的功能,再画面右下角加入一个floating action button,并在点选后呈现另一个选单!

画面设计如下:

<button mat-fab color="accent" class="float-menu" (click)="displayMenu()" #originFab>
  <mat-icon>add</mat-icon>
</button>

<ng-template #overlayMenuList>
  <div class="fab-menu-panel">
    <mat-nav-list>
      <a mat-list-item>新增信件</a>
      <a mat-list-item>管理聯絡人</a>
    </mat-nav-list>
  </div>
</ng-template>






我们在画面上加了一个按钮,以及一个选单的样板,这个样板稍后会出现在按钮的附近,接着我们透过CSS让按钮固定在画面右下方,CSS如下:

.float-menu {
  position: fixed !important;
  right: 15px;
  bottom: 15px;
  z-index: 2;
}

.fab-menu-panel {
  border: 1px solid black;
  background-color: white;
}

.fab-menu-panel .mat-nav-list {
  padding-top: 0px;
}






最后就是程式部分啦!

export class InboxComponent implements OnInit {
  @ViewChild('overlayMenuList') overlayMenuList: TemplateRef<any>;
  @ViewChild('originFab') originFab: MatButton;
  overlayRef: OverlayRef;

  constructor(private overlay: Overlay, private viewContainerRef: ViewContainerRef) {}

  ngOnInit() {
    const strategy = this.overlay
      .position()
      .connectedTo(this.originFab._elementRef, { originX: 'end', originY: 'top' }, { overlayX: 'end', overlayY: 'bottom' });
    this.overlayRef = this.overlay.create({
      positionStrategy: strategy
    });
  }
  
  displayMenu() {
    if (this.overlayRef && this.overlayRef.hasAttached()) {
      this.overlayRef.detach();
    } else {
      this.overlayRef.attach(new TemplatePortal(this.overlayMenuList, this.viewContainerRef));
    }
  }
}






以上程式中的步骤大致描述如下:

  1. 注入Overlay

  2. ngOninit()中,使用const strategy = this.overlay.position().connectedTo(),建立一个ConnectedPositionStrategy,代表overlay要与某个物件连结的策略,其中的参数分别为:

    1. 要被连结的物件(也就是我们的originFab这个按钮)要被连结的物件(也就是我们的originFab这个按钮)

    2. 连结物件的连结点位置,以这里的程式来说,就是右上角为连结点。

    3. overlay连结物件时的连结点位置,以这里的程式来说,就是右下角为连结点。

    4. 用图解释的话大概是这种感觉:
      cdkOverlay connect来解释

  3. 使用overlay.create()建立一个新的OverlayRefcreate()方法可以传入许多设定资料,在这里我们设定上一步骤所建立的连结策略。

  4. displayMenu()方法中,检查是否有attach东西上去,如果有,就执行detach(),如果没有,就把overlayMenuList这个template转成TemplatePortal并attach上去。

对于attach()有感到眼熟吗?没错!就是昨天介绍的Portal功能,而Overlay正是使用Portal的功能,来决定要把什么东西放到Overlay上面!    

接着我们就可以来看看结果啦!

浮动操作菜单演示

当我们点下固定在右下角的按钮,就会看到一个简单的选单自动「黏」在我们的按钮上!很酷吧!

设定withFallbackPosition

在设定完connectedTo()后,我们能接着设定withFallbackPosition(),如果connectedTo显示overlay时会超过萤幕画面,会改使用withFallbackPosition()的设定值,因此我们可以透过withFallbackPosition()来设定无法完整显示时,重新调整连结点的Plan B、Plan C…Plan Z 。

例如之前我们在介绍menu时提过,当画面卷动时,menu会先试着 抢镜头 让自己能被完整呈现,实作的程式码大致看起来如下:

this._overlay.position()
  .connectedTo(this._element, {originX, originY}, {overlayX, overlayY})
  .withFallbackPosition(
    {originX: originFallbackX, originY},
    {overlayX: overlayFallbackX, overlayY})
  .withFallbackPosition(
    {originX, originY: originFallbackY},
    {overlayX, overlayY: overlayFallbackY},
    undefined, -offsetY)
  .withFallbackPosition(
    {originX: originFallbackX, originY: originFallbackY},
    {overlayX: overlayFallbackX, overlayY: overlayFallbackY},
    undefined, -offsetY);






参考:https://github.com/angular/material2/blob/5.0.x/src/lib/menu/menu-trigger.ts

使用Overlay让物件显示在画面上,不连结任何物件

刚刚我们已经成功让选单物件连结到按钮物件上了,接下来我们要试试看让选单不要连结到任何画面上,只需要把原来的strategy修改一下:

const strategy = this.overlay
  .position()
  .global()
  .width('500px')
  .height('100px')
  .centerHorizontally()
  .centerVertically();






在这里我们改用overlay.position().global()来产生一个GlobalPositionStrategy,代表部连结任何物件,是全域的显示策略接着用width()height()给予基本的尺寸,再加上centerHorizontally()centerVertically()来调整放到画面的正中间,成果如下:

全局菜单

一个显示在画面正中央的选单就出现啦!

如果不希望显示在正中间,也可以使用top()bottom()left()right()来设定overlay显示的座标。

接下来我们来看看在overlay.create()时,可以使用哪些参数,让显示更加灵活!

设定OverlayConfig

看过上面两种Overlay显示方式后,我们再来看看使用overlay.create()时,可以加入哪些参数,这些参数的型别为OverlayConfig,以下简单说明:

  • hasBackdrop:是否要显示一个预设灰底的backdrop。

  • backdropClass:让我们能自订backdrop的样式。

  • direction:LTR或RTL。

  • heightminHeightmaxHeight:设定高度相关资讯。

  • widthminWidthmaxWidth:设定宽度相关资讯。

  • panelClass:给予显示的Overlay(也就是panel)一个基本的样式,需要特别注意这个属性,在ConnectedPositionStrategy上面时overlayRef.deatch()会正常把panelClass拿掉;但GlobalPositionStrategy时,使用overlayRef.deatch()时会无法拿掉这个样式,需要改用overlayRef.dispose(),而dispose()的话,下次还需要使用overlay.create()重新建立。

  • positionStrategy:显示位置的策略,文章前半段提到的就是在切换这个策略。

  • scrollStrategy:当画面卷动时,该如何处置Overlay的策略,稍后会详细说明。

接着我们来看看特定几个属性的设定。

设定hasBackdrop

设定这个属性为true后,会显示一个基本的灰底backdrop。

const config = new OverlayConfig({
  hasBackdrop: true,
  positionStrategy: strategy
});
this.overlayRef = this.overlay.create(config);






我们也可以设定当backdrop被点击后,就自动关闭目前的overlay:

this.overlayRef.backdropClick().subscribe(() => {
  this.overlayRef.detach();
});






成果如下:

背景

设定backdropClass

预设Angular CDK的Overlay会帮我们加上一个cdk-overlay-dark-backdrop的css class,我们可以透过backdropClass更换它,例如Angular CDK内建了一个cdk-overlay-transparent-backdrop可以帮我们移除掉灰色的背景,但依然有一个透明的backdop在中间,让我们不能直接跟底层的元件互动:

const config = new OverlayConfig({
  hasBackdrop: true,
  backdropClass: 'cdk-overlay-transparent-backdrop',
  positionStrategy: strategy
});
this.overlayRef = this.overlay.create(config);






成果如下:

背景类透明

有了cdk-overlay-transparent-backdrop,滑鼠移到按钮上时,就没有hover的效果,直到按下去关掉overlay时,才一切又正常了。

设定scrollStrategy

最后我们来聊一下scrollStrategy,在ConnectedPositionStrategy模式下,我们能透过设定scrollStrategy来决定当滑鼠滚轮卷动时,overlay该如何处置,例如预设如下:

const config = new OverlayConfig({
  scrollStrategy: this.overlay.scrollStrategies.noop()
});






此时滑鼠卷动不会影响overlay,overlay的位置依然呈现在原来的位置。

如果希望跟着连结的元件一起移动,可以设定为reposition()

const config = new OverlayConfig({
  scrollStrategy: this.overlay.scrollStrategies.reposition()
});






成果如下:

重新定位滚动

这里可以看到选单会盖过上面的toolbar,这是因为overlay预设的z-index为1000,所以我们只需要把toolbar的z-index设定为超过1000,就可以解决这个问题啰。    

例外还有两个可以设定的策略:

  • close():卷动时自动关闭overlay。

  • block():不允许卷动。

应该不难想像结果,就不多写程式拖时间啰,有兴趣的读者可以自己修改看看。

当然,设定hasBackdrop后,因为连scroll bar都被backdrop盖掉无法互动,所以这些卷动就会自动失效,变成类似block()的状态了。    

本日小结

今天我们把Angular CDK目前(5.0.0)主功能分类的最后一块拼图-Overlay给介绍完了。这个功能可以让我们的操作介面更具立体感,应用层面也非常广,非常多的Angular Material元件都依赖着Overlay功能,因此要写的程式也不少,不过相信大致操作过一遍后,就能发现这个功能的强大及易用!而且光是想像自己要达到这些功能需要写多少程式码,考量到多少状态,就觉得Angular CDK实在是太贴心啦

本日的程式码GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-30-cdk-overlay

分支:day-30-cdk-overlay

相关资源

后话,这不是终点

终于写完30篇了,写到这篇,我们也算完全把目前所有Angular Material文件上的Components和CDK两大主轴全部都介绍了一遍,说真的是满辛苦的,为了不要传递错误的讯息,整个Angular Material的文件来来回回看了不下10遍,还要不断的找原始码来弥补文件上不足的部分,偏偏内容又多到无法完整介绍完!为了兼具能够呈现最多讯息与最重要的讯息,真的是绞尽脑汁在规划,几乎每篇都花费3个小时以上再规划,有几篇甚至花了5个小时,根本是时间太多吧(误)!心里还默默地认为自己说不定是目前中文社群里面最熟悉Angular Material的人了吧(大误)

但看到订阅人数有在上升,也与不少网友互动,真的特别有成就感。虽然有种累到明年不想再参加的感觉,但是...我想明年应该还是会再次跳入火坑XD。

/images/emoticon/emoticon18.gif

在这次完赛后,让我再次感受到Angular Material的威力及潜力,所以30篇完赛不是终点,而是推坑的开始,之后我会努力推人进Angular Material这美好坑的(误)。

然后偷偷的说,接下来我还会再写几篇相关的实用小技巧,敬请期待XD


来自   https://ithelp.ithome.com.tw/articles/10197492



[Angular Material完全攻略] Day 31 Angular CDK(隐藏版) - Coercion、Platform


今天我们来讲两个Angular CDK文件上没有介绍,但很有机会使用到的 隐藏版功能,分别是型别转换(coercion)、平台侦测(platform )。

关于Angular CDK的Coercion

在之前那么多天介绍Angular Material的元件时,不知道你有没有发现一件有趣的事情,以曾经介绍过的<mat-chip>为例,当我要设定selected属性时,当时是这么写的:

<mat-chip selected="true">JavaScript</mat-chip>






我们都知道要处理属性绑定(property binding)时,如果资料是numberboolean或变数名称之类的话,应该要用中括号[]把属性包起来,如:[selected]="true",否则传进去的直指会被当作是string,如果是单纯的字串,则[property]="'string'"property="string"都是可以的。

因此假设我们以selected="false"撰写的时候,程式中若这样判断

if(selected) {
    // do something
}






这样的条件还是会进入的,因为字串'false',在if检查时是会过的,要真正的boolean值false,才不会进入。

偏偏上面提到的selected很有趣,写成selected="true"会被选取没有问题,而写成selected="false"也完全没问题不会被选取,因为Angular Material都帮我们处理好这种小细节了!

这时候我们可以想看看这个用来处理truefalse的属性selected,它明明是一个<mat-chip>@Input,但偏偏它又可以不用中括号设定非字串资料,那他宣告的型别到底要是string还是boolean呢?如果是string,后续又该如何处理?

其实我们不用想太多,因为这种贴心小细节在Angular Material中被使用的机会太高了!因此也被拉到Angular CDK中,也就是型别转换功能-coercion

至于该怎么使用呢?我们继续往下看。

开始使用Angular CDK的Coercion

Coercion只有三个现成的方法,方便我们做型别转换,不需要加入任何module,直接使用就可以了!

使用coerceBooleanProperty

coerceBooleanProperty是用来把输入内容转成boolean的一个方法,它的逻辑很简单,输入的参数若是null或字串的'false',就会被当作boolean的false

因此我们可以为我们的元件加入一个可以接受字串'false'当作boolean的属性,例如:

// 記得import這個方法
import { coerceBooleanProperty } from '@angular/cdk/coercion';

@Component({ ... })
export class CoercionDemoBoxComponent implements OnInit {
  private _display: boolean;

  @Input()
  get display(): boolean {
    return this._display;
  }

  // value: boolean,代表預期的參數型別是boolean
  // 但我們都知道,javascript其實是弱型別語言
  set display(value: boolean) {
    // 就算傳進來是string,也會被轉成boolean
    this._display = coerceBooleanProperty(value);
  }

  someMethod() {
      if(display) {
          // do something
      }
  }
}






这时候我们就可以使用

<!-- display會被當作boolean的true -->
<app-coercion-demo-box display="true"></app-coercion-demo-box>






或是

<!-- display會被當作boolean的false -->
<app-coercion-demo-box display="false"></app-coercion-demo-box>






就不用再多花力气加上中括弧[display]="false",看起来也更加清爽!

使用coerceNumberProperty

跟刚刚介绍的coerceBooleanProperty一样的意思,coerceNumberProperty是用来帮助我们把传入的资料强制转为number的工具方法,例如'100.5' + 10的结果是'100.510',这只要有一定JavaScript经验的开发人员都知道,而透过coerceNumberProperty('100.5') + 10就能够得到110.5的结果啦!

另外我们coerceNumberProperty的第二个参数,也能帮助我们设定当转成number失败时,预设的值是多少(没填的话预设为0),例如coerceNumberProperty('xxx')就会得到结果为0

以下面程式码为例:

import { coerceNumberProperty } from '@angular/cdk/coercion';

@Component({ })
export class CoercionDemoBoxComponent implements OnInit {
  private _height: number;

  @Input()
  get height(): number {
    return this._height;
  }

  set height(value: number) {
    this._height = coerceNumberProperty(value);
  }
}






component的内容为:

<p>
  我的高度是 {{ height }},再加上10會變成 {{ height + 10 }}
</p>






这时候使用上就可以在设定height属性时不用加上中夸号啦!

<app-coercion-demo-box display="true" height="10"></app-coercion-demo-box>






使用coerceArray

有前面两个方法的经验,应该不难猜出来coerceArray的用途就是把资料转为阵列,当使用coerceArray<number>(1)的时候,就会得到结果[1],如此一来我们就能让我们的属性接受单一值或阵列值,反正到component时我们都用程式转为阵列啦!

跟前面的coerceXxxxxProperty不一样,这个方法名字为coerceArray,没有Property后缀,要特别注意。    

开始使用Angular CDK的Platform

在Angular CDK中,把浏览器支援度相关的功能放在Platform分类中,我们需要先加入PlatformModule

import { PlatformModule } from '@angular/cdk/platform';

@NgModule({
  exports: [
    OverlayModule
  ]
})
export class PlatformModule {}






如此便可使用Platformservice来得知目前使用的浏览器。例外Pltform中还提供一个getSupportedInputTypes工具方法,来取得目前浏览器针对<input>所支援的types,单独使用这个方法的话,不需要加入PlatformModule

使用Platform service

透过Platform,我们可以用来侦测目前使用者使用的浏览器,虽然前端技术与浏览器都越来越纯熟,针对标准的支援度也越来越好,但毕竟还是有些差异,更不用说若使用版本过旧,差异就更大了!真的遇到这种状况时,就能够使用Platform这个service,先针对使用者的浏览器检查一下了!

Platform service包含几个判断属性:

属性            说明            
isBrowser            是否为使用浏览器(要知道Angular可以使用的范围可是跨出浏览器的)            
边缘            浏览器是否为EDGE            
潮流            浏览器的render engine是否为Microsoft Trident            
            浏览器的render engine是否为Blink            
网页套件            浏览器的render engine是否为WebKit            
iOS            作业系统是否为iOS            
安卓            作业系统是否为Android            
FIREFOX            浏览器是否为firefox            
苹果浏览器            浏览器是否为safari            

我们可以先在程式中注入Platformservice,再来检查这些属性:

import { Platform } from '@angular/cdk/platform';

@Component({ })
export class MainComponent {
  constructor(public platform: Platform) {}
}






HTML如下:

<div>
  <p>Is Browser: {{ platform.isBrowser }}</p>
  <p>Is Android: {{ platform.ANDROID }}</p>
  <p>Is iOS: {{ platform.IOS }}</p>
  <p>Is Firefox: {{ platform.FIREFOX }}</p>
  <p>Is Blink: {{ platform.BLINK }}</p>
  <p>Is Webkit: {{ platform.WEBKIT }}</p>
  <p>Is Trident: {{ platform.TRIDENT }}</p>
  <p>Is Edge: {{ platform.EDGE }}</p>
</div>






以笔者使用Macbook+Chrome的结果如下:

平台演示

使用getSupportedInputTypes

getSupportedInputTypes()是一个工具方法,用来取得目前浏览器所支援<input>的type清单,回传结果为Set<string>,直接看程式比较简单:

import { getSupportedInputTypes } from '@angular/cdk/platform';

@Component({ })
export class MainComponent {
  supportInputTypes = getSupportedInputTypes();
}






HTML如下:

<h2>目前瀏覽器支援的input types</h2>
<ul>
    <li *ngFor="let type of supportInputTypes">{{ type }}</li>
</ul>






以笔者使用Macbook+Chrome的结果如下:

getSupportedInputTypes

本日小结

今天我们补充了两个Angular CDK目前文件没介绍的功能:

  • Coercion:用来帮助我们在设计元件时,打造更好的开发人员使用经验,节省许多时候property binding的无谓程式,也让HTML画面更加一致好懂,这样的元件设计思维非常直得让人学习,不愧是以高品质为目标的Angular Material,不仅使用者体验一流,连开发人员也能得到一流的体验

  • Platform:与浏览器平台相关的功能,目前可以判断使用者的浏览器,也能得知input支援的types,虽然使用的机会可能不多,但真的遇到需要针对不同平台做细微调整时,我们也知道了有个现成的工具可以用哩。

本日的程式码GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-31-cdk-coerce-platform

分支:day-30-cdk-overlay

到这边我们终于把所有Angular CDK中用来打造一流元件的功能都介绍了一遍,当然还有几个属于元件的分类是我们之前使用Angular Material元件时就有感觉的,因此没有多加介绍,但有过使用Angular Material元件的经验,要阅读文件也会很容易上手!

透过Angular CDK,真的能帮助我们节省很多程式码,笔者目前工作上也已经开始在专案中加入Angular CDK相关的功能,来打造一些Angular Material目前无法提供的功能,体验到了其强大的威力,真的非常适合推荐给所有使用Angular的开发人员,既然要使用轮子,当然要使用最高级的轮子啦!XD

接下来我们会再花几天时间介绍一些关于使用Angular Material和Angular CDK的相关小技巧,明天见!

相关资源

来自  https://ithelp.ithome.com.tw/articles/10197609



[Angular Material完全攻略] Day 32 - 杂项技巧(1) - 自己的theme自己设计


Angular Material内建了4种不同主题的theme,未来应该还会持续增加,但这些theme未必是我们喜欢的,而在Angular Material中,要设计自己的theme非常简单,我们就来看看该如何做吧!

建立自己的theme

建立一个theme相关的scss档

Angular Material使用SCSS来设计,并提供了许多的@mixin可以使用,让我们轻易就能够客制化颜色的功能,首先第一步,我们先在专案下建立一个custom-theme.scss档案,如下图:

New theme scss

汇入核心功能

加入包含Angular Material的theming,并汇入基本的样式:

@import '~@angular/material/theming';

@include mat-core();






在执行@include mat-core()后,会将所有元件都共用的基本样式都加入,因此这个动作只需要做一次就好。

自订颜色

接着我们就可以来设定theme的颜色啰,Material Design已经订了许多颜色的调色盘,同时在Angular Material中都有设计好对应主色的变数,举例来说:light blue的颜色就可以使用$mat-light-blue,而pink则可以使用$mat-pink;我们可以透过Angular Material另位提供的辅助工具mat-palette(),来设定这些颜色的亮度。例如:

$custom-primary: mat-palette($mat-light-blue);






另外我们也能提供3个参数,分别为,颜色主要的亮度(预设为500),前色的色调以及深色的色调

$custom-accent:  mat-palette($mat-orange, 500, A100, A700);






不过最后两个参数在theme中其实用不到,我们可以忽略它。

在这里我们先把Material Design中的三种主要色彩都定义好,如下:

$custom-primary: mat-palette($mat-light-blue, 500); /* 500是預設值,也可以忽略 */
$custom-accent: mat-palette($mat-green);
$custom-warn: mat-palette($mat-brown);






建立theme

接下来我们可以使用mat-light-theme建立浅色主题,或mat-dark-theme建立深色主题,之后只需要使用@include angular-material-theme()就可以取得所有的颜色结果啦!

/* 建立深色主題 */
$custom-theme: mat-dark-theme($custom-primary, $custom-accent, $custom-warn);

@include angular-material-theme($custom-theme);






加入自订的样式

最后我们要在style.css中加入样式,不过要记得我们目前是用SCSS,因此要将style.css改为style.scss,同时修改.angular-cli.json的app.styles,最后在style.scss中加入我们自订的样式,就完成啦!

结果如下:

Custom Theme     

看起来是不是别有一番风味啊!

如果不知道颜色怎么搭比较好,可以到Material Design Color Palette Generator这个网站,随意选择两种颜色,就可以看到效果参考啰!

建立多个theme并动态切换

要建立多个theme也很简单,把@include angular-material-theme();的部分放到一个css class下,再切换不同的class就可以了,如下:

@import '~@angular/material/theming';

@include mat-core();

$custom-primary: mat-palette($mat-light-blue);
$custom-accent: mat-palette($mat-green);
$custom-warn: mat-palette($mat-brown);

$custom-theme: mat-dark-theme($custom-primary, $custom-accent, $custom-warn);

.custom-theme-1 {
  @include angular-material-theme($custom-theme);
}

$custom-primary-2: mat-palette($mat-yellow, 800);
$custom-accent-2: mat-palette($mat-deep-orange);
$custom-warn-2: mat-palette($mat-pink);

$custom-theme-2: mat-dark-theme($custom-primary-2, $custom-accent-2, $custom-warn-2);

.custom-theme-2 {
  @include angular-material-theme($custom-theme-2);
}






上面的程式中,我们使用两组变数,并分别放到.custom-theme-1.custom-theme-2之中,只需要切换不同的class,就可以改变整个画面啰!

Toggle Theme

多个theme切换时的注意事项

当有多个theme时,由于overlay通常会在theme的范围之外,因次在需要dialog这类的程式显示为异常,如下:

Toggle theme problem

这时候我们必须做额外的设定,这时候我们需要注入OverlayContainer,并透过它取得overlay的container,然后把样式加上去:

import { OverlayContainer } from '@angular/cdk/overlay';

@Component({ })
export class DashboardComponent implements OnInit {
  theme = 'custom-theme-1';

  constructor(private overlayContainer: OverlayContainer) { }

  ngOnInit() {
    this.overlayContainer.getContainerElement().classList.add(this.theme);
  }

  toggleTheme() {
    const originalTheme = this.theme;
    this.theme = this.theme === 'custom-theme-1' ? 'custom-theme-2' : 'custom-theme-1';
    this.overlayContainer.getContainerElement().classList.remove(originalTheme);
    this.overlayContainer.getContainerElement().classList.add(this.theme);
  }
}






这时候再切换就会一切正常啦!

Fix the toggle theme problem

本日小结

今天我们学到使用Angular Material所提供的SCSS,并了解到Material Design的调色盘中,在Angular Material都有对应的颜色,只需要使用$mat-xxxx变数即可,而透过mat-palette()可以得到实际颜色的配置,包含亮色调及暗色调。

最后我们使用mat-dark-theme()来取得深色的主题颜色,当然也能够使用mat-light-theme来得到亮色的主题,最后再使用angular-material-theme()得到完整Angular Material里面相关元件的class。

由于SCSS的特性,我们也能轻易把这些主题样式包装到另外一个class之中,来达到切换样式的效果,可以说是非常的有弹性啊!

本日的程式码GitHub:https://github.com/wellwind/it-ironman-demo-angular-material/tree/day-32-theme

下面这几个可以看一看 看颜色

https://material.io/design/color/#

https://www.materialpalette.com/

https://www.uplabs.com/

https://material.io/design/color/#color-theme-creation

https://github.com/angular/components/blob/5.0.x/src/lib/core/theming/_palette.scss



分支:day-32-theme

相关资源

来自  https://ithelp.ithome.com.tw/articles/10197670




[Angular Material完全攻略] Day 33 - 杂项技巧(2) - 其他CSS技巧

今天来分享一些笔者近期撰写Angular Material文章,以及开始在实际专案中使用Angular Material所整理出来的一些CSS小技巧,有些在文件上可以轻松找到,有些则是遇到后才整理出来的,希望能对各位读者大大们在使用Angular Material时有所帮助!

透过Angular Material使用Material Design中的颜色

昨天的文章在介绍自订theme时,我们提到了内建颜色的$mat-XXXX变数,这些变数的实际原始码看起来如下:

垫子红色样本

要直接取得里面的颜色,可以搭配使用SASS的map-get()方法,例如要取得红色色票中亮度为500的颜色代码,可以使用如下方式:

.custom-style {
    color: map-get($mat-red, 500);
}






如此一来就能取得#f44336的色码啦!

另外在Angular Material中还有定义一个mat-color()方法,方便我们设定颜色,以及他的透明度,甚至可以不用担心亮度,直接使用darkerlighter参数,来决定定义好的亮度,除此之外也可以直接设定透明度,被用在许多Angular Material中的背景颜色的设定,如以下样式我们选用暗色调的红色,以透明度50%,作为样式的背景:

.custom-color {
  background: mat-color(mat-palette($mat-red), darker, 0.5);
}






关于颜色的定义,除了上Material Design网站上观看调色盘外,也可以直接查Angular Material的_palette.scss,可以看到完整的颜色变数。

使用material-colors套件

其实这已经脱离Angular Material的范围了,但很值得简单介绍一下,因为Angular Material的颜色一定要搭配SASS并写在scss档中,有时候比较懒惰,只想要单纯使用颜色时,若有人直接把相关颜色都订成对应的class,那实在是太方便了!material-colors套件就是一个帮我们把Material Design颜色都订成CSS class的好东西!

加入这个style样式后,我们可以直接使用class="color-red-500",来取得红色色票且亮度为500的颜色,另外被景色也可以使用class="bg-red-500"来达到预期的效果。

除此之外material-colors套件也有定义好的SASS档,相当值得参考。

透过Angular Material使用Material Design中的阴影

在Material Design中,为了让画面具有立体感,对于阴影(shadows)的使用可以说是非常讲究,这种阴影设计也被称为elevation,作为元件与元件之间高低差的概念,也就是阴影越深,高度看起来就越高。

下图是从Material Design设计指南中撷取的图片,对于各种高度效果的建议:

材料设计立面     

数值越高代表在画面呈现上应该在越上层,例如Dialog类型的应该要在最高的位置24,选单Menu在8,而子选单会比选单在高一点,因此在高度9的位置。

当我们自己在设计元件时,为了让元件更有立体感,可以参考这个高度表,评估一下自己设计的元件属于哪种元件分类,进而选择适当的高度,来达到画面呈现的一致感!

至于具体的CSS样式到底该如何设计呢?其实Angular Material已经都帮我们设计好啦!只要套用mat-elevation-zX的样式就可以了!X的部分,请直接换成对应的高度,例如我们在使用overlay并显示在整个画面上时,通常是类似dialog的角色,这时候就可以要显示元件上加一个class="mat-elevation-z24",就能够具有一致的显示啦!

另外我们也能够使用mat-elevation()这个mixin,只需要以适当的高度当作参数带入,就会产生对应高度的阴影样式:

@import '~@angular/material/theming';

.my-custom-button {
  @include mat-elevation(2);
}






等于得到如下的阴影样式:

.my-custom-button {
  box-shadow: 
    0px 3px 1px -2px rgba(0, 0, 0, 0.2), 
    0px 2px 2px 0px rgba(0, 0, 0, 0.14), 
    0px 1px 5px 0px rgba(0, 0, 0, 0.12);
}






真是省时又省力啊!

替某个Angular Material元件客制化样式

这个技巧在之前其实已经提过几次,但真的很实用也很重要,因此在这边再次提一下。在Angular Material中,几乎所有的元件与directive,都会加上一个与它自己同名的CSS class,例如mat-button这个directive会在它的元件上加入同名的CSS class。因此我们可以直接在撰写CSS时,透过这个class来调整样式,例如:

.mat-button {
    font-size: 24px;
}






透过这样的方式,要针对某个元件为调整符合需求的呈现就变得非常简单哩。

善用panelClass

Angualr Material中,有许多popup的呈现,都能透过设定panelClass来改变整个popup的呈现,以笔者实际遇到的一个状况为例:专案需要一个dialog,已经由美工设计好,这个dialog是没有padding的,但使用Angular Material打开的dialog预设会套用一个mat-dialog-container样式,团队讨论后不希望直接像上一段描述的调整全域mat-dialog-container样式,这时候我们就替要打开的dialog设定了一个panelClass:custom-dialog,并调整样式把里面mat-dialog-container的padding设为0,如下:

.custom-dialog {
  .mat-dialog-container {
    padding: 0px;
  }
}






之后打开dialog时,程式调整成:

this.dialog.open(SomeDialogComponent, {
  panelClass: 'custom-dialog'
});






就能够改变显示dialog底层panel的样式啦!

其他可能的状况如背景颜色、调整阴影等等,都可以透过panelClass来做设定!

这个技巧在所有popup类型的元件都可以使用,除了dialog、snackbar是使用程式控制以外,其他元件如<mat-select><mat-menu>等,都有一个panelClass的input可以直接使用。

另外使用Angular CDK的overlay时,也能够透过panelClass来设定样式。

单独使用某个Angular Material元件

要单独使用某个元件其实不难,只要import对应的Module就好,就算把所有Module都import近来好了,Angular Material也会在build时透过tree shaking机制来甩掉用不到的程式,因此原则上是不用担心会入过多程式造成档案庞大的问题,但这是程式面的部分,而CSS就不是这么一回事了!

当我们使用@include angular-material-theme()的同时,也就代表所有Angular Material元件的样式都被加了进来,但或许我们真的只需要用到整个Angular Material的1~2个功能,又对档案大小有所要求时,其实我们还是能单独汇入特定元件的样式。     

简单的步骤说明如下:

定义基本主题颜色

要单独使用某个样式,当然就必须抛弃现有theme的CSS档(或看看未来Angular Material会不会提供单纯theme的SCSS定义档),因此我们必须自己定义theme的颜色,这部分在昨天的文章已经介绍过了,如下:

@import '~@angular/material/theming';

@include mat-core();

$custom-primary: mat-palette($mat-light-blue);
$custom-accent: mat-palette($mat-green);
$custom-warn: mat-palette($mat-brown);

$custom-theme: mat-dark-theme($custom-primary, $custom-accent, $custom-warn);






加入核心样式

接下来我们要加入核心的样式,这些样式是大部分元件都通用的,因此必须先加进来,除了mat-core()已经有了最基本的共用样式外,还需要加入mat-core-theme()

.custom-theme-1 {
  @include mat-core-theme($custom-theme);
}






加入元件样式

最后,我们只加入想要的元件样式就好,Angular Material中将所有的元件都依模组切成了不同的mixin,只需要针对对应模组加入mat-xxxxx-theme,例如button对应的就是mat-button-theme

.custom-theme-1 {
  @include mat-core-theme($custom-theme);
  @include mat-button-theme($custom-theme);
}






这时候就只有按钮元件会套用相关的样式啦!

进一步瘦身

上列步骤是一个简单且保险的方法,但还是可能会加入许多不必要的样式;举例来说,mat-core()包含了overlay相关的样式,在只使用button时根本用不到;而mat-core-theme()里面其实也没有与button相关的共用样式,加入这个设定只会产生用不到的样式而已,这时候我们可以来看看_core.scssmat-core()mat-core-theme()到底加入了些什么共用的样式:

// Mixin that renders all of the core styles that are not theme-dependent.
@mixin mat-core($typography-config: null) {
  // Provides external CSS classes for each elevation value. Each CSS class is formatted as
  // `mat-elevation-z$zValue` where `$zValue` corresponds to the z-space to which the element is
  // elevated.
  @for $zValue from 0 through 24 {
    .#{$_mat-elevation-prefix}#{$zValue} {
      @include mat-elevation($zValue);
    }
  }

  @include angular-material-typography($typography-config);
  @include mat-ripple();
  @include mat-option();
  @include mat-optgroup();
  @include cdk-a11y();
  @include cdk-overlay();
}

// Mixin that renders all of the core styles that depend on the theme.
@mixin mat-core-theme($theme) {
  @include mat-ripple-theme($theme);
  @include mat-option-theme($theme);
  @include mat-optgroup-theme($theme);
  @include mat-pseudo-checkbox-theme($theme);

  // Wrapper element that provides the theme background when the
  // user's content isn't inside of a `mat-sidenav-container`.
  .mat-app-background {
    $background: map-get($theme, background);
    background-color: mat-color($background, background);
  }

  // Marker that is used to determine whether the user has added a theme to their page.
  .mat-theme-loaded-marker {
    display: none;
  }
}






可以看到有些其实是用不到的,但当使用<mat-select>时,mat-option-theme()就可能是必要的!这时候我们就能够排除使用mat-core()mat-core-theme(),自己选择要加入的mixin啰,再进一步的瘦身啦!

以上方法其实已经对于档案大小有吹毛求疵的要求了,一般状况很少用到,纯粹炫耀笔者对Angular Material的理解程度之深(误) 但当真的有所要求或追求极致时,就是一个可以考虑调教的地方啰!    

顺带一提,以笔者目前的范例专案,使用Angular CLI 1.6.0的ng serve指令产出未压缩style.bundle.js档,使用上述方法瘦身后,减少约150k的大小(当然,许多样式都消失了)。    

如何单独使用Angular CDK

Angular CDK是Angular Material共用的部分抽取出来的工具,在之前的文章我们也提过,不是每个专案都需要使用Material Design,但每个专案基本上都需要打造自己的元件因此我们可以不装Angular Material,但强烈建议把Angular CDK加入专案中,但Angular CDK其实还是有基本的样式,像是overaly的灰底等等,那么只加入Angular CDK的专案,该怎么把这些样式加进来呢?

其实从上一章套用部分样式中我们就能略知一二,在mat-core()中我们加入了cdk-a11y()cdk-overlay()两个mixin,给Angular CDK使用,所以我们只需要把Angular CDK的SCSS路径找出来,再加入style.scss之中就好啰:

@import "~@angular/cdk/_a11y.scss";
@import "~@angular/cdk/_overlay.scss";

@include cdk-a11y();
@include cdk-overlay();






当然啦!如果没用到Angular CDK的a11y和overlay功能,一样可以不加入这两组样式。

本日小结

在笔者一段不算长的时间学习以及正式专案中部分使用Angular Material及Angular CDK经验中,所遇到的一些需求,如下:

  1. 细部调整元件的呈现

  2. 打造更加一致具有Material Design风格的元件

  3. 档案瘦身

在Angular Material中已经帮我们把这些都设想好了,我们只要依照前人走好的路,就能更轻松达成这些目标,真的是太幸福啦!

相关资源

   
游冥霏 Sunny You        
iT邦新手 5 級 ‧ 2018-11-13 16:32:52        

用 mat-elevation-zX 來加陰影真的是很好用呢!
我看到 angular material 官網的導覽列上有加 mat-elevation-z6
官方文件也有說到這部分:
https://material.angular.io/guide/elevation            



来自  https://ithelp.ithome.com.tw/articles/10197682




[Angular Material完全攻略] Day 34 - 学习Angular Material的正确姿势


今天来聊聊笔者这些日子学习Angular Material的方式,希望可以帮助大家能以更快的速度深入Angular Material,并能灵活运用在自己的专案当中。

在笔者的经验中,Angular Material要学得好,除了需要一定程度的Angular知识以外,另外还有Angular Material三宝:文件、demo app和source code

透过文件学习Angular Material

不论任何技术,想要快速地上手,文件绝对是不可或缺的一部份,Angular Material的文件本身已经提供不少详细的说明,但在目前还稍嫌不完整,但要帮助初学者上手已经非常足够了。

Angular Material的文件主要分成两个部分

  • Guides:包含了最基础的「Getting started」,让我们能快速开始使用Angular Material;还有一些持续在增加中的文章,补充Angular Material各种功能可以加强的部分。

    Document - Guides         

  • Components、CDK:Angular Material的核心功能,包含了元件(Components)与CDK,在元件页面,详列了目前所有Angular Material可以使用的元件功能,每个功能的文件都包含了「Overview」、「API 」与「Examples」三个页签;CDK则只有「Overview」和「API」

    Document - Components        

    • 在Overview页签中,我们可以看到整个功能的基本使用方式,以及一些常见可以设定的属性,方法等等。

    • 在API页签中,则是该功能的module下所有的元件、service和directives等等,以及详列所有相关的属性和方法,对于要进一步使用元件的功能,这个页面是不可或缺的参考资料。

    • Examples则是一些简单的范例和程式码。

在Angular Material的文件中,包含了许多基本的范例DEMO,如下:

Document - Example     

右方有两个按钮,第一个按钮是直接检视相关的原始码,不过目前大部分点开原始码出现的程式,版本都不正确,都是md-xxxx而不是mat-xxxx,这点需要非常小心:

Document -Example - Source Code     

再旁边还有一个按钮,则会跳到StackBlitz的网站,并显示这个范例的程式码,并且放在StackBlitz网站的程式码都是正确的!方便我们copy/paste

只要好好的把握文件的使用脉络,要能够掌握大部分Angular Material的功能,基本上绝对不是问题!

透过demo app学习Angular Material

Angular Material的原始码本身包含了一个demo app,这个demo app具有非常多的范例程式,如果读者在阅读文件时觉得不太知道该如何应用,可以直接看看demo app的程式,会有很多收获!

使用demo app的方法很简单,首先我们把整个material2的专案都clone下来:

git clone https://github.com/angular/material2.git

是不是 地址变了 变成了 https://github.com/angular/components.git 

(见  https://github.com/angular/components  它是由  https://github.com/angular/material2 跳转而来 )  

(所以 原来的  git clone https://github.com/angular/material2.git  也是可以的,它们的内容是完全一样的)

为啥不行呢, 到  https://github.com/angular  里面去找找其它的吧  


https://github.com/katan/angular-material-demos   这个肯定行ok 有大用




接着进入专案,还原npm套件后,执行demo-app的script

cd material2
npm install
npm run demo-app






这时候在进入http://localhost:4200/,就能够看到一个基本的demo页面啰

Demo App     

点击左上方的选单按钮,就能够看到所有的功能demo连结

Demo App     

至于demo app的原始码,则是放在material2专案目录的src/demo-app下面,读者可以在里面找到许多范例程式码,也能够直接修改里面的程式,看看demo app会对应产生什么变化。

透过source code学习Angular Material

虽然直接看原始码通常是最后手段,但不得不说Angular Material的原始码写得非常漂亮,可度性很高,如果在看过demo app后还是对于使用上有所疑惑,或是单纯好奇某个元件功能是怎么办到的时候,就是阅读原始码的时机啦!

Angular Material的主要元件功能程式码都方放在src/lib中,另外Angular CDK的原始码则是放在src/cdk

偶尔多看一下Angular Material的原始码,不仅能更加深刻理解Angular Material背后运作的原理,更能从中学习到许多元件功能设计的思维,可以说是一举数得啊!

其他学习资源

上面是笔者在学习Angular Material时,最常使用的三个资源,下面再补充几个学习Angular Material不可错过的好去处:

  • Angular Taiwan Facebook社团 | 论坛:最大的好处,不用多说当然就是里面所有的人都说中文啦!这里主要是讨论Angular的地方,但是如果对于Angular Material有问题,提出来也是很多高手会回答的!

  • StackOverflow - Angular Material:StackOverflow应该不用多说,是全世界最大学习各种技术copy/paste 的最佳来源,卡关的时候,先来这里找找就对了!

  • Angular Material的issues:在Angular Material的issues里面,不会提供某个元件该怎么用之类的说明,但是却可以帮助我们确认某些功能是否真的有它的限制,毕竟有时候某些功能无效,不一定都是我们得问题,也有可能是Angular Material本身的限制,甚至是bug,这时候上issues找最准确啦!

  • Gitter - angular/material2:Angular Material的线上聊天室,遇到问题时,在这里问问题也是很棒的选择!

本日小结

今天介绍了笔者在学习Angular Material时所使用的资源,这些资源都有很丰富的内容可以学习,只要愿意稍微花点时间,相信要成为Angular Material高手绝对不是一件困难的事情!让我们一起迈向Angular Material大师的伟大航道吧!

参考资源

   
游冥霏Sunny You        
iT邦新手5级‧ 2018-11-13 16:39:41        

/images/emoticon/emoticon42.gif
原来还有demo app这个大绝招!
/images/emoticon/emoticon34.gif
我用yarn的指令也可以用,真的好棒啊,可以不停copy/paste
/images/emoticon/emoticon37.gif            



来自  https://ithelp.ithome.com.tw/articles/10197691




普通分类: