GithubHelp home page GithubHelp logo

angularjs-styleguide's Introduction

AngularJS styleguide (ES2015)

Up-to-date with AngularJS 1.6 best practices. Architecture, file structure, components, one-way dataflow, lifecycle hooks.


Want an example structure as reference? Check out my component based architecture 1.5 app.


A sensible styleguide for teams by @toddmotto

This architecture and styleguide has been rewritten from the ground up for ES2015, the changes in AngularJS 1.5+ for future-upgrading your application to Angular. This guide includes new best practices for one-way dataflow, event delegation, component architecture and component routing.

You can find the old styleguide here, and the reasoning behind the new one here.

Join the Ultimate AngularJS learning experience to fully master beginner and advanced AngularJS features to build real-world apps that are fast, and scale.

Table of Contents

  1. Modular architecture
    1. Theory
    2. Root module
    3. Component module
    4. Common module
    5. Low-level modules
    6. File naming conventions
    7. Scalable file structure
  2. Components
    1. Theory
    2. Supported properties
    3. Controllers
    4. One-way dataflow and Events
    5. Stateful Components
    6. Stateless Components
    7. Routed Components
  3. Directives
    1. Theory
    2. Recommended properties
    3. Constants or Classes
  4. Services
    1. Theory
    2. Classes for Service
  5. Styles
  6. ES2015 and Tooling
  7. State management
  8. Resources
  9. Documentation
  10. Contributing

Modular architecture

Each module in an Angular app is a module component. A module component is the root definition for that module that encapsulates the logic, templates, routing and child components.

Module theory

The design in the modules maps directly to our folder structure, which keeps things maintainable and predictable. We should ideally have three high-level modules: root, component and common. The root module defines the base module that bootstraps our app, and the corresponding template. We then import our component and common modules into the root module to include our dependencies. The component and common modules then require lower-level component modules, which contain our components, controllers, services, directives, filters and tests for each reusable feature.

Back to top

Root module

A root module begins with a root component that defines the base element for the entire application, with a routing outlet defined, example shown using ui-view from ui-router.

// app.component.js
export const AppComponent = {
  template: `
    <header>
        Hello world
    </header>
    <div>
        <div ui-view></div>
    </div>
    <footer>
        Copyright MyApp 2016.
    </footer>
  `
};

A root module is then created, with AppComponent imported and registered with .component('app', AppComponent). Further imports for submodules (component and common modules) are made to include all components relevant for the application. You'll notice styles are also being imported here, we'll come onto this in later chapters in this guide.

// app.module.js
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { AppComponent } from './app.component';
import { ComponentsModule } from './components/components.module';
import { CommonModule } from './common/common.module';
import './app.scss';

export const AppModule = angular
  .module('app', [
    ComponentsModule,
    CommonModule,
    uiRouter
  ])
  .component('app', AppComponent)
  .name;

Back to top

Component module

A Component module is the container reference for all reusable components. See above how we import ComponentsModule and inject them into the Root module, this gives us a single place to import all components for the app. These modules we require are decoupled from all other modules and thus can be moved into any other application with ease.

// components/components.module.js
import angular from 'angular';
import { CalendarModule } from './calendar/calendar.module';
import { EventsModule } from './events/events.module';

export const ComponentsModule = angular
  .module('app.components', [
    CalendarModule,
    EventsModule
  ])
  .name;

Back to top

Common module

The Common module is the container reference for all application specific components, that we don't want to use in another application. This can be things like layout, navigation and footers. See above how we import CommonModule and inject them into the Root module, this gives us a single place to import all common components for the app.

// common/common.module.js
import angular from 'angular';
import { NavModule } from './nav/nav.module';
import { FooterModule } from './footer/footer.module';

export const CommonModule = angular
  .module('app.common', [
    NavModule,
    FooterModule
  ])
  .name;

Back to top

Low-level modules

Low-level modules are individual component modules that contain the logic for each feature block. These will each define a module, to be imported to a higher-level module, such as a component or common module, an example below. Always remember to add the .name suffix to each export when creating a new module, not when referencing one. You'll noticed routing definitions also exist here, we'll come onto this in later chapters in this guide.

// calendar/calendar.module.js
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { CalendarComponent } from './calendar.component';
import './calendar.scss';

export const CalendarModule = angular
  .module('calendar', [
    uiRouter
  ])
  .component('calendar', CalendarComponent)
  .config(($stateProvider, $urlRouterProvider) => {
    'ngInject';
    $stateProvider
      .state('calendar', {
        url: '/calendar',
        component: 'calendar'
      });
    $urlRouterProvider.otherwise('/');
  })
  .name;

Back to top

File naming conventions

Keep it simple and lowercase, use the component name, e.g. calendar.*.js*, calendar-grid.*.js - with the name of the type of file in the middle. Use *.module.js for the module definition file, as it keeps it verbose and consistent with Angular.

calendar.module.js
calendar.component.js
calendar.service.js
calendar.directive.js
calendar.filter.js
calendar.spec.js
calendar.html
calendar.scss

Back to top

Scalable file structure

File structure is extremely important, this describes a scalable and predictable structure. An example file structure to illustrate a modular component architecture.

├── app/
│   ├── components/
│   │  ├── calendar/
│   │  │  ├── calendar.module.js
│   │  │  ├── calendar.component.js
│   │  │  ├── calendar.service.js
│   │  │  ├── calendar.spec.js
│   │  │  ├── calendar.html
│   │  │  ├── calendar.scss
│   │  │  └── calendar-grid/
│   │  │     ├── calendar-grid.module.js
│   │  │     ├── calendar-grid.component.js
│   │  │     ├── calendar-grid.directive.js
│   │  │     ├── calendar-grid.filter.js
│   │  │     ├── calendar-grid.spec.js
│   │  │     ├── calendar-grid.html
│   │  │     └── calendar-grid.scss
│   │  ├── events/
│   │  │  ├── events.module.js
│   │  │  ├── events.component.js
│   │  │  ├── events.directive.js
│   │  │  ├── events.service.js
│   │  │  ├── events.spec.js
│   │  │  ├── events.html
│   │  │  ├── events.scss
│   │  │  └── events-signup/
│   │  │     ├── events-signup.module.js
│   │  │     ├── events-signup.component.js
│   │  │     ├── events-signup.service.js
│   │  │     ├── events-signup.spec.js
│   │  │     ├── events-signup.html
│   │  │     └── events-signup.scss
│   │  └── components.module.js
│   ├── common/
│   │  ├── nav/
│   │  │     ├── nav.module.js
│   │  │     ├── nav.component.js
│   │  │     ├── nav.service.js
│   │  │     ├── nav.spec.js
│   │  │     ├── nav.html
│   │  │     └── nav.scss
│   │  ├── footer/
│   │  │     ├── footer.module.js
│   │  │     ├── footer.component.js
│   │  │     ├── footer.service.js
│   │  │     ├── footer.spec.js
│   │  │     ├── footer.html
│   │  │     └── footer.scss
│   │  └── common.module.js
│   ├── app.module.js
│   ├── app.component.js
│   └── app.scss
└── index.html

The high level folder structure simply contains index.html and app/, a directory in which all our root, component, common and low-level modules live along with the markup and styles for each component.

Back to top

Components

Component theory

Components are essentially templates with a controller. They are not Directives, nor should you replace Directives with Components, unless you are upgrading "template Directives" with controllers, which are best suited as a component. Components also contain bindings that define inputs and outputs for data and events, lifecycle hooks and the ability to use one-way data flow and event Objects to get data back up to a parent component. These are the new defacto standard in AngularJS 1.5 and above. Everything template and controller driven that we create will likely be a component, which may be a stateful, stateless or routed component. You can think of a "component" as a complete piece of code, not just the .component() definition Object. Let's explore some best practices and advisories for components, then dive into how you should be structuring them via stateful, stateless and routed component concepts.

Back to top

Supported properties

These are the supported properties for .component() that you can/should use:

Property Support
bindings Yes, use '@', '<', '&' only
controller Yes
controllerAs Yes, default is $ctrl
require Yes (new Object syntax)
template Yes
templateUrl Yes
transclude Yes

Back to top

Controllers

Controllers should only be used alongside components, never anywhere else. If you feel you need a controller, what you really need is likely a stateless component to manage that particular piece of behaviour.

Here are some advisories for using Class for controllers:

  • Drop the name "Controller", i.e. use controller: class TodoComponent {...} to aid future Angular migration
  • Always use the constructor for dependency injection purposes
  • Use ng-annotate's 'ngInject'; syntax for $inject annotations
  • If you need to access the lexical scope, use arrow functions
  • Alternatively to arrow functions, let ctrl = this; is also acceptable and may make more sense depending on the use case
  • Bind all public functions directly to the Class
  • Make use of the appropriate lifecycle hooks, $onInit, $onChanges, $postLink and $onDestroy
    • Note: $onChanges is called before $onInit, see resources section for articles detailing this in more depth
  • Use require alongside $onInit to reference any inherited logic
  • Do not override the default $ctrl alias for the controllerAs syntax, therefore do not use controllerAs anywhere

Back to top

One-way dataflow and Events

One-way dataflow was introduced in AngularJS 1.5, and redefines component communication.

Here are some advisories for using one-way dataflow:

  • In components that receive data, always use one-way databinding syntax '<'
  • Do not use '=' two-way databinding syntax anymore, anywhere
  • Components that have bindings should use $onChanges to clone the one-way binding data to break Objects passing by reference and updating the parent data
  • Use $event as a function argument in the parent method (see stateful example below $ctrl.addTodo($event))
  • Pass an $event: {} Object back up from a stateless component (see stateless example below this.onAddTodo).
    • Bonus: Use an EventEmitter wrapper with .value() to mirror Angular, avoids manual $event Object creation
  • Why? This mirrors Angular and keeps consistency inside every component. It also makes state predictable.

Back to top

Stateful components

Let's define what we'd call a "stateful component".

  • Fetches state, essentially communicating to a backend API through a service
  • Does not directly mutate state
  • Renders child components that mutate state
  • Also referred to as smart/container components

An example of a stateful component, complete with its low-level module definition (this is only for demonstration, so some code has been omitted for brevity):

/* ----- todo/todo.component.js ----- */
import templateUrl from './todo.html';

export const TodoComponent = {
  templateUrl,
  controller: class TodoComponent {
    constructor(TodoService) {
      'ngInject';
      this.todoService = TodoService;
    }
    $onInit() {
      this.newTodo = {
        title: '',
        selected: false
      };
      this.todos = [];
      this.todoService.getTodos().then(response => this.todos = response);
    }
    addTodo({ todo }) {
      if (!todo) return;
      this.todos.unshift(todo);
      this.newTodo = {
        title: '',
        selected: false
      };
    }
  }
};

/* ----- todo/todo.html ----- */
<div class="todo">
  <todo-form
    todo="$ctrl.newTodo"
    on-add-todo="$ctrl.addTodo($event);"></todo-form>
  <todo-list
    todos="$ctrl.todos"></todo-list>
</div>

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .name;

This example shows a stateful component, that fetches state inside the controller, through a service, and then passes it down into stateless child components. Notice how there are no Directives being used such as ng-repeat and friends inside the template. Instead, data and functions are delegated into <todo-form> and <todo-list> stateless components.

Back to top

Stateless components

Let's define what we'd call a "stateless component".

  • Has defined inputs and outputs using bindings: {}
  • Data enters the component through attribute bindings (inputs)
  • Data leaves the component through events (outputs)
  • Mutates state, passes data back up on-demand (such as a click or submit event)
  • Doesn't care where data comes from - it's stateless
  • Are highly reusable components
  • Also referred to as dumb/presentational components

An example of a stateless component (let's use <todo-form> as an example), complete with its low-level module definition (this is only for demonstration, so some code has been omitted for brevity):

/* ----- todo/todo-form/todo-form.component.js ----- */
import templateUrl from './todo-form.html';

export const TodoFormComponent = {
  bindings: {
    todo: '<',
    onAddTodo: '&'
  },
  templateUrl,
  controller: class TodoFormComponent {
    constructor(EventEmitter) {
        'ngInject';
        this.EventEmitter = EventEmitter;
    }
    $onChanges(changes) {
      if (changes.todo) {
        this.todo = Object.assign({}, this.todo);
      }
    }
    onSubmit() {
      if (!this.todo.title) return;
      // with EventEmitter wrapper
      this.onAddTodo(
        this.EventEmitter({
          todo: this.todo
        })
      );
      // without EventEmitter wrapper
      this.onAddTodo({
        $event: {
          todo: this.todo
        }
      });
    }
  }
};

/* ----- todo/todo-form/todo-form.html ----- */
<form name="todoForm" ng-submit="$ctrl.onSubmit();">
  <input type="text" ng-model="$ctrl.todo.title">
  <button type="submit">Submit</button>
</form>

/* ----- todo/todo-form/todo-form.module.js ----- */
import angular from 'angular';
import { TodoFormComponent } from './todo-form.component';
import './todo-form.scss';

export const TodoFormModule = angular
  .module('todo.form', [])
  .component('todoForm', TodoFormComponent)
  .value('EventEmitter', payload => ({ $event: payload }))
  .name;

Note how the <todo-form> component fetches no state, it simply receives it, mutates an Object via the controller logic associated with it, and passes it back to the parent component through the property bindings. In this example, the $onChanges lifecycle hook makes a clone of the initial this.todo binding Object and reassigns it, which means the parent data is not affected until we submit the form, alongside one-way data flow new binding syntax '<'.

Back to top

Routed components

Let's define what we'd call a "routed component".

  • It's essentially a stateful component, with routing definitions
  • No more router.js files
  • We use Routed components to define their own routing logic
  • Data "input" for the component is done via the route resolve (optional, still available in the controller with service calls)

For this example, we're going to take the existing <todo> component, refactor it to use a route definition and bindings on the component which receives data (the secret here with ui-router is the resolve properties we create, in this case todoData directly map across to bindings for us). We treat it as a routed component because it's essentially a "view":

/* ----- todo/todo.component.js ----- */
import templateUrl from './todo.html';

export const TodoComponent = {
  bindings: {
    todoData: '<'
  },
  templateUrl,
  controller: class TodoComponent {
    constructor() {
      'ngInject'; // Not actually needed but best practice to keep here incase dependencies needed in the future
    }
    $onInit() {
      this.newTodo = {
        title: '',
        selected: false
      };
    }
    $onChanges(changes) {
      if (changes.todoData) {
        this.todos = Object.assign({}, this.todoData);
      }
    }
    addTodo({ todo }) {
      if (!todo) return;
      this.todos.unshift(todo);
      this.newTodo = {
        title: '',
        selected: false
      };
    }
  }
};

/* ----- todo/todo.html ----- */
<div class="todo">
  <todo-form
    todo="$ctrl.newTodo"
    on-add-todo="$ctrl.addTodo($event);"></todo-form>
  <todo-list
    todos="$ctrl.todos"></todo-list>
</div>

/* ----- todo/todo.service.js ----- */
export class TodoService {
  constructor($http) {
    'ngInject';
    this.$http = $http;
  }
  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import uiRouter from 'angular-ui-router';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [
    uiRouter
  ])
  .component('todo', TodoComponent)
  .service('TodoService', TodoService)
  .config(($stateProvider, $urlRouterProvider) => {
    'ngInject';
    $stateProvider
      .state('todos', {
        url: '/todos',
        component: 'todo',
        resolve: {
          todoData: TodoService => TodoService.getTodos()
        }
      });
    $urlRouterProvider.otherwise('/');
  })
  .name;

Back to top

Directives

Directive theory

Directives gives us template, scope bindings, bindToController, link and many other things. The usage of these should be carefully considered now that .component() exists. Directives should not declare templates and controllers anymore, or receive data through bindings. Directives should be used solely for decorating the DOM. By this, it means extending existing HTML - created with .component(). In a simple sense, if you need custom DOM events/APIs and logic, use a Directive and bind it to a template inside a component. If you need a sensible amount of DOM manipulation, there is also the $postLink lifecycle hook to consider, however this is not a place to migrate all your DOM manipulation to, use a Directive if you can for non-Angular things.

Here are some advisories for using Directives:

  • Never use templates, scope, bindToController or controllers
  • Always restrict: 'A' with Directives
  • Use compile and link where necessary
  • Remember to destroy and unbind event handlers inside $scope.$on('$destroy', fn);

Back to top

Recommended properties

Due to the fact directives support most of what .component() does (template directives were the original component), I'm recommending limiting your directive Object definitions to only these properties, to avoid using directives incorrectly:

Property Use it? Why
bindToController No Use bindings in components
compile Yes For pre-compile DOM manipulation/events
controller No Use a component
controllerAs No Use a component
link functions Yes For pre/post DOM manipulation/events
multiElement Yes See docs
priority Yes See docs
require No Use a component
restrict Yes Defines directive usage, always use 'A'
scope No Use a component
template No Use a component
templateNamespace Yes (if you must) See docs
templateUrl No Use a component
transclude No Use a component

Back to top

Constants or Classes

There are a few ways to approach using ES2015 and directives, either with an arrow function and easier assignment, or using an ES2015 Class. Choose what's best for you or your team, keep in mind Angular uses Class.

Here's an example using a constant with an Arrow function an expression wrapper () => ({}) returning an Object literal (note the usage differences inside .directive()):

/* ----- todo/todo-autofocus.directive.js ----- */
import angular from 'angular';

export const TodoAutoFocus = ($timeout) => {
  'ngInject';
  return {
    restrict: 'A',
    link($scope, $element, $attrs) {
      $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => {
        if (!newValue) {
          return;
        }
        $timeout(() => $element[0].focus());
      });
    }
  }
};

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoAutofocus } from './todo-autofocus.directive';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .directive('todoAutofocus', TodoAutoFocus)
  .name;

Or using ES2015 Class (note manually calling new TodoAutoFocus when registering the directive) to create the Object:

/* ----- todo/todo-autofocus.directive.js ----- */
import angular from 'angular';

export class TodoAutoFocus {
  constructor($timeout) {
    'ngInject';
    this.restrict = 'A';
    this.$timeout = $timeout;
  }
  link($scope, $element, $attrs) {
    $scope.$watch($attrs.todoAutofocus, (newValue, oldValue) => {
      if (!newValue) {
        return;
      }
      this.$timeout(() => $element[0].focus());
    });
  }
}

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoAutofocus } from './todo-autofocus.directive';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .directive('todoAutofocus', ($timeout) => new TodoAutoFocus($timeout))
  .name;

Back to top

Services

Service theory

Services are essentially containers for business logic that our components shouldn't request directly. Services contain other built-in or external services such as $http, that we can then inject into component controllers elsewhere in our app. We have two ways of doing services, using .service() or .factory(). With ES2015 Class, we should only use .service(), complete with dependency injection annotation using $inject.

Back to top

Classes for Service

Here's an example implementation for our <todo> app using ES2015 Class:

/* ----- todo/todo.service.js ----- */
export class TodoService {
  constructor($http) {
    'ngInject';
    this.$http = $http;
  }
  getTodos() {
    return this.$http.get('/api/todos').then(response => response.data);
  }
}

/* ----- todo/todo.module.js ----- */
import angular from 'angular';
import { TodoComponent } from './todo.component';
import { TodoService } from './todo.service';
import './todo.scss';

export const TodoModule = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .service('TodoService', TodoService)
  .name;

Back to top

Styles

Using Webpack we can now use import statements on our .scss files in our *.module.js to let Webpack know to include that file in our stylesheet. Doing this lets us keep our components isolated for both functionality and style; it also aligns more closely to how stylesheets are declared for use in Angular. Doing this won't isolate our styles to just that component like it does with Angular; the styles will still be usable application wide but it is more manageable and makes our applications structure easier to reason about.

If you have some variables or globally used styles like form input elements then these files should still be placed into the root scss folder. e.g. scss/_forms.scss. These global styles can then be @imported into your root module (app.module.js) stylesheet like you would normally do.

Back to top

ES2015 and Tooling

ES2015
  • Use Babel to compile your ES2015+ code and any polyfills
  • Consider using TypeScript to make way for any Angular upgrades
Tooling
  • Use ui-router latest alpha (see the Readme) if you want to support component-routing
    • Otherwise you're stuck with template: '<component>' and no bindings/resolve mapping
  • Consider preloading templates into $templateCache with angular-templates or ngtemplate-loader
  • Consider using Webpack for compiling your ES2015 code and styles
  • Use ngAnnotate to automatically annotate $inject properties
  • How to use ngAnnotate with ES6

Back to top

State management

Consider using Redux with AngularJS 1.5 for data management.

Back to top

Resources

Back to top

Documentation

For anything else, including API reference, check the AngularJS documentation.

Contributing

Open an issue first to discuss potential changes/additions. Please don't open issues for questions.

License

(The MIT License)

Copyright (c) 2016-2018 Todd Motto

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

angularjs-styleguide's People

Contributors

adiman9 avatar alextsg avatar benbr-personal avatar chihab avatar ctaepper avatar davidnotes avatar etripier avatar jackpu avatar joaogarin avatar jodytate avatar josephlin55555 avatar josueggh avatar jvandemo avatar kurtpeters avatar lipis avatar lixey avatar maks3w avatar malikabed avatar nstanard avatar prayagverma avatar sebastibe avatar sryzycki avatar stephenpitchford avatar stryju avatar tadayosi avatar theredfish avatar timroes avatar toddmotto avatar vladimirvalov avatar vogloblinsky avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angularjs-styleguide's Issues

Controllers#Inheritance : what about BaseCtrl in the global namespace ???

The rule for inheritance seems to advocate to put ours base Controllers (the 'parent' controllers) in the global namespace ... since we cant use injector to get a reference to the base controller where we would want it ( in IIFE definition of another (child) Controller )...or am I missing something ?

Thanks a lot

Generic standards

Is it worth considering splitting the document into generic JS and Angular specific standards with a view to, possibly, extending it to include, for example, specific Node practices (possibly rebranding the repo to js-styleguide??)?

For example, using JSDoc is a generic recommendation (and very poorly adopted) for JavaScript as a whole.

Also, running talks like this through a filter would also be worthwhile.

Just thinking out loud.

My Module declaration best practices

Great writeup!

I have found the following best advices.
Just my opinion, will be happy to know any possible flaws in my arguments.

  • Use only one angular.module("myModule",[...]) at the top of each file, with the file being called my-module.js. That way there is one-to-one correspondence between files and Modules and it is very easy to know where each Module is located.
  • As an extension of this rule, use myDirectory/myFile as Module name for my-directory/my-file.
  • Directory structure should possibly be flat to make search easier. Once a directory starts looking "too crowded", this becomes a reason to create a subdirectory.
  • Don't use angular.module("...") without [], use chaining instead! If you want to use it in another file - don't! Make your file a new Module and embed it instead!
  • Surprisingly the following simple pattern is not mentioned, which is the one I prefer:
angular
  .module('app', [])

  // anonymous function, no global variable
  .controller('MainCtrl', function () {

  })

  .service('SomeService', function () {

  })
;
// note how the semicolon is in its own line, 
// that makes it very easy to add/remove blocks

From my perspective, this way is superior to repeating the function name first as global variable, and then, again, inside module method. That repetition is not DRY. Also no global variable is created, so no IIFE is needed (but can still be used).

  • A good reason to use IIFE is to put use strict inside, which should not be in global scope.
  • Finally, angular modules are awesome because they can be loaded in any order! That means, I can simply concatenate my files with Gulp and so never need to worry about maintaining their paths :)

Controller resolve dont using then() to formatter response

Hi,

I have liked formatter response on Service before output.

from:

PlaylistsCtrl.resolve = {
    doSomething: function(PlaylistService) {
        return PlaylistService.getPlaylists();

to:

PlaylistsCtrl.resolve = {
    doSomething: function(PlaylistService) {
        var _list = PlaylistService.getPlaylists();
        _list = JSON.stringify(_list);

using algo then():

return PlaylistService.getPlaylists().then(function(response) {.......});

Who is?

Help me, please!

Edit

ngRepeat:dupes] Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: playlist in playlists

HTML

<ion-list>
      <ion-item ng-repeat="vm in playlists track by $index | orderBy:'id'" href="#/app/playlists/{{ ::vm.id}}">
        {{ ::vm.title}}
      </ion-item>
    </ion-list>

controllers.js:

var ctrl = angular.module('starter.controllers', []);

...

function PlaylistsCtrl(PlaylistService) {
    this.something = PlaylistService.getPlaylists;
}
PlaylistsCtrl.resolve = {
    doSomething: function(PlaylistService) {
        return PlaylistService.getPlaylists();
    }
}
ctrl.controller('PlaylistsCtrl', function($scope, doSomething) {
    $scope.playlists = JSON.stringify(doSomething);
});

Services vs. Factories

"All Angular Services are singletons, using .service() or .factory() differs the way Objects are created."

It is correct that all Angular Services return singleton objects. The service gets the result of instantiating the constructor function passed. One adds properties to the singleton by referencing "this" in the constructor function.

Angular Factories, on the other hand, gets the returned value of the function, which may return singleton objects, primitive types, or functions. One can certainly "new" a Factory if the Factory function returns a function.

Named exports vs. defaults

This is a great guide!

I've been working with Angular 1.5 and ES6 for several months now, trying to find good ways to make it work and make it clean. I wasn't following any guides, and I got to almost the same practices as what you describe here. Therefore, I like it :)

I'm still reading this guide, but one thing that I do a little differently is exports and imports:

I use the same export pattern with the modules themselves, using export default angular.module('appStuff', ...).name, and then in the parent directory, I import it using import appStuffModule from ... to include it in the dependencies of the "container" module.

However, I export named components explicitly using their names, and not by default export. By this I mean components (or directives, if it can't be avoided), services, factories, providers, constants, functions and basically anything that's not a module that is exported by a variable.
An actual example from a project I'm working on (removed implementation and I'm also using TypeScript, but the idea is the same):

sysMonitor.ts

/**
 * @ngdoc controller
 * @name SysMonitorController
 */
class SysMonitorController {
  static $inject = ['$scope', 'dataProviderReportType', 'avrChartDataService', 'BIP_MultiDataProvider'];

  /*- Bindings -------------------------------------------------------------*/
  public timerange;

  /*- Template vars ($ctrl props) -------------------------------------*/
  public isLoading = false;
  public isOpen = true;
  // ...

  /*- Private props --------------------------------------------------------*/
  private chartConfig = <ILineChartConfig>{};
  private dataProvider: statsDataProvider;
  // ...

  /*- Public methods ----------------------------------------------------------*/
  /**
   * Constructor used for DIs and binding of methods
   */
  constructor(private $scope: ng.IScope,
              private dataProviderReportType,
              private avrChartDataService,
              private BIP_MultiDataProvider) {
    // binding callbacks from prototype to current instance
    this.onDataUpdated = this.onDataUpdated.bind(this);
  }

  public $onInit() {
    // After everything is initialized, prepare data providers and charts configuration
  }

  public $onChanges(changes) {
    // changes monitoring
  }

  public $onDestroy() {
    // remove callbacks from data providers
  }

  /*------------------------------------------------------------------------*/

  private onDataUpdated(data, error, notification) {
    // ...
  }

}

/**
 * @ngdoc component
 * @name avrSystemMonitor
 * @restrict E
 * @param timerange
 */
export const sysMonitorComponent: ng.IComponentOptions = {
  bindings: {
    timerange: '<'
  },
  controller: SysMonitorController,
  templateUrl: `...`
};

and then in a directly above it, I have components.ts

///<reference path="../../../../../../typings/index.d.ts"/>
import someServicesModule from '../services/someServices'; // <-- module
import { sysMonitorComponent } from "./sysMonitor/sysMonitor"; // <-- named component

/**
 * @ngdoc module
 * @name app.components
 */
export default angular
    .module('app.components', [
      someServicesModule
    ])
    .component('avrSystemMonitor', sysMonitorComponent)
    // ...
    .name;

And of course, one level above it, there is the main app.ts file which has, among others:

import componentsModule from './components/components'; // <-- I am still considering using 'index.js' (.ts) for the "glue" file in each directory.
// import other modules here
import { someConfigFunction } from './config';

/**
 * @ngdoc module
 * @name app
 */
angular.module('dosVisibility', [
    componentsModule,
    // other imported modules
  ])
  .config(['$compileProvider', function($compileProvider) {
    $compileProvider.debugInfoEnabled(false);
  }])
  .run(someConfigFunction);

The app file is the entry point (for webpack), so it doesn't really need an export. If it would have been a library, then the entry point would also have an export.

I find this pattern very useful. Additionally, exporting by name instead of defaults is pretty much the only way any IDE knows what you mean, even before you made the import.
For example, in both VSCode and JetBrains IDEs, you can use an object from another file, and it will automatically add the import. It will not do it for "default" export since the names mean nothing before these are imported. Just for this, I think it's worth to use named exports.

I wonder what are the thoughts of the community about it.

About components and common module...

i thought about those modules while working on the other PR yesterday. you are not really explaining the purpose of those modules, other that one single place to inject component modules.

however, is this really best practice? lets take the common/footer component for example. you inject it via the common module into the root module, so the footer module is available throughout the whole app, i can use this component everywhere, for examples sake i use it now in my (view-) component called home which represents the initial view of my app. now, i want to test my home component in isolation. this component now no knowledge about the common/footer component, because you didn't explicitly inject the dependency into the module definition for home, but rely on the module being injected into the root module...

in our setup, we import and inject every single module that is required by the current module. at first, it seems like a bit of overhead, but this way, we always have all necessary dependencies available, when testing a module in isolation.

Angular wrapper references ( link fn params wrong)...

Hi Todd.

I think here is a typo, $document service should be injected as parameter of the directive function right?
instead of this:

// recommended
function dragUpload () {
  return {
    link: function ($scope, $element, $attrs, $document) {
      $document.addEventListener('click', function () {

      });
    }
  };
}

this:

// recommended
function dragUpload ($document) {
  return {
    link: function ($scope, $element, $attrs) {
      $document.addEventListener('click', function () {

      });
    }
  };
}

also I think, that it's a good practice, not to prefix positional link function arguments with dollar sign, because those are not injected services via DI.

And last one: It's more readable, imho, to extract controller/link/compile function outside of the directive definition object like this:

;(function () {
    angular.module('myApp', [])
        .directive('myDirective', myDirective);

    function myDirective( /* DI */ ) {

        return {
            restrict: 'EA',
            controller: Ctrl,
            controllerAs: 'ctrl',
            link: linkFn,
            template: '<div>My name is {{ ::ctrl.myName }}</div>'
        };

        function Ctrl($log) {
            // ...
            this.$log = $log;
            var vm = this;

            vm.myName = 'Martin';
        }

        Ctrl.prototype.sayHi = function () {
            this.$log(this.myName);
        }

        function linkFn(scope, element, attrs) {
            // ...
        }
    }
})();

What do you say?
thanks and btw you did a great job on writing this guide!

Portuguese (Portugal pt-pt) version

Hello Todd,

I can submit a pt-pt translation if that is interesting. People from Brazil / Portugal and many countries from Africa would probably benefit from this. And since I saw a Spanish version was submitted, why not have it in some more languages?

=)

Services instead of factories

how would i apply this pattern to a ES2015 service?

function PersonService($http) {
  function Person() {
    this.foo = function () {

    };
  }
  Person.prototype.bar = function () {

  };
  return Person;
}
angular
  .module('app')
  .factory('PersonService', PersonService);

this is an example of your post at https://toddmotto.com/factory-versus-service

question/comment: routing resolves

Having a little difficulty following your section on Routing Resolves.

Note: I'm using Browserify, Restangular, and ui-router in my app.

You do the following in your example MainCtrl:
this.something = SomeService.something

MainCtrl appears to have a property of resolve (an obj; not a promise):

Main.resolve: {
  doSomething: function() { // How come `SomeService` doesn't need to be injected here??
    return SomeService.doSomething();
  }
}

I was thinking that you'd want to resolve doSomething before you instantiate the controller:

function config ($routeProvider) {
  $routeProvider
  .when('/', {
    templateUrl: 'views/main.html',
    controllerAs: 'main',
    controller: 'MainCtrl'
    resolve: MainCtrl.resolve.doSomething // instead of just resolve: MainCtrl.resolve
  });
}

I'm making the assumption that SomeService.doSomething() is a promise that's then'able...

SomeService.doSomething.then( function( somethings ) {
  // May want to map `somethings` to controller's $scope, but how??
});
Below is what I'm trying to accomplish by following the guide, but a bit lost on proper implementation...

Routing:

// league-config.js
...
$stateProvider
  .state('league.creation', {
        url: '/creation',
        controller: 'LeagueCreationController as leagueCreation',
        resolve: require('./league.creation-controller').resolve,
        views: {
          'main@': {
            templateUrl: './partials/league/league-creation.html'
          }
        }
      });

Controller:

// league.creation.controller.js
var LeagueCreationController =  function( Restangular ) {
    var self = this;
    var leagues = Restangular.all( 'leagues ');
    // Would be great if this could be resolved outside of the controller...
    leagues.getList().then(function( allLeagues ) {
      self.leagues = allLeagues;
    }
};

LeagueCreationController.resolve = {
  leagues: function( Restangular ) {
    return Restangular.all( 'leagues' ).getList();
  }
};

module.exports = LeagueCreationController;

Help is greatly appreciated. The guide is absolutely a joy to have. :)

Add style guide for unit tests

Hi guys, this is a very helpful article. It'd be nice to have your thoughts on unit test structure and organisation for each type of Angular components (directives, services, filters, etc).

Why so specific in naming Services?

I've been living by this style guide for awhile now, and it's fantastic.

The Service naming is irritating, though. I'd suggest a more generic name for the Service object inside each service. Example:

function AnotherService () {
  var AnotherService = {};
  /* ... */
  return AnotherService;
}

could become...

function AnotherService () {
  var Service = {};
  /* ... */
  return Service;
}

There are two three reasons why:

  1. jsHint throws irritating "already defined" notices when the internal object is the same name.
  2. It makes it easier to rename services or repurpose snippets in other service.
  3. It's more consistent with the way that you're naming controller objects (always with "vm").

I'd love your thoughts on this. If there's a good reason, I'd like to know and avoid any issues.

Thank you!
Dustin

Controller.resolve versus IIFE

Hello,

I just wonder how you can use the Controller.resolve property when using IIFE (assuming the routing config and the controller are in separate files).
Using tools like Browserify solves this problem, but when you simply concat all your script files I don't see a way to use a property of the controller while configuring the router.
Any recommandation?

[Question] Typescript specifc remarks / info?

Hey Todd,
What would be your opinion about including some typescript related info into you guide?
If PRs are welcome, should those be included as a new chapter, or inline? I guess a new chapter would be less disturbing overall, but easier to miss...

module file naming

First of all : great styleguide!

We are using a very similar approach for some time now. With one little difference in naming conventions: we call our module file index.js. This allows the module to be imported with import Todo from "./todo"; (instead of from "./todo/todo"). Also, everyone knows, index.* would be the start of a module directory. Any thoughts?

Can you explain why we should not bind resolve logic to the router?

Here it talks about putting our resolves in the controller, instead of in the router. This is interesting to me, because I thought that you wanted to keep your controllers as slim as possible (as a general rule, and in prep for Angular 2.0). Can you elaborate a little more about why you would want to put your resolve logic inside a controller?

Thanks! ^_^

Spec file

Could you give an example of a spec file?

I'm wondering how do you plan your tests, since it might be a little awkward (at least to me) to mix service tests with controller tests on the same file?

I'm most likely missing something here, but I guess you could give some guidance.
Thanks :)

routed component

Hello todd,

thanks for this new version of your styleguide. You mention in routed component that we can use a component prop when declaring a state but I can't find any documentation in ui-router about it

    $stateProvider
      .state('todos', {
        url: '/todos',
        component: 'todo',
        resolve: {
          todoData: TodoService => TodoService.getTodos();
        }
      });
  })

I like the idea a lot can you have any infos on that ?

Routing resolves

Hi,
about the Routing resolves solution you suggest, it looks very good but there is a problem when the controller is not in the same file as the app.js.

How do you suggest to solve it?

IIFE Scoping

In regards to:

(function () {

  // MainCtrl.js
  function MainCtrl () {

  }

  angular
    .module('app')
    .controller('MainCtrl', MainCtrl);

  // SomeService.js
  function SomeService () {

  }

  angular
    .module('app')
    .service('SomeService', SomeService);

  // ...

})();

Would it not be better to pass the module in like:

(function (app) {

  // MainCtrl.js
  function MainCtrl () {

  }

  app.controller('MainCtrl', MainCtrl);

  // SomeService.js
  function SomeService () {

  }

  app.service('SomeService', SomeService);

  // ...

})(angular.module('app'));

Thoughts?

Overhaul this guide

This guide needs overhauling now Angular 1.5.x is out. Things to cover:

  • One-way dataflow (data down, events up)
  • Component method (and polyfill: https://github.com/toddmotto/angular-component)
  • Component architecture
  • Each block is a module (huge feature modules are obselete)
  • Stateful, stateless and routed components

Minor grammatical/syntactical review

Hello,

This style guide is incredibly helpful and I've found it to be a great Angular resource. I'd like to contribute some minor grammatical and syntactical review; some of the language was slightly confusing, and I think small corrections would improve the guide's readability and clarity.

Example:
Before: "Directives and Filters are the only providers that we have the first letter as lowercase, this is due to strict naming conventions in Directives due to the way Angular translates camelCase to hyphenated, so dragUpload will become drag-upload when used on an element."

Proposed change: "Directives and Filters are the only providers that have the first letter as lowercase; this is due to strict naming conventions in Directives. Angular hyphenates camelCase, so dragUpload will become drag-upload when used on an element."

I won't be changing any of the examples or presented code, just grammatical gloss. Please let me know if this could be a valuable addition.

Naming conventions

Why should we use component name for each file related to component? For instance, we have component main,

├── main
   ├── main.component.js
   ├── main.config.js
   ├── main.spec.js
   ├── index.js
   ├── main.html
   └── main.scss

files are located in main folder, it meens that these files related only to main component and I think it is not necessary add namespace name to each file

├── main
   ├── component.js
   ├── config.js
   ├── spec.js
   ├── index.js
   ├── main.html
   └── main.scss

component has child component

contacts
   ├── config.js
   ├── contacts.html
   ├── contacts.scss
   ├── controller.js
   ├── spec.js
   ├── edit
      ├── config.js
      ├── controller.js
      ├── edit.html
      ├── spec.js
      └── index.js

instead of

contacts
   ├── contacts.config.js
   ├── contacts.html
   ├── contacts.scss
   ├── contacts.controller.js
   ├── contacts.spec.js
   ├── edit
      ├── contacts.edit.config.js
      ├── contacts.edit.controller.js
      ├── edit.html
      ├── contacts.edit.spec.js
      └── index.js

Does it make sense?

Dependency injection for base controllers on Inheritance

The style guide presents this code:

function BaseCtrl () {
  this.doSomething = function () {

  };
}
BaseCtrl.prototype.someObject = {};
BaseCtrl.prototype.sharedSomething = function () {

};

AnotherCtrl.prototype = Object.create(BaseCtrl.prototype);

function AnotherCtrl () {
  this.anotherSomething = function () {

  };
}

My problem is with this line: AnotherCtrl.prototype = Object.create(BaseCtrl.prototype);

See in this case it works because BaseCtrl has no dependencies, but that's hardly the case in most scenarios, so I can't just use Object.create, what do we do then?

Controllers in Directives?

What you guys think in add something about this topic? I personality like to put all logic of a directive in controllers.. something like that:

.directive('myDirective', function(apiService){
    return {
        restrict: 'EA'
        controller: function($scope){
            $scope.fetch = function(){
                return apiService.getData();
            };
        },
        link: function(scope, element, attrs){
            var data = scope.fetch().then(function(){
                element.text(data);
            });
        }
    }
})

It's a good practice?

Thanks

First thoughts

Todd, this is awesome. I love to see these types of things being written in the Angular community. It's challenging, it's much needed, and it's refreshing. I have a few thoughts after reading through it and thinking about some of the things my team went through when building an ng 1.5 app using these principles and similar architecture.

Module exports

First, is there a reason not to just export the name from the module? It looks cleaner in the module setter, and has the same functionality. Instead of having to do:

import {Module} from './module';

angular.module([ Module.name ]) 

you could just do:

import { Module } from './module';

angular.module([Module])

My team think the latter snippet looks cleaner (for a couple reasons). There's not much benefit to doing it either way though, I suppose.

Naming convention

We mostly follow the naming pattern suggested here, but lately I've been wondering if it is necessary to name things like calendar.controller.js when Cmd+P in Atom, Sublime, etc. can find the controller in the calendar folder for you. Just a thought, but it would really cut down on the amount of typing you do for file names over time.

Binding readability

We found that having a utility file that exports a const for each of the binding types makes it easier to see what types of bindings are being used, and it's a little less "cypherish". Really helps with file readability. It's basically:

export const CALLBACK = '&';
export const ONE_WAY = '<';
export const STRING = '@';
  • Notice the omission?

DI

When writing apps with ES2015+, it's inevitable to need a build system. With that in mind, is it smarter to encourage the use of $inject or to strongly encourage ngAnnotate? Much less manual work, and much less error-prone with strict-di. I think it's better to let ngAnnotate do the heavy lifting, and it works with all the major build systems.

Class methods or pure functions?

From our experience, any pure functions that are not bound directly to our classes are easier to test in isolation. It's annoying to have to mock the controller and use ng-mocks to mock dependencies to test simple actions when you don't have to. Granted, we've tried to keep most of our functions are pure, and we've managed to rely very little on Angular services and dependency injection. It's been really nice and it's helped us a lot with writing tests and reusing code. Plus, mocha can run 600+ tests as quickly as you save a file. Karma can run 600+ tests about as fast as you can download angular 2 on a bad internet connection.

Lifecycle order

Another thing; one gotcha that got us over and over was that, somewhat inexplicably, $onChanges gets called before $onInit. I don't know where it's relevant to point that out, but it should probably be pointed out nonetheless.

Single-file components?

What's the benefit of defining the template inline, but not the controller? why not do:

const TodoComponent = {
  bindings: {
    whatever: '<',
  },
  controller: class TodoController {
    constructor(TodoService) {
      'ngInject';
      this.TodoService = TodoService;
    }
    $onInit() {
      this.newTodo = {
        title: '',
        selected: false
      };
      this.todos = [];

    this.TodoService.getTodos
      .then(response => this.todos = response);
    }
    addTodo(event.todo) {
      if (!event.todo) return;
      this.todos.unshift(event.todo);
      this.newTodo = {
        title: '',
        selected: false
      };
    }
  },
  template: `
    <div class="todo">
      <todo-form 
        todo="$ctrl.newTodo"
        on-add-todo="$ctrl.addTodo($event);">
      <todo-list 
        todos="$ctrl.todos"></todo-list>
    </div>
  `
};

We found that cutting file count whenever possible tends to help DX on a day-to-day basis. We've even tinkered with putting everything, even module definition, in one file. It tends to be kind of nice if you're keeping your components small.

Classes or arrow functions

That non-class directive pattern really seems a lot more natural to me. I know, I know, ng2. But still. Look how nice that looks, right? So clean, so little instantiating a class just to get the same functionality ¯\_(ツ)_/¯

File structure

Here's how we organize files. Not really suggesting this be the structure, just an another look at organization, may be helpful to someone:

├── client/
|  ├── app/
|  |  ├── redux/
|  |  |  └── modules/
|  |  |    └── events-signup/
|  |  |      ├── index.js
|  |  |      ├── selectors.js
|  |  |      └── state.js
|  |  └── scenes/
|  |  |  └── events-signup/
|  |  |    ├── index.js
|  |  |    ├── events-signup.controller.js
|  |  |    ├── events-signup.route.js
|  |  |    ├── events-signup.html
|  |  |    └── events-signup.scss
|  |  ├── index.js
|  |  └── reducers.js
|  └──global/
|     ├── core/
|     |  ├── core.config.js
|     |  ├── core.run.js
|     |  └── index.js
|     ├── components/
|     |  ├── nav/
|     |  |  ├── nav.js
|     |  |  ├── nav.scss
|     |  └── index.js
|     ├── scenes/
|     |  └── layout/
|     |     ├── index.js
|     |     ├── layout.route.js
|     |     ├── layout.scss
|     |     └── layout.controller.js
|     └── utils/
|        ├── bindings.js
|        ├── env.js
|        └── ng-utils.js
└── index.html

The reason global lives outside of app is the it contains a lot of utilities and boilerplate that isn't "app" specific, and could very easily be used by another application we want to quickly start building.

Also, we keep all state management (in our case, Redux) code outside of the components. If state is normalized correctly, it should be a 1:1 mapping to components, it should behave more like a database. We've just found this structure to help us think of our data independently of our components. We called all of our routes scenes I think just because of some React/Redux article I read somewhere or something, no benefit to scenes over routes

Require vs. bindings

Could we better explain the benefit of passing a binding instead of just requiring the parent? Devil's advocate: Why would i go through the long process of setting up all those bindings for one-way communication when I could just say require: '^^parentComponent' and accomplish the same thing, and cover all my bases in one line of code instead of 10+?

Using $event as the payload of & bindings

Using $event === smart. I love it, and wish I would've been doing that all along. It's much better to have that consistent API for outputs.

Use resolves to make smart components

Shoutout to Chris Thielen for this idea, but in a lot of cases, when you're routing straight to components, there's a good chance that most of the bindings you'd do in smart components can probably be done in resolves on a ui-router state. Just one more step that can be cut out.

NG2.0 vs. the world

Finally, I know there's been a lot of thought put into this style guide to help with transitioning to Angular 2. One of the thoughts that came to my head as I read this is: Do we, while catalyzing modern best practices in the community, want to encourage good Angular 2 practices, or good JavaScript practices. One example: there is HUGE benefit to factory functions. They're easy. They're perfect for the types of things we want a service to do. They're very useful. They're gone in ng2. RIP .factory().

I think one of the great things about the React community is that they truly strive to make people better JavaScript devs. To me, that's a useful thing. That transcends framework boundaries, and makes a lasting impact on all of us as developers. I really believe this guide, along with a lot of the things you write, are going to help foster more of that in the ng 1.x community. Thanks for doing what you do, and I'm looking forward to future discussions!

Using ui-router 0.3.x (stable)

@toddmotto, for developers, such as myself, who aren't comfortable moving forward with an alpha release of ui-router, there is an alternative route-to-component approach. Starting with 0.3.0 a developer now has access to $resolve. While it requires being more verbose, it offers the following approach:

const todo = angular
  .module('todo', [])
  .component('todo', TodoComponent)
  .service('TodoService', TodoService)
  .config(($stateProvider, $urlRouterProvider) => {
    $stateProvider
      .state('todos', {
        url: '/todos',
        // full component html with binding attributes
        template: '<todo todo-data="$resolve.todoData"></todo>',
        resolve: {
          todoData: PeopleService => PeopleService.getAllPeople();
        }
      });
    $urlRouterProvider.otherwise('/');
  })
  .name;

ng-bind vs curly brackets

Hi everybody!

I read this guide and try to follow advices contained here, but today I faced problem with curly brackets, because I had JS imports in <footer> element at bottom of page - I had to move it to <head> section and use ng-cloak on body (which isn't adviced... But works 😄 ). Then I found ngBind directive, which completely removes this problem. As I googled, it is also faster than {{}} but some people are complaining that it created unnecessary boxing with <span ng-bind="something"></span> and isn't as clear as curly brackets syntax (maybe it's only old habit?). I am curious of your opinion - maybe it's worth mentioning in @toddmotto's / @johnpapa's styleguides?

Controller-as and "this"

Not sure if I'm missing something here, but you first recommend using Controller-as syntax, referencing "this" in the controller for model properties and methods. Then, further on, you recommend setting "this" to "vm" (for ViewModel). Should this just be one recommendation, using Controller-as syntax and assigning "this" to a variable in the context of the controller?

Thanks

Missing import of AppComponent root component in the guide

It seems that the import of the root component AppComponent is missing in the styleguide (in this section in the app.js file)

Something like that I think :

import AppComponent from './app.component';

along with these:

import Common from './common/common';
import Components from './components/components';

or maybe I missed something ?

question about the way to unbind multiple listeners on $destroy

In event pub/sub section, why do we unbind listener within multiple $destroy event binding?

var rootListeners = {
  'customEvent1': $rootScope.$on('customEvent1'[, callback]),
  'customEvent2': $rootScope.$on('customEvent2'[, callback]),
  'customEvent3': $rootScope.$on('customEvent3'[, callback])
};
for (var unbind in rootListeners) {
  $scope.$on('$destroy', rootListeners[unbind]);
}

Can we do this instead? move for loop into the $destroy event handler

$scope.$on('$destroy', function() {
  for (var unbind in rootListeners) {
    rootListeners[unbind]();
  }
});

What about file organization ?

I just discovered your styleguide, and it looks awesome ! I can't to use it.

But, I think a chapter about file organization would be nice.

I just saw this : https://github.com/andyjessop/angular-starter#file-structure

But I'm not sure this is the organization I want : in this example, if I have a user registration and a user profile stuff, this would be two different modules, or I should put many views, controllers, services... in the same folder... this would look messy...

Anyway, what's the best practice for file organisation ?

$timeout & this

Given your controller with a $timeout:

function MainCtrl ($timeout) {
  this.clock = 'loading...';
  this.someObject = {};
  this.doSomething = function () {

  };

  var tick = function () {
    var today = new Date();
    // 'this.clock' isn't accessible here 
    $timeout(tick, 1000);
  };

  // Call it the first time to kick it off
  $timeout(tick, 0);
}

Should I be assigning a local variable like:

var _this = this;

// Then use (in the tick function):
_this.clock = today;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.