Smoothly integrate Qubit Experiences on Angular websites.
Wrap or inject page components using qubit-angular/wrapper and change their rendering behaviour from within Experiences to provide segment targeting, personalisation and A/B testing.
Install from npm.
npm install --save qubit-angular
The package is compatible with Angular versions 2 to 6.
To expose a component for use in Experiences, wrap the relevant site components with <qubit-angular> component.
First import the QubitAngularModule in each module where you will be using the <qubit-angular> component.
import { QubitAngularModule } from 'qubit-angular/wrapper';
@NgModule({
  declarations: [],
  imports: [ QubitAngularModule ],
  providers: [],
  bootstrap: [ AppComponent ]
})
export class AppModule {}Now you can use the <qubit-angular> component. Specify the id attribute for Qubit experiences to reference when taking over. Optionally specify the data attribute to pass some data to the experience, could be a product, user, or any other data you think might be relevant for this experience.
<div>
  <qubit-angular id="header" [data]="trip">
    <app-header></app-header>
  </qubit-angular>
</div>Alternatively, it's possible to add the <qubit-angular> component without wrapping existing content. By default they won't show anything, but could then be used by Qubit experiences to inject new content.
<div>
  <app-content></app-content>
  <qubit-angular id="campaign"></qubit-angular>
</div>After the site has been setup with at least one wrapper component, we can move to integrating from Qubit Experiences by using the qubit-angular/experience module.
First initiate the library in the experience activation by passing in the experience meta information.
module.exports = function experienceActivation (options, cb) {
  const experience = require('qubit-angular/experience')(options)
}Qubit Angular uses a concept of wrapper ownership. This means that only a single experience can control the contents of a given wrapper id at any given time. This removes conflicts between experiences attempting to modify the same component.
In order to take ownership of a wrapper during the experience activation phase, use the experience.register function.
module.exports = function experienceActivation (options, cb) {
  const experience = require('qubit-angular/experience')(options)
  const release = experience.register(['header'], (slots) => {
    options.state.set('slots', slots)
    cb()
  })
  return {
    // important to release the ownership of the wrappers
    // so that other experiences on other virtual pageviews
    // can claim them.
    remove: release
  }
}And in the experience execution, we can now render alternative content into the registered slots:
module.exports = function experienceExecution (options) {
  const { NewHeader } = require('./utils')
  const slots = options.state.get('slots')
  slots.render('header', NewHeader)
  // unrender, if you only wanted to render the new content temporarily
  slots.unrender('header', NewHeader)
  return {
    remove: slots.release
  }
}Here's the NewHeader component implementation, which is kept in utils.js. It's not an angular component, but a simple JavaScript class with constructor render method and some lifecycle hooks.
- constructor- gets passed a DOM element reference- elwhich it can use to render whatever content in. It also gets originalEl with original content that it can use to read information from. Finally,- dataif data was passed to the wrapper component.
- render- only called once, right after instantiating the component, it should use the DOM element passed in the constructor to render content and attach event listeners.
- onChanges- called when bound data changes.
- doCheck- if a nested data attribute changes and not the whole object,- onChangeswill not detect that change and so you can use- doCheckhook to detect changes manually
- onDestroy- called once when component is unmounted from the DOM, use it to perform cleanup - clear timers, remove event listeners, etc.
the onChanges, doCheck and onDestroy hooks are equivalents of ngOnChanges, ngDoCheck and ngOnDestroy hooks documented in the official Angular docs.
module.exports.NewHeader = class NewHeader {
  constructor (el, originalEl, data) {
    this.el = el
    this.originalEl = originalEl
    this.data = data
  }
  render () {
    return this.el.innerHTML = 'NEW HEADER'
  }
  onChanges () {
    // if the data passed in changed
  }
  doCheck () {
    // onChanges will not always detect changes automatically
    // use this to manually check if this.data changed, and rerender
    // e.g.
    if (this.title != this.data.title) {
      this.title = this.data.title;
      this.render();
    }
  }
  onDestroy () {
    // cleanup if necessary, e.g. unbind event listeners
  }
}By default, the <qubit-angular> transparently renders all of the children components using <ng-content>. But when experience claims the wrapper, it hides the <ng-content> and reveals a <div #outlet> - a DOM element that is passed by reference to an experience. On a route change, or state change, if the component containing <qubit-angular> is removed, the experience gets a chance to cleanup via an onDestroy lifecycle hook and the ownership of the wrapper is released.
Set __qubit.angular.debug = true to see additional logging output about the state and lifecycles of the wrapper components.
The implementation of the component and module is over here:
The implementation of the experience registration hook is here:
To run this locally and see how it works, use the included demo project.
1. git clone git@github.com:QubitProducts/qubit-angular.git
2. cd qubit-angular/demo
3. npm install
4. npm start
5. open http://localhost:4200
You should see a green themed website with some purple elements being controlled by the wrappers. See demo/src/experiences/index.ts to see how the wrappers are used to render alternative content.
Run the following in console to simulate what happens when Qubit smartserve is off or on:
window.unloadSmartserve()
window.loadSmartserve()
To release a new version of the package, run
npm run release