Service Portal widgets MUST have perfect communication between Server Script, Client Controller, and HTML Template. This is not optional - widgets fail when these components don't talk to each other correctly.
The Three-Way Contract
Every widget requires synchronized communication:
1. Server Script Must:
-
Initialize ALL
data.*properties that HTML will reference -
Handle EVERY
input.actionthat client sends viac.server.get() -
Return data in the format the client expects
2. Client Controller Must:
-
Implement EVERY method called by
ng-clickin HTML -
Use
c.server.get({action: 'name'})for server communication -
Update
c.datawhen server responds
3. HTML Template Must:
-
Only reference
data.*properties that server provides -
Only call methods defined in client controller
-
Use correct Angular directives and bindings
Data Flow Patterns
Server → Client → HTML
// SERVER SCRIPT
(function() {
data.incidents = [];
data.loading = true;
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.setLimit(10);
gr.query();
while (gr.next()) {
data.incidents.push({
sys_id: gr.getUniqueValue(),
number: gr.getValue('number'),
short_description: gr.getValue('short_description')
});
}
data.loading = false;
})();
// CLIENT CONTROLLER
api.controller = function($scope) {
var c = this;
c.selectIncident = function(incident) {
c.selectedIncident = incident;
};
};
<!-- HTML TEMPLATE -->
<div ng-if="data.loading">Loading...</div>
<div ng-if="!data.loading">
<div ng-repeat="incident in data.incidents"
ng-click="c.selectIncident(incident)">
{{incident.number}}: {{incident.short_description}}
</div>
</div>
Client → Server (Actions)
// CLIENT CONTROLLER
c.saveIncident = function() {
c.server.get({
action: 'save_incident',
incident_data: c.formData
}).then(function(response) {
if (response.data.success) {
c.data.message = 'Saved successfully';
}
});
};
// SERVER SCRIPT
if (input && input.action === 'save_incident') {
var gr = new GlideRecord('incident');
gr.initialize();
gr.setValue('short_description', input.incident_data.short_description);
data.new_sys_id = gr.insert();
data.success = !!data.new_sys_id;
}
Validation Checklist
Before deploying a widget, verify:
Every data.property in server is used in HTML or client
Every ng-click="c.method()" has matching c.method in client
Every c.server.get({action: 'x'}) has matching if(input.action === 'x') in server
No orphaned methods or unused data properties
All data.* properties are initialized in server (even if empty)
Common Failures
Action Name Mismatch
// CLIENT - sends 'saveIncident'
c.server.get({action: 'saveIncident'});
// SERVER - expects 'save_incident' (MISMATCH!)
if (input.action === 'save_incident') { }
Method Name Mismatch
<!-- HTML - calls saveData() -->
<button ng-click="c.saveData()">Save</button>
// CLIENT - defines save() (MISMATCH!)
c.save = function() { };
Undefined Data Properties
<!-- HTML - references user.email -->
<span>{{data.user.email}}</span>
// SERVER - only sets user.name (user.email is undefined!)
data.user = { name: userName };
Angular Directives Reference
| ng-if
| Conditionally render element
| ng-show/ng-hide
| Toggle visibility (element stays in DOM)
| ng-repeat
| Iterate over array
| ng-click
| Handle click events
| ng-model
| Two-way data binding
| ng-class
| Dynamic CSS classes
| ng-disabled
| Disable form elements