将“Angular 2”功能添加到“Angular 1”应用程序中





5.00/5 (2投票s)
如何将“Angular 2”功能添加到“Angular 1”应用程序中
目前,我有一个用“Angular 1”编写的现有应用程序,并希望在“Angular 2”中实现新功能。你能给我一个计划吗?
这种情况有两种解决方案,请考虑如下:
- 方案 1:重建整个应用程序,适用于小型应用程序或预算充足的情况。这在实际软件开发中很少适用。因为我们通常会将现有功能迁移到“Angular 2”,同时实现新功能。
- 方案 2:在“Angular 2”中构建新功能,同时逐一将现有功能从“Angular 1”迁移到“Angular 2”。这种解决方案在实际工作中更适用,因为应用程序可以正常使用。
好的,“方案 2”似乎是个不错的选择。应用程序源代码在此:“https://github.com/techcoaching/MigrateToAngular2.git”,你能告诉我怎么做吗?
我们先看一下当前的应用程序。我们有
用户列表
点击“编辑”图标将用户带到编辑页面
我应该从哪里开始?
请打开您的index.html文件,并在body的末尾(在</body>
之前)添加此script
标签:
<script>
System.import('src/main').then(null, console.error.bind(console));
</script>
我们在“src/main.ts”文件中做了什么?
这是一个typescript文件,它将按如下方式加载和引导默认的angular2模块:
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import {UpgradeModule} from "@angular/upgrade/static";
import {Angular2AppModule} from "./angular2AppModule";
platformBrowserDynamic().bootstrapModule(Angular2AppModule).then((ref: any) => {
let upgrade = ref.injector.get(UpgradeModule) as UpgradeModule;
upgrade.bootstrap(document.body, ["angular1"]);
});
在此代码中,我们加载“Angular2AppModule
”默认模块。
platformBrowserDynamic().bootstrapModule(Angular2AppModule)
并在完成后继续引导默认的angular 1模块。
upgrade.bootstrap(document.body, ["angular1"])
所以这意味着“Angular 1”模块不是像以前那样直接引导的,而是通过“Angular 2”模块(名为Angular2AppModule
模块)间接引导的。
好的,我明白了Angular2模块可以作为引导angular1模块的桥梁。那么我们在Angular2AppModule模块中会做什么?
在Angular2AppModule
中,我们将注册包含“ng-view
”指令的默认页面。这是Angular 1显示相应路由内容的地方。
在Angular2AppModule
类中
@NgModule({
imports: [...],
declarations: [DefaultComponent],
bootstrap: [DefaultComponent]
})
export class Angular2AppModule { }
在此类中,我们将“DefaultComponent
”引导为应用程序的布局。
DefaultComponent
包含html
<div>
<div ng-view></div>
</div>
以及ts文件。
@Component({
selector:"default",
templateUrl:"src/defaultComponent.html"
})
export class DefaultComponent{}
在此类中,我们只声明组件,没有任何逻辑。
请注意,我们使用“default
”名称注册了DefaultComponent
。此名称将从index.html文件中使用。
我的index.html文件中已经有了ng-view,并且defaultComponent.html中也有新的ng-view。我只是担心应用程序无法正常运行?
我知道,我们在index.html中使用ng-view
作为显示页面内容的占位符。现在我们将用“<default></default>
”标签替换它。
这个“default
”标签与上面DefaultComponent
的选择器匹配。因此,在运行时,DefaultComponent
的内容将被渲染到该位置,而默认的“Angular 1”组件将被渲染到“ng-view
”(此标签位于defaultComponent.html文件中),如下图所示:
至此,您已完成了将应用程序迁移到“Angular 2”的第一步。
好的,明白了。到目前为止,我的“Angular 1”可以正常启动,但它是从“Angular 2”模块引导的。我可以在“Angular 2”中定义新指令并在我的现有“Angular 1”中使用它们吗?
我将向您展示如何将现有的“Angular 2”指令注入“Angular 1”应用程序。
我假设我们已经创建了一个名为“CategoryPreview
”的“Angular 2”指令,如下所示:
categoryPreview.html
<div>
This is content of category preview directive
</div>
以及categoryPreview.ts
@Component({
selector: "category-preview",
templateUrl: "src/security/categoryPreview.html"
})
export class CategoryPreview {
}
请将以下代码添加到“angular2AppModule.ts”中:
window.angular.module('angular1').directive
('categoryPreview', downgradeComponent({ component: CategoryPreview}));
此代码将“CategoryPreview
”从“Angular 2”转换为“Angular 1”,并将其注册为“angular1
”模块中的“categoryPreview
”。
然后,将CategoryPreview
添加到users.html模板文件中(angular 1组件):
<category-preview></category-preview>
让我们尝试构建并运行应用程序。我们在浏览器中看到如下输出:
恭喜!您已成功在“Angular 1”应用程序中使用“CategoryPreview
”(Angular 2)指令。
我如何将参数从“Angular 1”应用程序传递到“Angular 2”指令?
好的,让我们尝试公开CategoryPreview
指令的输入参数,如下所示:
export class CategoryPreview {
@Input() item: any = null;
}
并更改HTML文件。
<div *ngIf="item">
<h3>Summary of {{item.name}}</h3>
<form class="form-horizontal form-label-left ">
<div class="form-group ">
<label>Name</label>
<span>{{item.name}}</span>
</div>
<div class="form-group ">
<label>Description</label>
<span>{{item.description}}</span>
</div>
</form>
</div>
并在Angular2AppModule
类中更新如下:
window.angular.module('angular1').directive
('categoryPreview', downgradeComponent({ component: CategoryPreview, inputs:["item"]}));
在此代码中,我们在downgradeComponent
调用中添加了‘inputs:["item"]
’。
在users.html中,将item参数传递给“CategoryPreview
”指令。
<category-preview [item]="{name:'name', description:'description'}"></category-preview>
再次构建并运行应用程序,我们在浏览器中收到输出:
这意味着,从“Angular 1”应用程序,我们可以将参数传递到“Angular 2”指令。
明白了!那么“Angular 2”服务呢?我能从“Angular 1”应用程序调用它吗?
当然可以,我们采用与发布指令相同的方式。让我们尝试将GroupService
(Angular 2)发布到“Angular 1”应用程序。
GroupService.ts的内容如下:
export class GroupService {
private static groups: Array<any> = [
{ id: 1, name: "category 1", description: "Description"},
{ id: 2, name: "category 2", description: "Description 2"},
];
public getAllGroups(): Array<any> {
return GroupService.groups;
}
}
这是一个纯粹的typescript (ts) 类,使用硬编码的数据。
与“categoryPreview
”指令类似。将此行代码添加到“angular2AppModule.ts”中:
window.angular.module('angular1').factory("groupService", downgradeInjectable(GroupService));
这意味着我们将GroupService
(angular 2)转换为“Angular 1”,并将其注册为工厂。
然后,我们可以在“Angular 1”中的其他组件中注入它,如下所示:
function UsersCtrl($scope, $location, groupService) {
$scope.group = groupService.getAllGroups()[0];
}
UsersCtrl.$inject = ["$scope", "$location", "groupService"];
我们还需要在users.html中更新如下:
<category-preview [item]="{name:group.name, description:group.description}"></category-preview>
让我们再次构建并运行应用程序,输出如下:
我看到我们可以为Angular 2指令从Angular 1应用程序传递输入参数。那么@Output参数呢?
对于输出参数,它与@Input
参数几乎相同。
现在,我们将在CategoryPreview
指令中添加一个新的删除按钮。
<button (click)="onButtonClicked()">Delete</button>
并如下修改CategoryPreview.ts:
export class CategoryPreview {
@Input() item: any = null;
@Output() onDeleteClicked: EventEmitter<any> = new EventEmitter<any>();
public onButtonClicked() {
this.onDeleteClicked.emit(this.item);
}
}
在这种情况下,我们添加了一个新的“onButtonClicked
”函数,当用户点击CategoryPreview
中的“Delete”按钮时将调用该函数。该函数将通过onDeleteClicked.emit
调用通知侦听器。
我们还需要在“angular2AppModule.ts”中进行更改:
window.angular.module('angular1').directive('categoryPreview', downgradeComponent
({ component: CategoryPreview, inputs: ["item"], outputs: ["onDeleteClicked"] }));
在此代码中,我们为downgradeComponent
调用添加了outputs
选项。
并且还在users.html中更新:
<category-preview (on-delete-clicked)="onAngular1DeleteClicked($event)"
[item]="{name:group.name, description:group.description}"></category-preview>
请注意,“CategoryPreview
”中的“onDeleteClicked
”事件在Angular 1中将被转换为kebab-case
。这意味着“OnDeleteClicked
”在“Angular 1”中将被更改为“on-delete-clicked
”。
我们将此事件映射到Angular 1组件中的“onAngular1DeleteClicked
”。此方法是从“usersCtrl.js”文件填充的。让我们将此函数添加到UsersCtrl
中,如下所示:
$scope.onAngular1DeleteClicked = function () {
console.log("onDeleteClicked")
}
现在,构建并刷新浏览器。
我已按照您的说明进行操作,但我的代码无法运行。我使用requirejs在我的应用程序中引导angular模块。我该怎么办?
传统上,所有控制器、指令和angular应用程序都通过直接包含在index.html文件中来加载,如下所示:
<script src="src/app.js"></script>
<script src="src/userPreview.js"></script>
<script src="src/userService.js"></script>
<script src="src/usersCtrl.js"></script>
<script src="src/userDetailCtrl.js"></script>
有些应用程序可以使用angular与requirejs进行懒加载依赖项。
因此,当我们运行“System.import
”(导入并运行angular 2代码)时,一些angular 1资源尚未准备好。
在这种情况下,我们进行了一些改进,如下所示:
将System.import
包装在boostrapAngular2App
函数中。
<script>
function boostrapAngular2App(){
System.import('src/main').then(null, console.error.bind(console));
}
</script>
在“angular 1”应用程序的main.js文件中,将其从
require.config({
/*setting for require js*/
});
require([
/*List of resource need to be loaded before bootstrapping angular 1 module*/
,], function() {
angular.bootstrap(document, ['angular1']);
});
to
require.config({
/*setting for require js*/
});
require([
/*List of resource need to be loaded before bootstrapping angular 1 module*/
,], function() {
/*angular.bootstrap(document, ['angular1']);*/
if(boostrapAngular2App){
boostrapAngular2App();
}
});
在上面的代码中,我们将不会引导默认的Angular 1模块,而是引导Angular 2代码。
请注意,“boostrapAngular2App
”是从index.html文件定义的。
感谢阅读。
注意:如果您认为这是一篇有用的文章,请点赞并与您的朋友分享,我将非常感激。