65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2017年3月1日

Apache

6分钟阅读

viewsIcon

8041

如何将“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文件定义的。

感谢阅读。

注意:如果您认为这是一篇有用的文章,请点赞并与您的朋友分享,我将非常感激。

© . All rights reserved.