- angular.component(): Introducing "components", a special sort of directive that are easy to configure and promote best practices, bring Angular 1 applications closer to Angular 2's style of architecture. Angular 1.5 takes a big step towards preparing developers for a smoother transition to Angular 2 in the future.
- Multi-slot transclusion: Enabling the design of more powerful and complex UI elements with a much simpler configuration and reduced boilerplate.
There is nothing component() can do but directive() can not do.
<my-component first-name="'Alan'" last-name="'Rickman'"></my-component>
myModule.component('myComponent', {
template: '<h1>Hello {{ $ctrl.getFullName() }}</h1>',
bindings: {
firstName: '<',
lastName: '<'
},
controller: function() {
this.getFullName = function() {
return this.firstName + ' ' + this.lastName;
};
}
});
myModule.directive('myComponent', {
restrict: 'E',
template: '<h1>Hello {{ ctrl.getFullName() }}</h1>',
scope: {},
bindToController: {
firstName: '<',
lastName: '<'
},
controller: function() {
this.getFullName = function() {
return this.firstName + ' ' + this.lastName;
};
},
controllerAs: 'ctrl'
});
Why bindToController when using controllerAs in directive? That is reason what if name should be two-way bound?
<div ng-controller="MainCtrl as main">
<my-component
first-name="'Alan'"
last-name="'Rickman'"
name="main.name">
</my-component>
</div>
myModule.controller('MainCtrl', function () {
this.name = 'JimmyLv';
});
myModule.directive('myComponent', {
restrict: 'E',
template: '<h1>Hello {{ ctrl.getFullName() }} to {{ ctrl.name }}</h1>',
scope: {
firstName: '<',
lastName: '<',
name: '='
},
controller: function($scope) {
this.getFullName = function() {
return this.firstName + ' ' + this.lastName;
};
this.name = 'Pascal';
$scope.$watch('name', function (newValue) {
this.name = newValue;
}.bind(this));
},
controllerAs: 'ctrl'
});
In AngularJS, a child scope normally prototypically inherits from its parent scope.
- scope: { ... } -- this creates an "isolate" scope that does not prototypically inherit. (defalut in
.component()
, scope is always isolate) - scope:true -- then prototypical inheritance will be used for that directive. all properties will be inherited in directive
- scope:false -- shared scope with parent scope. (default in
.directive()
)
Ideally, the whole application should be a tree of components that implement clearly defined inputs and outputs, and minimize two-way data binding. That way, it's easier to predict when data changes and what the state of a component is.
-
Components only control their own View and Data: Components should never modify any data or DOM that is out of their own scope. Normally, in Angular it is possible to modify data anywhere in the application through scope inheritance and watches. This is practical, but can also lead to problems when it is not clear which part of the application is responsible for modifying the data. That is why component directives use an isolate scope, so a whole class of scope manipulation is not possible.
-
Components have a well-defined public API - Inputs and Outputs: However, scope isolation only goes so far, because Angular uses two-way binding. So if you pass an object to a component like this - bindings: {item: '='}, and modify one of its properties, the change will be reflected in the parent component. For components however, only the component that owns the data should modify it, to make it easy to reason about what data is changed, and when.
myMod.component('home', {
template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
controller: function() {
this.user = {name: 'world'};
}
});
$routeProvider.when('/', {
template: '<home></home>'
});
var myMod = angular.module('myMod', ['ngRoute']);
myMod.component('home', {
template: '<h1>Home</h1><p>Hello, {{ $ctrl.user.name }} !</p>',
bindings: {
user: '<'
}
});
myMod.config(function($routeProvider) {
$routeProvider.when('/', {
template: '<home user="$resolve.user"></home>',
resolve: {
user: function($http) { return $http.get('...'); }
}
});
});
myMod.directive('pane', function(){
return {
restrict: 'E',
transclude: {
'title': '?paneTitle',
'body': 'paneBody',
'footer': '?paneFooter'
},
templateUrl: 'pane.html'
};
})
.controller('ExampleController', ['$scope', function($scope) {
$scope.title = 'Lorem Ipsum';
$scope.link = "https://google.com";
$scope.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
}]);
<div>
<div ng-transclude="title">Fallback Title</div>
<p ng-transclude="body"></p>
<div ng-transclude="footer">Fallback Footer</div>
</div>
<div ng-controller="ExampleController as ctrl">
<input ng-model="ctrl.title">
<textarea ng-model="ctrl.text"></textarea>
<pane>
<pane-title>
<a ng-href="{{ ctrl.link }}">{{ ctrl.title }}</a>
</pane-title>
<pane-body>{{ ctrl.text }}</pane-body>
</pane>
</div>
myMod.component('pane', function(){
return {
templateUrl: 'pane.html'
bindings: {
'title': '<paneTitle',
'body': '<paneBody',
'footer': '<?paneFooter'
}
};
})
<div>
<div>{{ $ctrl.title}}</div>
<p>{{ $ctrl.body}}</p>
<div>{{ $ctrl.footer}}</div>
</div>
<div ng-controller="ExampleController as ctrl">
<input ng-model="ctrl.title">
<textarea ng-model="ctrl.text"></textarea>
<pane pane-title="ctrl.title"
pane-body="ctrl.text">
</pane>
</div>