Loading...
Area: Episerver B2B Commerce

Extending or overriding existing Angular functionality in Classic

Recommended reading 

This articles provides some recommended best practices for extending or overriding existing Angular functionality for key functional areas of the site (Products, Categories, etc).

  • Do not edit stock SDK angular files.
  • When copying the files, try to use new folders, not stock SDK folders.
  • Be sure to read the section about Custom Javascript and Bundling below.

Directives/Client Side

Do not edit the files in the Responsive theme folder, if you need to make changes, then create a copy in the Themes/{ClientWebsite}/ folder structure.

Overriding directives

There should not be very many scenarios when you need to override a directive. However, if you run into a situation where you need to change a directive, it is recommended that you create a new directive and override the view to use that new directive in place of the base directive.

Overriding angular controllers

All angular controllers in base code are marked with the export attribute and can be inherited from. To override a controller, create your new controller in the /Scripts/Custom folder so it loads after the base controller. Then use the extends keyword to extend the controller.

class CustomHeaderController extends HeaderController { }

Then you can just override methods in the base controller by creating a method with the same signature as the base method. You can call the base method from within your overridden method by calling:

super.methodname().

When you add your controller to your angular module you need to make sure you add it using the same key the base controller used. By registering the CustomHeaderController to the HeaderController key we are essentially overriding the base implementation of the Header Controller.

angular.module("insite").controller("HeaderController", CustomHeaderController)

If you need to call an overridden method that is called inside the init() base controller that is using a new injected parameter on the child controller, then you will need to override the base init() method as well and create a new initChild() method that is called inside the child controller's constructor and call that overridden method that is in the child controller from that new initChild() method. This is because the newly injected child constructor object will not be available until after the base constructor executes.

Passing Arbitrary Data to and from Handlers

Every rest call can accept a dictionary of data in the properties field. This is defined on BaseModel so any methods which take objects derived from it will expose a properties field in typescript. For example to add extra data to CartLineModel, do the following

cartLine.properties = <any>{};
cartLine.properties["extradata"] = "test";
cartLine.properties["extradata2"] = "test2";

Other rest calls which pass query string data also implicitly accept a properties dictionary. For example to pass extra data to the /products method, add a query string value in the form:

&properties={'extradata':'test','extradata2':'test2' }

These property values will automatically pass through to the handlers, in the Properties dictionary on the ParameterBase derived service parameter object.

For both methods above the Properties collection is populated after the mapper has been run, but before the handler chain is run.

It is also be possible to send custom data on the query string as any arbitrary parameter and pick these up in a custom mapper, but this requires custom code and possibly overrides, which should be avoided whenever possible in order to ease upgrades.

In addition, many of the result objects that have child objects (usually in collections) will automatically have the Properties dictionary on each child object populated with the properties data from corresponding service result child object. For example, WishListModel has a child collection of WishListLineModel objects with a Properties collection on each one and these will automatically get the properties dictionaries from the WishListLineResult objects created by the handler.

To return arbitrary data from a rest call, put data in the Properties collection with Properties.Add and it will be automatically returned to the client. To pass back complex objects, use the AddObjectToResultProperties method in HandlerBase to get the correct serialization:

this.AddObjectToResultProperties(result, "extradata", new { Something = "test", SomethinglElse = "test2" });

On the client, deserialize these complex objects with JSON.parse:

var extradata = JSON.parse(data.properties["extradata"]);

Overriding angular IHttpPromise service methods

In some cases you may want to modify a method in an Episerver client side service class, in order to minimize code duplication that would result from modifying all of the callers to the service. Many of these service methods make calls to the server and return either IPromise or IHttpPromise return types. The IPromise methods do not pose any problem when overriding, but in the case if IHttpPromise, it is tricky to inject in code that modifies the result of the $http.get call and then still return the IHttpPromise type. The following technique can be used to accomplish this:

If a base Episerver method looks like this

method(): ng.IHttpPromise<SomeModel> {
	return this.$http.get(this.uri);
}

the override method in custom code can take this form:

method(): ng.IHttpPromise<SomeModel> {
return this.$http.get(this.uri).
success((result: SomeModel) => {
// modify result as needed here.
});
}

Custom Javascript and Bundling

A mechanism is provided to include client javscript in the minified bundles generated by the base website BundleConfig.cs class without requiring modification of C# code.

In order to add custom typescript/javascript to the main global.min.js application bundle, script files should be added in the .Web project within the /Scripts/Custom folder or subfolders. The bundle global.min.js is automatically generated with all of the core javascript first and then any js in /Scripts/Custom, followed by any javascript in subfolders of /Scripts/Custom. This allows adding of new class override files without any code changes.

In the case where the order of the custom scripts must be controlled, the file /Scripts/Custom/custom.xml can be edited to include priority scripts. Scripts listed in this file will be include after the default scripts but before the automatically added scripts.

The other common use case is to modify the list of 3rd party js files that are included in the libraries.min.js file. The list of files included in this bundle is defined by the contents of the xml file /Scripts/Libaries/libraries.bundle.xml. Add files starting from the /Libraries root or other subfolders of /Scripts/, for example

<file>/Libraries/angular/1.3.15/angular.js</file>

Edit this file to remove or add new 3rd party javascript code. These paths will not be themed.

Autobundling gotcha

Loose javascript files in the /Scripts/App subfolders and /Scripts/Custom folder will get bundled even if they are not in the project. So if you copy or rename a .ts file, be sure that you remove any remnants (hidden .js/.map) in the folder as these will still be used by the website.

Do you find this information helpful? Please log in to provide feedback.

Last updated: Dec 11, 2020

Recommended reading