AngularJS: Loader¶
What happens when a user visits a CiviCRM-Angular page? For example, let's consider this URL:
https://example.org/civicrm/a/#/caseType
Broadly speaking, two things happen:
- (Server-side) CiviCRM processes the request for
civicrm/a
. It displays a web-page with all your Angular modules -- such asngRoute
,crmAttachment
,crmCaseType
,crmUi
, and so on. - (Client-side) AngularJS processes the HTML/JS/CSS. It finds that the
module
crmCaseType
includes a route for#/caseType
and loads the appropriate HTML template.
The client-side behavior is well-defined by Angular ngRoute. We'll explore the server-side in greater depth because that is unique to the CiviCRM-Angular integration.
Caution: Discusses new/experimental interfaces
Some elements of this document have been around since CiviCRM v4.6 and should remain well-supported. Other elements are new circa v4.7.21 and are still flagged experimental.
Library of modules¶
The CiviCRM-Angular loader needs a list of available AngularJS modules.
This list depends on your system configuration (e.g. which CiviCRM
extensions are enabled). To view the current list, run cv
:
$ cv ang:module:list
For a full list, try passing --user=[username].
+-------------------+-------------+------------------------------------------------------------------------------------+
| name | basePages | requires |
+-------------------+-------------+------------------------------------------------------------------------------------+
| angularFileUpload | civicrm/a | |
| bw.paging | (as-needed) | |
| civicase | (as-needed) | crmUi, crmUtil, ngRoute, angularFileUpload, bw.paging, crmRouteBinder, crmResource |
| crmApp | civicrm/a | |
| crmAttachment | civicrm/a | angularFileUpload, crmResource |
| crmAutosave | civicrm/a | crmUtil |
| crmCaseType | civicrm/a | ngRoute, ui.utils, crmUi, unsavedChanges, crmUtil, crmResource |
...snip...
Tip: More options for cv ang:module:list
Use --columns
to specify which columns to display. Ex:
$ cv ang:module:list --columns=name,ext,extDir
Use --out
to specify an output format. Ex:
$ cv ang:module:list --out=json-pretty
Use --user
to specify a login user. This may reveal permission-dependent modules. Ex:
$ cv ang:module:list --user=admin
Under-the-hood, this library of modules is built via hook_civicrm_angularModules, e.g.
/**
* Implements hook_civicrm_angularModules.
*/
function foobar_civicrm_angularModules(&$angularModules) {
$angularModules['myBigAngularModule'] = array(
'ext' => 'org.example.foobar',
'basePages' => array('civicrm/a'),
'requires' => array('crmUi', 'crmUtils', 'ngRoute'),
'js' => array('ang/myBigangularModule/*.js'),
'css' => array('ang/myBigangularModule/*.css'),
'partials' => array('ang/myBigangularModule'),
);
}
Tip: Generating skeletal code with civix
In practice, one usually doesn't need to implement this hook directly.
Instead, generate skeletal code with civix
. For details, see
AngularJS: Quick Start.
Default base-page¶
CiviCRM includes a "base-page" named civicrm/a
. By default, this page
includes the core AngularJS files as well as all the modules in the library.
The page is generated with a PHP class, AngularLoader
, using logic like this:
$loader = new \Civi\Angular\AngularLoader();
$loader->setModules(array('crmApp'));
$loader->setPageName('civicrm/a');
$loader->load();
The load()
function determines the necessary JS/CSS/HTML/JSON resources
and loads them on the page. This will include:
- Any AngularJS modules explicitly listed in
setModules(...)
. (Ex:crmApp
) - Any AngularJS modules with matching
basePages
. (Ex: The valuecivicrm/a
is specified by bothsetPageName(...)
[above] andmyBigAngularModule
[above].) - Any AngularJS modules transitively required by the above.
What makes civicrm/a
special?
When declaring a module, the property basePages
will default to
array('civicrm/a')
. In other words, if you don't specify otherwise,
all modules are loaded on civicrm/a
.
How does load()
output the <script>
tag(s)?
load()
uses CRM_Core_Resources
to register JS/CSS files.
Other base-pages¶
Loading all Angular modules on one page poses a trade-off. On one hand, it warms up the caches and enables quick transitions between screens. On the other hand, the page could become bloated with modules that aren't actually used.
If this is a concern, then you can create new base-pages which are tuned to a more specific use-case. For example, suppose we want to create a page which only has the CiviCase administrative UI.
-
Create a skeletal CiviCRM page:
$ civix generate:page CaseAdmin civicrm/caseadmin
-
Edit the new PHP file and update the
run()
function. Create anAngularLoader
to load all the JS/CSS files:public function run() { $loader = new \Civi\Angular\AngularLoader(); $loader->setModules(array('crmCaseType')); $loader->setPageName('civicrm/caseadmin'); $loader->useApp(array( 'defaultRoute' => '/caseType', )); $loader->load(); parent::run(); }
Initializing AngularJS
In this example, we called
useApp()
. This automatically boots AngularJS on the client side by outputting the HTML snippet<div ng-app="crmApp">
. This is suitable for a simple, standalone base-page.If you want to control the AngularJS boot, then omit the call to
useApp()
. Instead, read the AngularJS Bootstrap Guide and define your own boot code. -
Flush the cache
$ cv flush
-
Visit the new page. (The command below will lookup the URL in a Drupal 7 site.)
$ cv url 'civicrm/caseadmin/#/caseType' "http://dmaster.l/civicrm/caseadmin/#/caseType"
Embedding Angular in other contexts
In the example, we created a new, standalone page. But you can use
AngularLoader
in other ways -- eg, you might listen for
hook_civicrm_pageRun and embed Angular onto a pre-existing,
non-Angular page. Some extensions do this -- though it remains to be
seen whether this is wise.