AngularJS应用/控制器的可靠封装:ServiceNOW开发者技术咨询
作为一名常年和ServiceNOW打交道的开发者,我完全理解你现在的处境——一边是新的AngularJS服务门户,另一边是还在靠Jelly撑着的处理者界面,要在老界面里嵌入AngularJS代码还得保证封装性,确实得花点心思。下面是我实践下来靠谱的几种封装方式:
1. 用IIFE隔离AngularJS模块作用域
ServiceNOW的全局环境里可能混着各种第三方脚本和OOTB代码,直接定义全局Angular模块很容易触发命名冲突。用**立即执行函数表达式(IIFE)**把你的模块和控制器包起来,能彻底隔离作用域:
// 放在UI Script里,按需设置Global属性为true/false (function(window, angular, undefined) { 'use strict'; // 定义专属模块,避免和OOTB模块重名(比如别用"sn"开头的通用前缀) angular.module('sn-custom-workspace-widget', []) .controller('CustomWorkspaceCtrl', ['$scope', '$http', function($scope, $http) { $scope.widgetData = {}; // 示例:调用ServiceNOW REST API获取数据 $http.get('/api/now/table/incident', { params: { sysparm_limit: 5, sysparm_query: 'active=true' } }).then(function(response) { $scope.widgetData.incidents = response.data.result; }); }]); })(window, angular);
2. 通过UI Script管理依赖加载
ServiceNOW的UI Script支持设置加载顺序,能确保依赖先于你的代码加载:
- 如果处理者界面已经复用了服务门户的AngularJS核心库,无需重复加载;
- 把自定义UI Script的
Global属性设为false,只在需要的界面加载,减少全局负担; - 可以用
Script Includes封装通用的ServiceNOW业务逻辑,在Angular控制器里通过$http或GlideAjax调用(注意GlideAjax的异步处理要适配Angular的Promise)。
3. 用UI Macro做Jelly与Angular的桥梁
UI Macro是嵌入Angular容器的最佳载体,还能轻松传递ServiceNOW上下文数据到Angular:
<?xml version="1.0" encoding="utf-8" ?> <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null"> <!-- 用ng-bind避免Jelly的${}和Angular的{{}}语法冲突 --> <div ng-app="sn-custom-workspace-widget" ng-controller="CustomWorkspaceCtrl"> <div class="widget-header"> <h3>我的待处理事件</h3> <p>当前用户:<span ng-bind="currentUser"></span></p> </div> <ul class="incident-list"> <li ng-repeat="incident in widgetData.incidents"> <span ng-bind="incident.number"></span> - <span ng-bind="incident.short_description"></span> </li> </ul> </div> <!-- 通过Javascript把Jelly变量传递给Angular作用域 --> <script> angular.element(document.querySelector('[ng-controller="CustomWorkspaceCtrl"]')).scope().$apply(function(scope) { scope.currentUser = '${gs.getUser().getDisplayName()}'; }); </script> </j:jelly>
4. 封装可复用的Angular服务
把重复的API调用、数据格式化逻辑封装成Angular服务,能大幅提升代码复用性和可维护性:
(function(window, angular, undefined) { 'use strict'; angular.module('sn-custom-workspace-widget') .service('SNAPIService', ['$http', function($http) { // 获取活跃事件列表 this.getActiveIncidents = function(limit = 10) { return $http.get('/api/now/table/incident', { params: { sysparm_limit: limit, sysparm_query: 'active=true' } }).then(res => res.data.result); }; // 其他通用业务方法... }]); })(window, angular);
然后在控制器里注入使用:
.controller('CustomWorkspaceCtrl', ['$scope', 'SNAPIService', function($scope, SNAPIService) { SNAPIService.getActiveIncidents(5).then(incidents => { $scope.widgetData.incidents = incidents; }); }]);
5. 处理生命周期与内存泄漏
Jelly界面没有服务门户小部件的生命周期钩子,Angular作用域不会自动销毁,所以要手动清理:
- 监听页面卸载事件,销毁Angular作用域;
- 避免绑定全局事件,若绑定则记得在作用域销毁时解绑。
示例:
.controller('CustomWorkspaceCtrl', ['$scope', '$window', function($scope, $window) { // 页面卸载时销毁作用域,避免内存泄漏 const unloadListener = $window.addEventListener('beforeunload', () => { $scope.$destroy(); $window.removeEventListener('beforeunload', unloadListener); }); }]);
6. 规避语法冲突
Jelly和Angular都用大括号做插值,要注意:
- 优先用
ng-bind代替{{}}; - 若必须用
{{}},在Jelly里用<j:out value="{{xxx}}" escape="false" />输出,避免被Jelly解析。
内容的提问来源于stack exchange,提问作者Robert Polickoski
相关产品推荐
相关产品推荐

