前言

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. 解决方案 | Python中安装pix2tex latex ocr出现报错Cannot mix incompatible Qt library (6.6.2) with this library (6.7.2)

    一.问题 Python中安装pix2tex latex ocr出现报错Cannot mix incompatible Qt library (6.6.2) with this library (6.7 ...

  2. 10.2 web服务器

    Web客户端和服务器之间的交互用的是一个基于文本的应用级协议,叫做HTTP(Hypertext Transfer Protocol,超文本传输协议).HTTP是一个简单的协议.一个Web客户端(即浏览 ...

  3. supervisor.conf部署及维护

    启动服务 supervisord -c /etc/supervisord.conf 启动服务 supervisorctl start 关闭服务 supervisorctl stop

  4. [oeasy]python0100_wintel联盟_intel_微软_microsoft_msDOS_基尔代尔

    wintel联盟 回忆上次内容 上次 了解了IBM的 背水一战 IBM 已经不在乎 软硬一体全自主的设计 了 而采用了 开放的架构 任何 硬件厂商和软件厂商 都可以来合作 以丧失 自主控制力的方式 获 ...

  5. [oeasy]教您玩转python - 0004 - 万行代码之梦

    ​ 继续运行 回忆上次内容 上次从1行代码进化到了2行代码 yy p粘贴剪贴板中的内容 将剪贴板中的代码粘贴9999次 9999p 真的实现了万行代码梦 是真·圆梦 没有撒谎的那种 不过圆梦之后多少有 ...

  6. C# WinForm自制项目模板入坑记

    1. 创建模板配置 1.1 在项目目录中创建.template.config文件夹 1.2 创建一个名为"template.json" 的新文件 { "author&qu ...

  7. 顺序表之单链表(C实现)

    // Code file created by C Code Develop #include "ccd.h"#include "stdio.h"#includ ...

  8. vue使用Echarts常见警告处理方法

    [警告一][ECharts] DEPRECATED: textStyle hierarchy in label has been removed since 4.0. All textStyle pr ...

  9. 【WebSocket】一个简单的前后端交互Demo

    WebSocket资料参考: https://www.jianshu.com/p/d79bf8174196 使用SpringBoot整合参考: https://blog.csdn.net/KeepSt ...

  10. 【Project】原生JavaWeb工程 02 登陆业务的流程(第一阶段样例)

    1.对用户信息的描述 首先用户有一些基本信息: 最简单的: 用户名称 + 用户密码 然后是用户状态,例如封号,注销,停用,等等 用户名称 + 用户密码 + 账号状态 接着为了防止脚本攻击,又产生了图形 ...