SlideShare ist ein Scribd-Unternehmen logo
1 von 126
Downloaden Sie, um offline zu lesen
Embrace the Angular 2 Ethos
in Angular 1.x
Ethos
the distinctive character, spirit, and
attitudes of a people, culture, era, etc
GAP!
GAP!
Angular 2
is not only a
framework…
…but a set of very
useful patterns.
essence details
A Brief
History
of Angular
tiny app == tiny view + tiny controller
Growing Application
Growing
View
Growing
Controller
Realistic Application
Growing
View
Growing
Controller
Uh oh!
Large 1.x Application
Named
Route
Named
Route
Named
Route
Large 1.x Application
Directive
Directive
Directive
Any Angular 2 Application
Component
Component
Component
Still
one small
problem…
Structure
Communication
Let's step back a
moment...
The Big Picture
viewcontroller
module
config
routes
$scope
service directive
It has gotten
even simpler...
The Simplified Picture
component
module
config
routes
service
Angular 1.x Application
Component
Component
Component
The best way to
become a great
Angular developer
is to focus on
becoming a great
developer
Common sense
Established
practices
JOHN PAPA'S 

STYLE GUIDE
Hello
Progression
angular.module('app')

.controller('CategoriesListCtrl', function($scope, CategoriesModel) {

CategoriesModel.getCategories()

.then(function(result){

$scope.categories = result;

});



$scope.onCategorySelected = function(category) {

CategoriesModel.setCurrentCategory(category);

}

});
Classic Controller
<div ng-controller="CategoriesListCtrl">

<!-- categories list markup -->

</div>
Classic View
angular.module('app')

.controller('CategoriesListCtrl', function(CategoriesModel) {

CategoriesModel.getCategories()

.then(function(result){

this.categories = result;

});



this.onCategorySelected = function(category) {

CategoriesModel.setCurrentCategory(category);

}

});
Moving to controller as
<div ng-controller="CategoriesListCtrl as categoriesListCtrl">

<!-- categories list markup -->

</div>
Moving to controller as
function CategoriesListCtrl(CategoriesModel) {

CategoriesModel.getCategories()

.then(function(result){

this.categories = result;

});



this.onCategorySelected = function(category) {

CategoriesModel.setCurrentCategory(category);

}

}



angular.module('app')

.controller('CategoriesListCtrl', CategoriesListCtrl);
Extract the controller function
function CategoriesListCtrl(CategoriesModel) {

CategoriesModel.getCategories()

.then(function(result){

this.categories = result;

});



this.onCategorySelected = function(category) {

CategoriesModel.setCurrentCategory(category);

}

}



var CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'CategoriesListCtrl'

}



angular.module('app')

.component('categoriesList', CategoriesList);
Convert to component
<categories-list></categories-list>
Convert to component
class CategoriesListCtrl {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}



const CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'categoriesListCtrl'

};



angular.module('app')

.component('categoriesList', CategoriesList)

;
Convert to ES6
class CategoriesListCtrl {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}

$onInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}

onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}

const CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'categoriesListCtrl'

};

angular.module('app')

.component('categoriesList', CategoriesList)

;
Use lifecycle hooks
@Component({

selector: 'categories-list',

template: `<div>Hello Category List Component</div>`,

providers: [CategoriesModel]

})

export class CategoriesList {

constructor(CategoriesModel: CategoriesModel) {

this.CategoriesModel = CategoriesModel;

}



ngOnInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}
Angular 2 equivalent
@Component({

selector: 'categories-list',

template: `<div>Hello Category List Component</div>`,

providers: [CategoriesModel]

})

export class CategoriesList {

constructor(CategoriesModel: CategoriesModel) {

this.CategoriesModel = CategoriesModel;

}



ngOnInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}
class CategoriesListCtrl {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



$onInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}



const CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'categoriesListCtrl'

};



angular.module('app')

.component('categoriesList', CategoriesList)

;
Similar shapes
@Component({

selector: 'categories-list',

template: `<div>Hello Category List Component</div>`,

providers: [CategoriesModel]

})

export class CategoriesList {

constructor(CategoriesModel: CategoriesModel) {

this.CategoriesModel = CategoriesModel;

}



ngOnInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}
class CategoriesListCtrl {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



$onInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}



const CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'categoriesListCtrl'

};



angular.module('app')

.component('categoriesList', CategoriesList)

;
Component configuration
@Component({

selector: 'categories-list',

template: `<div>Hello Category List Component</div>`,

providers: [CategoriesModel]

})

export class CategoriesList {

constructor(CategoriesModel: CategoriesModel) {

this.CategoriesModel = CategoriesModel;

}



ngOnInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}
class CategoriesListCtrl {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



$onInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}



const CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'categoriesListCtrl'

};



angular.module('app')

.component('categoriesList', CategoriesList)

;
Component controller
@Component({

selector: 'categories-list',

template: `<div>Hello Category List Component</div>`,

providers: [CategoriesModel]

})

export class CategoriesList {

constructor(CategoriesModel: CategoriesModel) {

this.CategoriesModel = CategoriesModel;

}



ngOnInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}
class CategoriesListCtrl {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



$onInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}

}



const CategoriesList = {

template: '<div><!-- categories list markup --></div>',

controller: CategoriesListCtrl,

controllerAs: 'categoriesListCtrl'

};



angular.module('app')

.component('categoriesList', CategoriesList)

;
Entry point into the application
Use ES6
for classes
and modules
import {Injectable} from '@angular/core';



@Injectable()

export class MessageService {

private message = 'Hello Message';



getMessage(): string {

return this.message;

};



setMessage(newMessage: string): void {

this.message = newMessage;

};

}
Simple Angular 2 service
class MessageService {

constructor() {

this.message = 'Hello Message'

}



getMessage() {

return this.message;

};



setMessage(newMessage) {

this.message = newMessage;

};

}



export default MessageService;
Simple Angular 1.x service
import angular from 'angular';

import BookmarksModule from './bookmarks/bookmarks';

import CategoriesModule from './categories/categories';



const ComponentsModule = angular.module('app.components', [

BookmarksModule.name,

CategoriesModule.name

]);



export default ComponentsModule;
Importing in Angular 1.x
Create a
top-level
component
import { Component } from '@angular/core';



@Component({

selector: 'app',

templateUrl: './app.component.html',

styleUrls: ['./app.component.css']

})

export class AppComponent {}
Top-level component
import { BrowserModule } from '@angular/platform-browser';

import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';



@NgModule({

declarations: [ AppComponent ],

imports: [ BrowserModule ],

bootstrap: [ AppComponent ]

})

export class AppModule {}
Module
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { enableProdMode } from '@angular/core';

import { environment } from './environments/environment';

import { AppModule } from './app/';



if (environment.production) {

enableProdMode();

}



platformBrowserDynamic().bootstrapModule(AppModule);
Bootstrap
<body>

<app>Loading…</app>

</body>
Entry point
import template from './app.html';

import './app.styl';



const AppComponent = {

template

};



export default AppComponent;
Top-level component
import angular from 'angular';

import appComponent from './app.component';

import CommonModule from './common/common';

import ComponentsModule from './components/components';



angular.module('app', [

CommonModule.name,

ComponentsModule.name

])

.component('app', appComponent)

;
Module
<body ng-app="app" ng-strict-di ng-cloak>

<app>Loading...</app>

</body>
Entry point
Organize your
application with
subcomponents
Directives vs Components
Components are simplified Directives
Components have a MUCH simpler configuration object
Components will generally accomplish everything you need
You still need directives when you need to do DOM manipulation or create a class based
directive
Advanced functionality such as terminal, priority, etc require a directive
Basic Component Structure
The basic structure for a component is module.component('name',	{	});	
Notice that unlike a directive, component takes a configuration object and not
a function
The most common component configuration properties will be controller,
template, templateUrl and bindings
Component Driven Architecture
Components only control their own View and Data
Components have a well-defined public API aka Inputs and Outputs
Components have a well-defined lifecycle
A well architected application is a tree of components
import {Component} from '@angular/core';



@Component({

moduleId: module.id,

selector: 'items',

templateUrl: 'items.component.html',

styleUrls: ['items.component.css']

})

export class ItemsComponent {}
Parent component
import {Component, Input} from '@angular/core';

import {Item} from '../shared';



@Component({

moduleId: module.id,

selector: 'app-item-details',

templateUrl: 'item-details.component.html',

styleUrls: ['item-details.component.css']

})



export class ItemDetailsComponent {

@Input() item: Item;



addItem(): void {

this.item.count += 1;

};

}
Child component
<div>

<div>

<h2>{{ title }}</h2>

</div>

<div>

{{ body }}

</div>

</div>



<item-details *ngFor="let item of items" [item]="item"></item-details>
Parent component
import template from './categories.html';

import controller from './categories.controller';

import './categories.styl';



const categoriesComponent = {

template,

controller,

controllerAs: 'categoriesListCtrl'

};



export default categoriesComponent;
Parent component
import template from './category-item.html';

import './category-item.styl';



const categoryItemComponent = {

bindings: {

category: '<',

selected: '&'

},

template,

controllerAs: 'categoryItemCtrl'

};



export default categoryItemComponent;
Child component
<ul class="nav nav-sidebar">

<li ng-repeat="category in categoriesListCtrl.categories">

<category-item

category="category"

selected="categoriesListCtrl.onCategorySelected(category)">

</category-item>

</li>

</ul>
Parent component
<div class="categoryItem"

ng-click="categoryItemCtrl.selected({category:categoryItemCtrl.category})">

{{categoryItemCtrl.category.name}}

</div>
Child component
Create a
component
controller in ES6
import {Component, OnInit} from '@angular/core';

import {MessageService} from '../shared';



@Component({

moduleId: module.id,

selector: 'home',

templateUrl: 'home.component.html',

styleUrls: ['home.component.css']

})

export class HomeComponent {}
Basic component
export class HomeComponent implements OnInit {

title: string = 'Home Page';

body: string = 'This is the about home body';

message: string;



constructor(private messageService: MessageService) { }



ngOnInit() {

this.message = this.messageService.getMessage();

}



updateMessage(m: string): void {

this.messageService.setMessage(m);

}

}
Basic component class
import template from './categories.html';

import './categories.styl';



const categoriesComponent = {

template

};



export default categoriesComponent;
Basic component
class CategoriesController {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



$onInit() {

this.CategoriesModel.getCategories()

.then(result => this.categories = result);

}



onCategorySelected(category) {

this.CategoriesModel.setCurrentCategory(category);

}



isCurrentCategory(category) {

return this.CategoriesModel.getCurrentCategory() &&

this.CategoriesModel.getCurrentCategory().id === category.id;

}

}



export default CategoriesController;
Basic controller class
import template from './categories.html';

import controller from './categories.controller';

import './categories.styl';



const categoriesComponent = {

template,

controller,

controllerAs: 'categoriesListCtrl'

};



export default categoriesComponent;
Basic component with controller
Refactor
controller logic
to services
class CategoriesController {

constructor() {

this.categories = [

{"id": 0, "name": "Development"},

{"id": 1, "name": "Design"},

{"id": 2, "name": "Exercise"},

{"id": 3, "name": "Humor"}

];

}

}



export default CategoriesController;
Basic controller
class CategoriesModel {

constructor() {

this.categories = [

{"id": 0, "name": "Development"},

{"id": 1, "name": "Design"},

{"id": 2, "name": "Exercise"},

{"id": 3, "name": "Humor"}

];

}

}



export default CategoriesModel;
Basic service
@Injectable()

export class CategoriesModel {

private categories = [

{"id": 0, "name": "Development"},

{"id": 1, "name": "Design"},

{"id": 2, "name": "Exercise"},

{"id": 3, "name": "Humor"}

];



getCategories() {

return this.categories;

};

}
Basic service
Dependency
injection in ES6
class CategoriesModel {

constructor($q) { // $q is scoped to the constructor only

this.categories = [

{"id": 0, "name": "Development"},

{"id": 1, "name": "Design"},

{"id": 2, "name": "Exercise"},

{"id": 3, "name": "Humor"}

];

}



getCategories() {

return $q.when(this.categories); // wont work!

}

}



export default CategoriesModel;
Scoped to function
class CategoriesModel {

constructor($q) {

'ngInject'; // ng-annotate ftw



this.$q = $q; // constructor assignment

this.categories = [

{"id": 0, "name": "Development"},

{"id": 1, "name": "Design"},

{"id": 2, "name": "Exercise"},

{"id": 3, "name": "Humor"}

];

}



getCategories() {

return this.$q.when(this.categories); // now we can use $q

}

}



export default CategoriesModel;
Local assignment
class CategoriesModel {

constructor($q) {}

}
var CategoriesModel = (function () {

function CategoriesModel($q) {

}

return CategoriesModel;

}());
Exhibit A: TypeScript
class CategoriesModel {

constructor(private $q) {}

}
var CategoriesModel = (function () {

function CategoriesModel($q) {

this.$q = $q;

}

return CategoriesModel;

}());
Exhibit B: TypeScript
Initialize
components with
lifecycle hooks
Component Lifecycle Hooks
Components have well defined lifecycle hooks that allow us to perform specific
operations during the lifespan of our component
We can use $onInit to know when our controller has been has been constructed
and its bindings initialized
We can also use $onInit to know when a dependent component is available
We can use $onDestroy to perform clean when our component is removed
class CategoriesController {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

this.CategoriesModel.getCategories()

.then(categories => this.categories = categories);

}

}



export default CategoriesController;
Logic in constructor
class CategoriesController {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



$onInit() {

this.CategoriesModel.getCategories()

.then(categories => this.categories = categories);

}

}



export default CategoriesController;
Logic in lifecycle hook
export class HomeComponent implements OnInit {

message: string;



constructor(private messageService: MessageService) { }



ngOnInit() {

this.message = this.messageService.getMessage();

}

}
Logic in lifecycle hook
Container and
presentational
components
Isolated Scope
Isolated scope secures the perimeter of your component so that you can
control what goes in and out
Isolated scope is great for defining an API to your directive
There are now four ways to interact with isolated scope: via an attribute, one-
way binding, two-way binding or an expression
Inputs and Outputs
Inputs are denoted with an < or @ symbol
Outputs are denoted with an & symbol
@ indicates an attribute binding which is one-way and string based
< indicates a one-way binding that is object based
& is an expression binding which fires a callback on the parent component
bindings: {

hero: '<',

comment: '@'

},



bindings: {

onDelete: '&',

onUpdate: '&'

},
Inputs and outputs
<editable-field

on-update="$ctrl.update('location', value)">

</editable-field>

<button

ng-click="$ctrl.onDelete({hero: $ctrl.hero})">

Delete

</button>
Outputs
import template from './category-item.html';

import './category-item.styl';



let categoryItemComponent = {

bindings: {

category: '<',

selected: '&'

},

template,

controllerAs: 'categoryItemCtrl'

};



export default categoryItemComponent;
Inputs and outputs via bindings
<div class="categoryItem"

ng-click="categoryItemCtrl.selected({category:categoryItemCtrl.category})">

{{categoryItemCtrl.category.name}}

</div>
Inputs and outputs in child template
<ul class="nav nav-sidebar">

<li ng-repeat="category in categoriesListCtrl.categories">

<category-item

category="category"

selected="categoriesListCtrl.onCategorySelected(category)">

</category-item>

</li>

</ul>
Inputs and outputs in parent template
class CategoriesController {

constructor(CategoriesModel) {

'ngInject';



this.CategoriesModel = CategoriesModel;

}



onCategorySelected(category) {

console.log('CATEGORY SELECTED', category);

}

}



export default CategoriesController;
Parent controller
import {Component, Input} from '@angular/core';

import {Item} from '../shared';



@Component({

moduleId: module.id,

selector: 'app-item-details',

templateUrl: 'item-details.component.html',

styleUrls: ['item-details.component.css']

})



export class ItemDetailsComponent {

@Input() item: Item;



addItem(): void {

this.item.count += 1;

};

}
Inputs in child component
<div>

<div>

<h2>{{ title }}</h2>

</div>

<div>

{{ body }}

</div>

</div>



<item-details *ngFor="let item of items" [item]="item"></item-details>
Inputs in parent template
Create lightweight
controllers by
binding to models
class BookmarksController {

//...



$onInit() {

this.BookmarksModel.getBookmarks()

.then(bookmarks => this.bookmarks = bookmarks);



this.getCurrentCategory =

this.CategoriesModel.getCurrentCategory.bind(this.CategoriesModel); // Lexical
scope! :(

}

}



export default BookmarksController;
Bind to model in controller
<div class="bookmarks">

<div ng-repeat="bookmark in bookmarksListCtrl.bookmarks 

| filter:{category:bookmarksListCtrl.getCurrentCategory().name}">

<button type="button" class="close">&times;</button>

<button type="button" class="btn btn-link">

<span class="glyphicon glyphicon-pencil"></span>

</button>

<a href="{{bookmark.url}}" target="_blank">{{bookmark.title}}</a>

</div>

</div>
Transparent in template
Isolating
state mutations
in components
import template from './save-bookmark.html';

import controller from './save-bookmark.controller';



let saveBookmarkComponent = {

bindings: {

bookmark: '<',

save: '&',

cancel: '&'

},

template,

controller,

controllerAs: 'saveBookmarkCtrl'

};



export default saveBookmarkComponent;
Child component
<div class="save-bookmark">

<form ng-submit="saveBookmarkCtrl.save({bookmark:saveBookmarkCtrl.bookmark})" >

<div class="form-group">

<label>Bookmark Title</label>

<input type="text" ng-model="saveBookmarkCtrl.bookmark.title">

</div>

<div class="form-group">

<label>Bookmark URL</label>

<input type="text" ng-model="saveBookmarkCtrl.bookmark.url">

</div>

<button type="submit">Save</button>

<button type="button" ng-click="saveBookmarkCtrl.cancel()">Cancel</button>

</form>

</div>
Child template
class SaveController {

$onChanges() {

this.editedBookmark = Object.assign({}, this.bookmark);

}

}



export default SaveController;
Lifecycle hook FTW!
<div class="save-bookmark">

<form ng-submit="saveBookmarkCtrl.save({bookmark:saveBookmarkCtrl.editedBookmark})">

<div class="form-group">

<label>Bookmark Title</label>

<input type="text" ng-model="saveBookmarkCtrl.editedBookmark.title">

</div>

<div class="form-group">

<label>Bookmark URL</label>

<input type="text" ng-model="saveBookmarkCtrl.editedBookmark.url">

</div>

<button type="submit">Save</button>

<button type="button" ng-click="saveBookmarkCtrl.cancel()">Cancel</button>

</form>

</div>
Updated template
Communicate
state changes
with an event bus
class CategoriesModel {

constructor($q, $rootScope) {

'ngInject';



this.$q = $q;

this.$rootScope = $rootScope;

this.currentCategory = null;

}



setCurrentCategory(category) {

this.currentCategory = category;

this.$rootScope.$broadcast('onCurrentCategoryUpdated');

}

}



export default CategoriesModel;
Broadcast
class BookmarksController {

constructor($scope, CategoriesModel, BookmarksModel) {

'ngInject';



this.$scope = $scope;

this.CategoriesModel = CategoriesModel;

this.BookmarksModel = BookmarksModel;

}



$onInit() {

this.BookmarksModel.getBookmarks()

.then(bookmarks => this.bookmarks = bookmarks;);



this.$scope.$on('onCurrentCategoryUpdated', this.reset.bind(this));

}



reset() {

this.currentBookmark = null;

}

}



export default BookmarksController;
Listen
Testing components with
$componentController
describe('Categories', () => {

let component, $componentController, CategoriesModel;

beforeEach(() => {

window.module('categories');

window.module($provide => {

$provide.value('CategoriesModel', {

getCategories: () => { return { then: () => {} }; }

});

});

});

beforeEach(inject((_$componentController_, _CategoriesModel_) => {

CategoriesModel = _CategoriesModel_;

$componentController = _$componentController_;

}));

describe('Controller', () => {

it('calls CategoriesModel.getCategories immediately', () => {

spyOn(CategoriesModel, 'getCategories').and.callThrough();

component = $componentController('categories', {

CategoriesModel

});

component.$onInit();

expect(CategoriesModel.getCategories).toHaveBeenCalled();

});

});

});
Testing a component controller
describe('Categories', () => {

let component, $componentController, CategoriesModel;

beforeEach(() => {

window.module('categories');

window.module($provide => {

$provide.value('CategoriesModel', {

getCategories: () => { return { then: () => {} }; }

});

});

});

beforeEach(inject((_$componentController_, _CategoriesModel_) => {

CategoriesModel = _CategoriesModel_;

$componentController = _$componentController_;

}));

describe('Controller', () => {

it('calls CategoriesModel.getCategories immediately', () => {

spyOn(CategoriesModel, 'getCategories').and.callThrough();

component = $componentController('categories', {

CategoriesModel

});

component.$onInit();

expect(CategoriesModel.getCategories).toHaveBeenCalled();

});

});

});
Testing a component controller
https://github.com/simpulton/dashing
https://github.com/toddmotto/angular-1-5-components-app
http://ngmigrate.telerik.com/
https://www.angular2patterns.com/
https://egghead.io/courses/using-angular-2-patterns-in-angular-1-x-apps
https://ultimateangular.com/
Thanks!

Weitere ähnliche Inhalte

Ähnlich wie Embrace the Angular 2 Ethos in Angular 1.x

Building a dashboard using AngularJS
Building a dashboard using AngularJSBuilding a dashboard using AngularJS
Building a dashboard using AngularJSRajthilakMCA
 
Commit University - Exploring Angular 2
Commit University - Exploring Angular 2Commit University - Exploring Angular 2
Commit University - Exploring Angular 2Commit University
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0Jeado Ko
 
Hidden Docs in Angular
Hidden Docs in AngularHidden Docs in Angular
Hidden Docs in AngularYadong Xie
 
Angular js 2.0, ng poznań 20.11
Angular js 2.0, ng poznań 20.11Angular js 2.0, ng poznań 20.11
Angular js 2.0, ng poznań 20.11Kamil Augustynowicz
 
Single Page Applications in Angular (italiano)
Single Page Applications in Angular (italiano)Single Page Applications in Angular (italiano)
Single Page Applications in Angular (italiano)Fabio Biondi
 
MVC Design Pattern in JavaScript by ADMEC Multimedia Institute
MVC Design Pattern in JavaScript by ADMEC Multimedia InstituteMVC Design Pattern in JavaScript by ADMEC Multimedia Institute
MVC Design Pattern in JavaScript by ADMEC Multimedia InstituteRavi Bhadauria
 
Understanding angular js
Understanding angular jsUnderstanding angular js
Understanding angular jsAayush Shrestha
 
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)Ontico
 
Angular1x and Angular 2 for Beginners
Angular1x and Angular 2 for BeginnersAngular1x and Angular 2 for Beginners
Angular1x and Angular 2 for BeginnersOswald Campesato
 
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...Akira Hatsune
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next FrameworkCommit University
 
[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법Jeado Ko
 
Android Support Library: Using ActionBarCompat
Android Support Library: Using ActionBarCompatAndroid Support Library: Using ActionBarCompat
Android Support Library: Using ActionBarCompatcbeyls
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Jeado Ko
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점 Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점 WebFrameworks
 
Angular workshop - Full Development Guide
Angular workshop - Full Development GuideAngular workshop - Full Development Guide
Angular workshop - Full Development GuideNitin Giri
 
angularJs Workshop
angularJs WorkshopangularJs Workshop
angularJs WorkshopRan Wahle
 

Ähnlich wie Embrace the Angular 2 Ethos in Angular 1.x (20)

Building a dashboard using AngularJS
Building a dashboard using AngularJSBuilding a dashboard using AngularJS
Building a dashboard using AngularJS
 
Commit University - Exploring Angular 2
Commit University - Exploring Angular 2Commit University - Exploring Angular 2
Commit University - Exploring Angular 2
 
준비하세요 Angular js 2.0
준비하세요 Angular js 2.0준비하세요 Angular js 2.0
준비하세요 Angular js 2.0
 
Hidden Docs in Angular
Hidden Docs in AngularHidden Docs in Angular
Hidden Docs in Angular
 
Angular js 2.0, ng poznań 20.11
Angular js 2.0, ng poznań 20.11Angular js 2.0, ng poznań 20.11
Angular js 2.0, ng poznań 20.11
 
Single Page Applications in Angular (italiano)
Single Page Applications in Angular (italiano)Single Page Applications in Angular (italiano)
Single Page Applications in Angular (italiano)
 
MVC Design Pattern in JavaScript by ADMEC Multimedia Institute
MVC Design Pattern in JavaScript by ADMEC Multimedia InstituteMVC Design Pattern in JavaScript by ADMEC Multimedia Institute
MVC Design Pattern in JavaScript by ADMEC Multimedia Institute
 
Understanding angular js
Understanding angular jsUnderstanding angular js
Understanding angular js
 
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
Паразитируем на React-экосистеме (Angular 4+) / Алексей Охрименко (IPONWEB)
 
Angular1x and Angular 2 for Beginners
Angular1x and Angular 2 for BeginnersAngular1x and Angular 2 for Beginners
Angular1x and Angular 2 for Beginners
 
Angularjs 2
Angularjs 2 Angularjs 2
Angularjs 2
 
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...MVP Community Camp 2014 - How to useenhanced features of Windows 8.1 Store ...
MVP Community Camp 2014 - How to use enhanced features of Windows 8.1 Store ...
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next Framework
 
React vs Angular2
React vs Angular2React vs Angular2
React vs Angular2
 
[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법[FEConf Korea 2017]Angular 컴포넌트 대화법
[FEConf Korea 2017]Angular 컴포넌트 대화법
 
Android Support Library: Using ActionBarCompat
Android Support Library: Using ActionBarCompatAndroid Support Library: Using ActionBarCompat
Android Support Library: Using ActionBarCompat
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
 
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점 Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
Angular를 활용한 웹 프론트단 개발과 2.0에서 달라진점
 
Angular workshop - Full Development Guide
Angular workshop - Full Development GuideAngular workshop - Full Development Guide
Angular workshop - Full Development Guide
 
angularJs Workshop
angularJs WorkshopangularJs Workshop
angularJs Workshop
 

Mehr von Lukas Ruebbelke

Components Are the New Thin Client
Components Are the New Thin ClientComponents Are the New Thin Client
Components Are the New Thin ClientLukas Ruebbelke
 
ng-conf 2017: Angular Mischief Maker Slides
ng-conf 2017: Angular Mischief Maker Slidesng-conf 2017: Angular Mischief Maker Slides
ng-conf 2017: Angular Mischief Maker SlidesLukas Ruebbelke
 
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and FirebaseGo Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and FirebaseLukas Ruebbelke
 
Get that Corner Office with Angular 2 and Electron
Get that Corner Office with Angular 2 and ElectronGet that Corner Office with Angular 2 and Electron
Get that Corner Office with Angular 2 and ElectronLukas Ruebbelke
 
The REAL Angular Keynote
The REAL Angular KeynoteThe REAL Angular Keynote
The REAL Angular KeynoteLukas Ruebbelke
 
Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Lukas Ruebbelke
 
Turn Your Designers Into Death Stars with Angular
Turn Your Designers Into Death Stars with AngularTurn Your Designers Into Death Stars with Angular
Turn Your Designers Into Death Stars with AngularLukas Ruebbelke
 
Badges? We don't need no stinkin' badges!
Badges? We don't need no stinkin' badges!Badges? We don't need no stinkin' badges!
Badges? We don't need no stinkin' badges!Lukas Ruebbelke
 
Ionic Crash Course! Hack-a-ton SF
Ionic Crash Course! Hack-a-ton SFIonic Crash Course! Hack-a-ton SF
Ionic Crash Course! Hack-a-ton SFLukas Ruebbelke
 
ngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJS
ngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJSngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJS
ngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJSLukas Ruebbelke
 
AngularJS Directives - DSL for your HTML
AngularJS Directives - DSL for your HTMLAngularJS Directives - DSL for your HTML
AngularJS Directives - DSL for your HTMLLukas Ruebbelke
 

Mehr von Lukas Ruebbelke (12)

Components Are the New Thin Client
Components Are the New Thin ClientComponents Are the New Thin Client
Components Are the New Thin Client
 
ng-conf 2017: Angular Mischief Maker Slides
ng-conf 2017: Angular Mischief Maker Slidesng-conf 2017: Angular Mischief Maker Slides
ng-conf 2017: Angular Mischief Maker Slides
 
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and FirebaseGo Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
Go Beast Mode with Realtime Reactive Interfaces in Angular 2 and Firebase
 
Get that Corner Office with Angular 2 and Electron
Get that Corner Office with Angular 2 and ElectronGet that Corner Office with Angular 2 and Electron
Get that Corner Office with Angular 2 and Electron
 
The REAL Angular Keynote
The REAL Angular KeynoteThe REAL Angular Keynote
The REAL Angular Keynote
 
Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015Impress Your Friends with EcmaScript 2015
Impress Your Friends with EcmaScript 2015
 
Turn Your Designers Into Death Stars with Angular
Turn Your Designers Into Death Stars with AngularTurn Your Designers Into Death Stars with Angular
Turn Your Designers Into Death Stars with Angular
 
Badges? We don't need no stinkin' badges!
Badges? We don't need no stinkin' badges!Badges? We don't need no stinkin' badges!
Badges? We don't need no stinkin' badges!
 
ngAnimate crash course
ngAnimate crash coursengAnimate crash course
ngAnimate crash course
 
Ionic Crash Course! Hack-a-ton SF
Ionic Crash Course! Hack-a-ton SFIonic Crash Course! Hack-a-ton SF
Ionic Crash Course! Hack-a-ton SF
 
ngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJS
ngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJSngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJS
ngEurope 2014: Become a Realtime Cage Dragon with Firebase and AngularJS
 
AngularJS Directives - DSL for your HTML
AngularJS Directives - DSL for your HTMLAngularJS Directives - DSL for your HTML
AngularJS Directives - DSL for your HTML
 

Kürzlich hochgeladen

tonesoftg
tonesoftgtonesoftg
tonesoftglanshi9
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Bert Jan Schrijver
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrandmasabamasaba
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionOnePlan Solutions
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...masabamasaba
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...masabamasaba
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...Shane Coughlan
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...masabamasaba
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyviewmasabamasaba
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisamasabamasaba
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...masabamasaba
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024VictoriaMetrics
 
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT  - Elevating Productivity in Today's Agile EnvironmentHarnessing ChatGPT  - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT - Elevating Productivity in Today's Agile EnvironmentVictorSzoltysek
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnAmarnathKambale
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 

Kürzlich hochgeladen (20)

tonesoftg
tonesoftgtonesoftg
tonesoftg
 
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand%in Midrand+277-882-255-28 abortion pills for sale in midrand
%in Midrand+277-882-255-28 abortion pills for sale in midrand
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
%+27788225528 love spells in Huntington Beach Psychic Readings, Attraction sp...
 
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
%+27788225528 love spells in Boston Psychic Readings, Attraction spells,Bring...
 
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
%in Hazyview+277-882-255-28 abortion pills for sale in Hazyview
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
 
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa%in tembisa+277-882-255-28 abortion pills for sale in tembisa
%in tembisa+277-882-255-28 abortion pills for sale in tembisa
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT  - Elevating Productivity in Today's Agile EnvironmentHarnessing ChatGPT  - Elevating Productivity in Today's Agile Environment
Harnessing ChatGPT - Elevating Productivity in Today's Agile Environment
 
VTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learnVTU technical seminar 8Th Sem on Scikit-learn
VTU technical seminar 8Th Sem on Scikit-learn
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 

Embrace the Angular 2 Ethos in Angular 1.x

  • 1. Embrace the Angular 2 Ethos in Angular 1.x
  • 2. Ethos the distinctive character, spirit, and attitudes of a people, culture, era, etc
  • 3.
  • 6.
  • 7. Angular 2 is not only a framework…
  • 8. …but a set of very useful patterns.
  • 11. tiny app == tiny view + tiny controller
  • 17. Any Angular 2 Application Component Component Component
  • 19.
  • 22. Let's step back a moment...
  • 24. It has gotten even simpler...
  • 27. The best way to become a great Angular developer
  • 28. is to focus on becoming a great developer
  • 31.
  • 33.
  • 35. angular.module('app')
 .controller('CategoriesListCtrl', function($scope, CategoriesModel) {
 CategoriesModel.getCategories()
 .then(function(result){
 $scope.categories = result;
 });
 
 $scope.onCategorySelected = function(category) {
 CategoriesModel.setCurrentCategory(category);
 }
 }); Classic Controller
  • 36. <div ng-controller="CategoriesListCtrl">
 <!-- categories list markup -->
 </div> Classic View
  • 37. angular.module('app')
 .controller('CategoriesListCtrl', function(CategoriesModel) {
 CategoriesModel.getCategories()
 .then(function(result){
 this.categories = result;
 });
 
 this.onCategorySelected = function(category) {
 CategoriesModel.setCurrentCategory(category);
 }
 }); Moving to controller as
  • 38. <div ng-controller="CategoriesListCtrl as categoriesListCtrl">
 <!-- categories list markup -->
 </div> Moving to controller as
  • 39. function CategoriesListCtrl(CategoriesModel) {
 CategoriesModel.getCategories()
 .then(function(result){
 this.categories = result;
 });
 
 this.onCategorySelected = function(category) {
 CategoriesModel.setCurrentCategory(category);
 }
 }
 
 angular.module('app')
 .controller('CategoriesListCtrl', CategoriesListCtrl); Extract the controller function
  • 40. function CategoriesListCtrl(CategoriesModel) {
 CategoriesModel.getCategories()
 .then(function(result){
 this.categories = result;
 });
 
 this.onCategorySelected = function(category) {
 CategoriesModel.setCurrentCategory(category);
 }
 }
 
 var CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'CategoriesListCtrl'
 }
 
 angular.module('app')
 .component('categoriesList', CategoriesList); Convert to component
  • 42. class CategoriesListCtrl {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 }
 
 const CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'categoriesListCtrl'
 };
 
 angular.module('app')
 .component('categoriesList', CategoriesList)
 ; Convert to ES6
  • 43. class CategoriesListCtrl {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 }
 const CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'categoriesListCtrl'
 };
 angular.module('app')
 .component('categoriesList', CategoriesList)
 ; Use lifecycle hooks
  • 44. @Component({
 selector: 'categories-list',
 template: `<div>Hello Category List Component</div>`,
 providers: [CategoriesModel]
 })
 export class CategoriesList {
 constructor(CategoriesModel: CategoriesModel) {
 this.CategoriesModel = CategoriesModel;
 }
 
 ngOnInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 } Angular 2 equivalent
  • 45. @Component({
 selector: 'categories-list',
 template: `<div>Hello Category List Component</div>`,
 providers: [CategoriesModel]
 })
 export class CategoriesList {
 constructor(CategoriesModel: CategoriesModel) {
 this.CategoriesModel = CategoriesModel;
 }
 
 ngOnInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 } class CategoriesListCtrl {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 }
 
 const CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'categoriesListCtrl'
 };
 
 angular.module('app')
 .component('categoriesList', CategoriesList)
 ; Similar shapes
  • 46. @Component({
 selector: 'categories-list',
 template: `<div>Hello Category List Component</div>`,
 providers: [CategoriesModel]
 })
 export class CategoriesList {
 constructor(CategoriesModel: CategoriesModel) {
 this.CategoriesModel = CategoriesModel;
 }
 
 ngOnInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 } class CategoriesListCtrl {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 }
 
 const CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'categoriesListCtrl'
 };
 
 angular.module('app')
 .component('categoriesList', CategoriesList)
 ; Component configuration
  • 47. @Component({
 selector: 'categories-list',
 template: `<div>Hello Category List Component</div>`,
 providers: [CategoriesModel]
 })
 export class CategoriesList {
 constructor(CategoriesModel: CategoriesModel) {
 this.CategoriesModel = CategoriesModel;
 }
 
 ngOnInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 } class CategoriesListCtrl {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 }
 
 const CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'categoriesListCtrl'
 };
 
 angular.module('app')
 .component('categoriesList', CategoriesList)
 ; Component controller
  • 48. @Component({
 selector: 'categories-list',
 template: `<div>Hello Category List Component</div>`,
 providers: [CategoriesModel]
 })
 export class CategoriesList {
 constructor(CategoriesModel: CategoriesModel) {
 this.CategoriesModel = CategoriesModel;
 }
 
 ngOnInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 } class CategoriesListCtrl {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 }
 
 const CategoriesList = {
 template: '<div><!-- categories list markup --></div>',
 controller: CategoriesListCtrl,
 controllerAs: 'categoriesListCtrl'
 };
 
 angular.module('app')
 .component('categoriesList', CategoriesList)
 ; Entry point into the application
  • 50. import {Injectable} from '@angular/core';
 
 @Injectable()
 export class MessageService {
 private message = 'Hello Message';
 
 getMessage(): string {
 return this.message;
 };
 
 setMessage(newMessage: string): void {
 this.message = newMessage;
 };
 } Simple Angular 2 service
  • 51. class MessageService {
 constructor() {
 this.message = 'Hello Message'
 }
 
 getMessage() {
 return this.message;
 };
 
 setMessage(newMessage) {
 this.message = newMessage;
 };
 }
 
 export default MessageService; Simple Angular 1.x service
  • 52. import angular from 'angular';
 import BookmarksModule from './bookmarks/bookmarks';
 import CategoriesModule from './categories/categories';
 
 const ComponentsModule = angular.module('app.components', [
 BookmarksModule.name,
 CategoriesModule.name
 ]);
 
 export default ComponentsModule; Importing in Angular 1.x
  • 54. import { Component } from '@angular/core';
 
 @Component({
 selector: 'app',
 templateUrl: './app.component.html',
 styleUrls: ['./app.component.css']
 })
 export class AppComponent {} Top-level component
  • 55. import { BrowserModule } from '@angular/platform-browser';
 import { NgModule } from '@angular/core';
 import { AppComponent } from './app.component';
 
 @NgModule({
 declarations: [ AppComponent ],
 imports: [ BrowserModule ],
 bootstrap: [ AppComponent ]
 })
 export class AppModule {} Module
  • 56. import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
 import { enableProdMode } from '@angular/core';
 import { environment } from './environments/environment';
 import { AppModule } from './app/';
 
 if (environment.production) {
 enableProdMode();
 }
 
 platformBrowserDynamic().bootstrapModule(AppModule); Bootstrap
  • 58. import template from './app.html';
 import './app.styl';
 
 const AppComponent = {
 template
 };
 
 export default AppComponent; Top-level component
  • 59. import angular from 'angular';
 import appComponent from './app.component';
 import CommonModule from './common/common';
 import ComponentsModule from './components/components';
 
 angular.module('app', [
 CommonModule.name,
 ComponentsModule.name
 ])
 .component('app', appComponent)
 ; Module
  • 60. <body ng-app="app" ng-strict-di ng-cloak>
 <app>Loading...</app>
 </body> Entry point
  • 62.
  • 63. Directives vs Components Components are simplified Directives Components have a MUCH simpler configuration object Components will generally accomplish everything you need You still need directives when you need to do DOM manipulation or create a class based directive Advanced functionality such as terminal, priority, etc require a directive
  • 64. Basic Component Structure The basic structure for a component is module.component('name', { }); Notice that unlike a directive, component takes a configuration object and not a function The most common component configuration properties will be controller, template, templateUrl and bindings
  • 65. Component Driven Architecture Components only control their own View and Data Components have a well-defined public API aka Inputs and Outputs Components have a well-defined lifecycle A well architected application is a tree of components
  • 66. import {Component} from '@angular/core';
 
 @Component({
 moduleId: module.id,
 selector: 'items',
 templateUrl: 'items.component.html',
 styleUrls: ['items.component.css']
 })
 export class ItemsComponent {} Parent component
  • 67. import {Component, Input} from '@angular/core';
 import {Item} from '../shared';
 
 @Component({
 moduleId: module.id,
 selector: 'app-item-details',
 templateUrl: 'item-details.component.html',
 styleUrls: ['item-details.component.css']
 })
 
 export class ItemDetailsComponent {
 @Input() item: Item;
 
 addItem(): void {
 this.item.count += 1;
 };
 } Child component
  • 68. <div>
 <div>
 <h2>{{ title }}</h2>
 </div>
 <div>
 {{ body }}
 </div>
 </div>
 
 <item-details *ngFor="let item of items" [item]="item"></item-details> Parent component
  • 69. import template from './categories.html';
 import controller from './categories.controller';
 import './categories.styl';
 
 const categoriesComponent = {
 template,
 controller,
 controllerAs: 'categoriesListCtrl'
 };
 
 export default categoriesComponent; Parent component
  • 70. import template from './category-item.html';
 import './category-item.styl';
 
 const categoryItemComponent = {
 bindings: {
 category: '<',
 selected: '&'
 },
 template,
 controllerAs: 'categoryItemCtrl'
 };
 
 export default categoryItemComponent; Child component
  • 71. <ul class="nav nav-sidebar">
 <li ng-repeat="category in categoriesListCtrl.categories">
 <category-item
 category="category"
 selected="categoriesListCtrl.onCategorySelected(category)">
 </category-item>
 </li>
 </ul> Parent component
  • 74. import {Component, OnInit} from '@angular/core';
 import {MessageService} from '../shared';
 
 @Component({
 moduleId: module.id,
 selector: 'home',
 templateUrl: 'home.component.html',
 styleUrls: ['home.component.css']
 })
 export class HomeComponent {} Basic component
  • 75. export class HomeComponent implements OnInit {
 title: string = 'Home Page';
 body: string = 'This is the about home body';
 message: string;
 
 constructor(private messageService: MessageService) { }
 
 ngOnInit() {
 this.message = this.messageService.getMessage();
 }
 
 updateMessage(m: string): void {
 this.messageService.setMessage(m);
 }
 } Basic component class
  • 76. import template from './categories.html';
 import './categories.styl';
 
 const categoriesComponent = {
 template
 };
 
 export default categoriesComponent; Basic component
  • 77. class CategoriesController {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(result => this.categories = result);
 }
 
 onCategorySelected(category) {
 this.CategoriesModel.setCurrentCategory(category);
 }
 
 isCurrentCategory(category) {
 return this.CategoriesModel.getCurrentCategory() &&
 this.CategoriesModel.getCurrentCategory().id === category.id;
 }
 }
 
 export default CategoriesController; Basic controller class
  • 78. import template from './categories.html';
 import controller from './categories.controller';
 import './categories.styl';
 
 const categoriesComponent = {
 template,
 controller,
 controllerAs: 'categoriesListCtrl'
 };
 
 export default categoriesComponent; Basic component with controller
  • 80. class CategoriesController {
 constructor() {
 this.categories = [
 {"id": 0, "name": "Development"},
 {"id": 1, "name": "Design"},
 {"id": 2, "name": "Exercise"},
 {"id": 3, "name": "Humor"}
 ];
 }
 }
 
 export default CategoriesController; Basic controller
  • 81. class CategoriesModel {
 constructor() {
 this.categories = [
 {"id": 0, "name": "Development"},
 {"id": 1, "name": "Design"},
 {"id": 2, "name": "Exercise"},
 {"id": 3, "name": "Humor"}
 ];
 }
 }
 
 export default CategoriesModel; Basic service
  • 82. @Injectable()
 export class CategoriesModel {
 private categories = [
 {"id": 0, "name": "Development"},
 {"id": 1, "name": "Design"},
 {"id": 2, "name": "Exercise"},
 {"id": 3, "name": "Humor"}
 ];
 
 getCategories() {
 return this.categories;
 };
 } Basic service
  • 84. class CategoriesModel {
 constructor($q) { // $q is scoped to the constructor only
 this.categories = [
 {"id": 0, "name": "Development"},
 {"id": 1, "name": "Design"},
 {"id": 2, "name": "Exercise"},
 {"id": 3, "name": "Humor"}
 ];
 }
 
 getCategories() {
 return $q.when(this.categories); // wont work!
 }
 }
 
 export default CategoriesModel; Scoped to function
  • 85. class CategoriesModel {
 constructor($q) {
 'ngInject'; // ng-annotate ftw
 
 this.$q = $q; // constructor assignment
 this.categories = [
 {"id": 0, "name": "Development"},
 {"id": 1, "name": "Design"},
 {"id": 2, "name": "Exercise"},
 {"id": 3, "name": "Humor"}
 ];
 }
 
 getCategories() {
 return this.$q.when(this.categories); // now we can use $q
 }
 }
 
 export default CategoriesModel; Local assignment
  • 86. class CategoriesModel {
 constructor($q) {}
 } var CategoriesModel = (function () {
 function CategoriesModel($q) {
 }
 return CategoriesModel;
 }()); Exhibit A: TypeScript
  • 87. class CategoriesModel {
 constructor(private $q) {}
 } var CategoriesModel = (function () {
 function CategoriesModel($q) {
 this.$q = $q;
 }
 return CategoriesModel;
 }()); Exhibit B: TypeScript
  • 89. Component Lifecycle Hooks Components have well defined lifecycle hooks that allow us to perform specific operations during the lifespan of our component We can use $onInit to know when our controller has been has been constructed and its bindings initialized We can also use $onInit to know when a dependent component is available We can use $onDestroy to perform clean when our component is removed
  • 90. class CategoriesController {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 this.CategoriesModel.getCategories()
 .then(categories => this.categories = categories);
 }
 }
 
 export default CategoriesController; Logic in constructor
  • 91. class CategoriesController {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 $onInit() {
 this.CategoriesModel.getCategories()
 .then(categories => this.categories = categories);
 }
 }
 
 export default CategoriesController; Logic in lifecycle hook
  • 92. export class HomeComponent implements OnInit {
 message: string;
 
 constructor(private messageService: MessageService) { }
 
 ngOnInit() {
 this.message = this.messageService.getMessage();
 }
 } Logic in lifecycle hook
  • 94.
  • 95. Isolated Scope Isolated scope secures the perimeter of your component so that you can control what goes in and out Isolated scope is great for defining an API to your directive There are now four ways to interact with isolated scope: via an attribute, one- way binding, two-way binding or an expression
  • 96. Inputs and Outputs Inputs are denoted with an < or @ symbol Outputs are denoted with an & symbol @ indicates an attribute binding which is one-way and string based < indicates a one-way binding that is object based & is an expression binding which fires a callback on the parent component
  • 97.
  • 98. bindings: {
 hero: '<',
 comment: '@'
 },
 
 bindings: {
 onDelete: '&',
 onUpdate: '&'
 }, Inputs and outputs
  • 100. import template from './category-item.html';
 import './category-item.styl';
 
 let categoryItemComponent = {
 bindings: {
 category: '<',
 selected: '&'
 },
 template,
 controllerAs: 'categoryItemCtrl'
 };
 
 export default categoryItemComponent; Inputs and outputs via bindings
  • 102. <ul class="nav nav-sidebar">
 <li ng-repeat="category in categoriesListCtrl.categories">
 <category-item
 category="category"
 selected="categoriesListCtrl.onCategorySelected(category)">
 </category-item>
 </li>
 </ul> Inputs and outputs in parent template
  • 103. class CategoriesController {
 constructor(CategoriesModel) {
 'ngInject';
 
 this.CategoriesModel = CategoriesModel;
 }
 
 onCategorySelected(category) {
 console.log('CATEGORY SELECTED', category);
 }
 }
 
 export default CategoriesController; Parent controller
  • 104. import {Component, Input} from '@angular/core';
 import {Item} from '../shared';
 
 @Component({
 moduleId: module.id,
 selector: 'app-item-details',
 templateUrl: 'item-details.component.html',
 styleUrls: ['item-details.component.css']
 })
 
 export class ItemDetailsComponent {
 @Input() item: Item;
 
 addItem(): void {
 this.item.count += 1;
 };
 } Inputs in child component
  • 105. <div>
 <div>
 <h2>{{ title }}</h2>
 </div>
 <div>
 {{ body }}
 </div>
 </div>
 
 <item-details *ngFor="let item of items" [item]="item"></item-details> Inputs in parent template
  • 107. class BookmarksController {
 //...
 
 $onInit() {
 this.BookmarksModel.getBookmarks()
 .then(bookmarks => this.bookmarks = bookmarks);
 
 this.getCurrentCategory =
 this.CategoriesModel.getCurrentCategory.bind(this.CategoriesModel); // Lexical scope! :(
 }
 }
 
 export default BookmarksController; Bind to model in controller
  • 108. <div class="bookmarks">
 <div ng-repeat="bookmark in bookmarksListCtrl.bookmarks 
 | filter:{category:bookmarksListCtrl.getCurrentCategory().name}">
 <button type="button" class="close">&times;</button>
 <button type="button" class="btn btn-link">
 <span class="glyphicon glyphicon-pencil"></span>
 </button>
 <a href="{{bookmark.url}}" target="_blank">{{bookmark.title}}</a>
 </div>
 </div> Transparent in template
  • 110. import template from './save-bookmark.html';
 import controller from './save-bookmark.controller';
 
 let saveBookmarkComponent = {
 bindings: {
 bookmark: '<',
 save: '&',
 cancel: '&'
 },
 template,
 controller,
 controllerAs: 'saveBookmarkCtrl'
 };
 
 export default saveBookmarkComponent; Child component
  • 111. <div class="save-bookmark">
 <form ng-submit="saveBookmarkCtrl.save({bookmark:saveBookmarkCtrl.bookmark})" >
 <div class="form-group">
 <label>Bookmark Title</label>
 <input type="text" ng-model="saveBookmarkCtrl.bookmark.title">
 </div>
 <div class="form-group">
 <label>Bookmark URL</label>
 <input type="text" ng-model="saveBookmarkCtrl.bookmark.url">
 </div>
 <button type="submit">Save</button>
 <button type="button" ng-click="saveBookmarkCtrl.cancel()">Cancel</button>
 </form>
 </div> Child template
  • 112. class SaveController {
 $onChanges() {
 this.editedBookmark = Object.assign({}, this.bookmark);
 }
 }
 
 export default SaveController; Lifecycle hook FTW!
  • 113. <div class="save-bookmark">
 <form ng-submit="saveBookmarkCtrl.save({bookmark:saveBookmarkCtrl.editedBookmark})">
 <div class="form-group">
 <label>Bookmark Title</label>
 <input type="text" ng-model="saveBookmarkCtrl.editedBookmark.title">
 </div>
 <div class="form-group">
 <label>Bookmark URL</label>
 <input type="text" ng-model="saveBookmarkCtrl.editedBookmark.url">
 </div>
 <button type="submit">Save</button>
 <button type="button" ng-click="saveBookmarkCtrl.cancel()">Cancel</button>
 </form>
 </div> Updated template
  • 115. class CategoriesModel {
 constructor($q, $rootScope) {
 'ngInject';
 
 this.$q = $q;
 this.$rootScope = $rootScope;
 this.currentCategory = null;
 }
 
 setCurrentCategory(category) {
 this.currentCategory = category;
 this.$rootScope.$broadcast('onCurrentCategoryUpdated');
 }
 }
 
 export default CategoriesModel; Broadcast
  • 116. class BookmarksController {
 constructor($scope, CategoriesModel, BookmarksModel) {
 'ngInject';
 
 this.$scope = $scope;
 this.CategoriesModel = CategoriesModel;
 this.BookmarksModel = BookmarksModel;
 }
 
 $onInit() {
 this.BookmarksModel.getBookmarks()
 .then(bookmarks => this.bookmarks = bookmarks;);
 
 this.$scope.$on('onCurrentCategoryUpdated', this.reset.bind(this));
 }
 
 reset() {
 this.currentBookmark = null;
 }
 }
 
 export default BookmarksController; Listen
  • 118. describe('Categories', () => {
 let component, $componentController, CategoriesModel;
 beforeEach(() => {
 window.module('categories');
 window.module($provide => {
 $provide.value('CategoriesModel', {
 getCategories: () => { return { then: () => {} }; }
 });
 });
 });
 beforeEach(inject((_$componentController_, _CategoriesModel_) => {
 CategoriesModel = _CategoriesModel_;
 $componentController = _$componentController_;
 }));
 describe('Controller', () => {
 it('calls CategoriesModel.getCategories immediately', () => {
 spyOn(CategoriesModel, 'getCategories').and.callThrough();
 component = $componentController('categories', {
 CategoriesModel
 });
 component.$onInit();
 expect(CategoriesModel.getCategories).toHaveBeenCalled();
 });
 });
 }); Testing a component controller
  • 119. describe('Categories', () => {
 let component, $componentController, CategoriesModel;
 beforeEach(() => {
 window.module('categories');
 window.module($provide => {
 $provide.value('CategoriesModel', {
 getCategories: () => { return { then: () => {} }; }
 });
 });
 });
 beforeEach(inject((_$componentController_, _CategoriesModel_) => {
 CategoriesModel = _CategoriesModel_;
 $componentController = _$componentController_;
 }));
 describe('Controller', () => {
 it('calls CategoriesModel.getCategories immediately', () => {
 spyOn(CategoriesModel, 'getCategories').and.callThrough();
 component = $componentController('categories', {
 CategoriesModel
 });
 component.$onInit();
 expect(CategoriesModel.getCategories).toHaveBeenCalled();
 });
 });
 }); Testing a component controller