Open jiayisheji opened 5 years ago
这是一个什么错误,翻译成为无法分配给引用或变量(小细节:有些错误,你不知道什么意思时候,你可以把英文翻译成中文,你英文很差情况下,谷歌翻译很不错)
引用或变量,我不知道是什么鬼,但是我知道angular有个东西叫模板引用变量。 为什么它叫:模板引用变量,顾名思义就是引用模板。既然是引用变量,那么他应用了谁?这些变量提供了从模块中直接访问元素的能力,在标识符前加上井号 (#) 就能声明一个模板引用变量。
优点:这个模板完全是完全自包含的。它没有绑定到组件,组件也没做任何事情。这里的自包含的意思是:它不用与Component进行交互。
举个栗子:
<my-select #select></select>
比如我写了一个my-select
,默认情况下,是点击它就打开option
列表面板,但是产品需求点击一个按钮把它。
<my-select #select></select>
// 新需求
<button (click)="select.open()">打开</button>
就是这么容易。如果你还不知道赶紧用它把,它还可以ts获取:
// 获取一个
@ViewChild('select', { read: SelectComponent }) select: SelectComponent;
// 获取一组
@ViewChildren('selects', { read: SelectComponent }) select: QueryList<SelectComponent>;
ViewChild
和ViewChildren
请去看文档,这里不深入套路,它们不是重点。
回到错误,这才是关键。
我们看到模板引用变量调用是a.b
,那么问题来了,如果我在ts里面定一个了一个变量a
,它的调用也是a
,这个时候就有冲突了。angular也一脸懵逼,你到底要闹哪样。
那么很好理解的错误,无法分配给引用或变量,这里引用是指模板引用变量,变量指我们在ts里面定义class的属性。
解决方案很简单,别让他们重名就好了。
注意:指令的模板引用变量默认指向
HTMLElement
,如果想要指向这个指令怎么办?
指令需要写导出
@Directive({
selector: '[appRefresh]',
exportAs: 'refresh'
})
export class RefreshDirective implements OnDestroy {
load() {}
}
<div appRefresh #refreshRef="refresh">...code</div>
<button (click)="refreshRef.load()">加载</button>
最后说一点:写指令的时候一点要记得写
exportAs
.
翻译中文:模块“XxxxxModule”导入的意外值“undefined”。
我们先看下angular模块怎么定义的:
NgModule({
imports: [
SharedModule,
RouterModule.forChild(routes)
],
declarations: [XxxxComponent]
})
export class XxxxxModule { }
这是一个基本的业务模块,没有exports
,没有providers
;
现在错误是提示根据字面意思是来自imports
,那么我们根据imports
来找问题。
如果你找不到问题的时候,可以通过排除法。因为我们已经锁定范围了,剩下就一个一个找问题。
我们先把SharedModule
,注释掉,看是否能运行,如果ok,那么目标就锁定了,它里面有问题,我们需要去排查这个模块经历什么。
因为这个模块看命名知道是共享模块,大概就是导出当前模块需要的一些其他模块,组件什么的。
可以先将SharedModule
里面申明,导入,导出,全部清空掉,看有没有报错,如果有报错,找其他原因。
我先说下我这个报错原因:
我开始是这么写的
import { SharedModule } from '../shared';
因为在shared
文件夹里面写了index.ts
导出shared.module
;
按我正常理解是不需要在这样写:../shared/shared.module
我改成新的写法:
import { SharedModule } from '../shared/shared.module';
改成这样就不报错了,哈哈,那么就说明我这个index.ts
导出的有问题。
export * from './shared.module';
我写的这样的,并没有错,不知道原因它不找不到(可能cli抽风)。
这个问题原因就是导入模块时候,没有拿到导出模块,那个导入模块就变成了undefined
,这也解释了,模块“XxxxxModule”导入的意外值“undefined”这句话的意思。
什么叫循环依赖,比如我们创建一个select
,它里面有两个特有子组件option
和optgroup
。
optgroup
是分组,包含option
组件。
那么我们组件写法就是
<my-select>
<my-option>项</my-option>
</my-select>
<my-select>
<my-optgroup label="分组">
<my-option>项</my-option>
</my-optgroup>
</my-select>
如果我在optgroup
设置了disabled
,那么它下面option
就默认自动disabled
。
在angular写法就比较简单,只需要这样既可:
export class OptionComponent {
constructor(
@Optional() readonly group: OptgroupComponent
) {}
}
@Optional()
: 当组件或服务声明某个依赖项时,该类的构造函数会以参数的形式接收那个依赖项。 通过给这个参数加上 @Optional()
装饰器,可以告诉 Angular
,该依赖是可选的。注意:当使用 @Optional()
时,你的代码必须能正确处理 null
值。
这样没啥问题,没什么问题。
我现在有个需求想知道optgroup
里面的option
有没有,如果没有就加一个option-empty
的class,给使用者去做些其他事情。
先看下我们的optgroup
html:
<label class="optgroup-label">{{ label }}</label>
<ng-content select="my-option, ng-container"></ng-content>
按我们正常理解使用:
/** 所有定义的选择选项。 */
@ContentChildren(OptionComponent, { descendants: true }) options: QueryList<OptionComponent>;
这样就可以获取到optgroup
下所有的option
。
ngAfterContentInit() {
this.options.changes.subscribe((option) => console.log('ngAfterContentInit', option));
}
这样就好获取到option
变化了。注意:要在AfterContentInit
生命期钩子里面,不然就会报错。
翻译错误:无法为“SimOptgroupComponent”的属性“options”构造查询,因为没有定义查询选择器。
简单理解就是找不到OptionComponent
。angular
为我们提供forwardRef
来解决这个问题。这里有篇博客专门介绍它,传送门
改成下面写法就行啦,需要加上tslint报错问题。
/** 所有定义的选择选项。 */
// tslint:disable-next-line:no-use-before-declare
@ContentChildren(forwardRef(() => OptionComponent), { descendants: true }) options: QueryList<OptionComponent>;
如果你的optgroup
和option
组件不在一个文件里,angular-cli
编译也会出现警告WARNING in Circular dependency detected:
说我们有循环依赖问题,解决方案放在一个文件里即可。
ERROR in : Cannot determine the module for class BasisConfigComponent in E:/gitlab/angular/projects/my-app/src/app/feature/link/basis-config/basis-config.component.ts! Add BasisConfigComponent to the NgModule to fix it.
这是一个什么错误,看着一脸懵逼,翻译错误 无法确定BasisConfigComponent类的模块
。
虽然翻译有点直白,大概意思就是,这个组件没有在模块里面申明,又存在这个组件,打包时候就找不到组件和模块依赖关系。
引起这个原因有2个:
你不是用cli命令创建的组件,ng g c 组件名
你之前在模块里面申明这个组件,后面以为业务需要这个组件被废弃了,你取消了模块申明,但是没有删除它。
那么解决问题方案就是:如果需要使用去模块申明它,如果无用的废弃组件及时删除它。
*ngFor
使用报错问题ERROR
Error: Cannot find a differ supporting object '[object Object]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
错误翻译:无法找到类型为“object”的不同支持对象“[object object]”。NgFor只支持绑定到数组等迭代器。直接翻译很蛋疼,简单翻译NgFor只支持绑定到数组等迭代器,不支持[object object]
对象。
[object object]
对象,这是一个什么样的对象。在js
里面数据类型分为原始类型和对象类型,对象又分很多种,我们可以借助Object.prototype.toString.call(value)
来区分它们是什么。
在jq
源码有2个判断:
isPlainObject: function( obj ) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
if ( !obj || toString.call( obj ) !== "[object Object]" ) {
return false;
}
proto = getProto( obj );
// Objects with no prototype (e.g., `Object.create( null )`) are plain
if ( !proto ) {
return true;
}
// Objects with prototype are plain iff they were constructed by a global Object function
Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
},
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var fnToString = hasOwn.toString;
function toType( obj ) {
if ( obj == null ) {
return obj + "";
}
// Support: Android <=2.3 only (functionish RegExp)
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call( obj ) ] || "object" :
typeof obj;
}
// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );
[object object]
对象就是一个普通对象。
它们是谁:
// 1. 对象字面量,又有很多其他叫法,json对象,字典对象
Object.prototype.toString.call({})
// "[object Object]"
// 2. 标准对象
Object.prototype.toString.call(new Object);
// "[object Object]"
// 2. 构造函数对象
const fun = function() {};
Object.prototype.toString.call(new fun);
// "[object Object]"
数组等迭代器是什么?
遍历Array
可以采用下标循环,遍历Map
和Set
就无法使用下标。为了统一集合类型,ES6标准引入了新的iterable
类型,Array
、Map
和Set
都属于iterable
类型。具有iterable
类型的集合可以通过新的for ... of
循环来遍历。
那感情好,我们可以使用Array
、Map
和Set
啦。然并卵
使用Map
一样会报错:
ERROR
Error: Cannot find a differ supporting object '[object Map]' of type 'object'. NgFor only supports binding to Iterables such as Arrays.
意思默认只能使用Array
和Set
。
*ngFor
是angular里面一个比较重要的结构型指令,还有2个比较常用的结构型指令:*ngSwitch
和*ngIf
。
这三个指令对应大多数编程语言的:for
、switch
、if/else
。那他们作用就不用再介绍了。
我先根据angular文档,*ngFor
写法很多。
常见2种写法:
第一种写法
<li *ngFor="let item of items; index as i; trackBy: trackByFn">...</li>
第二种写法
<ng-template ngFor let-item [ngForOf]="items" let-i="index" [ngForTrackBy]="trackByFn">
<li>...</li>
</ng-template>
其实第一种写法是第二种写法的语法糖,简写。
那么先我需要遍历[object Object]
或者'[object Map]'怎么办,angular团队也想到这个问题,就在6.1.0 (2018-07-25)推出一个管道keyValue,来弥补这个缺陷。
<div *ngFor="let item of object | keyvalue">
{{item.key}}:{{item.value}}
</div>
<div *ngFor="let item of map | keyvalue">
{{item.key}}:{{item.value}}
</div>
那如果是字符串我也想遍历怎么办了:
<li *ngFor="let item of '123456'.split('')">{{item}}</li>
在vue
里面如果是一个数字可以直接遍历,这个又该如何做到了:
<li *ngFor="let item of 10 | times">{{item}}</li>
import { Pipe, PipeTransform } from '@angular/core';
import { toNumber } from '@angular/cdk/coercion';
const MAX_SAFE_INTEGER = 9007199254740991;
const MAX_ARRAY_LENGTH = 4294967295;
@Pipe({
name: 'times'
})
export class TimesPipe implements PipeTransform {
transform(value: number): number[] {
// 先把value转成数字 如果转换失败会返回0
value = toNumber(value);
// 如果小于1或者大于限制就直接返回空数组
if (value < 1 || value > MAX_SAFE_INTEGER) {
return [];
}
// 获取长度
const length = Math.min(value, MAX_ARRAY_LENGTH);
// 循环创建数组
let index = -1;
const result = [];
while (++index < length) {
result[index] = index;
}
return result;
}
}
angular指令是个好东西,借助它可以增强很多特性。
最后说一点比较重要的事情,能在
ts
把数据处理好,优先处理好,在html里面去处理,会消耗一定的性能,特别大量数据循环的时候。
Angular开发中,有时候有些错误让人一脸懵逼,不知道该如何下手,接下来我就介绍一下我在我使用angular中遇到的问题和解决方案(欢迎你留下你的问题和解决方案,让我们angular开发更轻松容易):
关于依赖注入问题
经常看有人在群里问下面这张图是什么问题,
上面问题解答是
AppComponent
依赖NameService
服务,NameService
却没有申明。解决方案去申明注册:(注意:服务注册位置决定服务作用域)
全局申明:(一般用于全局数据共享使用,如果是注册到全局,推荐第一种方式,因为它对打包会有优化)
直接在服务里面申明作用域
根模块注册到providers里
模块申明:(一般用于该模块下数据共享使用,你也可以导出给其他模块使用)
组件申明1:(一般用于该组件下数据共享使用,它会携带一个
OnDestroy
生命周期)组件申明2:(一般用于该组件下数据共享使用,它会携带一个
OnDestroy
生命周期)什么是
contentChildren
,就是<ng-content></ng-content>
的内容。为什么依赖注入需要写
private
基本很多栗子都是这样的来写依赖注入:
有人就奇怪为什么我一定要写一个
private
,可以不写么,可以,但是会报错,如果不写ts只是当 他是类参数,其实constructor(private nameService: NameService) { }
是一个语法糖。如果不写
private
:编辑器会提示:类型“AppComponent”上不存在属性“nameService”
试想一下ES6的class怎么写的:
constructor里的参数,ts看来它是构造参数,不是一个类的属性,如果要实现属性功能,你需要这样来写:
最后总结:你在ts写方法和属性时候公开可以不用写
public
关键字,但是在constructor
里写依赖注入时如果需要写成公开时候一定要写public
关键词。为什么创建angular组件模块都需要带上
CommonModule
。angular使用中一定要注意,组件,指令,管道,服务都是封装的在模块里,如果想要给其他模块里面组件使用,一定要导出。如果当前模块想要使用别人模块一定要导入。
CommonModule
里面携带angular自带组件,指令,管道等,如果你带上它不能使用*ngIf
,*ngFor
等。最后总结,记住两点,你可以轻松玩转angular模块:
exports
imports
'app-xxx' is not a known element
这是一个什么沙雕错误,翻译成中文
app-xxx
不是一个已知的元素,再说简单点就不是一个标准的HTML标签,算一个自定义标签,angular不认识它。问题找到根源了,出现这个错误有个原因:
这里是
vs code
提示错误,翻译里面3句话比较重要:一个组件只能在一个
NgModule
申明,不能重复申明,如果A和B模块都申明一个c组件,那么就会报错,提醒你写一个D模块去申明c组件,A和B模块去引用D模块。这就是传说的共享模块思路来源。这是内置的angular指令,如果你当前模块没有导入
CommonModule
,就会报错Can't bind to 'ngIf' since it isn't a known property of 'div'.
解决方案只需要导入
CommonModule
,使用其他模块组件,指令,管道也是一样的道理。Angular Language Service
提示:Can't bind to 'appCode' since it isn't a known property of 'div'
这又是一个什么沙雕错误,翻译成中文
appCode
不是一个div上面一个属性,再说简单点就不是一个标准的HTML标签标准属性,算一个自定义属性,angular不认识它。需要先去了解一下HTML attribute 与 DOM property 的对比
这里只能推测你有2个意图:
那你需要这样去操作:使用
attr.xxxx
@Input
属性。这种情况也分2种,一直是你没有申明,或者导入。
这个参照'app-xxx' is not a known element解决。
意思是你没有在组件或指令使用
@Input()
装饰器申明它,或者你属性名写错了。解决方案请正确书写和申明。