Home >Web Front-end >JS Tutorial >A Guide to Building Quality Angular 1.5 Components
Key Points
$onInit
and $onDestroy
to effectively manage the settings and disassembly of components to ensure that resources are properly initialized and cleaned. $scope
, which is in line with Angular 2 practices and improves the modularity and reusability of components. This article was reviewed by Mark Brown and Jurgen Van de Moere. Thanks to all the peer reviewers at SitePoint for getting SitePoint content to its best!
January 10, 2017: The article updated, clarified the section about one-way binding and added information about single-time binding. ---
In Angular 1, components are the mechanisms for creating custom HTML elements. This was possible in the past with Angular directives, but the components built on various improvements to Angular and enforce best practices in building and design.
In this article, we will dive into the design of components and how to use them in your application. If you haven't started using components in Angular 1, you can read our recent tutorial on their syntax and design. My goal is to outline some best practices that will improve the quality of your application.
It should also be noted that many best practices for Angular 2 have been introduced into Angular 1 through the new component API, allowing you to build applications that are easier to refactor later. Angular 2 influences the way we think about and design Angular 1 components, but there are still many obvious differences. Angular 1 is still a very powerful tool for building applications, so I believe that even if you are not going to or are not ready to migrate to Angular 2, it is worth investing in using components to improve your application.
Components should be designed with many key features in mind to make them a powerful building block for applications. We will explore each feature in more detail, but here are the main concepts that components should follow.
Let us now first understand why and how to isolate and encapsulate the components from the rest of the application.
The evolution of Angular 1 functionality is to enable isolated and encapsulated components, for good reason. Some early applications were highly coupled with the use of $scope
and nested controllers. Initially Angular didn't provide a solution, but now it has it.
Good components do not expose their internal logic. This is easy to achieve due to the way they are designed. However, unless absolutely necessary (e.g., send/broadcast events), abuse of component use $scope
should be avoided.
Components should assume a single role. This is very important for testability, reusability and simplicity. It is better to create additional components instead of overloading individual components. This doesn't mean you won't have bigger or more complex components, it just means that each component should focus on its main work.
I divide the components into four main groups based on their role in the application to help you think about how to design components. There is no different syntax to build these different types of components—just consider the specific roles the components assume.
These types are based on my 5+ years of experience using Angular. You can choose to organize slightly differently, but the fundamental concept is to ensure that your components have a clear role.
Only one application component can act as the root of the application. You can think of it as having only one component in the body of a web application, through which all other logic is loaded.
<code>> <app>></app>> > </code>
This is mainly recommended for Angular 2 design consistency, so it will be easier in the future if you wish to migrate. It also helps with testing by moving all the root content of the application into a single component (rather than putting some of it in the index.html file). The application component also gives you a place to instantiate your application so you don't have to do this in the application run method, which enhances testability and reduces dependency on $rootScope
.
This component should be as simple as possible. If possible, it may contain only templates, without any bindings or controllers. However, it does not replace ng-app
or boot the application needs.
In the past, we linked controllers and templates in ui-router state (or ngRoute routing). The route can now be linked directly to the component, so the component is still where the controller and template pair, but there are also the advantages of being routable.
For example, using ui-router, which is how we link templates and controllers.
<code>> <app>></app>> > </code>
You can now link the URL directly to the component.
<code>$stateProvider.state('mystate', { url: '/', templateUrl: 'views/mystate.html', controller: MyStateController }); </code>
These components can bind data from routing parameters such as project IDs, and their role is to focus on setting up routing to load other components required. This seemingly minor change to routing definition is actually very important for Angular 2 migration capabilities, but it is just as important in Angular 1.5, as it better encapsulates templates and controllers at the component level.
Angular 1 actually has two routing modules, ngRoute and ngComponentRouter. Only ngComponentRouter supports components, but it is also deprecated. I think the best way is to use ui-router.
Most of the only components you build for your application are stateful. Here you will actually place the application business logic, issue HTTP requests, process forms, and other stateful tasks. These components may be unique to your application, and they focus on maintaining data rather than visual rendering.
Suppose you have a controller loading user profile data for display, and there is also a corresponding template (not shown here) linked together in the directive. This code snippet is probably the most basic controller to do the job.
<code>$stateProvider.state('mystate', { url: '/', component: 'mystate' }); </code>
Using components, you can design it better than before. Ideally, you should also use the service instead of using $http
directly in the controller.
<code>.controller('ProfileCtrl', function ($scope, $http) { $http.get('/api/profile').then(function (data) { $scope.profile = data; }); }) .directive('profile', function() { return { templateUrl: 'views/profile.html', controller: 'ProfileCtrl' } }) </code>
Now you have a component that loads its own data, so it becomes stateful. These types of components are similar to routing components, except that they may not be used to link to a single route.
The stateful component will use other (stateless) components to actually render the UI. Also, you still want to use the service instead of putting the data access logic directly in the controller.
Stateless components focus on rendering without managing business logic and do not have to be unique to any particular application. For example, most components used for UI elements such as form controls, cards, etc. also do not handle logic like loading data or saving forms. They are designed to be highly modular, reusable and isolated.
If the stateless component displays only data or everything in the control template, a controller may not be needed. They will accept input from stateful components. This example gets the value from the stateful component (profile example above) and displays the avatar.
<code>.component('profile', { templateUrl: 'views/profile.html', controller: function($http) { var vm = this; // 当组件准备好时调用,见下文 vm.$onInit = function() { $http.get('/api/profile').then(function (data) { vm.profile = data; }); }; } }) </code>
To use it, the stateful component will pass the username through the attribute, as shown below: <avatar username="vm.profile.username">.</avatar>
Many libraries you use are collections of stateless components (and possible services). They can certainly accept configurations to modify their behavior, but they are not intended to be responsible for logic outside of themselves.
This is not new to the component, but it is usually wise to use it in the component. The purpose of one-way binding is to avoid loading more work into the digest cycle, which is a major factor in application performance. Data now flows into the component without looking outside it (which leads to some coupling problems that exist today), and the component can simply render itself based on that input. This design also works with Angular 2, which helps with future migrations.
In this example, the title attribute is bound to the component only once based on the provided initial value. If the title is changed by an external actor, it will not be reflected in the component. The syntax that indicates that the binding is unidirectional is to use
<code>> <app>></app>> > </code>
When the title property changes, the component will still be updated. We will explain how to listen for changes to the title property. It is recommended that you use one-way binding whenever possible.
Angular also has the ability to bind data in a single time, so you can optimize the digest cycle. Essentially, Angular will wait to provide the first non-undefined value into the binding, bind that value, and then (once all bindings have been parsed) remove the relevant observer from the digest cycle. This means that a specific binding does not add any processing time to a future digest loop.
This is done by preceding the binding expression with ::
. If you know that the input binding will not change during the life cycle, it only makes sense to do so. In this example, if the title is a one-way binding, it will continue to update inside the component, but the binding here will not be updated because we specify it as a single-time binding.
<code>$stateProvider.state('mystate', { url: '/', templateUrl: 'views/mystate.html', controller: MyStateController }); </code>
You may have noticed the $onInit
function as a new function. Components have lifecycles and corresponding events that you should use to help manage certain aspects of the component.
$onInit()
The first step in the component life cycle is initialization. This event runs after the controller and binding are initialized. You should almost always use this method for component setup or initialization. It will ensure that all values are available for the component before running. If you access bound values directly in the controller, you cannot guarantee that these values are available.
<code>$stateProvider.state('mystate', { url: '/', component: 'mystate' }); </code>
$postLink()
The next step is to link any child elements in the template. When the component is initialized, there is no guarantee that it has also rendered any child elements used in the template. This is important if you need to operate the DOM in any way. An important caveat is that asynchronously loaded templates may not have been loaded when the event is triggered. You can always use a template caching solution to ensure that the template is always available.
<code>> <app>></app>> > </code>
$onChanges()
When the component is active, it may need to react to changes in input values. One-way binding will still update your component, but we have a new $onChanges
event binding to listen for when the input changes.
For this example, assume that the component is provided with a product title and description. You can detect changes as shown below. You can view the object passed to the function, which has objects mapped to available bindings, containing the current value and the previous value.
<code>$stateProvider.state('mystate', { url: '/', templateUrl: 'views/mystate.html', controller: MyStateController }); </code>
$onDestroy()
The final stage is to remove components from the page. This event runs before the controller and its scope are destroyed. It is important to clean up anything that the component may create or hold memory, such as event listeners, observers, or other DOM elements.
<code>$stateProvider.state('mystate', { url: '/', component: 'mystate' }); </code>
To configure and initialize components with a set of data, components should use bindings to accept these values. This is sometimes considered a component API, which is just a different way of describing how a component accepts input.
The challenge here is to provide a concise but clear name for the binding. Sometimes developers try to shorten the name to make it very concise, but this is dangerous for the use of components. Suppose we have a component that accepts the stock code as input, which of the following is better?
<code>.controller('ProfileCtrl', function ($scope, $http) { $http.get('/api/profile').then(function (data) { $scope.profile = data; }); }) .directive('profile', function() { return { templateUrl: 'views/profile.html', controller: 'ProfileCtrl' } }) </code>
I hope you think symbol
is better. Sometimes developers also like to prefix components and bindings as a way to avoid name conflicts. It is wise to prefix components, for example md-toolbar
is the Material toolbar, but prefixing for all bindings can become verbose and should be avoided.
In order to communicate with other components, the component should issue a custom event. There are many examples of using services and two-way data binding to synchronize the number between components
It is, but events are a better design choice. Events are much more efficient as a way to communicate with pages (and are the fundamental part of the JavaScript language and how it works in Angular 2, no coincidence).
Events in can be used with $emit
(up to scope tree) or $broadcast
(down to scope tree). This is a practical application of a quick sample event.
<code>.component('profile', { templateUrl: 'views/profile.html', controller: function($http) { var vm = this; // 当组件准备好时调用,见下文 vm.$onInit = function() { $http.get('/api/profile').then(function (data) { vm.profile = data; }); }; } }) </code>
There are two main situations in which you need to communicate between components: between components you know and between components you don't know. To illustrate this difference, let's assume we have a set of components to help manage tabs on the page, and a toolbar with corresponding help page links.
<code>.component('avatar', { template: '<img src="/static/imghwm/default1.png" data-src="https://img.php.cn/upload/article/000/000/000/173975605585460.png" class="lazy" ng- alt="A Guide to Building Quality Angular 1.5 Components" >', bindings: { username: ' }, controllerAs: 'vm' }) </code>
In this case, the my-tabs
and my-tab
components may know each other because they work together to create a set of three different tabs. However, my-toolbar
components are beyond their cognitive scope.
Whenever a different tab is selected (this will be an event on the my-tab
component instance), the my-tabs
component needs to know so that it can adjust the display of the tab to display the instance. The my-tab
component can issue events upwards to the parent my-tabs
component. This type of communication is like internal communication between two components that work together to create a single function (a tabbed interface).
But what if my-toolbar
want to know which tab is currently selected so that it can change the help button based on the visible content? The my-tab
event will never reach my-toolbar
because it is not a parent. So another option is to use $rootScope
to issue events for the entire component tree down, which allows any component to listen and React. The potential downside here is that your events now reach each controller, and if another component uses the same event name, you may trigger unexpected effects.
Determine which approach is suitable for your use case, but whenever another component may need to know about the event, you may want to issue events to the entire component tree using the second option.
Angular 1 applications can now be written using components, which changes the best practices and nature of our application writing. This is for better, but just using components is not necessarily better than you used before. Keep the following points in mind when building Angular 1 components.
$onChanges
lifecycle event to observe changes. Are you using components in your Angular 1.x application? Or, are you going to wait until you switch to Angular 2? I'd love to hear about your experience in the comments below.
Angular 1.5 component is a simpler and more intuitive API for creating directives. Although instructions are powerful, they can be difficult to use due to their flexibility. On the other hand, components have simpler configurations and are designed to be used to build UI elements. They also facilitate the use of one-way data binding and lifecycle hooks, which can lead to more predictable data flow and easier debugging.
One-way data binding can be achieved using the bindings
attribute in Angular 1.5 component.
Lifecycle hook is a function called at a specific point in the life cycle of a component. Angular 1.5 introduces several life cycle hooks, such as $onInit
, $onChanges
, $onDestroy
and $postLink
. These hooks can be used to perform tasks such as initializing data, cleaning up resources, or making Reacts to binding changes.
Communication between components can be achieved using bindings and events in Angular 1.5. Parent-to-son communication can be done using binding, while child-to-parent communication can be done using events. This facilitates one-way data flow, which can make your application easier to understand.
Migrating from directives in Angular 1.5 to components involves several steps. First, replace the directive to define the object with the component definition. Then, replace the link function with the life cycle hook. Finally, replace the two-way data binding with one-way data binding and events.
The components in Angular 1.5 offer several benefits over instructions. They have a simpler API that facilitates one-way data binding and one-way data flow, and provide lifecycle hooks. These features can make your code easier to understand, debug, and maintain.
Transcription can be achieved in Angular 1.5 components using the transclude
option in component definition. This allows you to insert custom content into the component's template, which is very useful for creating reusable UI elements.
Multi-slot transcription can be implemented in Angular 1.5 components using the transclude
option with object syntax. This allows you to define multiple transcription slots in the component's template that can be filled with custom content.
$onChanges
Lifecycle hook in Angular 1.5 component? Whenever a one-way binding is updated, the $onChanges
lifecycle hook in Angular 1.5 component is called. It receives a change object containing the current and previous values of the binding. This can be used to make Reacts to the binding changes and perform tasks such as updating component state or getting data.
$postLink
Lifecycle hook in Angular 1.5 component? After the element of the component and its child elements are linked, the $postLink
lifecycle hook in the Angular 1.5 component will be called. This can be used to perform tasks that require access to the DOM elements of the component, such as setting up an event listener or operating the DOM.
The above is the detailed content of A Guide to Building Quality Angular 1.5 Components. For more information, please follow other related articles on the PHP Chinese website!