To ensure the highest quality and maintainability for Clarity components, we adhere to the following guidelines when coding.
Some of our code hasn't been updated to these standards yet, but these do take precedence. Copy-pasting existing code without updating it to follow these guidelines will result in a stalled PR.
- Components or directives should be named without a suffix like
datagrid-row.ts
, and be placed in the main module folder like/datagrid/datagrid-row.ts
. - Providers, factories, services should be named with a suffix like
wizard-navigation.service.ts
.- If the component has a single provider, the provider file can be placed directly in the same folder as the component itself.
- In the case of more complex components (like the Datagrid or the Wizard) where many providers are needed for the various subcomponents to communicate, the providers should be placed inside of a
providers
subfolder of the module folder like/wizard/providers/wizard-navigation.service.ts
.
- Interfaces and abstract classes used as interfaces should be named with a suffix like
datagrid-state.interface.ts
* If the component has a single provider, the provider file can be placed directly in the same folder as the component itself- In the case of more complex components (like the Datagrid or the Wizard) where many providers are needed for the various subcomponents to communicate, the providers should be placed inside of a
providers
subfolder of the module folder like/wizard/providers/wizard-navigation.service.ts
.
- In the case of more complex components (like the Datagrid or the Wizard) where many providers are needed for the various subcomponents to communicate, the providers should be placed inside of a
- Interfaces and abstract classes used as interfaces should be named with a suffix like
datagrid-state.interface.ts
.- If the component has one or two interfacees/abstract classes the file can be placed in the same folder as the component itself
- In the case of more complex components (w/ 3 or more) interfaces/abstract classes should be placed inside an interface subfolder like
/datagrid/interfaces/string-filter.interface.ts
- Enums should be named with a suffix like
sort-order.enum.ts
.- If the component has one or two enums the file can be placed in the same folder as the component
- In the case of more complex components(w/ 3 or more enums) they should be placed in an
enum
subfolder like/datagrid/enums/sort-order.enum.ts
- Prefer full words over abbreviations
- Anything public and meant to be used by consumers should have the
clr
prefix.
- SCSS variables should use the
clr
prefix and follow thekebab-case
convention:$clr-some-variable
. - Coming soon...
Constants internal to Clarity are named following the SCREAMING_SNAKE_CASE
convention.
Constants exported as part of our public API follow the same convention but need to be prefixed: CLR_SCREAMING_SNAKE_CASE
.
The enum itself is prefixed by Clr
since it is public, and each of the values are SCREAMING_SNAKE_CASE
because they are constant. For instance:
ClrDirection.DOWN;
Any class exported publicly needs to be prefixed with Clr
. For instance:
class ClrBourgeoisie {}
class ClrProletariat {}
Properties and methods on Typescript classes should not be prefixed or suffixed. In particular, private properties should not be prefixed with _
.
public open: boolean;
public next() { ... }
private subscription: Subscription;
There are 2 exceptions to this rule, and they're the only cases where we allow the use of an _
prefix:
- A private property is hidden behind a getter (and maybe a setter) that has the same name:
private _open: boolean;
public get open(): boolean;
public set open(value: boolean);
private _openChange: Subject<boolean>;
public get openChange(): Observable<boolean>;
- A property or method needs to be public for integration purposes but should not be considered public by the consumer:
@ContentChildren(Something) _children: QueryList<Something>;
_internalMethodCalledFromAnotherClass()
All Angular selectors are prefixed with clr
, then follow the typical snake_case
or camelCase
depending on whether they're element or attribute selectors:
clr-component
[clrDirective]
When larger components have sub-components, the sub-component should use these selectors:
clr-component-subcomponent
[clrComponentSubdirective]
This is slightly verbose, but will prevent conflicts between components. This is a case where abbreviations are acceptable to reduce verbosity.
To avoid conflict with non-Clarity components, inputs and outputs of our directives and components should be prefixed with clr
:
[clrInput]
(clrInputChange)
(clrOutput)
Remember that for [(clrProperty)]
two-way binding to work, you need to expose both a [clrProperty]
input and
a (clrPropertyChange)
output.
Components and directives are exported Typescript classes, so they respect the earlier convention and are prefixed with Clr
. Contrary to the recommendation on the Angular style guide, they do not have any Component
or Directive
suffix, in order to allow us to switch from one to the other without breaking changes. For instance:
ClrWizard (component)
ClrIfOpen (structural directive)
ClrLoading (directive)
Providers that are exported for consumers should be prefixed with Clr
and suffixed with Service
. This avoids conflict with similar component names. If they are only used internally they are not prefixed but should still be suffixed with Service
:
ClrSomeExternalService
ClrIfOpenService
AnInternalService
AnotherInternalService
Prefixed with Clr
as usual, and suffixed with Module
:
ClrWizardModule
ClrFormsModule
ClarityModule
is the exception, since the actual "Clarity" name is already there.
- Custom texts should almost always be received through content projection, not inputs. This will allow for icons, images, links, etc.
- Offer an output for every internal state change, whether it's triggered by a user action or an internal operation (expand/collapse, on/off, option selected, etc).
- Offer two-way binding rather than simple outputs when possible.
- Use structural directives to receive
TemplateRef
instances, in order to offer a less verbose API than<ng-template>
to the consumer. - Have related components (like parent / child) communicate by default through services.
- Never use explicitly named template reference variables as part of the API, to query them through
@ContentChild
. Simply receive structural directives or inputs. - Never make HTTP calls, and never impose formatting constraints on their backend API.
- In cases where a directive cannot be used unless it is a child of another directive, it is usually best to throw a new Error in the constructor to alert developers that it is not supported. Example
Finally, and this is harder to put in black-or-white terms, keep the API as simple and natural as possible, even if it means more work on Clarity's side. Implementing multiple directives and components and making them communicate through services will lead to a much more pleasant integration than forcing your consumer to pass data manually from one to the other. Here are a couple examples of what to do and not to do (taken from real-life carousel components):
- Don't: Using an
<ng-template>
and passing an$implicit
value to it, that the consumer will have to tie to a template local variable:
<clr-carousel [clrItems]="items">
<ng-template let-item>
<h1>{{item}}</h1>
</ng-template>
</clr-carousel>
- Don't: Receive an input on the "wrong" component, and force the user to handle the communication between this component and the other, linked ones (in this case through a template reference variable):
<clr-carousel #carousel [clrItems]="items">
<h1 *ngFor="let item of carousel.visible">
{{item}}
</h1>
</clr-carousel>
- Do: Introduce a new structural directive that behaves like an
*ngFor
, but will automatically communicate with the parent component through services:
<clr-carousel>
<h1 *clrVisible="let item of items">
{{item}}
</h1>
</clr-carousel>
- If the template is just a few lines, it should go inline in the component's metadata with
template
. Otherwise, it should be in a separate HTML file usingtemplateUrl
. - Make sure to use CSS classnames with your components. We avoid using component tags in our CSS selectors. This often means adding a
host: { '[class.your-classname-here]': 'true' }
to your component definition. And in the CSS you would then use the classname defined on the host in your CSS selectors instead of the component element tag. So instead of usingclr-signpost { .. }
to apply styles to a component, you would use.signpost { .. }
. - Bindings that are always true should be declared directly in the component's metadata using the
host
option. Other bindings should be on the corresponding property or getter, using the@HostBinding
annotation. - Host listeners should always use the
@HostListener
annotation. - Methods used as event listeners in a component or directive should be named in a way that describe what they do, not when they trigger. For instance, if a method is used when the user clicks a button to toggle the selection, the method name should not be
onClick()
, it should betoggleSelection()
. Here are more typical method names that are commonly used but that you should avoid:onClick()
,onHover()
,onFocus()
,onScroll()
, etc.
- Make sure your component or feature is fully accessible.
- Never hardcode any text in your template or in the component. Any text visible to the user should be received from the consumer.
- Avoid using native elements API as much as possible. Use simple bindings or the renderer to achieve the same effect.
- An
@Output
should not fire when we receive a new@Input
value from the app. - Any button in your template should have
type="button"
, in case the component is used inside of a form.
- We expect extensive unit test coverage of any code submitted.
- Unit tests should not duplicate coverage. In particular, avoid multiple unit tests failing for the same error.
- Do not test several components at the same time, unless you're specifically writing an integration test. You should manually declare parent components that might be needed for your test as providers, or even better mock them. In other words, make sure the only components your are declaring in your testing module are the currently tested component and the test host.
- Split your unit tests in Typescript API, Template API and View (details coming soon).
- Remove unused HTML wrappers that might be left from previous prototypes of your code.
- Avoid styling elements themselves, use CSS classes. This also means applying CSS styles to the classes on your Angular components. For example, don't use
clr-signpost > .btn
as a selector in your CSS. Use.signpost > .btn
instead. - Keep your CSS selectors as flat as possible, try not to exceed 2 levels (e.g.
.parent .child
). - Do not hardcode colors, use the SCSS variables provided in
color/_variables.color.scss
. - Only use
rem
and%
sizes. The only exception is1px
borders that should remain 1px even on larger font sizes. - All of your SCSS styles should be wrapped in an
@include exports('XXX.clarity') { ... }
, to avoid producing duplicates in the deliverable CSS. - The baseline should always be respected to ensure proper vertical rhythm.