在 2 Angular 个组件和服务之间重用属性和方法的正确逻辑是什么?
What would be the right logic to reuse properties and methods between 2 Angular Components and Services?
我有 2 个 Angular 组件 ProductComponent 和 ImportListComponent
我为产品创建了一个模型,如下所示:
import { Image } from './image.model';
import { Category } from './category.model';
export class Product {
constructor(
public id: number,
public name: string,
public priceRange: string,
public description: string,
public imageUrl: string,
public initialPrice?: number,
public importListPrice?: number,
public category?: Category,
public inStock?: boolean,
public amountInStock?: number,
public sku?: string,
public images?: Image[]
) {}
}
我还为 ImportList 创建了一个模型,它扩展了产品模型,如下所示:
import { Product } from './product.model';
import { Image } from './image.model';
import { Category } from './category.model';
export class ImportList extends Product {
constructor(
id: number,
name: string,
priceRange: string,
description: string,
imageUrl: string,
initialPrice?: number,
importListPrice?: number,
category?: Category,
inStock?: boolean,
amountInStock?: number,
sku?: string,
images?: Image[]
) {
super(id, name, priceRange, description, imageUrl, initialPrice, importListPrice);
}
}
Product 和 ImportList 的区别在于价格、描述和名称可以在添加到 ImportList 并推送到商店之前更改。
我们不希望更改的属性影响或改变原始产品。因此需要创建单独的 ImportList。系统上的每个用户都可以有不同的 ImportList。但他们可以访问相同的基础产品。
从 API 获取新产品的服务和逻辑如下:
@Injectable()
export class ProductService {
private productsUrl = 'api/products'; // URL to web api
constructor(private http: Http) {}
getProducts(): Promise<Product[]> {
return this.http.get(this.productsUrl)
.toPromise()
.then(response => response.json().data as Product[])
.catch(this.handleError);
}
}
如何使模型中的属性 class 可重用?
以及如何使 ProductService 中的方法可由 ImportListService 重用,以及 ProductComponent 中的方法可由 ImportListComponent 重用 class?
因为当我尝试在 ImportListComponent 中使用 ImportList 模型时只是为了进行这样的测试:
export class ImportListComponent {
importList : ImportList[
new ImportList()
];
}
我收到一个令人困惑且不太清楚的错误 Type 'new () => ImportList' cannot be used as an index type import ImportList
。即使已经导入了 ImportList。如何解决这个问题?
由于 类 没有自定义逻辑或方法,我将只定义接口,并为导入列表提供一个新的 属性 类型的产品,以便它可以存储其原始产品的副本
export interface Product {
id: number,
name: string,
priceRange: string,
description: string,
imageUrl: string,
initialPrice?: number,
importListPrice?: number,
category?: Category,
inStock?: boolean,
amountInStock?: number,
sku?: string,
images?: Image[]
}
export interface ImportList {
id:number,
name:string.
priceRange: string,
description: string,
imageUrl: string
initialPrice: number;
importListPrice: number;
originalProduct?: Product;
}
使用 Angular4 HttpClient,您可以键入检查正在读取的 JSON:
http.get<ImportList>('/apipath/here').subscribe(data => {
this.productList = data;
});
假设 JSON 与 ImportList 接口足够接近以正确键入。如果需要事先修改,只需使用 get() 而不是类型化的 get
好的 - 界面速成班,如果你决定走那条路。
importList : ImportList[ new
关于你如何开始它的两个问题。您不能实例化一个数组并在同一个声明中键入它。你必须做
importList : ImportList[] = [ ...declaration here... ]
另一个问题是尝试使用我提到的 'new' 关键字。只需按字面意思构建对象。如果您想在一个声明中完成所有操作,它看起来像:
importList : ImportList[] = [
{ id: 1,
name: 'somename',
priceRange: '10$-15$,
description: 'something',
imageurl: 'someurl',
originalProduct: this.variableNameOfOtherProduct,
},
{ id: 2,
name: 'othername'. ... and so on and so on...
}
]
以上实际上也不适用于我编写接口的方式,因为它仍然需要初始价格和进口价格,这两个参数都未标记为可选。打字稿会给出错误“无法设置类型 - 导入列表 - 属性 'initialPrice',并且 'importPrice' 丢失”或类似的错误。
因此您也可以在界面中将这两个参数设置为可选,或者确保在创建项目时提供它们。如果您查看许多 angular 核心接口,它们通常将几乎每个参数都设置为可选。这允许非常灵活地根据需要逐步添加到您的对象,但仍然具有强大的类型检查。我在我的界面中做同样的事情,我经常将每个参数设置为可选的,除了其中一个或两个,这是唯一标识它所需的绝对最小值。在您的情况下,这可能是 id。因此,每个其他 属性 名称都可以在其名称后加上一个问号,除了那个,并且您只需要 需要 在创建它时指定一个 ID,并且可以稍后添加其他属性。必须逐字地为每个对象键入 属性 名称而不是只包含它们的值似乎很麻烦,但这会使代码无限多 readable/maintainable。
需要注意的一件事是,如果您尝试在尚未实例化的类型的对象上引用 属性,typescript 将会混淆。因此,如果您创建了:
[
{ id: 85, name 'somename' }
...
]
作为 this.importList 数组中的第一项,(这是假设除 id 和 name 之外的所有内容都是可选的),然后您尝试通过编写 [=26 添加 priceRange 属性 =]
this.importList[0].priceRange = '10$ - 15$';
它会感到困惑,因为 属性 尚未创建。在这些情况下,您只需要使用 javascript 'bracket notation' 为现有对象添加新属性。它的完成方式类似于引用数组值的方式,但使用 属性 的字符串名称而不是内部的数字。
简单的例子,如果你有一些对象
this.bob : BobInterface = { id: 1 } ;
并想添加一个名为 'age' 的 属性,即使年龄 属性 可能在 Bob 接口中指定,但尚未创建,所以您去不了
this.bob.age = 50;
你必须写
this.bob['age'] = 50;
这会同时实例化和分配 属性。
因此,在您的情况下,如果您需要在声明列表后向列表添加其他属性,您可以编写类似
的内容
this.importList[0]['priceRange']= '10$ - 15$';
第一组括号指定我们指的是importList的哪个数组项,第二组括号是对应ImportList项的属性。
像那样声明 属性 之后,你可以写
this.importList[0].priceRange;
像往常一样,打字稿会知道该怎么做。
SO - 回到正点...要将现有产品添加到 ImportList,您只需将它们的标识符引用为原始产品 属性 的值。
如果您在没有它们的情况下初始化列表,您可以稍后通过以下方式添加它们:
this.importList[0]['originalProduct']= referenceToProductObject;
但是,如果您在最初声明数组值时将其全部包括在内,则只需编写
[
....
{... priceRange: '10$ - 15$', originalProduct: this.originalProductList[25] .... }
....
]
在上面的示例中,我假设 originalProduct 在索引 # 25 处的不同数组中,但它可以在任何地方,只要它是 Product 类型的对象即可。
我希望这是有道理的。一开始可能有点棘手,因为 classes 倾向于提供更多结构 - 您通常知道 class 的所有属性都已实例化,因此您通常可以在没有括号符号的情况下引用它们。但是,像这样松散地使用接口可以成为处理对象的更强大的方式 - 获得极大的灵活性 和 强类型检查。两全其美。
编辑:另外,可能是学习如何在 typescript 中创建 'Map' 的好时机。您可以像这样在组件中声明一个对象
importedProductsMap : { [index:string] : ImportList } = {};
以上可以说是非常厉害了。它创建一个对象并告诉打字稿该对象将仅包含 ImportList 对象,并且这些 ImportList 对象将全部由某个标识符引用,该标识符可以是任何字符串。对象中可能有 100 万个或零个。没关系。只要是通过字符串命名的 ImportList 对象,都可以包含。这为数组提供了一个很好的替代方案,因为它们可以像数组一样容纳许多项目,但是您可以直接使用它们的 id 或名称(或者任何真正的,只要它对每个项目都是唯一的)而不是它们在中的数字位置来引用单个项目一个数组,可以很随意。
因此,如果您处于 for 循环中,您通常会在其中将 ImportList 对象添加到具有
的数组中
this.importList.push(aNewImportListObject);
您可以将其添加到上面定义的 importedProductsMap 中:
this.importedProductsMap[aNewImportListObject.name] = aNewImportListObject;
这会将 ImportList 对象添加到您的地图,使用它自己的名称作为其标识符(在这种情况下,名称必须是唯一的,但仅作为示例,我们可以忘记这一点)。然后你可以引用它
console.log(this.importedProductsMap.someName.id);
并且它会记录该对象的 ID 属性。如果您有一个经常引用和调整的对象列表,那么使用映射比使用数组要容易得多。使您不必不断地遍历列表并找到匹配项。
我不知道为什么要写这些,但我真诚地希望它能有所帮助。
关于所有这一切的疯狂之处......它实际上并没有对编译器生成的结果代码产生任何影响。接口编译为空……它们基本上消失了,它们只是在那里帮助您巧妙地与对象交互,编写更结构化的代码,并充分利用静态类型检查。
我有 2 个 Angular 组件 ProductComponent 和 ImportListComponent 我为产品创建了一个模型,如下所示:
import { Image } from './image.model';
import { Category } from './category.model';
export class Product {
constructor(
public id: number,
public name: string,
public priceRange: string,
public description: string,
public imageUrl: string,
public initialPrice?: number,
public importListPrice?: number,
public category?: Category,
public inStock?: boolean,
public amountInStock?: number,
public sku?: string,
public images?: Image[]
) {}
}
我还为 ImportList 创建了一个模型,它扩展了产品模型,如下所示:
import { Product } from './product.model';
import { Image } from './image.model';
import { Category } from './category.model';
export class ImportList extends Product {
constructor(
id: number,
name: string,
priceRange: string,
description: string,
imageUrl: string,
initialPrice?: number,
importListPrice?: number,
category?: Category,
inStock?: boolean,
amountInStock?: number,
sku?: string,
images?: Image[]
) {
super(id, name, priceRange, description, imageUrl, initialPrice, importListPrice);
}
}
Product 和 ImportList 的区别在于价格、描述和名称可以在添加到 ImportList 并推送到商店之前更改。 我们不希望更改的属性影响或改变原始产品。因此需要创建单独的 ImportList。系统上的每个用户都可以有不同的 ImportList。但他们可以访问相同的基础产品。 从 API 获取新产品的服务和逻辑如下:
@Injectable()
export class ProductService {
private productsUrl = 'api/products'; // URL to web api
constructor(private http: Http) {}
getProducts(): Promise<Product[]> {
return this.http.get(this.productsUrl)
.toPromise()
.then(response => response.json().data as Product[])
.catch(this.handleError);
}
}
如何使模型中的属性 class 可重用? 以及如何使 ProductService 中的方法可由 ImportListService 重用,以及 ProductComponent 中的方法可由 ImportListComponent 重用 class?
因为当我尝试在 ImportListComponent 中使用 ImportList 模型时只是为了进行这样的测试:
export class ImportListComponent {
importList : ImportList[
new ImportList()
];
}
我收到一个令人困惑且不太清楚的错误 Type 'new () => ImportList' cannot be used as an index type import ImportList
。即使已经导入了 ImportList。如何解决这个问题?
由于 类 没有自定义逻辑或方法,我将只定义接口,并为导入列表提供一个新的 属性 类型的产品,以便它可以存储其原始产品的副本
export interface Product {
id: number,
name: string,
priceRange: string,
description: string,
imageUrl: string,
initialPrice?: number,
importListPrice?: number,
category?: Category,
inStock?: boolean,
amountInStock?: number,
sku?: string,
images?: Image[]
}
export interface ImportList {
id:number,
name:string.
priceRange: string,
description: string,
imageUrl: string
initialPrice: number;
importListPrice: number;
originalProduct?: Product;
}
使用 Angular4 HttpClient,您可以键入检查正在读取的 JSON:
http.get<ImportList>('/apipath/here').subscribe(data => {
this.productList = data;
});
假设 JSON 与 ImportList 接口足够接近以正确键入。如果需要事先修改,只需使用 get() 而不是类型化的 get
好的 - 界面速成班,如果你决定走那条路。
importList : ImportList[ new
关于你如何开始它的两个问题。您不能实例化一个数组并在同一个声明中键入它。你必须做
importList : ImportList[] = [ ...declaration here... ]
另一个问题是尝试使用我提到的 'new' 关键字。只需按字面意思构建对象。如果您想在一个声明中完成所有操作,它看起来像:
importList : ImportList[] = [
{ id: 1,
name: 'somename',
priceRange: '10$-15$,
description: 'something',
imageurl: 'someurl',
originalProduct: this.variableNameOfOtherProduct,
},
{ id: 2,
name: 'othername'. ... and so on and so on...
}
]
以上实际上也不适用于我编写接口的方式,因为它仍然需要初始价格和进口价格,这两个参数都未标记为可选。打字稿会给出错误“无法设置类型 - 导入列表 - 属性 'initialPrice',并且 'importPrice' 丢失”或类似的错误。
因此您也可以在界面中将这两个参数设置为可选,或者确保在创建项目时提供它们。如果您查看许多 angular 核心接口,它们通常将几乎每个参数都设置为可选。这允许非常灵活地根据需要逐步添加到您的对象,但仍然具有强大的类型检查。我在我的界面中做同样的事情,我经常将每个参数设置为可选的,除了其中一个或两个,这是唯一标识它所需的绝对最小值。在您的情况下,这可能是 id。因此,每个其他 属性 名称都可以在其名称后加上一个问号,除了那个,并且您只需要 需要 在创建它时指定一个 ID,并且可以稍后添加其他属性。必须逐字地为每个对象键入 属性 名称而不是只包含它们的值似乎很麻烦,但这会使代码无限多 readable/maintainable。
需要注意的一件事是,如果您尝试在尚未实例化的类型的对象上引用 属性,typescript 将会混淆。因此,如果您创建了:
[
{ id: 85, name 'somename' }
...
]
作为 this.importList 数组中的第一项,(这是假设除 id 和 name 之外的所有内容都是可选的),然后您尝试通过编写 [=26 添加 priceRange 属性 =]
this.importList[0].priceRange = '10$ - 15$';
它会感到困惑,因为 属性 尚未创建。在这些情况下,您只需要使用 javascript 'bracket notation' 为现有对象添加新属性。它的完成方式类似于引用数组值的方式,但使用 属性 的字符串名称而不是内部的数字。
简单的例子,如果你有一些对象
this.bob : BobInterface = { id: 1 } ;
并想添加一个名为 'age' 的 属性,即使年龄 属性 可能在 Bob 接口中指定,但尚未创建,所以您去不了
this.bob.age = 50;
你必须写
this.bob['age'] = 50;
这会同时实例化和分配 属性。
因此,在您的情况下,如果您需要在声明列表后向列表添加其他属性,您可以编写类似
的内容 this.importList[0]['priceRange']= '10$ - 15$';
第一组括号指定我们指的是importList的哪个数组项,第二组括号是对应ImportList项的属性。
像那样声明 属性 之后,你可以写
this.importList[0].priceRange;
像往常一样,打字稿会知道该怎么做。
SO - 回到正点...要将现有产品添加到 ImportList,您只需将它们的标识符引用为原始产品 属性 的值。
如果您在没有它们的情况下初始化列表,您可以稍后通过以下方式添加它们:
this.importList[0]['originalProduct']= referenceToProductObject;
但是,如果您在最初声明数组值时将其全部包括在内,则只需编写
[
....
{... priceRange: '10$ - 15$', originalProduct: this.originalProductList[25] .... }
....
]
在上面的示例中,我假设 originalProduct 在索引 # 25 处的不同数组中,但它可以在任何地方,只要它是 Product 类型的对象即可。
我希望这是有道理的。一开始可能有点棘手,因为 classes 倾向于提供更多结构 - 您通常知道 class 的所有属性都已实例化,因此您通常可以在没有括号符号的情况下引用它们。但是,像这样松散地使用接口可以成为处理对象的更强大的方式 - 获得极大的灵活性 和 强类型检查。两全其美。
编辑:另外,可能是学习如何在 typescript 中创建 'Map' 的好时机。您可以像这样在组件中声明一个对象
importedProductsMap : { [index:string] : ImportList } = {};
以上可以说是非常厉害了。它创建一个对象并告诉打字稿该对象将仅包含 ImportList 对象,并且这些 ImportList 对象将全部由某个标识符引用,该标识符可以是任何字符串。对象中可能有 100 万个或零个。没关系。只要是通过字符串命名的 ImportList 对象,都可以包含。这为数组提供了一个很好的替代方案,因为它们可以像数组一样容纳许多项目,但是您可以直接使用它们的 id 或名称(或者任何真正的,只要它对每个项目都是唯一的)而不是它们在中的数字位置来引用单个项目一个数组,可以很随意。
因此,如果您处于 for 循环中,您通常会在其中将 ImportList 对象添加到具有
的数组中 this.importList.push(aNewImportListObject);
您可以将其添加到上面定义的 importedProductsMap 中:
this.importedProductsMap[aNewImportListObject.name] = aNewImportListObject;
这会将 ImportList 对象添加到您的地图,使用它自己的名称作为其标识符(在这种情况下,名称必须是唯一的,但仅作为示例,我们可以忘记这一点)。然后你可以引用它
console.log(this.importedProductsMap.someName.id);
并且它会记录该对象的 ID 属性。如果您有一个经常引用和调整的对象列表,那么使用映射比使用数组要容易得多。使您不必不断地遍历列表并找到匹配项。
我不知道为什么要写这些,但我真诚地希望它能有所帮助。
关于所有这一切的疯狂之处......它实际上并没有对编译器生成的结果代码产生任何影响。接口编译为空……它们基本上消失了,它们只是在那里帮助您巧妙地与对象交互,编写更结构化的代码,并充分利用静态类型检查。