I am trying to implement a component in AngularJS 1.5.8 with multi-slot transclusion.
My test works fine if I use a directive, but with a component, it seems like not even the slot is found!.
This is how I declare the component
app.component('asComponent', function() {
return {
transclude: {
'title': '?paneTitle',
'body': 'paneBody',
'footer': '?paneFooter'
},
template: `<h1>I am a component</h1>
<div style="border: 2px solid red;">
<div class="title" ng-transclude="title">Fallback Title</div>
<div ng-transclude="body"></div>
<div class="footer" ng-transclude="footer">Fallback Footer</div>
</div>`
}});
app.controller('ExampleController', [ function() {
var vm = this;
vm.title = 'Lorem Ipsum';
vm.link = 'https://google.com';
vm.text = 'Neque porro quisquam est qui dolorem ipsum quia dolor...';
}]);
And here the HTML
<div ng-controller="ExampleController as $ctrl" class="container">
<as-component>
<pane-title>
<a ng-href="{{$ctrl.link}}">{{title}}</a>
</pane-title>
<pane-body>
<p>{{$ctrl.text}}</p>
</pane-body>
</as-component>
</div>
Official AngularJS documentation says
In AngularJS, a Component is a special kind of directive that uses a simpler configuration which is suitable for a component-based application structure.
If this is the case, then multi-slot transclusion should work perfectly with components as well.
I know I am missing something, but I cannot see what it is!
I have crated a small Plunker with both a directive and a component.
https://plnkr.co/edit/yTMRD4SrH8CWLK4LQEwe?p=info
Thanks
For components, use an object (not a function):
app.component('asComponent', {
transclude: {
'title': '?paneTitle',
'body': 'paneBody',
'footer': '?paneFooter'
},
template: `<h1>I am a component</h1>
<div style="border: 2px solid red;">
<div class="title" ng-transclude="title">Fallback Title</div>
<div ng-transclude="body"></div>
<div class="footer" ng-transclude="footer">Fallback Footer</div>
</div>`
});
Also, you're missing $ctrl in {{ title }}. It should be:
<a ng-href="{{$ctrl.link}}">{{$ctrl.title}}</a>
Here it is working in a plnkr.
Related
There is a following code snippet:
<my-header></my-header>
<div ng-switch="$ctrl.page">
<div ng-switch-when="1"><component1></component1></div>
<div ng-switch-when="2"><component2></component2></div>
<div ng-switch-when="3"><component3></component3></div>
</div>
I want that component myHeader would be constructed before ngSwitch directive takes an action. Now component1 is constructed before myHeader.
Routing represents following code:
$stateProvider
.state({
name: 'myApp',
url: '/',
component: 'loader',
})
.state({
name: 'myApp.pages',
url: 'pages/{id:int}',
component: 'loader'
});
$urlRouterProvider.otherwise('/pages/1');
You can achieve this by exposing your controller in the link function inside the myHeader directive.
With that, you can easily add variables to the controller and control the visibility of the ng-switch div with ng-if. Check the code snippet down here.
Ah, don't forget to add ng-cloak to the div containing the ng-switch directive.
angular
.module('app', [])
.controller('TestController', function($scope) {
this.page = 1;
})
.directive('myHeader', function () {
return {
link: function (scope, element, attrs) {
// With element.controller() we can reach the controller that is wrapping our directive. Then, we can simply set the headerIsLoaded variable to true.
element.controller().headerIsLoaded = true;
},
scope: true,
templateUrl: 'my-header.html'
}
});
<div ng-controller="TestController as ctrl">
<my-header></my-header>
<!-- Add a visual feedback so user knows the components are being loaded -->
<div ng-if="!ctrl.headerIsLoaded">
Loading...
</div>
<!-- If ctrl.headerIsLoaded is set to true, the ng-switch will appear -->
<div ng-if="ctrl.headerIsLoaded"
ng-cloak>
<div ng-switch="ctrl.page">
<div ng-switch-when="1">
Page 1
</div>
<div ng-switch-when="2">
Page 2
</div>
<div ng-switch-when="3">
Page 3
</div>
</div>
</div>
</div>
I am developing Angularjs SPA application. I have one div tag where I am binding few details using ng-repeat as below.
<div class="grid-container">
<div class="grid" ng-repeat="car in carDetails">
<img class="car" src="{{car.WebImageURL}}">
<div class="car-name">{{car.make}}<span class="make">{{car.model}}</span></div>
<div class="car-des">lorem ipsum dolor sit amet,</div>
<div class="car-price">{{car.Price}} <span class="sar">{{car.style}}</span></div>
</div>
</div>
On clicking on the div tag(with class="grid-container") i want to redirect to new state. So How can I use ui-sref and ng-click? I am little confused about this. Can someone help me in this regard? Thanks.
You can try like the below code,
<div class="grid-container">
<div class="grid" ng-repeat="car in carDetails">
<img class="car" src="{{car.WebImageURL}}">
<a ui-sref="MainPageRoute.SubPage1Route({name: car.make})"><div class="car-name">{{car.make}}<span class="make">{{car.model}}</span></div></a>
<a ui-sref="MainPageRoute.SubPage2Route({des: car.desc})"><div class="car-des">lorem ipsum dolor sit amet,</div></a>
<a ui-sref="MainPageRoute.SubPage3Route({price: car.Price})"><div class="car-price">{{car.Price}} <span class="sar">{{car.style}}</span></div></a>
</div>
</div>
<ui-view></ui-view>
route file should be like:
$stateProvider.state('MainPageRoute', {
url: "/MainPage",
templateUrl: "templates/main-template.html",
controller: 'main-ctrl'
});
$stateProvider.state('MainPageRoute.SubPage1Route', {
url: "/SubPage1/:name",
templateUrl: "templates/sub-page1-template.html",
controller: 'sub-page1-ctrl'
});
$stateProvider.state('MainPageRoute.SubPage2Route', {
url: "/SubPage2/:des",
templateUrl: "templates/sub-page2-template.html",
controller: 'sub-page2-ctrl'
});
$stateProvider.state('MainPageRoute.SubPage3Route', {
url: "/SubPage3/:price",
templateUrl: "templates/sub-page3-template.html",
controller: 'sub-page3-ctrl'
});
Add a goto function to your controller like this:
$scope.goto = function(sref) {
$state.go(sref);
}
And call your function via ng-click:
<div class="car-name" ng-click="goto('ui-sref')">
{{car.make}}
<span class="make">{{car.model}}</span>
</div>
So I have this problem with creating a new view. The routeing works just fine because it shows the required HTML perfectly, but the expressions which worked just fine in the main view don't show up in the new.
<a href="#/view/{{todo.id}}" id="super" ng-repeat="todo in todos | filter: search">
<div id="main" ng-class="{'done': todo.done}">
<input type="checkbox" ng-model="todo.done" />
<span>{{todo.title}}</span>
<button id="info">i</button>
<div class="inf">
<span>Prioritás: {{todo.priority}}</span>
</div>
<div class="inf" id="sec">
<span>Határidő: {{todo.deadLine | date: "yyyy. MMMM. dd"}}</span>
</div>
</div>
</a>
These are the expressions in the main view, they work like a charm.
myTodoList.config(['$routeProvider', function($routeProvider, $locationProvider) {
$routeProvider
.when('', {
templateUrl: 'index.html',
controller: "mainController"
})
.when('/view/:id', {
templateUrl: 'view.html',
controller: "mainController"
}).otherwise({
redirectTo: ''
});
This is the routeing part, this is working.
<div id="mainContent">Master Detail
<div ng-controller="mainController" ng-view></div>
</div>
This is the div where the new view goes.
<div>
<p>Should be the ID: {{todo.id}}</p>
<p> should be the title: {{todo.title}}</p>
<p> this works: {{1+2}}</p></div>
And this is the view.html. The third expression is working, so I have the problem with de other two expressions. I think I messed up this because I can't reach the data I want. todo.id and todo.title are data created by a function in real time.
$scope.addTodo = function(title, priority, deadLine, id) {
$scope.todos.push({
'id': lastId,
'title': $scope.newTodo,
'done': false,
'priority': $scope.newPriority,
'deadLine': $scope.newDeadLine
});
$scope.newTodo = ''
$scope.newPriority = ''
$scope.newDeadLine = ''
lastId++;
}
This is the function I am using. I hope I described the problem well.
Are you using ng-repeat in your new view ?
<div>
<p>Should be the ID: {{todo.id}}</p>
<p> should be the title: {{todo.title}}</p>
<p> this works: {{1+2}}</p></div>
Your Scope variable name is $socpe.todos but here you are trying to access todo make sure if you are inside the ng-repeat if this is not your problem then share your full code on fiddle or codepen.
Just like Jeyenthaaran Nanjappan said, i think your div should look like this:
<div ng-repeat="todo in todos">
<p>Should be the ID: {{todo.id}}</p>
<p> should be the title: {{todo.title}}</p>
<p> this works: {{1+2}}</p>
</div>
Okay, I solved the problem. I needed to separate the different objects. So I made a function.
$scope.currentTodo = $scope.todos[0];
$scope.selectTodo = function(todo) {
$scope.currentTodo = todo;
}
I made a div and called this function with it.
<div ng-click="selectTodo(todo);" id="super" ng-repeat="todo in todos | filter: search">
And the problematic div now looks like this.
<div id="mainContent">Master Detail
<div>
<p>Should be the ID: {{currentTodo.id}}</p>
<p> should be the title: {{currentTodo.title}}</p>
</div>
</div>
Thank you for your help! I think both of you helped me through this problem.
Some issues in the nesting of the div and html. Change this:
<div id="mainContent">Master Detail
<div ng-repeat="todo in todos" ng-controller="mainController" ng-view></div>
<p>Should be the ID: {{todo.id}}</p>
<p> should be the title: {{todo.title}}</p>
<p> this works: {{1+2}}</p>
</div>
to this:
<div id="mainContent">Master Detail
<div ng-repeat="todo in todos" ng-controller="mainController">
<p>Should be the ID: {{todo.id}}</p>
<p> should be the title: {{todo.title}}</p>
<p> this works: {{1+2}}</p>
</div>
</div>
I'm creating a set of widgets with AngularJS 1.5's new components. The problem is, when using the same widget multiple times, they somehow share their controller or scope. I thought one of the things about components was that their scope is completely isolated?
My main html template which hold the widgets:
<widget-list
title="Books"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Movies"
class="col-xs-12 col-md-4">
</widget-list>
<widget-list
title="Albums"
class="col-xs-12 col-md-4">
</widget-list>
My widget template:
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
My widget component:
app.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title : '#',
},
controllerAs: '$widget',
controller: function($timeout) {
$widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
});
When running my code, this is what my console looks like:
Title on init: Books
Title on init: Movies
Title on init: Albums
(3) Title after 3 seconds: Albums
Also after rendering, all three widgets display No content in their template. But, when clicking the doSomething() button in either one of the three widgets, only the content of the last widget updates to Something.
What is happening here? Why are my components not 'isolated'?
Looks like you have a global variable called $widget here, try this:
var $widget = this;
instead of
$widget = this;
It creates a mess since the $widget variable holds a reference to the controller that has been recently initialized, in this case to the controller of the third component.
The problem with your code is that you are declaring the $widget on window scope, that's why your controller prints the last value, bacause it was being overridden every time the controller was getting instantiated. Use a var $widget instead and your code will work fine.
The following snippet solves this issue:
angular.module('app', [])
.component('widgetList', {
templateUrl: 'template/widget/widget-list.html',
bindings: {
title: '#',
},
controllerAs: '$widget',
controller: WidgetListController
});
function WidgetListController($timeout) {
var $widget = this;
console.log('Title on init: ', $widget.title)
$timeout(function() {
console.log('Title after 3 seconds: ', $widget.title)
}, 3000)
$widget.doSomething = function() {
$widget.content = "Something";
}
}
angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.1/angular.min.js"></script>
<widget-list title="Books" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Movies" class="col-xs-12 col-md-4">
</widget-list>
<widget-list title="Albums" class="col-xs-12 col-md-4">
</widget-list>
<script type="text/ng-template" id="template/widget/widget-list.html">
<div class="widget widget-list">
<div class="panel b-a">
<div class="panel-heading b-b b-light">
<h5>{{$widget.title}}</h5>
<div class="pull-right">
<button type="button" class="btn btn-default btn-sm" ng-click="$widget.doSomething()">
Do something
</button>
</div>
</div>
<div class="panel-content">
{{$widget.content || 'No content'}}
</div>
</div>
</div>
</script>
I have two directives defined in an angular.js module. The HTML element that is declared first executes its directive, but the second HTML element that uses the other directive does not execute it.
Given this HTML:
<div ng-app="myApp">
<div ng-controller="PlayersCtrl">
<div primary text="{{primaryText}}"/>
<div secondary text="{{secondaryText}}"/>
</div>
</div>
and this angular.js code:
var myApp = angular.module('myApp', []);
function PlayersCtrl($scope) {
$scope.primaryText = "Players";
$scope.secondaryText = "the best player list";
}
myApp.directive('primary', function(){
return {
scope: {
text: '#'
},
template: '<h1>{{text}}</h1>',
link: function(scope, element, attrs){
console.log('primary directive');
}
};
});
myApp.directive('secondary', function(){
return {
scope: {
text: '#'
},
template: '<h3>{{text}}</h3>',
link: function(scope, element, attrs){
console.log('secondary directive');
}
};
});
The resulting HTML is only the "primary" directive, and the "secondary" directive does not render:
<div ng-app="myApp" class="ng-scope">
<div ng-controller="PlayersCtrl" class="ng-scope">
<div primary="" text="Players" class="ng-isolate-scope ng-scope">
<h1 class="ng-binding">Players</h1>
</div>
</div>
</div>
The console output verifies this as well, as only the "primary directive" text is output.
Then if I switch the order of the primary and secondary elements, the secondary directive is executed and the primary directive is not:
<!-- reversed elements -->
<div secondary text="{{secondaryText}}"/>
<div primary text="{{primaryText}}"/>
<!-- renders this HTML (secondary, no primary) -->
<div ng-app="myApp" class="ng-scope">
<div ng-controller="PlayersCtrl" class="ng-scope">
<div secondary="" text="the best player list" class="ng-isolate-scope ng-scope">
<h3 class="ng-binding">the best player list</h3>
</div>
</div>
</div>
Why is this? What am I doing wrong?
div's are not void elements and require a closing tag.
<div ng-app="myApp">
<div ng-controller="PlayersCtrl">
<div primary text="{{primaryText}}"></div>
<div secondary text="{{secondaryText}}"></div>
</div>
</div>
Example