Qlik Community

Qlik Design Blog

All about product and Qlik solutions: scripting, data modeling, visual design, extensions, best practices, etc.

Employee
Employee

Angularjs and Capabilities API - Memory management and "qv-collapsed-listbox-delegated-open" error

In my previous post i explained how to use my template and build a website with Angular js and the Qlik Sense's Capabilities API.

Creating a website with Angular and the Capabilities API

When we build controllers with Angular and creating bindings among objects, on navigation, Angular is doing a very good job on managing the controllers, directives, services etc, but it does not handle properly the bindings from Qlik Sense. Thus, how ever many controllers and pages you may have, the objects that you have called with app.getObject(), are still there. That is why when you make a selection in one object and you navigate to another page, you get the "Error: [$compile:ctreq] Controller 'qv-collapsed-listbox-delegated-open', required by directive 'ngClass', can't be found!" error.

tutorial_2.png

Even though this does not affect the user experience, its still an error. Furthermore, if you have a large website with many object, as we usually do in our Demo and Best Practices Team, then this becomes a problem because there is memory allocation on each of the objects that were created with app.getObject().

Trying to solve it was a trivial process, since there is no documentation on how to destroy the objects. What I have done is, after the app.getObject() put a then(model), put the models into an array of objects and manage them on every page change. So I destroy all of them before I call $location with model.close() and then assign the new ones after the route has completed loading the new template and controller.

Let me explain the code change. I assume that you have already read on how to use the template Creating a website with Angular and the Capabilities API

  • Define the object array holder in our app.js

var me = {

  obj: {

       qlik: null,

       app: null,

       angularApp: null,

       model: [],

  }

};

  • In the html, put the usual code instead of the directive previously used. In dashboard.html replace L30 with this

<div class="qvobject" data-qvid="a5e0f12c-38f5-4da9-8f3f-0e4566b28398" id="a5e0f12c-38f5-4da9-8f3f-0e4566b28398"></div>

  • In our controller dashboard.js, replace L23 with the object array that we will call for binding,

me.objects = ['a5e0f12c-38f5-4da9-8f3f-0e4566b28398'];

  • In the same file, under the events, add the new function

me.getObjects = function () {

  api.getObjects(me.objects);

}

  • Now lets add the two functions in the API service. We need one function to create the objects and another one to destroy them

  me.getObjects = function (obj) {

       var deferred = $q.defer(),

       promises = [];

       angular.forEach(obj, function(value, key) {

            app.obj.app.getObject(value, value).then(function(model){

                 app.obj.model.push(model);

                 deferred.resolve(value);

            });

            promises.push(deferred.promise);

       });

       return $q.all(promises);

  };

  me.destroyObjects = function () {

       var deferred = $q.defer();

       var promises = [];

       if (app.obj.model.length >= 1) {

            angular.forEach(app.obj.model, function(value, key) {

                 value.close();

                 deferred.resolve();

                 promises.push(deferred.promise);

            });

            app.obj.model = [];

            return $q.all(promises);

       } else {

            deferred.resolve();

            return deferred.promise;

       }

  };

  • Finally, on every page change, call destroy objects and then move to the new page

  api.destroyObjects().then(function(){

       $location.url('/' + page);

  });

Make sure to check the latest code on

Git: https://github.com/yianni-ververis/capabilities-api-angular-template

Qlik Branch: http://branch.qlik.com/#/project/56b4a40140a985c431a64b08

2 Comments
Employee
Employee

nice

0 Likes
208 Views
boonhaw_tan
New Contributor III

Hi Yianni,

Thank you for sharing.

I built a mashup with Angularjs framework and encountered the same error as mentioned above after make few selection in objects and navigate to few pages.


I tried to follow the method explained above but still struggle how could i add destroy object codes before navigate to new pages.

Would appreciate if you could help to take a look on the scripts below and advise how can i can included the destroy objects feature into script below before navigate to new pages.

//qlik app

var app;

function getQlikApp () {

  //return qlik.openApp( "4bf04442-aa89-43ff-870a-917c86c92990", config )

  return qlik.openApp( "TRMY_UI.qvf", config )

}

//callbacks -- inserted here --

// Defining Module

var mashupApp = angular.module( "mashupApp", ['ngRoute', 'vs-repeat']);

//var App = angular.module( "App", ['vs-repeat']);

// Defining Routes

mashupApp.config( function ( $routeProvider ) {

  $routeProvider

  .when('/intro', {

  templateUrl: 'intro.html',

  controller: 'IntroCtrl'

  })

  .when('/dashboard', {

  templateUrl: 'dashboard.html',

  controller: 'DashboardCtrl'

  })

  .when('/kpidashboard', {

  templateUrl: 'kpidashboard.html',

  controller: 'KPIDashboardCtrl'

  })

  .when('/sales', {

  templateUrl: 'sales.html',

  controller: 'SalesCtrl'

  })

  .when('/salestrend', {

  templateUrl: 'salestrend.html',

  controller: 'SalesTrendCtrl'

  })

  .when('/product', {

  templateUrl: 'product.html',

  controller: 'ProductCtrl'

  })

  .when('/checkcount', {

  templateUrl: 'checkcount.html',

  controller: 'ChcekCountCtrl'

  })

  .when('/coveranalysis', {

  templateUrl: 'coveranalysis.html',

  controller: 'CoverAnalysisCtrl'

  })

  .when('/marketbasket', {

  templateUrl: 'marketbasket.html',

  controller: 'MarketBasketCtrl'

  })

  .when('/whatif', {

  templateUrl: 'whatif.html',

  controller: 'WhatIfCtrl'

  })

  .otherwise( {

  redirectTo: '/intro'

  });

});

function getGlobalV() {

  var global = qlik.getGlobal(config);

  global.getAuthenticatedUser(function(reply){

  var vStr = reply.qReturn;

  var vEnd = vStr.length-1;

  var vBegin = vStr.indexOf("UserId")+7;

  var vUser = vStr.substr(vBegin, vEnd);

  $('.div-user').html(vUser);

  });

}

function getQlikFilters() {

  //Filter Pane Objects

  app.getObject('fp_1','BZHaeQ'); //Year filter pane

  app.getObject('fp_2','gCpUmjQ'); //Quater filter pane

  app.getObject('fp_3','DzKaJ'); //Month filter pane

  app.getObject('fp_4','Wpqc'); //Date filter pane

  app.getObject('fp_5','uWkBGj'); //Calender pane

  app.getObject('fp_6','KkfNgL'); //Region Filter Panel

  app.getObject('fp_7','AFmBNa'); //Store Name Filter Panel

  app.getObject('fp_8','pQftSV'); //Major Filter Panel

  app.getObject('fp_9','pBxtW'); //Menu Group Filter Panel

  app.getObject('fp_10','mRzenW'); //Product Filter Panel

  app.getObject('fp_11','tnGt'); //Transaction Filter Panel

  //Current Selections

  app.getObject('CurrentSelections','CurrentSelections');

}

//controllers

mashupApp.controller( "IntroCtrl", ['$scope', function ( $scope ) {

  //$scope.$destroy();

  if ( !app ) {

  app = getQlikApp();

  getGlobalV();

  getQlikFilters();

  }

  app.getObject('qv1_1','amsC'); //last load label

}]);

mashupApp.controller( "DashboardCtrl", ['$scope', function ( $scope ) {

  if ( !app ) {

  app = getQlikApp();

  getGlobalV();

  getQlikFilters();

  }

  app.getObject('qv2_1','fypPU'); //Total Sales KPI

  app.getObject('qv2_2','ehFuBa'); //Budget KPI

  app.getObject('qv2_3','AKLeDrV'); //Variance KPI

  app.getObject('qv2_4','VNqZeV'); //Cylic Group

  app.getObject('qv2_5','pKmfpJY'); //Overall Top Store By Sales

  app.getObject('qv2_6','SbyDsU'); //Map Object

  app.getObject('qv2_7','fhBsVm'); //Slider

  app.getObject('qv2_8','LSaNu'); //Top Store By Sales

  app.getObject('qv2_9','FRcvYb'); //Bottom Store By Sales

}]);

Thanks,

Boon Haw

0 Likes
208 Views