前言

CDK Drag and Drop 和 CDK Scrolling 都是在 Angular Material v7 中推出的。

它们有一个巧妙的共同点,那就是与 Material Design 没有什么关联。

这也导致了它和 CDK Scrolling 一样,功能不完善,只能满足非常简单的开发需求。

我们之前说过,CDK 是 Angular Material 团队在开发 Angular Material 时顺手抽象出来的。

它们的关系是 CDK 服务于 Angular Material,而 Angular Material 服务于 Google Products。

因此,如果一个 CDK 功能没有被 Angular Material 或 Google Products 重用,那么这个功能注定不会得到关注,不会完善,也不会好用 (不管社区多么努力 vote)。

本篇,让我们一起看看这个不太好用的 CDK Drag and Drop 吧

参考

Docs – CDK Drag and Drop

Github Issue – Drag Drop Sortable, mixed orientation support

Free Drag

这是一个 box

<div class="box">Drag me around</div>

Styles

.box {
width: 192px;
height: 192px;
background-color: pink;
color: blue;
display: flex;
justify-content: center;
align-items: center;
font-size: 20px;
border: 4px solid red;
cursor: grab;
}

效果

我们只要加上一个 CdkDrag 指令,它就是能 drag 移位了。

<div class="box" cdkDrag>Drag me around</div>

效果

The principle behind

Drag 的实现原理非常简单,就是监听 mouse down/move/up 然后添加 transform translate 到 element,让它顺着 mouse position 移位。

也因为这样,当 element 被 drag 以后,它不会影响原来的布局。

我们换一个例子展现这个特性

<div class="item-list">
<div class="item">Alex</div>
<div class="item" cdkDrag>David</div>
<div class="item">Jay</div>
<div class="item">Stefanie</div>
</div>

.item-list {
border: 1px solid black;
padding: 16px;
width: 256px;
display: flex;
flex-direction: column;
gap: 16px; .item {
padding: 16px;
border: 1px solid black;
background-color: white; &[cdkDrag] {
cursor: grab;
}
}
}

David 被拉出来后,Jay 并不会往上移动。

也因为它使用 translate,假如 item-list 有 overflow hidden,当 item 超出范围时会被 hide 掉。

.item-list {
overflow: hidden;
}

效果

Free drag options

drag 有一些简单的小配置可以玩。

  1. lock axis

    <div class="box" cdkDrag cdkDragLockAxis="x">Drag me around</div>

    加一个 @Input cdkDragLockAxis,它的功效是锁住一个方向 (x or y 轴)。

    lock axis x 意思是只能 drag 只会影响 translate x,translate y 不会改变。

    反过来,lock axis y 就是只改变 translate y,translate x 不会改变。

  2. boundary 边界

    我们包一个 container

    <div class="container">
    <div class="box" cdkDrag>Drag me around</div>
    </div>

    Styles

    .container {
    width: 360px;
    height: 360px;
    border: 1px solid black; .box {
    // 省略...
    }
    }

    效果

    目前 box 可以 drag 超出 container 范围。

    我们添加 @Input cdkDragBoundary

    <div class="box" cdkDrag cdkDragBoundary=".container">Drag me around</div>

    value 是一个 ancestor element selector,任何 ancestor element 都可以作为边界。

    效果

  3. drag handle

    <div class="box" cdkDrag>
    <span cdkDragHandle>Drag me around</span>
    </div>

    在 drag element 内添加一个 CdkDragHandle 指令,只有精准点击到这个 CdkDragHandle element 才能发起 drag。

    .box {
    // 省略... [cdkDragHandle] {
    background-color: blue;
    color: white;
    }
    }

    效果

    粉红区域是 drag 不到的,只有中间的蓝色区域可以 drag。

这些 options 都只是一些微不足道的小功能而已,它们都建立在 drag 的基础 (监听 mouse move 设置 translate) 之上。

Drag and Drop

单单使用 drag 功能是比较少见的,更多的情况我们是 drag and drop 一起使用。

Simple case

组件

export class TestMyDragComponent {
readonly names = signal(['Alex', 'David', 'Jay', 'Stefanie']);
}

Template

<div class="item-list">
@for (name of names(); track name) {
<div class="item">{{ name }}</div>
}
</div>

Styles

.item-list {
border: 1px solid black;
padding: 16px;
width: 256px;
display: flex;
flex-direction: column;
gap: 16px; .item {
padding: 16px;
border: 1px solid black;
background-color: pink;
}
}

效果

需求是 drag item 换位置。

每一个 item 都可以 drag 换位置,所以第一步是在 item element 添加 CdkDrag 指令

<div class="item" cdkDrag>{{ name }}</div>

效果

虽然可以 drag 可以换位置,但是排版乱七八糟。

我们需要添加 CdkDropList 指令到 item-list element 上

<div class="item-list" cdkDropList>

效果

仔细看,它其实已经有一些换位效果了,但依然有 2 个大问题:

  1. drag 的时候 item 的 styles 没了

  2. drop 的时候 item 的位置又跳回去了

The principle behind

要解决上述两个问题,我们先把 drap and drop 背后原理搞清楚。

当 CdkDrag 指令自己一个人时,它的任务很简单,就只是 set item translate。

但当它遇上 CdkDropList 指令后,它的任务就不同了。

当 mouse down + mouse move 以后,CdkDrag 会创建出 2 个 elements。

第一个 element 叫 placeholder,by default 它就是 clone from item element。

第二个 element 叫 preview,by default 它也是 clone from item element。

注:placeholder 和 preview 的设计是可以 customize 的,有兴趣的读友可以看官网的例子,太浅我就不教了。

placeholder 会被插入到原本 item 的位置上

原本的 item 会被 cut and paste 到 body,并且定位到千里之外,让它看不见。

preview 也会被 append 到 body 然后 position fixed + translate 定位到 mouse cursor 的位置。

Solve the style issue

好,了解了它的结构,我们的 styles issue 就有眉目了。

preview element 虽然是 clone from item element,但由于它被 append 到 body 了,所以我们之前定义的 CSS selector 就 select 不到它了。

我们把 .item selector 搬出来

效果

preview 的 styles 和 item styles 一模一样了。

Solve the drop jump back issue

dragging 时,item 的顺序已经发生了变化,但在 drop 下去的那一瞬间又都跳回了原位。

这背后的原理其实很简单。

在 dragging (mouse move) 的时候,CdkDrag 和 CdkDropList 会携手合作,查看 drop list 内所有的 drag item 的 bounding client rect,

每当 mouse 进入到 item 里,CdkDropList 就会换 item element 的位置。

注:它换位的方式是透过 set translate 而不是改变 element 在 DOM 里的结构哦。用 translate 的好处是可以 set animation,改变 DOM 结构搞不了 animation。

这个换位就是纯粹的 DOM manipulation 而已,而当 drop 下去时,之前换位 set 的 translate style 通通会被洗掉,于是它就跳回原位了。

要解决这个问题,或者说正确的做法应该是,监听 drop 事件,然后修改 view model items 的顺序,然后通过 @for render 让它渲染出正确的位置。

<div class="item-list" cdkDropList (cdkDropListDropped)="drop($event)">

添加 @Output cdkDropListDropped 和一个 drop 方法

drop(event: CdkDragDrop<unknown>) {
const clonedNames = [...this.names()];
moveItemInArray(clonedNames, event.previousIndex, event.currentIndex);
this.names.set(clonedNames);
}

moveItemInArray 是 CDK Drap and Drop built-in 的函数,它的作用是搭配 CdkDragDrop event 对 array items 做换位,这样我们就不需要自己写换位的 formula 了。

注:moveItemInArray 不是 immutable 设计流派的,它会直接 mutate array,虽然有人提议 Angular 团队推出 immutable 版本,但很遗憾,Angular 团队没有意识到它的重要性。

效果

More styling

效果是有了,但是体验还不够好。

我们知道 CDK 是不负责设计的,它只负责 add class。

那就让我们透过这些 class 来美化一下呗。

.item-list {
border: 1px solid black;
padding: 16px;
width: 256px;
display: flex;
flex-direction: column;
gap: 16px; .cdk-drag-placeholder {
opacity: 0; // 隐藏 placeholder
} &.cdk-drop-list-dragging .item:not(.cdk-drag-placeholder) {
transition: transform 0.25s; // dragging 换位时的 animation
}
} .item {
padding: 16px;
border: 1px solid black;
background-color: pink; &[cdkDrag] {
cursor: grab;
} &.cdk-drag-preview.cdk-drag-animating {
transition: transform 0.25s; // drop 时的 animation
}
}

最终效果

Drag and drop between multiple drop list

上面例子是一个 drop list,多个 item 在里面换位置。

这里在一个进阶版本,多个 drop list,item 在不同 drop list 间换位置。

组件

export class TestMyDragComponent {
readonly todoTasks = signal([
'Fix header styling',
'Update dependencies',
'Review pull request',
'Add error handling',
'Deploy new build',
]); readonly doneTasks = signal([
'Debug login issue',
'Optimize loading speed',
'Write unit tests',
'Refactor codebase',
'Implement dark mode',
]);
}

Template

<div class="task-group">
<div class="task-list" cdkDropList>
@for (task of todoTasks(); track task) {
<div class="task" cdkDrag>{{ task }}</div>
}
</div> <div class="task-list" cdkDropList>
@for (doneTask of doneTasks(); track doneTask) {
<div class="task" cdkDrag>{{ doneTask }}</div>
}
</div>
</div>

Styles

.task-group {
display: flex;
gap: 64px; .task-list {
border: 1px solid black;
padding: 16px;
width: 256px;
display: flex;
flex-direction: column;
gap: 16px; .cdk-drag-placeholder {
opacity: 0;
} &.cdk-drop-list-dragging .task:not(.cdk-drag-placeholder) {
transition: transform 0.25s;
}
}
} .task {
padding: 16px;
border: 1px solid black;
background-color: pink; &[cdkDrag] {
cursor: grab;
} &.cdk-drag-preview.cdk-drag-animating {
transition: transform 0.25s;
}
}

效果

目前 item 只能在自己的 drop list 里换位置,无法跨到其它 drop list,这是因为它们还不认识彼此。

这就好像 drag 必须要认识 drop list 以后,它们才可以携手完成 drag and drop 换位,同样的,不同 drop list 也需要互相认识才能携手完成跨 drop list 的 item 换位。

有两个做法可以让它们互相认识:

  1. CdkDropListGroup 指令

    它的作用是把其下所有 drop list connect 起来,让它们互相认识。

  2. @Input cdkDropListConnectedTo

    你中有我,我中有你。

不管哪一种方式都好,关键就是让它们链接上就可以了。

效果

注:在同一个 drop list 里换位置,它只是改变 placeholder 的 translate 而已,而换 drop list 则是 remove and append placeholder to target drop list。

Handle drop event

虽然 dragging 效果是正确的了,但是 drop 的时候还得更新 view model 丫。

我们在 Template 动点手脚

<div class="task-group" cdkDropListGroup>
<!-- @Input cdkDropListData 可以存入任何类型的资料,这里我们把对应的 Signal 传进去 -->
<!-- @Output cdkDropListDropped 统一用 drop 方法来处理 -->
<div class="task-list" cdkDropList [cdkDropListData]="todoTasks" (cdkDropListDropped)="drop($event)">
@for (task of todoTasks(); track task) {
<div class="task" cdkDrag>{{ task }}</div>
}
</div>
<!-- @Input cdkDropListData 可以存入任何类型的资料,这里我们把对应的 Signal 传进去 -->
<!-- @Output cdkDropListDropped 统一用 drop 方法来处理 -->
<div class="task-list" cdkDropList [cdkDropListData]="doneTasks" (cdkDropListDropped)="drop($event)">
@for (doneTask of doneTasks(); track doneTask) {
<div class="task" cdkDrag>{{ doneTask }}</div>
}
</div>
</div>

首先,所有的 drop list 统一使用同一个 drop 方法来处理。

接着我们需要给不同的 drop list 一个识别,这样 drop 方法才能依据不同情况做处理。

@Input cdkDropListData 可以传入任何资料,我们利用它传入那个 drop list 的 view model (tasks Signal),这样不同的 drop list 就有不同的 task signal,这就可以识别了。

然后是 drop 方法

drop(event: CdkDragDrop<WritableSignal<string[]>>) {
// 这里 container 指的是 drop list
// 一个 item 从 drop list A 移动到 drop list B
// 那 previous container 指的就是 drop list A
// current container 指的就是 drop list B
// 这里我们把 prev 和 curr 的 container data (也就是 tasks Signal) 拿出来。
const prevTasks = event.previousContainer.data;
const currTask = event.container.data; if (event.previousContainer === event.container) {
// 如果 prev 和 curr 相同,代表 item 是在同一个 drop list 里移位
// 那我们就用 moveItemInArray 函数处理就可以了
const clonedTasks = [...currTask()];
moveItemInArray(clonedTasks, event.previousIndex, event.currentIndex);
currTask.set(clonedTasks);
} else {
// 如果 prev 和 curr 不同,代表 item 已经移动到另一个 drop list 了
// 此时,两个 drop list 都需要 update
// 一个 remove, 一个 add
const clonedPrevTasks = [...prevTasks()];
const clonedCurrTasks = [...currTask()];
// transferArrayItem 函数是 CDK Drag and Drop built-in 的函数,专门用来处理跨 drop list 的换位
transferArrayItem(clonedPrevTasks, clonedCurrTasks, event.previousIndex, event.currentIndex);
prevTasks.set(clonedPrevTasks);
currTask.set(clonedCurrTasks);
}
}

注释已经解释清楚了,简单说:

  1. 比对 prev 和 curr container 看是 single drop list 换位,还是跨 drop list 换位

  2. single drop list 就用回 moveItemInArray 函数

  3. 跨 drop list 就用 transferArrayItem 函数

最终效果

Drag and drop with scrolling

CDK Drag and Drop 支持 scrolling。

效果

dragging 的时候可以使用 mouse wheel 移位,或者当 drag item 靠近两端口时,CDK Drag and Drop 会修改 scrollTop 移位。

注:CDK Drag and Drop 是依靠 ScrollDispatcher 来监听 scroll 事件的,所以我们要记得在 overflow element 上添加 CdkScrollable 指令 哦。

Mixed orientation drag and drop

Angular 在 v18.1 推出了 Mixed orientation drag and drop,以前只支持 1 direction,要嘛 vertical,要嘛 horizontal,不可以 mixed,但现在可以了。

组件

export class TestMixedOrientationComponent {
readonly tasks = signal([
'Fix header styling',
'Update dependencies',
'Review pull request',
'Add error handling',
'Deploy new build',
'Debug login issue',
'Optimize loading speed',
'Write unit tests',
'Refactor codebase',
'Implement dark mode',
]); drop(event: CdkDragDrop<unknown>) {
const clonedTasks = [...this.tasks()];
moveItemInArray(clonedTasks, event.previousIndex, event.currentIndex);
this.tasks.set(clonedTasks);
}
}

Template

<div class="task-list" cdkDropList (cdkDropListDropped)="drop($event)">
@for (task of tasks(); track task) {
<div class="task" cdkDrag>{{ task }}</div>
}
</div>

Styles

.task-list {
border: 1px solid black;
padding: 16px;
width: 512px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px; .cdk-drag-placeholder {
opacity: 0;
} &.cdk-drop-list-dragging .task:not(.cdk-drag-placeholder) {
transition: transform 0.25s;
}
} .task {
padding: 16px;
border: 1px solid black;
background-color: pink; &[cdkDrag] {
cursor: grab;
} &.cdk-drag-preview.cdk-drag-animating {
transition: transform 0.25s;
}
}

代码和上面的例子都差不多,只是改成了 grid layout。

效果

可以看到,dragging 时,整个 layout 都乱套了。

原因很简单,CDK Drag and Drop 只会把它当成 one direction (默认是 vertical) 来处理,在换位时只会设置 translateX,不会设置 translateY。

所以,它的位置是绝对不可能正确的。

我们可以透过设置 CdkDropList @Input orientation 让它支持 mixed orientation。

<div class="task-list" cdkDropList (cdkDropListDropped)="drop($event)" [cdkDropListOrientation]="'mixed'">

它支持 'vertical' (默认),'horizontal' 还有 'mixed'。

效果

注:'vertical' 和 'horizontal' 是透过 set translate 来实现移位的,移动时可以带有 animation。然而 'mixed' 却不是,mixed 是透过 remove and re-insert element 来实现移位的,所以它在移位时无法配上 animation (这显然是 Angular Material 团队不给力)。

v18 旧版本 (暂留)

说明:本篇撰写于 Angular Material v18,当时还不支持 mixed orientation drag and drop,以下是当时写的相关内容,暂时保留做纪念,以后会删除。

drag and drop 换位置只支持一个方向,要嘛 vertical (我们上面的例子都是 vertical) 要嘛 horizontal (可以透过 @Input cdkDropListOrientation 做配置),不可以像 grid layout 那样 2 directions。

这是一个常年霸榜的 feature request

很遗憾,Angular Material 团队到今天都没有意愿要解决。

我们来看具体例子。

组件

export class TestMixedOrientationComponent {
readonly tasks = signal([
'Fix header styling',
'Update dependencies',
'Review pull request',
'Add error handling',
'Deploy new build',
'Debug login issue',
'Optimize loading speed',
'Write unit tests',
'Refactor codebase',
'Implement dark mode',
]); drop(event: CdkDragDrop<unknown>) {
const clonedTasks = [...this.tasks()];
moveItemInArray(clonedTasks, event.previousIndex, event.currentIndex);
this.tasks.set(clonedTasks);
}
}

Template

<div class="task-list" cdkDropList (cdkDropListDropped)="drop($event)">
@for (task of tasks(); track task) {
<div class="task" cdkDrag>{{ task }}</div>
}
</div>

Styles

.task-list {
border: 1px solid black;
padding: 16px;
width: 512px;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px; .cdk-drag-placeholder {
opacity: 0;
} &.cdk-drop-list-dragging .task:not(.cdk-drag-placeholder) {
transition: transform 0.25s;
}
} .task {
padding: 16px;
border: 1px solid black;
background-color: pink; &[cdkDrag] {
cursor: grab;
} &.cdk-drag-preview.cdk-drag-animating {
transition: transform 0.25s;
}
}

代码和上面的例子都差不多,只是改成了 grid layout。

效果

可以看到,dragging 时,整个 layout 都乱套了。

原因很简单,CDK Drag and Drop 只会把它当成 one direction (默认是 vertical) 来处理,在换位时只会设置 translateX,不会设置 translateY。

所以,它的位置是绝对不可能正确的。

Workaround

社区提供了一个临时解决方案,这里我们分析一下它的原理。

首先,它把所有的 item 都 wrap 上了一层 drop list。

所以在 drag and drop 时,它并不是在一个 drop list 里面换位置,而是跨 drop list 换位置。

那依照这个路线走的话,当 item A 被 drag 到 item B 时,item A 会跑进去 item B 的 drop list,

此时 drop list A 应该变成空的,drop list B 里有 item A 和 B。

但是结果却不是这样,因为它在 drop list enter 时动了手脚。

我们分两段来看,第一段的任务是让 drop list A 和 drop list B 换位。(提醒:此时 drop list A 内是空的,drop list B 里有 item A 和 B)

这个换位是纯粹的 DOM manipulation,和 CDK Drag and Drop 没有任何关联。

第二段的任务是把 item A 放回 drop list A,这是透过 CDK Drag and Drop 私有功能 (DropListRef.enter) 完成的。

整个过程大概是这样

非常巧妙的思路

逛一逛源码

我们稍微逛一逛它的结构就好了,毕竟它都不完整嘛。

万物的开始是 CdkDrag 指令,它的源码在 drag.ts

在 constructor 阶段,inject DragDrop Service 然后调用 createDrag 方法创建一个 DragRef 实例。

DragDrop Root Level Provider 长这样,源码在 drag-drop.ts

没什么特别的,只是简单的 new DragRef 或 new DropListRef 而已。

DragRef 的源码在 drag-ref.ts

withRootElement 方法

这里监听了 mouse down。所谓的 dragging 第一个动作就是 mouse down,然后是 mouse move,最后是 mouse up。

_pointerDown 方法

_initializeDragSequence 方法

我们看重点就好,里面监听了 mouse move 和 mouse up 事件。

DragDropRegistry 是一个 Root Level Provider,源码在 drag-drop-registry.ts

pointerMove 和 pointerUp 是 RxJS Subject,而它的 source (事件源) 来自 document mouse move 和 mouse up 事件。

回到 DragRef._pointerMove 方法

假如这个 drag item 没有在任何 drop list 里面,那只需要 set drag item transform translate 就可以了。

_applyRootElementTransform 方法

好,上面这个就是最简单的 free dragging 例子,从 mouse down > move > set translate,up 的部分我们就不看了。

接着看看,drag and drop 的例子。

前半段都一模一样,一直到 move 的时候它们的处理方式就不同了。

回到 DragRef._pointerMove 方法

_startDragSequence 方法

这里主要是创建了 placeholder 和 preview element,还有 append 它们到相应的位置。

_createPlaceholderElement 函数长这样

如果我们没有传入指定的 CdkDragPlaceholder 指令作为 ng-template,那这里默认会 clone from drag item。

回到 DragRef._pointerMove 方法

_updateActiveDropContainer 方法

接着

这个 _dropContainer 的类型是 DropListRef,上面有提到过。

CdkDropList 指令在 constructor 阶段会透过 DragDrop Service 创建出 DropListRef,源码在 drop-list.ts

CdkDrag 指令在 constructor 阶段会 inject CdkDropList 作为 _dropContainer,这样它们就串联起来了。

我们继续看 DropListRef._sortItem 方法,源码在 drop-list-ref.ts

里面的关键是调用了 _sortStrategy 的 sort 方法。

_sortStrategy 是一个抽象类

它的具体实现是 SingleAxisSortStrategy 和 MixedSortStrategy,顾名思义,一个是 for vertical / horizontal,另一个是 for mixed orientation。

我们先看看 SingleAxisSortStrategy.sort 方法

总之就是一堆 formula 计算之后给每个 item set translate 换位。

MixedSortStrategy.sort 方法

总之就是一堆 formula 计算之后做 remove and re-insert element 来移位。

好,源码就逛到这里,以上就是本篇教程涉及到的相关源码。

总结

虽然 CDK Drap and Drop 非常简陋,但依然可以在真实项目中排上用场,比如

个人意见,目前最好不要太重用或依赖它,拿它来应付 1 direction 1 drop list 的场景就好,太复杂怕会掉坑。

另外,本篇没有介绍完所有 CDK Drag and Drop 的功能,建议读者也去看一下官方的文档。

目录

上一篇 Angular Material 18+ 高级教程 – Material Tooltip

下一篇 Angular Material 18+ 高级教程 – Material Form Field

想查看目录,请移步 Angular 18+ 高级教程 – 目录

喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding

Angular Material 18+ 高级教程 – CDK Drag and Drop的更多相关文章

  1. Angular Material (Components Cdk) 学习笔记 Table

    refer : https://material.angular.io/cdk/table/overview https://material.angular.io/components/table/ ...

  2. Angular Material 教程之布局篇

    Angular Material 教程之布局篇 (一) : 布局简介https://segmentfault.com/a/1190000007215707 Angular Material 教程之布局 ...

  3. Angular Material TreeTable Component 使用教程

    一. 安装 npm i ng-material-treetable --save npm i @angular/material @angular/cdk @angular/animations -- ...

  4. Angular Material design设计

    官网: https://material.io/design/ https://meterial.io/components 优秀的Meterial design站点: http://material ...

  5. 手势模型和Angular Material的实现

    iPhone的出现让手势操作大为流行,也使得手势编程成为开发人员的挑战. 拟物设计也把手势编程纳入在内,大概也想制定一个在交互模型标准.现阶段因为MD还在预发布阶段,因此还只实现了单点手势(一个指头) ...

  6. Material使用11 核心模块和共享模块、 如何使用@angular/material

    1 创建项目 1.1 版本说明 1.2 创建模块 1.2.1 核心模块 该模块只加载一次,主要存放一些核心的组件及服务 ng g m core 1.2.1.1 创建一些核心组件 页眉组件:header ...

  7. Siki_Unity_2-9_C#高级教程(未完)

    Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...

  8. Angular Material Starter App

      介绍 Material Design反映了Google基于Android 5.0 Lollipop操作系统的原生应用UI开发理念,而AngularJS还发起了一个Angular Material ...

  9. 关于 Angular引用Material出现node_modules/@angular/material/button-toggle/typings/button-toggle.d.ts(154,104): error TS2315: Type 'ElementRef' is not generic.问题

    百度了好久 ,,,最后谷歌出来了.. 该错误可能来自于您将@ angular / material设置为6.0.0, 但所有其他Angular包都是5.x.您应该始终确保Material主要版本与An ...

  10. Angular Material & Hello World

    前言 Angular Material(下称Material)的组件样式至少是可以满足一般的个人开发需求(我真是毫无设计天赋),也是Angular官方推荐的组件.我们通过用这个UI库来快速实现自己的i ...

随机推荐

  1. 有数大数据基础平台之智能运维平台EasyEagle介绍:集群队列篇

    他来啦,他来啦!大数据基础平台发布会中提到的智能运维平台,他来啦! 作为数据平台的用户们,下述问题一直困扰着我们: 集群资源水位如何,利用率如何,是否需要扩容? 队列为什么最近大量任务出现pendin ...

  2. git 更新某个目录或文件

    不多说直接贴代码 更新文件 $ git fetch remote: Counting objects: 8, done. remote: Compressing objects: 100% (3/3) ...

  3. [oeasy]教您玩转python - 0003 - 编写 py 文件

    ​ 编写 py 文件 回忆上次内容 次在解释器里玩耍 了解到字符串就是给一堆字符两边加引号 可以是单引号 也可以是双引号 这样游乐场就知道 这个不是一个名字 而是一个字符串 字符串可以用print函数 ...

  4. 常见的SQL数值型数据处理函数

    在数据驱动的时代,SQL 已成为数据分析和管理中不可或缺的工具.无论是处理简单的查询还是复杂的数据分析,SQL 都能帮助我们高效地完成任务. 然而,在处理数值型数据时,你是否感到过困惑,不知道如何运用 ...

  5. Github关于PAT(Personal Access Token)

    Github关于PAT(Personal Access Token) 创建个人访问令牌 您应该通过命令行或 API 创建个人访问令牌来代替密码. 注意: 如果您在命令行上使用 GitHub CLI 向 ...

  6. Java 线程池之Jetty 线程池学习总结

    Java 线程池之Jetty 线程池学习总结 前提 Jetty 11.0.x 为什么是Jetty? Java提供4中创建线程池的快捷方式 Executors.newFixedThreadPool(); ...

  7. python virtualenv虚拟环境配置与使用

    python virtualenv虚拟环境配置与使用 By:赖富玉 QQ:1033553122 概述 python开发过程中,我们可能需要同时开发多款应用,这些应用可能公用同一个版本的Python程序 ...

  8. vue8小时带刻度的时间轴,根据当前时间实时定位

    效果图: 需求: 1 开始时间.结束时间可配置2 时差固定8小时3 根据当前时间初始化位置4 每隔5s刷新位置5 超过结束时间停止刷新 HTML: <div class="time-a ...

  9. php使用jwt作登录验证

    JWT官网 https://jwt.io/ 选择第一个 composer require firebase/php-jwt use Firebase\JWT\ExpiredException;use ...

  10. java中的Context

    在java编程中,上下文(Context)是指程序运行时的环境和状态的集合.包括了类对象变量方法等运行时的相关数据 在类中,我们可以通过this获取当前类的变量.方法的上下文, 例如getset方法: ...