Skip to content

Commit

Permalink
AngularLoader: Support 'settingsFactory' callbacks in angular modules.
Browse files Browse the repository at this point in the history
This allows Angular modules with complex/expensive data to provide it with a callback,
which will only be invoked if the module is actively loaded on the page.

Normally, module settings are calculated on every page request, even if they are not needed.
  • Loading branch information
colemanw committed Oct 11, 2020
1 parent 5a7e2ac commit c4ade9a
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 2 deletions.
8 changes: 6 additions & 2 deletions Civi/Angular/AngularLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,13 @@ public function load() {
}

$res->addSettingsFactory(function () use (&$moduleNames, $angular, $res, $assetParams) {
// Merge static settings with the results of settingsFactory functions
$settingsByModule = $angular->getResources($moduleNames, 'settings', 'settings');
foreach ($angular->getResources($moduleNames, 'settingsFactory', 'settingsFactory') as $moduleName => $factory) {
$settingsByModule[$moduleName] = array_merge($settingsByModule[$moduleName] ?? [], $factory());
}
// TODO optimization; client-side caching
$result = array_merge($angular->getResources($moduleNames, 'settings', 'settings'), [
return array_merge($settingsByModule, [
'resourceUrls' => \CRM_Extension_System::singleton()->getMapper()->getActiveModuleUrls(),
'angular' => [
'modules' => $moduleNames,
Expand All @@ -125,7 +130,6 @@ public function load() {
'bundleUrl' => \Civi::service('asset_builder')->getUrl('angular-modules.json', $assetParams),
],
]);
return $result;
});

$res->addScriptFile('civicrm', 'bower_components/angular/angular.min.js', 100, $this->getRegion(), FALSE);
Expand Down
1 change: 1 addition & 0 deletions Civi/Angular/Manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@ public function getResources($moduleNames, $resType, $refType) {
break;

case 'settings':
case 'settingsFactory':
case 'requires':
if (!empty($module[$resType])) {
$result[$moduleName] = $module[$resType];
Expand Down
91 changes: 91 additions & 0 deletions tests/phpunit/Civi/Angular/LoaderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
| |
| This work is published under the GNU AGPLv3 license with some |
| permitted exceptions and without any warranty. For full license |
| and copyright information, see https://civicrm.org/licensing |
+--------------------------------------------------------------------+
*/

namespace Civi\Angular;

/**
* Test the Angular loader.
*/
class LoaderTest extends \CiviUnitTestCase {

public static $dummy_setting_count = 0;
public static $dummy_callback_count = 0;

public function setUp() {
parent::setUp();
$this->hookClass->setHook('civicrm_angularModules', [$this, 'hook_angularModules']);
self::$dummy_setting_count = 0;
self::$dummy_callback_count = 0;
$this->createLoggedInUser();
}

public function factoryScenarios() {
return [
['dummy1', 2, 1],
['dummy2', 2, 0],
['dummy3', 2, 2],
];
}

/**
* Tests that AngularLoader only conditionally loads settings via factory functions for in-use modules.
* Our dummy settings callback functions keep a count of the number of times they have been called.
*
* @dataProvider factoryScenarios
* @param $module
* @param $expectedSettingCount
* @param $expectedCallbackCount
*/
public function testSettingFactory($module, $expectedSettingCount, $expectedCallbackCount) {
(new \Civi\Angular\AngularLoader())
->setModules([$module])
->useApp()
->load();

// Run factory callbacks
$factorySettings = \Civi::resources()->getSettings();

// Dummy1 module's factory setting should be set if it is loaded directly or required by dummy3
$this->assertTrue(($expectedCallbackCount > 0) === isset($factorySettings['dummy1']['dummy_setting_factory']));
// Dummy3 module's factory setting should be set if it is loaded directly
$this->assertTrue(($expectedCallbackCount > 1) === isset($factorySettings['dummy3']['dummy_setting_factory']));

// Assert the callback functions ran the expected number of times
$this->assertEquals($expectedSettingCount, self::$dummy_setting_count);
$this->assertEquals($expectedCallbackCount, self::$dummy_callback_count);
}

public function hook_angularModules(&$modules) {
$modules['dummy1'] = [
'ext' => 'civicrm',
'settings' => $this->getDummySetting(),
'settingsFactory' => [self::class, 'getDummySettingFactory'],
];
$modules['dummy2'] = [
'ext' => 'civicrm',
'settings' => $this->getDummySetting(),
];
$modules['dummy3'] = [
'ext' => 'civicrm',
'settingsFactory' => [self::class, 'getDummySettingFactory'],
'requires' => ['dummy1'],
];
}

public function getDummySetting() {
return ['dummy_setting' => self::$dummy_setting_count++];
}

public static function getDummySettingFactory() {
return ['dummy_setting_factory' => self::$dummy_callback_count++];
}

}

0 comments on commit c4ade9a

Please sign in to comment.